Add support for CLAT to l3cfg

This commit is contained in:
Mary Strodl 2025-01-30 15:59:57 -05:00 committed by Beniamino Galvani
parent 3919f06e98
commit e262c84032
4 changed files with 555 additions and 0 deletions

View file

@ -50,6 +50,9 @@ struct _NML3ConfigData {
const NMPObject *best_default_route_x[2]; const NMPObject *best_default_route_x[2];
}; };
struct in6_addr pref64_prefix;
guint32 pref64_plen;
GArray *wins; GArray *wins;
GArray *nis_servers; GArray *nis_servers;
@ -168,6 +171,8 @@ struct _NML3ConfigData {
bool routed_dns_4 : 1; bool routed_dns_4 : 1;
bool routed_dns_6 : 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); _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]) { if (self->dhcp_lease_x[IS_IPv4]) {
gs_free NMUtilsNamedValue *options_free = NULL; gs_free NMUtilsNamedValue *options_free = NULL;
NMUtilsNamedValue options_buffer[30]; 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, .ndisc_retrans_timer_msec_set = FALSE,
.allow_routes_without_address_4 = TRUE, .allow_routes_without_address_4 = TRUE,
.allow_routes_without_address_6 = TRUE, .allow_routes_without_address_6 = TRUE,
.pref64_valid = FALSE,
}; };
_idx_type_init(&self->idx_addresses_4, NMP_OBJECT_TYPE_IP4_ADDRESS); _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); 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 NMMptcpFlags
nm_l3_config_data_get_mptcp_flags(const NML3ConfigData *self) 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_4, b->routed_dns_4);
NM_CMP_DIRECT_UNSAFE(a->routed_dns_6, b->routed_dns_6); 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); NM_CMP_FIELD(a, b, source);
} }
@ -3595,6 +3662,12 @@ nm_l3_config_data_merge(NML3ConfigData *self,
self->routed_dns_4 = TRUE; self->routed_dns_4 = TRUE;
if (src->routed_dns_6) if (src->routed_dns_6)
self->routed_dns_6 = TRUE; 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 * NML3ConfigData *

View file

@ -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); 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); NMMptcpFlags nm_l3_config_data_get_mptcp_flags(const NML3ConfigData *self);
gboolean nm_l3_config_data_set_mptcp_flags(NML3ConfigData *self, NMMptcpFlags mptcp_flags); gboolean nm_l3_config_data_set_mptcp_flags(NML3ConfigData *self, NMMptcpFlags mptcp_flags);

View file

@ -11,6 +11,10 @@
#include <linux/if_ether.h> #include <linux/if_ether.h>
#include <linux/rtnetlink.h> #include <linux/rtnetlink.h>
#include <linux/fib_rules.h> #include <linux/fib_rules.h>
#ifdef HAVE_CLAT
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#endif /* HAVE_CLAT */
#include "libnm-core-aux-intern/nm-libnm-core-utils.h" #include "libnm-core-aux-intern/nm-libnm-core-utils.h"
#include "libnm-glib-aux/nm-prioq.h" #include "libnm-glib-aux/nm-prioq.h"
@ -22,6 +26,13 @@
#include "n-acd/src/n-acd.h" #include "n-acd/src/n-acd.h"
#include "nm-l3-ipv4ll.h" #include "nm-l3-ipv4ll.h"
#include "nm-ip-config.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]; 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. */ /* Whether we earlier configured MPTCP endpoints for the interface. */
union { union {
struct { struct {
@ -353,6 +377,10 @@ typedef struct _NML3CfgPrivate {
bool rp_filter_handled : 1; bool rp_filter_handled : 1;
bool rp_filter_set : 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; } NML3CfgPrivate;
struct _NML3CfgClass { struct _NML3CfgClass {
@ -4123,6 +4151,9 @@ _l3cfg_update_combined_config(NML3Cfg *self,
guint i; guint i;
gboolean merged_changed = FALSE; gboolean merged_changed = FALSE;
gboolean commited_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(NM_IS_L3CFG(self));
nm_assert(!out_old || !*out_old); nm_assert(!out_old || !*out_old);
@ -4220,6 +4251,120 @@ _l3cfg_update_combined_config(NML3Cfg *self,
&hook_data); &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) { if (self->priv.ifindex == NM_LOOPBACK_IFINDEX) {
NMPlatformIPXAddress ax; NMPlatformIPXAddress ax;
NMPlatformIPXRoute rx; 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)) if (nm_l3_config_data_equal(l3cd, self->priv.p->combined_l3cd_merged))
goto out; 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); 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)); self->priv.p->combined_l3cd_merged = nm_l3_config_data_seal(g_steal_pointer(&l3cd));
merged_changed = TRUE; merged_changed = TRUE;
@ -5379,6 +5534,305 @@ _l3_commit_one(NML3Cfg *self,
_failedobj_handle_routes(self, addr_family, routes_failed); _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 static void
_l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) _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); _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); nm_assert(self->priv.p->commit_reentrant_count == 1);
self->priv.p->commit_reentrant_count--; 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 = 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_lst_head);
c_list_init(&self->priv.p->acd_event_notify_lst_head); c_list_init(&self->priv.p->acd_event_notify_lst_head);
c_list_init(&self->priv.p->commit_type_lst_head); c_list_init(&self->priv.p->commit_type_lst_head);
@ -5896,6 +6356,9 @@ finalize(GObject *object)
{ {
NML3Cfg *self = NM_L3CFG(object); NML3Cfg *self = NM_L3CFG(object);
gboolean changed; 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) { if (self->priv.netns) {
nm_netns_watcher_remove_all(self->priv.netns, _NETNS_WATCHER_IP_ADDR_TAG(self, AF_INET)); 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) if (changed)
nmp_global_tracker_sync_mptcp_addrs(self->priv.global_tracker, FALSE); 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.netns);
g_clear_object(&self->priv.platform); g_clear_object(&self->priv.platform);
nm_clear_pointer(&self->priv.global_tracker, nmp_global_tracker_unref); nm_clear_pointer(&self->priv.global_tracker, nmp_global_tracker_unref);

View file

@ -345,6 +345,7 @@ typedef enum {
NM_IP_CONFIG_SOURCE_VPN, NM_IP_CONFIG_SOURCE_VPN,
NM_IP_CONFIG_SOURCE_DHCP, NM_IP_CONFIG_SOURCE_DHCP,
NM_IP_CONFIG_SOURCE_NDISC, NM_IP_CONFIG_SOURCE_NDISC,
NM_IP_CONFIG_SOURCE_CLAT,
NM_IP_CONFIG_SOURCE_USER, NM_IP_CONFIG_SOURCE_USER,
} NMIPConfigSource; } NMIPConfigSource;