diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c index 4eeefe9b06..d4905e1dad 100644 --- a/src/core/nm-l3-config-data.c +++ b/src/core/nm-l3-config-data.c @@ -50,6 +50,9 @@ struct _NML3ConfigData { const NMPObject *best_default_route_x[2]; }; + struct in6_addr pref64_prefix; + guint32 pref64_plen; + GArray *wins; GArray *nis_servers; @@ -168,6 +171,8 @@ struct _NML3ConfigData { bool routed_dns_4 : 1; bool routed_dns_6 : 1; + + bool pref64_valid : 1; }; /*****************************************************************************/ @@ -520,6 +525,12 @@ nm_l3_config_data_log(const NML3ConfigData *self, _L("nis-domain: %s", self->nis_domain->str); } + if (!IS_IPv4 && self->pref64_valid) { + _L("pref64_prefix: %s/%d", + nm_utils_inet6_ntop(&self->pref64_prefix, sbuf_addr), + self->pref64_plen); + } + if (self->dhcp_lease_x[IS_IPv4]) { gs_free NMUtilsNamedValue *options_free = NULL; NMUtilsNamedValue options_buffer[30]; @@ -727,6 +738,7 @@ nm_l3_config_data_new(NMDedupMultiIndex *multi_idx, int ifindex, NMIPConfigSourc .ndisc_retrans_timer_msec_set = FALSE, .allow_routes_without_address_4 = TRUE, .allow_routes_without_address_6 = TRUE, + .pref64_valid = FALSE, }; _idx_type_init(&self->idx_addresses_4, NMP_OBJECT_TYPE_IP4_ADDRESS); @@ -1979,6 +1991,54 @@ nm_l3_config_data_set_network_id(NML3ConfigData *self, const char *value) return nm_ref_string_reset_str(&self->network_id, value); } +gboolean +nm_l3_config_data_set_pref64_valid(NML3ConfigData *self, gboolean val) +{ + if (self->pref64_valid == val) + return FALSE; + self->pref64_valid = val; + return TRUE; +} + +gboolean +nm_l3_config_data_get_pref64_valid(const NML3ConfigData *self, gboolean *out_val) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + NM_SET_OUT(out_val, self->pref64_valid); + return TRUE; +} + +gboolean +nm_l3_config_data_get_pref64(const NML3ConfigData *self, + struct in6_addr *out_prefix, + guint32 *out_plen) +{ + nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE)); + + if (!self->pref64_valid) + return FALSE; + NM_SET_OUT(out_prefix, self->pref64_prefix); + NM_SET_OUT(out_plen, self->pref64_plen); + return TRUE; +} + +gboolean +nm_l3_config_data_set_pref64(NML3ConfigData *self, struct in6_addr prefix, guint32 plen) +{ + if (self->pref64_valid) { + if (self->pref64_plen == plen + && nm_ip6_addr_same_prefix(&self->pref64_prefix, &prefix, plen)) { + return FALSE; + } + } else { + self->pref64_valid = TRUE; + } + self->pref64_prefix = prefix; + self->pref64_plen = plen; + return TRUE; +} + NMMptcpFlags nm_l3_config_data_get_mptcp_flags(const NML3ConfigData *self) { @@ -2532,6 +2592,13 @@ nm_l3_config_data_cmp_full(const NML3ConfigData *a, NM_CMP_DIRECT_UNSAFE(a->routed_dns_4, b->routed_dns_4); NM_CMP_DIRECT_UNSAFE(a->routed_dns_6, b->routed_dns_6); + NM_CMP_DIRECT(!!a->pref64_valid, !!b->pref64_valid); + if (a->pref64_valid) { + NM_CMP_DIRECT(a->pref64_plen, b->pref64_plen); + NM_CMP_RETURN_DIRECT( + nm_ip6_addr_same_prefix_cmp(&a->pref64_prefix, &b->pref64_prefix, a->pref64_plen)); + } + NM_CMP_FIELD(a, b, source); } @@ -3595,6 +3662,12 @@ nm_l3_config_data_merge(NML3ConfigData *self, self->routed_dns_4 = TRUE; if (src->routed_dns_6) self->routed_dns_6 = TRUE; + + if (src->pref64_valid) { + self->pref64_prefix = src->pref64_prefix; + self->pref64_plen = src->pref64_plen; + self->pref64_valid = src->pref64_valid; + } } NML3ConfigData * diff --git a/src/core/nm-l3-config-data.h b/src/core/nm-l3-config-data.h index b35f23c3e1..69ac3ff976 100644 --- a/src/core/nm-l3-config-data.h +++ b/src/core/nm-l3-config-data.h @@ -498,6 +498,16 @@ gboolean nm_l3_config_data_set_network_id(NML3ConfigData *self, const char *netw const char *nm_l3_config_data_get_network_id(const NML3ConfigData *self); +gboolean nm_l3_config_data_set_pref64_valid(NML3ConfigData *self, gboolean val); + +gboolean nm_l3_config_data_get_pref64_valid(const NML3ConfigData *self, gboolean *out_val); + +gboolean nm_l3_config_data_get_pref64(const NML3ConfigData *self, + struct in6_addr *out_prefix, + guint32 *out_plen); + +gboolean nm_l3_config_data_set_pref64(NML3ConfigData *self, struct in6_addr prefix, guint32 plen); + NMMptcpFlags nm_l3_config_data_get_mptcp_flags(const NML3ConfigData *self); gboolean nm_l3_config_data_set_mptcp_flags(NML3ConfigData *self, NMMptcpFlags mptcp_flags); diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 0d93f76bc2..9e8fcdc5d1 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -11,6 +11,10 @@ #include #include #include +#ifdef HAVE_CLAT +#include +#include +#endif /* HAVE_CLAT */ #include "libnm-core-aux-intern/nm-libnm-core-utils.h" #include "libnm-glib-aux/nm-prioq.h" @@ -22,6 +26,13 @@ #include "n-acd/src/n-acd.h" #include "nm-l3-ipv4ll.h" #include "nm-ip-config.h" +#include "nm-core-utils.h" +#ifdef HAVE_CLAT +#include "bpf/clat.h" +NM_PRAGMA_WARNING_DISABLE("-Wcast-align") +#include "bpf/clat.skel.h" +NM_PRAGMA_WARNING_REENABLE +#endif /* HAVE_CLAT */ /*****************************************************************************/ @@ -289,6 +300,19 @@ typedef struct _NML3CfgPrivate { NMIPConfig *ipconfig_x[2]; }; +#ifdef HAVE_CLAT + NMNetnsIPRange *clat_address_4_commited; + NMPlatformIP6Address clat_address_6_commited; + NMNetnsIPRange *clat_address_4; + NMPlatformIP6Address clat_address_6; + struct in6_addr clat_pref64; + /* If NULL, the BPF program hasn't been loaded or attached */ + struct clat_bpf *clat_bpf; + int clat_socket; + struct bpf_tc_opts clat_attach_ingress; + struct bpf_tc_opts clat_attach_egress; +#endif /* HAVE_CLAT */ + /* Whether we earlier configured MPTCP endpoints for the interface. */ union { struct { @@ -353,6 +377,10 @@ typedef struct _NML3CfgPrivate { bool rp_filter_handled : 1; bool rp_filter_set : 1; + + bool clat_address_6_valid : 1; + bool clat_address_6_commited_valid : 1; + bool clat_pref64_valid : 1; } NML3CfgPrivate; struct _NML3CfgClass { @@ -4123,6 +4151,9 @@ _l3cfg_update_combined_config(NML3Cfg *self, guint i; gboolean merged_changed = FALSE; gboolean commited_changed = FALSE; + gboolean pref64_valid = FALSE; + struct in6_addr pref64; + guint32 pref64_plen; nm_assert(NM_IS_L3CFG(self)); nm_assert(!out_old || !*out_old); @@ -4220,6 +4251,120 @@ _l3cfg_update_combined_config(NML3Cfg *self, &hook_data); } +#if HAVE_CLAT + nm_assert(nm_l3_config_data_get_pref64_valid(l3cd, &pref64_valid)); + if (pref64_valid) { + nm_assert(nm_l3_config_data_get_pref64(l3cd, &pref64, &pref64_plen)); + } + + if (pref64_valid) { + NMPlatformIPXRoute rx; + NMIPAddrTyped best_v6_gateway; + struct in6_addr ip6; + NMDedupMultiIter iter; + + const NMPlatformIP6Route *best_v6_route; + const NMPlatformIP6Address *ip6_entry; + + /* We need to assign an additional v6 /64 address for ourselves, according + to the spec: https://www.rfc-editor.org/rfc/rfc6877#section-6.3 */ + if (!self->priv.p->clat_address_6_valid) { + nm_l3_config_data_iter_ip6_address_for_each (&iter, l3cd, &ip6_entry) { + if (ip6_entry->addr_source == NM_IP_CONFIG_SOURCE_NDISC + && ip6_entry->plen == 64) { + ip6 = ip6_entry->address; + + nm_utils_ipv6_addr_set_stable_privacy(NM_UTILS_STABLE_TYPE_RANDOM, + &ip6, + nm_l3cfg_get_ifname(self, TRUE), + "TODO: Mary fixme", + 0); + self->priv.p->clat_address_6 = (NMPlatformIP6Address) { + .ifindex = self->priv.ifindex, + .address = ip6, + .peer_address = ip6, + .addr_source = NM_IP_CONFIG_SOURCE_CLAT, + .plen = ip6_entry->plen, + }; + self->priv.p->clat_address_6_valid = TRUE; + break; + } + } + } + + /* Don't get a v4 address if we have no v6 address (otherwise, we could + potentially create broken v4 connectivity) */ + if (!self->priv.p->clat_address_6_valid) { + _LOGW("CLAT is currently only supported when SLAAC is in use."); + /* Deallocate the v4 address unless it's the commited one */ + if (self->priv.p->clat_address_4 != self->priv.p->clat_address_4_commited) { + nm_clear_pointer(&self->priv.p->clat_address_4, nm_netns_ip_range_release); + } else { + self->priv.p->clat_address_4 = NULL; + } + } else if (!self->priv.p->clat_address_4) { + /* We need a v4 /32 */ + self->priv.p->clat_address_4 = + nm_netns_ip_range_allocate(self->priv.netns, NM_NETNS_IP_RANGE_TYPE_CLAT); + } + + if (self->priv.p->clat_address_4) { + best_v6_route = NMP_OBJECT_CAST_IP6_ROUTE( + nm_l3_config_data_get_direct_route_for_host(l3cd, AF_INET6, &pref64)); + if (!best_v6_route) { + best_v6_route = NMP_OBJECT_CAST_IP6_ROUTE( + nm_l3_config_data_get_best_default_route(l3cd, AF_INET6)); + } + if (best_v6_route) { + int mtu; + NMPlatformIP4Address addr = { + .ifindex = self->priv.ifindex, + .address = self->priv.p->clat_address_4->addr, + .peer_address = self->priv.p->clat_address_4->addr, + .addr_source = NM_IP_CONFIG_SOURCE_CLAT, + .plen = 32, + }; + + best_v6_gateway.addr_family = AF_INET6; + best_v6_gateway.addr.addr6 = best_v6_route->gateway; + + mtu = best_v6_route->mtu; + + if (!mtu || mtu > 1500) { + /* v4 Internet MTU */ + mtu = 1500; + } + /* 20 for the ipv6 vs ipv4 header plus 8 for a potential + fragmentation extension header */ + mtu -= 28; + + rx.r4 = (NMPlatformIP4Route) { + .ifindex = self->priv.ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_CLAT, + .network = 0, /* default route */ + .plen = 0, + .table_coerced = nm_platform_route_table_coerce(RT_TABLE_MAIN), + .scope_inv = nm_platform_route_scope_inv(RT_SCOPE_UNIVERSE), + .type_coerced = nm_platform_route_type_coerce(RTN_UNICAST), + .pref_src = self->priv.p->clat_address_4->addr, + .via = best_v6_gateway, + /* If real v4 connectivity is available from another interface, + that should probably be preferred */ + .metric = best_v6_route->metric + 50, + .mtu = mtu, + }; + nm_platform_ip_route_normalize(AF_INET, &rx.rx); + if (!nm_l3_config_data_lookup_route(l3cd, AF_INET, &rx.rx)) { + nm_l3_config_data_add_route_4(l3cd, &rx.r4); + } + nm_l3_config_data_add_address_4(l3cd, &addr); + } else { + _LOGW("Couldn't find a good ipv6 route! Unable to set up CLAT!"); + } + } + } +#endif /* HAVE_CLAT */ + if (self->priv.ifindex == NM_LOOPBACK_IFINDEX) { NMPlatformIPXAddress ax; NMPlatformIPXRoute rx; @@ -4280,6 +4425,16 @@ _l3cfg_update_combined_config(NML3Cfg *self, if (nm_l3_config_data_equal(l3cd, self->priv.p->combined_l3cd_merged)) goto out; + if (!l3cd) { + self->priv.p->clat_address_6_valid = FALSE; + /* Deallocate the v4 address unless it's the commited one */ + if (self->priv.p->clat_address_4 != self->priv.p->clat_address_4_commited) { + nm_clear_pointer(&self->priv.p->clat_address_4, nm_netns_ip_range_release); + } else { + self->priv.p->clat_address_4 = NULL; + } + } + l3cd_old = g_steal_pointer(&self->priv.p->combined_l3cd_merged); self->priv.p->combined_l3cd_merged = nm_l3_config_data_seal(g_steal_pointer(&l3cd)); merged_changed = TRUE; @@ -5379,6 +5534,305 @@ _l3_commit_one(NML3Cfg *self, _failedobj_handle_routes(self, addr_family, routes_failed); } +#ifdef HAVE_CLAT +static void +_l3_get_pref64_config(NML3Cfg *self, + struct clat_v4_config_key *v4_key, + struct clat_v4_config_value *v4_value, + struct clat_v6_config_key *v6_key, + struct clat_v6_config_value *v6_value, + gboolean commited) +{ + const NML3ConfigData *l3cd = self->priv.p->combined_l3cd_commited; + struct in6_addr pref64; + guint32 pref64_plen; + + memset(v4_key, 0, sizeof(*v4_key)); + memset(v6_key, 0, sizeof(*v6_key)); + memset(v4_value, 0, sizeof(*v4_value)); + memset(v6_value, 0, sizeof(*v6_value)); + + v4_key->ifindex = v6_key->ifindex = self->priv.ifindex; + + if (commited) { + if (self->priv.p->clat_address_4_commited) { + v6_value->local_v4.s_addr = v4_key->local_v4.s_addr = + self->priv.p->clat_address_4_commited->addr; + } + if (self->priv.p->clat_address_6_commited_valid) { + v4_value->local_v6 = v6_key->local_v6 = self->priv.p->clat_address_6_commited.address; + } + if (self->priv.p->clat_pref64_valid) { + v6_key->pref64 = v4_value->pref64 = self->priv.p->clat_pref64; + } + } else { + if (self->priv.p->clat_address_4) { + v6_value->local_v4.s_addr = v4_key->local_v4.s_addr = + self->priv.p->clat_address_4->addr; + } + if (self->priv.p->clat_address_6_valid) { + v4_value->local_v6 = v6_key->local_v6 = self->priv.p->clat_address_6.address; + } + if (nm_l3_config_data_get_pref64(l3cd, &pref64, &pref64_plen)) { + v6_key->pref64 = v4_value->pref64 = pref64; + } + } +} + +static void +_l3_clat_destroy(NML3Cfg *self) +{ + DECLARE_LIBBPF_OPTS(bpf_tc_hook, + hook, + .attach_point = BPF_TC_INGRESS | BPF_TC_EGRESS, + .ifindex = self->priv.ifindex); + int err = 0; + char buf[100]; + + hook.attach_point = BPF_TC_INGRESS; + self->priv.p->clat_attach_ingress.prog_fd = 0; + self->priv.p->clat_attach_ingress.prog_id = 0; + err = bpf_tc_detach(&hook, &self->priv.p->clat_attach_ingress); + if (err) { + libbpf_strerror(err, buf, sizeof(buf)); + _LOGW("failed to tear down CLAT BPF ingress hook: %s", buf); + } + hook.attach_point = BPF_TC_EGRESS; + self->priv.p->clat_attach_egress.prog_fd = 0; + self->priv.p->clat_attach_egress.prog_id = 0; + err = bpf_tc_detach(&hook, &self->priv.p->clat_attach_egress); + if (err) { + libbpf_strerror(err, buf, sizeof(buf)); + _LOGW("failed to tear down CLAT BPF egress hook: %s", buf); + } + nm_clear_pointer(&self->priv.p->clat_bpf, clat_bpf__destroy); +} + +static void +_l3_commit_pref64(NML3Cfg *self, NML3CfgCommitType commit_type) +{ + int err = 0; + const NML3ConfigData *l3cd = self->priv.p->combined_l3cd_commited; + struct in6_addr _l3cd_pref64_inner; + const struct in6_addr *l3cd_pref64 = NULL; + guint32 l3cd_pref64_plen; + char buf[100]; + struct clat_v6_config_key v6_key_commited, v6_key_new; + struct clat_v4_config_key v4_key_commited, v4_key_new; + struct clat_v6_config_value v6_value_commited, v6_value_new; + struct clat_v4_config_value v4_value_commited, v4_value_new; + gboolean v6_changed; + DECLARE_LIBBPF_OPTS(bpf_tc_hook, + hook, + .attach_point = BPF_TC_INGRESS | BPF_TC_EGRESS, + .ifindex = self->priv.ifindex); + DECLARE_LIBBPF_OPTS(bpf_tc_opts, attach_egress); + DECLARE_LIBBPF_OPTS(bpf_tc_opts, attach_ingress); + + if (l3cd && nm_l3_config_data_get_pref64(l3cd, &_l3cd_pref64_inner, &l3cd_pref64_plen)) { + l3cd_pref64 = &_l3cd_pref64_inner; + } + + if (l3cd_pref64 && self->priv.p->clat_address_4 && self->priv.p->clat_address_6_valid) { + if (!self->priv.p->clat_bpf) { + /* Bring up the CLAT */ + self->priv.p->clat_bpf = clat_bpf__open(); + err = libbpf_get_error(self->priv.p->clat_bpf); + if (err) { + libbpf_strerror(err, buf, sizeof(buf)); + _LOGW("failed to load CLAT BPF: %s", buf); + clat_bpf__destroy(self->priv.p->clat_bpf); + self->priv.p->clat_bpf = NULL; + return; + } + + /* Set up maps */ + bpf_map__set_max_entries(self->priv.p->clat_bpf->maps.v4_config_map, 16); + bpf_map__set_max_entries(self->priv.p->clat_bpf->maps.v6_config_map, 16); + + err = clat_bpf__load(self->priv.p->clat_bpf); + if (err) { + libbpf_strerror(err, buf, sizeof(buf)); + _LOGW("failed to open CLAT BPF: %s", buf); + clat_bpf__destroy(self->priv.p->clat_bpf); + self->priv.p->clat_bpf = NULL; + return; + } + + err = bpf_tc_hook_create(&hook); + if (err && err != -EEXIST) { + libbpf_strerror(err, buf, sizeof(buf)); + _LOGW("failed to create CLAT BPF hook: %s", buf); + clat_bpf__destroy(self->priv.p->clat_bpf); + self->priv.p->clat_bpf = NULL; + return; + } + + attach_ingress.prog_fd = bpf_program__fd(self->priv.p->clat_bpf->progs.clat_ingress); + if (attach_ingress.prog_fd < 0) { + _LOGW("couldn't find CLAT BPF ingress!"); + clat_bpf__destroy(self->priv.p->clat_bpf); + self->priv.p->clat_bpf = NULL; + return; + } + + attach_egress.prog_fd = bpf_program__fd(self->priv.p->clat_bpf->progs.clat_egress); + if (attach_egress.prog_fd < 0) { + _LOGW("couldn't find CLAT BPF egress!"); + clat_bpf__destroy(self->priv.p->clat_bpf); + self->priv.p->clat_bpf = NULL; + return; + } + + hook.attach_point = BPF_TC_INGRESS; + err = bpf_tc_attach(&hook, &attach_ingress); + if (err) { + libbpf_strerror(err, buf, sizeof(buf)); + _LOGW("failed to attach ingress to CLAT BPF hook: %s", buf); + clat_bpf__destroy(self->priv.p->clat_bpf); + self->priv.p->clat_bpf = NULL; + return; + } + self->priv.p->clat_attach_ingress = attach_ingress; + + hook.attach_point = BPF_TC_EGRESS; + err = bpf_tc_attach(&hook, &attach_egress); + if (err) { + libbpf_strerror(err, buf, sizeof(buf)); + _LOGW("failed to attach ingress to CLAT BPF hook: %s", buf); + clat_bpf__destroy(self->priv.p->clat_bpf); + self->priv.p->clat_bpf = NULL; + return; + } + self->priv.p->clat_attach_egress = attach_egress; + } + + _l3_get_pref64_config(self, + &v4_key_commited, + &v4_value_commited, + &v6_key_commited, + &v6_value_commited, + TRUE); + _l3_get_pref64_config(self, &v4_key_new, &v4_value_new, &v6_key_new, &v6_value_new, FALSE); + + if (memcmp(&v4_key_commited, &v4_key_new, sizeof(v4_key_new))) { + bpf_map_delete_elem(bpf_map__fd(self->priv.p->clat_bpf->maps.v4_config_map), + &v4_key_commited); + } + if (memcmp(&v6_key_commited, &v6_key_new, sizeof(v6_key_new))) { + bpf_map_delete_elem(bpf_map__fd(self->priv.p->clat_bpf->maps.v6_config_map), + &v6_key_commited); + } + if (memcmp(&v4_value_commited, &v4_value_new, sizeof(v4_value_new))) { + bpf_map_update_elem(bpf_map__fd(self->priv.p->clat_bpf->maps.v4_config_map), + &v4_key_new, + &v4_value_new, + BPF_ANY); + } + if (memcmp(&v6_value_commited, &v6_value_new, sizeof(v6_value_new))) { + bpf_map_update_elem(bpf_map__fd(self->priv.p->clat_bpf->maps.v6_config_map), + &v6_key_new, + &v6_value_new, + BPF_ANY); + } + + if (l3cd_pref64) { + self->priv.p->clat_pref64_valid = TRUE; + self->priv.p->clat_pref64 = *l3cd_pref64; + } else { + self->priv.p->clat_pref64_valid = FALSE; + } + + if (self->priv.p->clat_socket < 0) { + self->priv.p->clat_socket = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); + if (self->priv.p->clat_socket < 0) { + _LOGW("Couldn't create CLAT socket for %d! %s", + self->priv.ifindex, + nm_strerror_native(errno)); + } + } + if (self->priv.p->clat_socket >= 0) { + err = setsockopt(self->priv.p->clat_socket, + SOL_SOCKET, + SO_BINDTOIFINDEX, + &self->priv.ifindex, + sizeof(self->priv.ifindex)); + if (err) { + _LOGW("Couldn't bind CLAT socket to interface %d! %s", + self->priv.ifindex, + nm_strerror_native(errno)); + } + } + + v6_changed = + (self->priv.p->clat_address_6_valid != self->priv.p->clat_address_6_commited_valid) + || (self->priv.p->clat_address_6_valid && self->priv.p->clat_address_6_commited_valid + && memcmp(&self->priv.p->clat_address_6.address, + &self->priv.p->clat_address_6_commited.address, + sizeof(self->priv.p->clat_address_6_commited.address))); + + if (self->priv.p->clat_socket > 0 && v6_changed) { + struct ipv6_mreq mreq = {.ipv6mr_interface = self->priv.ifindex}; + + if (self->priv.p->clat_address_6_commited_valid) { + mreq.ipv6mr_multiaddr = self->priv.p->clat_address_6_commited.address; + + err = setsockopt(self->priv.p->clat_socket, + SOL_IPV6, + IPV6_LEAVE_ANYCAST, + &mreq, + sizeof(mreq)); + if (err) { + _LOGW("Couldn't leave CLAT anycast group on interface %d! %s", + self->priv.ifindex, + nm_strerror_native(errno)); + } + } + + if (self->priv.p->clat_address_6_valid) { + mreq.ipv6mr_multiaddr = self->priv.p->clat_address_6.address; + + err = setsockopt(self->priv.p->clat_socket, + SOL_IPV6, + IPV6_JOIN_ANYCAST, + &mreq, + sizeof(mreq)); + if (err) { + _LOGW("Couldn't join CLAT anycast group on interface %d! %s", + self->priv.ifindex, + nm_strerror_native(errno)); + } + } + } + } else { + if (self->priv.p->clat_bpf) { + _l3_clat_destroy(self); + } + + /* Commited will get cleaned up below */ + self->priv.p->clat_address_6_valid = FALSE; + + nm_clear_fd(&self->priv.p->clat_socket); + + /* Deallocate the v4 address. Commited address will get cleaned up below, + but we need to make sure there's no double-free */ + if (self->priv.p->clat_address_4 != self->priv.p->clat_address_4_commited) { + nm_clear_pointer(&self->priv.p->clat_address_4, nm_netns_ip_range_release); + } else { + self->priv.p->clat_address_4 = NULL; + } + } + + /* Record the new state */ + if (self->priv.p->clat_address_4_commited != self->priv.p->clat_address_4) { + nm_clear_pointer(&self->priv.p->clat_address_4_commited, nm_netns_ip_range_release); + self->priv.p->clat_address_4_commited = self->priv.p->clat_address_4; + } + self->priv.p->clat_address_6_commited = self->priv.p->clat_address_6; + self->priv.p->clat_address_6_commited_valid = self->priv.p->clat_address_6_valid; +} +#endif /* HAVE_CLAT */ + static void _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) { @@ -5461,6 +5915,10 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) _l3_acd_data_process_changes(self); +#ifdef HAVE_CLAT + _l3_commit_pref64(self, commit_type); +#endif /* HAVE_CLAT */ + nm_assert(self->priv.p->commit_reentrant_count == 1); self->priv.p->commit_reentrant_count--; @@ -5842,6 +6300,8 @@ nm_l3cfg_init(NML3Cfg *self) { self->priv.p = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_L3CFG, NML3CfgPrivate); + self->priv.p->clat_socket = -1; + c_list_init(&self->priv.p->acd_lst_head); c_list_init(&self->priv.p->acd_event_notify_lst_head); c_list_init(&self->priv.p->commit_type_lst_head); @@ -5896,6 +6356,9 @@ finalize(GObject *object) { NML3Cfg *self = NM_L3CFG(object); gboolean changed; +#ifdef HAVE_CLAT + DECLARE_LIBBPF_OPTS(bpf_tc_hook, hook, .attach_point = BPF_TC_INGRESS | BPF_TC_EGRESS); +#endif /* HAVE_CLAT */ if (self->priv.netns) { nm_netns_watcher_remove_all(self->priv.netns, _NETNS_WATCHER_IP_ADDR_TAG(self, AF_INET)); @@ -5950,6 +6413,14 @@ finalize(GObject *object) if (changed) nmp_global_tracker_sync_mptcp_addrs(self->priv.global_tracker, FALSE); +#ifdef HAVE_CLAT + self->priv.p->clat_address_4_commited = NULL; + nm_clear_pointer(&self->priv.p->clat_address_4, nm_netns_ip_range_release); + nm_clear_fd(&self->priv.p->clat_socket); + if (self->priv.p->clat_bpf) { + _l3_clat_destroy(self); + } +#endif g_clear_object(&self->priv.netns); g_clear_object(&self->priv.platform); nm_clear_pointer(&self->priv.global_tracker, nmp_global_tracker_unref); diff --git a/src/libnm-base/nm-base.h b/src/libnm-base/nm-base.h index 048c02416f..7806f7e750 100644 --- a/src/libnm-base/nm-base.h +++ b/src/libnm-base/nm-base.h @@ -345,6 +345,7 @@ typedef enum { NM_IP_CONFIG_SOURCE_VPN, NM_IP_CONFIG_SOURCE_DHCP, NM_IP_CONFIG_SOURCE_NDISC, + NM_IP_CONFIG_SOURCE_CLAT, NM_IP_CONFIG_SOURCE_USER, } NMIPConfigSource;