merge: branch 'feature/mstrodl/clat'

Add support for CLAT using a BPF program

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2107
This commit is contained in:
Mary Strodl 2025-12-17 18:05:58 +00:00
commit 4ecba23dc7
41 changed files with 2638 additions and 574 deletions

View file

@ -60,11 +60,11 @@ variables:
#
# This is done by running `ci-fairy generate-template` and possibly bumping
# ".default_tag".
ALPINE_TAG: 'tag-0c3a6f855fb8'
CENTOS_TAG: 'tag-c1c23df75dda'
DEBIAN_TAG: 'tag-d4bf5db9e214'
FEDORA_TAG: 'tag-c1c23df75dda'
UBUNTU_TAG: 'tag-d4bf5db9e214'
ALPINE_TAG: 'tag-8e4bbc59695b'
CENTOS_TAG: 'tag-caf6673db1a7'
DEBIAN_TAG: 'tag-e394e8e726e1'
FEDORA_TAG: 'tag-caf6673db1a7'
UBUNTU_TAG: 'tag-e394e8e726e1'
ALPINE_EXEC: 'bash .gitlab-ci/alpine-install.sh'
CENTOS_EXEC: 'bash .gitlab-ci/fedora-install.sh'

4
NEWS
View file

@ -23,6 +23,10 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE!
the 802.1X certificates and keys set in the connection.
* Introduce a libnm function that can be used by VPN plugins to check
user permissions on certificate and keys.
* Add support for CLAT (464XLAT) using a BPF program.
* Change the default value of the ipv4.dhcp-ipv6-only-preferred property
to a new value "auto" which automatically enables the option when CLAT
is enabled for the connection.
=============================================
NetworkManager-1.56

View file

@ -294,3 +294,6 @@
/* Define to 1 if dlvsym() is available */
#mesondefine HAVE_DLVSYM
/* Define to 1 if you want CLAT support. */
#mesondefine HAVE_CLAT

View file

@ -8,6 +8,7 @@ apk add \
'alpine-sdk' \
'autoconf' \
'bash' \
'bpftool' \
'clang' \
'curl-dev' \
'dbus' \
@ -23,6 +24,7 @@ apk add \
'iproute2' \
'iptables' \
'jansson-dev' \
'libbpf-dev' \
'libgudev-dev' \
'libndp-dev' \
'libnvme-dev' \

View file

@ -32,6 +32,7 @@ install_ignore_missing() {
install \
\
bpftool \
clang \
dbus \
dbus-x11 \
@ -43,6 +44,7 @@ install \
iproute2 \
iptables \
libaudit-dev \
libbpf-dev \
libcurl4-gnutls-dev \
libdbus-1-dev \
libgirepository1.0-dev \

View file

@ -49,6 +49,7 @@ install \
ModemManager-glib-devel \
audit-libs-devel \
bluez-libs-devel \
bpftool \
clang \
dbus-devel \
dbus-x11 \
@ -64,6 +65,7 @@ install \
iptables \
jansson-devel \
jq \
libbpf-devel \
libcurl-devel \
libndp-devel \
libnvme-devel \

View file

@ -198,6 +198,7 @@ Requires(postun): systemd
Requires: dbus >= %{dbus_version}
Requires: glib2 >= %{glib2_version}
Requires: %{name}-libnm%{?_isa} = %{epoch}:%{version}-%{release}
Requires: libbpf
Recommends: iputils
@ -248,6 +249,7 @@ Conflicts: NetworkManager-dispatcher-routing-rules <= 1:1.47.5-3
%endif
BuildRequires: gcc
BuildRequires: clang
BuildRequires: pkgconfig
BuildRequires: meson
BuildRequires: gettext-devel >= 0.19.8
@ -302,6 +304,8 @@ BuildRequires: firewalld-filesystem
BuildRequires: iproute
BuildRequires: iproute-tc
BuildRequires: libnvme-devel >= 1.5
BuildRequires: libbpf-devel
BuildRequires: bpftool
Provides: %{name}-dispatcher%{?_isa} = %{epoch}:%{version}-%{release}

View file

@ -972,7 +972,7 @@ ipv6.ip6-privacy=0
</varlistentry>
<varlistentry>
<term><varname>ipv4.dhcp-ipv6-only-preferred</varname></term>
<listitem><para>If left unspecified, the "IPv6-only preferred" DHCPv4 option is disabled.</para></listitem>
<listitem><para>If left unspecified, it defaults to "auto".</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>ipv4.dhcp-hostname-flags</varname></term>
@ -1026,6 +1026,10 @@ ipv6.ip6-privacy=0
global default is used. If the default is unspecified, the fallback value is either "stable-privacy"
or "eui64", depending on whether the per-profile setting is "default" or "default-or-eui64, respectively.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>ipv6.clat</varname></term>
<listitem><para>If left unspecified, CLAT is disabled.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>ipv6.ra-timeout</varname></term>
<listitem><para>If left unspecified, the default value depends on the sysctl solicitation settings.</para></listitem>

View file

@ -263,7 +263,7 @@ uuid_dep = dependency('uuid')
libelogind_dep = dependency('libelogind', version: '>= 219', required: false)
libudev_dep = dependency('libudev', version: '>= 175')
dbus_dep = dependency('dbus-1', version: '>= 1.1')
libndp_dep = dependency('libndp')
libndp_dep = dependency('libndp', version: '>= 1.9')
jansson_dep = dependency('jansson', version: '>= 2.7', required: false)
config_h.set10('WITH_JANSSON', jansson_dep.found())
@ -485,6 +485,14 @@ if enable_selinux
endif
config_h.set10('HAVE_SELINUX', enable_selinux)
# CLAT support
enable_clat = get_option('clat')
if enable_clat
libbpf = dependency('libbpf', version: '>= 0.1.0', required: false)
assert(libbpf.found(), 'You must have libbpf installed to build. Use -Dclat=false to disable use of it')
endif
config_h.set10('HAVE_CLAT', enable_clat)
# libaudit support
libaudit = get_option('libaudit')
enable_libaudit = libaudit.contains('yes')
@ -1152,5 +1160,6 @@ output += 'have-nss: ' + crypto_nss_dep.found().to_string() + ')\n'
output += ' sanitizers: ' + get_option('b_sanitize') + '\n'
output += ' Mozilla Public Suffix List: ' + enable_libpsl.to_string() + '\n'
output += ' vapi: ' + enable_vapi.to_string() + '\n'
output += ' clat: ' + enable_clat.to_string() + '\n'
output += ' readline: ' + with_readline + '\n'
message(output)

View file

@ -48,6 +48,9 @@ option('nm_cloud_setup', type: 'boolean', value: true, description: 'Build nm-cl
option('bluez5_dun', type: 'boolean', value: false, description: 'enable Bluez5 DUN support')
option('ebpf', type: 'combo', choices: ['auto', 'true', 'false'], description: 'Enable eBPF support (deprecated)')
option('nbft', type: 'boolean', value: true, description: 'Enable NBFT support in the initrd generator')
option('clat', type: 'boolean', value: true, description: 'Build with CLAT support')
option('bpf-compiler', type : 'combo', choices : ['auto', 'clang', 'gcc'],
description : 'compiler used to build BPF programs')
# configuration plugins
option('config_plugins_default', type: 'string', value: '', description: 'Default configuration option for main.plugins setting, used as fallback if the configuration option is unset')

View file

@ -1753,6 +1753,13 @@ nm_utils_ip_routes_to_dbus(int addr_family,
"{sv}",
"next-hop",
g_variant_new_string(nm_inet_ntop(addr_family, gateway, addr_str)));
} else if (addr_family == AF_INET && r->r4.via.addr_family == AF_INET6) {
g_variant_builder_add(
&route_builder,
"{sv}",
"next-hop-v6",
g_variant_new_string(
nm_inet_ntop(AF_INET6, r->r4.via.addr.addr_ptr, addr_str)));
}
g_variant_builder_add(&route_builder,

786
src/core/bpf/clat.bpf.c Normal file
View file

@ -0,0 +1,786 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright 2021 Toke Høiland-Jørgensen <toke@toke.dk> */
/* Copyright 2025 Mary Strodl <mstrodl@csh.rit.edu> */
/**
* This is an implementation of a CLAT in eBPF. BPF is a different environment
* than the rest of NetworkManager, and we don't have access to most of the
* C standard library, so some things might look a little different from what
* you're used to.
*
* Check out src/core/bpf/meson.build to see how this gets built.
**/
#include <linux/bpf.h>
#include <linux/icmp.h>
#include <linux/icmpv6.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/in.h>
#include <linux/in6.h>
#include <linux/pkt_cls.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/if_ether.h>
#include <stdbool.h>
#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>
#include "clat.h"
char _license[] SEC("license") = "GPL";
struct clat_config config;
#ifdef DEBUG
/* Note: when enabling debugging, you also need to add CAP_PERFMON
* to the CapabilityBoundingSet of the NM systemd unit. The messages
* will be printed to /sys/kernel/debug/tracing/trace_pipe */
#define DBG(fmt, ...) \
({ \
char ____fmt[] = "clat: " fmt; \
bpf_trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \
})
#else
#define DBG(fmt, ...)
#endif
/* Macros to read the sk_buff data* pointers, preventing the compiler
* from generating a 32-bit register spill. */
#define SKB_ACCESS_MEMBER_32(_skb, member) \
({ \
void *ptr; \
\
asm volatile("%0 = *(u32 *)(%1 + %2)" \
: "=r"(ptr) \
: "r"(_skb), "i"(offsetof(struct __sk_buff, member))); \
\
ptr; \
})
#define SKB_DATA(_skb) SKB_ACCESS_MEMBER_32(_skb, data)
#define SKB_DATA_END(_skb) SKB_ACCESS_MEMBER_32(_skb, data_end)
struct icmpv6_pseudo {
struct in6_addr saddr;
struct in6_addr daddr;
__u32 len;
__u8 padding[3];
__u8 nh;
} __attribute__((packed));
static void
update_l4_checksum(struct __sk_buff *skb,
struct ipv6hdr *ip6h,
struct iphdr *iph,
int ip_type,
bool v4to6)
{
int flags = BPF_F_PSEUDO_HDR;
__u16 offset;
__u32 csum;
if (v4to6) {
void *from_ptr = &iph->saddr;
void *to_ptr = &ip6h->saddr;
csum = bpf_csum_diff(from_ptr, 2 * sizeof(__u32), to_ptr, 2 * sizeof(struct in6_addr), 0);
offset = sizeof(struct ethhdr) + sizeof(struct iphdr);
} else {
void *from_ptr = &ip6h->saddr;
void *to_ptr = &iph->saddr;
csum = bpf_csum_diff(from_ptr, 2 * sizeof(struct in6_addr), to_ptr, 2 * sizeof(__u32), 0);
offset = sizeof(struct ethhdr) + sizeof(struct ipv6hdr);
}
switch (ip_type) {
case IPPROTO_TCP:
offset += offsetof(struct tcphdr, check);
break;
case IPPROTO_UDP:
offset += offsetof(struct udphdr, check);
flags |= BPF_F_MARK_MANGLED_0;
break;
default:
return;
}
bpf_l4_csum_replace(skb, offset, 0, csum, flags);
}
static void
update_icmp_checksum(struct __sk_buff *skb,
const struct ipv6hdr *ip6h,
void *icmp_before,
void *icmp_after,
bool v4to6)
{
struct icmpv6_pseudo ph = {.nh = IPPROTO_ICMPV6, .len = ip6h->payload_len};
__u16 h_before, h_after;
__u32 u_before, u_after;
__u16 offset;
__u32 csum;
__builtin_memcpy(&ph.saddr, &ip6h->saddr, sizeof(struct in6_addr));
__builtin_memcpy(&ph.daddr, &ip6h->daddr, sizeof(struct in6_addr));
/* Do checksum update in two passes: first compute the incremental
* checksum update of the ICMPv6 pseudo header, update the checksum
* using bpf_l4_csum_replace(), and then do a separate update for the
* ICMP type and code (which is two consecutive bytes, so cast them to
* u16). The bpf_csum_diff() helper can be used to compute the
* incremental update of the full block, whereas the
* bpf_l4_csum_replace() helper can do the two-byte diff and update by
* itself.
*/
csum = bpf_csum_diff((__be32 *) &ph,
v4to6 ? 0 : sizeof(ph),
(__be32 *) &ph,
v4to6 ? sizeof(ph) : 0,
0);
offset = sizeof(struct ethhdr) + (v4to6 ? sizeof(struct iphdr) : sizeof(struct ipv6hdr)) + 2;
/* first two bytes of ICMP header, type and code */
h_before = *(__u16 *) icmp_before;
h_after = *(__u16 *) icmp_after;
/* last four bytes of ICMP header, the data union */
u_before = *(__u32 *) (icmp_before + 4);
u_after = *(__u32 *) (icmp_after + 4);
bpf_l4_csum_replace(skb, offset, 0, csum, BPF_F_PSEUDO_HDR);
bpf_l4_csum_replace(skb, offset, h_before, h_after, 2);
if (u_before != u_after)
bpf_l4_csum_replace(skb, offset, u_before, u_after, 4);
}
static int
rewrite_icmp(struct __sk_buff *skb, const struct ipv6hdr *ip6h)
{
void *data_end = SKB_DATA_END(skb);
void *data = SKB_DATA(skb);
struct icmphdr icmp_buf; /* copy of the old ICMPv4 header */
struct icmp6hdr icmp6_buf; /* buffer for the new ICMPv6 header */
struct icmphdr *icmp;
struct icmp6hdr *icmp6;
__u32 mtu;
icmp = data + sizeof(struct ethhdr) + sizeof(struct iphdr);
if ((icmp + 1) > data_end)
return -1;
icmp_buf = *icmp;
icmp6 = (void *) icmp;
icmp6_buf = *icmp6;
/* These translations are defined in RFC6145 section 4.2 */
switch (icmp->type) {
case ICMP_ECHO:
icmp6_buf.icmp6_type = ICMPV6_ECHO_REQUEST;
break;
case ICMP_ECHOREPLY:
icmp6_buf.icmp6_type = ICMPV6_ECHO_REPLY;
break;
case ICMP_DEST_UNREACH:
icmp6_buf.icmp6_type = ICMPV6_DEST_UNREACH;
switch (icmp->code) {
case ICMP_NET_UNREACH:
case ICMP_HOST_UNREACH:
case ICMP_SR_FAILED:
case ICMP_NET_UNKNOWN:
case ICMP_HOST_UNKNOWN:
case ICMP_HOST_ISOLATED:
case ICMP_NET_UNR_TOS:
case ICMP_HOST_UNR_TOS:
icmp6_buf.icmp6_code = ICMPV6_NOROUTE;
break;
case ICMP_PROT_UNREACH:
icmp6_buf.icmp6_type = ICMPV6_PARAMPROB;
icmp6_buf.icmp6_code = ICMPV6_UNK_NEXTHDR;
icmp6_buf.icmp6_pointer = bpf_htonl(offsetof(struct ipv6hdr, nexthdr));
case ICMP_PORT_UNREACH:
icmp6_buf.icmp6_code = ICMPV6_PORT_UNREACH;
break;
case ICMP_FRAG_NEEDED:
icmp6_buf.icmp6_type = ICMPV6_PKT_TOOBIG;
icmp6_buf.icmp6_code = 0;
mtu = bpf_ntohs(icmp->un.frag.mtu) + 20;
/* RFC6145 section 6, "second approach" - should not be
* necessary, but might as well do this
*/
if (mtu < 1280)
mtu = 1280;
icmp6_buf.icmp6_mtu = bpf_htonl(mtu);
case ICMP_NET_ANO:
case ICMP_HOST_ANO:
case ICMP_PKT_FILTERED:
case ICMP_PREC_CUTOFF:
icmp6_buf.icmp6_code = ICMPV6_ADM_PROHIBITED;
default:
return -1;
}
break;
case ICMP_PARAMETERPROB:
if (icmp->code == 1)
return -1;
icmp6_buf.icmp6_type = ICMPV6_PARAMPROB;
icmp6_buf.icmp6_code = ICMPV6_HDR_FIELD;
/* The pointer field not defined in the Linux header. This
* translation is from Figure 3 of RFC6145.
*/
switch (icmp->un.reserved[0]) {
case 0: /* version/IHL */
icmp6_buf.icmp6_pointer = 0;
break;
case 1: /* Type of Service */
icmp6_buf.icmp6_pointer = bpf_htonl(1);
break;
case 2: /* Total length */
case 3:
icmp6_buf.icmp6_pointer = bpf_htonl(4);
break;
case 8: /* Time to Live */
icmp6_buf.icmp6_pointer = bpf_htonl(7);
break;
case 9: /* Protocol */
icmp6_buf.icmp6_pointer = bpf_htonl(6);
break;
case 12: /* Source address */
case 13:
case 14:
case 15:
icmp6_buf.icmp6_pointer = bpf_htonl(8);
break;
case 16: /* Destination address */
case 17:
case 18:
case 19:
icmp6_buf.icmp6_pointer = bpf_htonl(24);
break;
default:
return -1;
}
default:
return -1;
}
*icmp6 = icmp6_buf;
update_icmp_checksum(skb, ip6h, &icmp_buf, icmp6, true);
/* FIXME: also need to rewrite IP header embedded in ICMP error */
return 0;
}
/*
* Convert an IPv4 address to the corresponding "IPv4-Embedded IPv6 Address"
* according to RFC 6052 2.2.
*
* +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
* |PL| 0-------------32--40--48--56--64--72--80--88--96--104---------|
* +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
* |32| prefix |v4(32) | u | suffix |
* +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
* |40| prefix |v4(24) | u |(8)| suffix |
* +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
* |48| prefix |v4(16) | u | (16) | suffix |
* +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
* |56| prefix |(8)| u | v4(24) | suffix |
* +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
* |64| prefix | u | v4(32) | suffix |
* +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
* |96| prefix | v4(32) |
* +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*
*/
static __always_inline bool
v4addr_to_v6(__be32 addr4, struct in6_addr *addr6, const struct in6_addr *pref64, int pref64_len)
{
union {
__be32 a32;
__u8 a8[4];
} u;
u.a32 = addr4;
addr6->s6_addr32[0] = 0;
addr6->s6_addr32[1] = 0;
addr6->s6_addr32[2] = 0;
addr6->s6_addr32[3] = 0;
switch (pref64_len) {
case 96:
addr6->s6_addr32[0] = pref64->s6_addr32[0];
addr6->s6_addr32[1] = pref64->s6_addr32[1];
addr6->s6_addr32[2] = pref64->s6_addr32[2];
addr6->s6_addr32[3] = addr4;
break;
case 64:
addr6->s6_addr32[0] = pref64->s6_addr32[0];
addr6->s6_addr32[1] = pref64->s6_addr32[1];
addr6->s6_addr[9] = u.a8[0];
addr6->s6_addr[10] = u.a8[1];
addr6->s6_addr[11] = u.a8[2];
addr6->s6_addr[12] = u.a8[3];
break;
case 56:
addr6->s6_addr32[0] = pref64->s6_addr32[0];
addr6->s6_addr32[1] = pref64->s6_addr32[1];
addr6->s6_addr[7] = u.a8[0];
addr6->s6_addr[9] = u.a8[1];
addr6->s6_addr[10] = u.a8[2];
addr6->s6_addr[11] = u.a8[3];
break;
case 48:
addr6->s6_addr32[0] = pref64->s6_addr32[0];
addr6->s6_addr16[2] = pref64->s6_addr16[2];
addr6->s6_addr[6] = u.a8[0];
addr6->s6_addr[7] = u.a8[1];
addr6->s6_addr[9] = u.a8[2];
addr6->s6_addr[10] = u.a8[3];
break;
case 40:
addr6->s6_addr32[0] = pref64->s6_addr32[0];
addr6->s6_addr[4] = pref64->s6_addr[4];
addr6->s6_addr[5] = u.a8[0];
addr6->s6_addr[6] = u.a8[1];
addr6->s6_addr[7] = u.a8[2];
addr6->s6_addr[9] = u.a8[3];
break;
case 32:
addr6->s6_addr32[0] = pref64->s6_addr32[0];
addr6->s6_addr32[1] = addr4;
break;
default:
return false;
}
return true;
}
/*
* Extract the IPv4 address @addr4 and the NAT64 prefix @pref64 from an IPv6 address,
* given the known prefix length @pref64_len. See the table above.
*/
static __always_inline bool
v6addr_to_v4(const struct in6_addr *addr6, int pref64_len, __be32 *addr4, struct in6_addr *pref64)
{
union {
__be32 a32;
__u8 a8[4];
} u;
pref64->s6_addr32[0] = 0;
pref64->s6_addr32[1] = 0;
pref64->s6_addr32[2] = 0;
pref64->s6_addr32[3] = 0;
switch (pref64_len) {
case 96:
u.a32 = addr6->s6_addr32[3];
pref64->s6_addr32[0] = addr6->s6_addr32[0];
pref64->s6_addr32[1] = addr6->s6_addr32[1];
pref64->s6_addr32[2] = addr6->s6_addr32[2];
break;
case 64:
u.a8[0] = addr6->s6_addr[9];
u.a8[1] = addr6->s6_addr[10];
u.a8[2] = addr6->s6_addr[11];
u.a8[3] = addr6->s6_addr[12];
pref64->s6_addr32[0] = addr6->s6_addr32[0];
pref64->s6_addr32[1] = addr6->s6_addr32[1];
break;
case 56:
u.a8[0] = addr6->s6_addr[7];
u.a8[1] = addr6->s6_addr[9];
u.a8[2] = addr6->s6_addr[10];
u.a8[3] = addr6->s6_addr[11];
pref64->s6_addr32[0] = addr6->s6_addr32[0];
pref64->s6_addr32[1] = addr6->s6_addr32[1];
pref64->s6_addr[7] = 0;
break;
case 48:
u.a8[0] = addr6->s6_addr[6];
u.a8[1] = addr6->s6_addr[7];
u.a8[2] = addr6->s6_addr[9];
u.a8[3] = addr6->s6_addr[10];
pref64->s6_addr32[0] = addr6->s6_addr32[0];
pref64->s6_addr32[1] = addr6->s6_addr32[1];
pref64->s6_addr16[3] = 0;
break;
case 40:
u.a8[0] = addr6->s6_addr[5];
u.a8[1] = addr6->s6_addr[6];
u.a8[2] = addr6->s6_addr[7];
u.a8[3] = addr6->s6_addr[9];
pref64->s6_addr32[0] = addr6->s6_addr32[0];
pref64->s6_addr32[1] = addr6->s6_addr32[1];
pref64->s6_addr16[3] = 0;
pref64->s6_addr[5] = 0;
break;
case 32:
u.a32 = addr6->s6_addr32[1];
pref64->s6_addr32[0] = addr6->s6_addr32[0];
break;
default:
return false;
}
*addr4 = u.a32;
return true;
}
/* ipv4 traffic in from application on this device, needs to be translated to v6 and sent to PLAT */
static int
clat_handle_v4(struct __sk_buff *skb)
{
int ret = TC_ACT_OK;
void *data_end = SKB_DATA_END(skb);
void *data = SKB_DATA(skb);
struct ipv6hdr *ip6h;
struct ipv6hdr dst_hdr = {
.version = 6,
};
struct iphdr *iph;
struct ethhdr *eth;
iph = data + sizeof(struct ethhdr);
if (iph + 1 > data_end)
goto out;
if (iph->saddr != config.local_v4.s_addr)
goto out;
/* At this point we know the packet needs translation. If we can't
* rewrite it, it should be dropped.
*/
ret = TC_ACT_SHOT;
/* we don't bother dealing with IP options or fragmented packets. The
* latter are identified by the 'frag_off' field having a value (either
* the MF bit, or the fragmet offset, or both). However, this field also
* contains the "don't fragment" (DF) bit, which we ignore, so mask that
* out. The DF is the second-most-significant bit (as bit 0 is
* reserved).
*/
if (iph->ihl != 5 || (iph->frag_off & ~bpf_htons(1 << 14))) {
DBG("v4: pkt src/dst %pI4/%pI4 has IP options or is fragmented, dropping\n",
&iph->daddr,
&iph->saddr);
goto out;
}
if (!v4addr_to_v6(iph->daddr, &dst_hdr.daddr, &config.pref64, config.pref64_len))
goto out;
dst_hdr.saddr = config.local_v6;
dst_hdr.nexthdr = iph->protocol;
dst_hdr.hop_limit = iph->ttl;
/* weird definition in ipv6hdr */
dst_hdr.priority = (iph->tos & 0x70) >> 4;
dst_hdr.flow_lbl[0] = iph->tos << 4;
dst_hdr.payload_len = bpf_htons(bpf_ntohs(iph->tot_len) - sizeof(struct iphdr));
DBG("v4: Found mapping for dst %pI4 to %pI6c\n", &iph->daddr, &dst_hdr.daddr);
switch (dst_hdr.nexthdr) {
case IPPROTO_ICMP:
if (rewrite_icmp(skb, &dst_hdr))
goto out;
dst_hdr.nexthdr = IPPROTO_ICMPV6;
break;
case IPPROTO_TCP:
case IPPROTO_UDP:
update_l4_checksum(skb, &dst_hdr, iph, dst_hdr.nexthdr, true);
break;
default:
break;
}
if (bpf_skb_change_proto(skb, bpf_htons(ETH_P_IPV6), 0))
goto out;
data = SKB_DATA(skb);
data_end = SKB_DATA_END(skb);
eth = data;
if (eth + 1 > data_end)
goto out;
ip6h = (void *) (eth + 1);
if (ip6h + 1 > data_end)
goto out;
eth->h_proto = bpf_htons(ETH_P_IPV6);
*ip6h = dst_hdr;
ret = bpf_redirect_neigh(skb->ifindex, NULL, 0, 0);
out:
return ret;
}
static __u16
csum_fold_helper(__u32 csum)
{
__u32 sum;
sum = (csum >> 16) + (csum & 0xffff);
sum += (sum >> 16);
return ~sum;
}
static int
rewrite_icmpv6(struct __sk_buff *skb)
{
void *data_end = SKB_DATA_END(skb);
void *data = SKB_DATA(skb);
struct icmp6hdr *icmp6;
struct icmphdr *icmp;
struct icmphdr icmp_buf; /* buffer for the new ICMPv4 header */
struct icmp6hdr icmp6_buf; /* copy of the old ICMPv6 header */
struct ipv6hdr *ip6h;
__u32 mtu;
__u32 ptr;
icmp6 = data + sizeof(struct ethhdr) + sizeof(struct ipv6hdr);
if (icmp6 + 1 > data_end)
return -1;
icmp6_buf = *icmp6;
icmp = (void *) icmp6;
icmp_buf = *icmp;
/* These translations are defined in RFC6145 section 5.2 */
switch (icmp6->icmp6_type) {
case ICMPV6_ECHO_REQUEST:
icmp_buf.type = ICMP_ECHO;
break;
case ICMPV6_ECHO_REPLY:
icmp_buf.type = ICMP_ECHOREPLY;
break;
case ICMPV6_DEST_UNREACH:
icmp_buf.type = ICMP_DEST_UNREACH;
switch (icmp6->icmp6_code) {
case ICMPV6_NOROUTE:
case ICMPV6_NOT_NEIGHBOUR:
case ICMPV6_ADDR_UNREACH:
icmp_buf.code = ICMP_HOST_UNREACH;
break;
case ICMPV6_ADM_PROHIBITED:
icmp_buf.code = ICMP_HOST_ANO;
break;
case ICMPV6_PORT_UNREACH:
icmp_buf.code = ICMP_PORT_UNREACH;
break;
default:
return -1;
}
break;
case ICMPV6_PKT_TOOBIG:
icmp_buf.type = ICMP_DEST_UNREACH;
icmp_buf.code = ICMP_FRAG_NEEDED;
mtu = bpf_htonl(icmp6->icmp6_mtu) - 20;
if (mtu > 0xffff)
return -1;
icmp_buf.un.frag.mtu = bpf_htons(mtu);
break;
case ICMPV6_TIME_EXCEED:
icmp_buf.type = ICMP_TIME_EXCEEDED;
break;
case ICMPV6_PARAMPROB:
switch (icmp6->icmp6_code) {
case 0:
icmp_buf.type = ICMP_PARAMETERPROB;
icmp_buf.code = 0;
break;
case 1:
icmp_buf.type = ICMP_DEST_UNREACH;
icmp_buf.code = ICMP_PROT_UNREACH;
ptr = bpf_ntohl(icmp6->icmp6_pointer);
/* Figure 6 in RFC6145 - using if statements b/c of
* range at the bottom
*/
if (ptr == 0 || ptr == 1)
icmp_buf.un.reserved[0] = ptr;
else if (ptr == 4 || ptr == 5)
icmp_buf.un.reserved[0] = 2;
else if (ptr == 6)
icmp_buf.un.reserved[0] = 9;
else if (ptr == 7)
icmp_buf.un.reserved[0] = 8;
else if (ptr >= 8 && ptr <= 23)
icmp_buf.un.reserved[0] = 12;
else if (ptr >= 24 && ptr <= 39)
icmp_buf.un.reserved[0] = 16;
else
return -1;
break;
default:
return -1;
}
break;
default:
return -1;
}
*icmp = icmp_buf;
ip6h = data + sizeof(struct ethhdr);
update_icmp_checksum(skb, ip6h, &icmp6_buf, icmp, false);
/* FIXME: also need to rewrite IP header embedded in ICMP error */
return 0;
}
static __always_inline bool
v6addr_equal(struct in6_addr *a, struct in6_addr *b)
{
int i;
for (i = 0; i < 4; i++) {
if (a->s6_addr32[i] != b->s6_addr32[i])
return false;
}
return true;
}
/* ipv6 traffic from the PLAT, to be translated into ipv4 and sent to an application */
static int
clat_handle_v6(struct __sk_buff *skb)
{
int ret = TC_ACT_OK;
void *data_end = SKB_DATA_END(skb);
void *data = SKB_DATA(skb);
__be32 saddr4;
struct in6_addr subnet_v6;
struct ethhdr *eth;
struct iphdr *iph;
struct ipv6hdr *ip6h;
struct iphdr dst_hdr = {
.version = 4,
.ihl = 5,
.frag_off = bpf_htons(1 << 14), /* set Don't Fragment bit */
};
ip6h = data + sizeof(struct ethhdr);
if (ip6h + 1 > data_end)
goto out;
if (!v6addr_equal(&ip6h->daddr, &config.local_v6))
goto out;
if (!v6addr_to_v4(&ip6h->saddr, config.pref64_len, &saddr4, &subnet_v6))
goto out;
if (!v6addr_equal(&subnet_v6, &config.pref64))
goto out;
/* At this point we know the packet needs translation. If we can't
* rewrite it, it should be dropped.
*/
ret = TC_ACT_SHOT;
/* drop packets with extension headers */
if (ip6h->nexthdr != IPPROTO_TCP && ip6h->nexthdr != IPPROTO_UDP
&& ip6h->nexthdr != IPPROTO_ICMPV6)
goto out;
dst_hdr.daddr = config.local_v4.s_addr;
dst_hdr.saddr = saddr4;
dst_hdr.protocol = ip6h->nexthdr;
dst_hdr.ttl = ip6h->hop_limit;
dst_hdr.tos = ip6h->priority << 4 | (ip6h->flow_lbl[0] >> 4);
dst_hdr.tot_len = bpf_htons(bpf_ntohs(ip6h->payload_len) + sizeof(struct iphdr));
switch (dst_hdr.protocol) {
case IPPROTO_ICMPV6:
if (rewrite_icmpv6(skb))
goto out;
dst_hdr.protocol = IPPROTO_ICMP;
break;
case IPPROTO_TCP:
case IPPROTO_UDP:
update_l4_checksum(skb, ip6h, &dst_hdr, dst_hdr.protocol, false);
break;
default:
break;
}
dst_hdr.check = csum_fold_helper(
bpf_csum_diff((__be32 *) &dst_hdr, 0, (__be32 *) &dst_hdr, sizeof(dst_hdr), 0));
if (bpf_skb_change_proto(skb, bpf_htons(ETH_P_IP), 0))
goto out;
data = SKB_DATA(skb);
data_end = SKB_DATA_END(skb);
eth = data;
if (eth + 1 > data_end)
goto out;
iph = (void *) (eth + 1);
if (iph + 1 > data_end)
goto out;
eth->h_proto = bpf_htons(ETH_P_IP);
*iph = dst_hdr;
ret = bpf_redirect(skb->ifindex, BPF_F_INGRESS);
out:
return ret;
}
static __always_inline int
_proto_is_vlan(__u16 h_proto)
{
return !!(h_proto == bpf_htons(ETH_P_8021Q) || h_proto == bpf_htons(ETH_P_8021AD));
}
static int
clat_handler(struct __sk_buff *skb, bool egress)
{
void *data = SKB_DATA(skb);
void *data_end = SKB_DATA_END(skb);
struct ethhdr *eth;
int eth_type;
eth = data;
if (eth + 1 > data_end)
return TC_ACT_OK;
eth_type = eth->h_proto;
if (_proto_is_vlan(eth_type))
return TC_ACT_OK;
if (eth_type == bpf_htons(ETH_P_IP) && egress)
return clat_handle_v4(skb);
else if (eth_type == bpf_htons(ETH_P_IPV6) && !egress)
return clat_handle_v6(skb);
return TC_ACT_OK;
}
SEC("tcx/egress")
int
clat_egress(struct __sk_buff *skb)
{
return clat_handler(skb, true);
}
SEC("tcx/ingress")
int
clat_ingress(struct __sk_buff *skb)
{
return clat_handler(skb, false);
}

14
src/core/bpf/clat.h Normal file
View file

@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __NAT64_H__
#define __NAT64_H__
#include <linux/in6.h>
struct clat_config {
struct in6_addr local_v6;
struct in6_addr pref64;
struct in_addr local_v4;
unsigned pref64_len;
};
#endif

237
src/core/bpf/meson.build Normal file
View file

@ -0,0 +1,237 @@
# SPDX-License-Identifier: LGPL-2.1+
# Ripped from systemd: https://github.com/systemd/systemd/pull/20429
if not enable_clat
subdir_done()
endif
bpf_compiler = get_option('bpf-compiler')
clang_found = false
clang_supports_bpf = false
bpf_gcc_found = false
bpftool_strip = false
if bpf_compiler == 'clang' or bpf_compiler == 'auto'
# Support 'versioned' clang/llvm-strip binaries, as seen on Debian/Ubuntu
# (like clang-10/llvm-strip-10)
if meson.is_cross_build() or cc.get_id() != 'clang' or cc.cmd_array()[0].contains('afl-clang') or cc.cmd_array()[0].contains('hfuzz-clang')
r = find_program('clang',
version : '>= 10.0.0')
clang_found = r.found()
if clang_found
if meson.version().version_compare('>= 0.55')
clang = r.full_path()
else
clang = r.path()
endif
endif
else
clang_found = true
clang = cc.cmd_array()
endif
if clang_found
# Check if 'clang -target bpf' is supported.
clang_supports_bpf = run_command(clang, '-target', 'bpf', '--print-supported-cpus', check : false).returncode() == 0
endif
elif bpf_compiler == 'gcc' or bpf_compiler == 'auto'
bpf_gcc = find_program('bpf-gcc',
'bpf-none-gcc',
'bpf-unknown-none-gcc',
version : '>= 13.1.0')
bpf_gcc_found = bpf_gcc.found()
endif
if bpf_compiler == 'auto'
if clang_supports_bpf and bpf_gcc_found
# Both supported, prefer the one matching our compiler:
if cc.get_id() == 'gcc'
bpf_compiler = 'gcc'
else
# Default to clang if we don't know this compiler
bpf_compiler = 'clang'
endif
elif clang_supports_bpf
bpf_compiler = 'clang'
elif bpf_gcc_found
bpf_compiler = 'clang'
endif
endif
if clang_supports_bpf or bpf_gcc_found
# Debian installs this in /usr/sbin/ which is not in $PATH.
# We check for 'bpftool' first, honouring $PATH, and in /usr/sbin/ for Debian.
# We use 'bpftool gen object' subcommand for bpftool strip, it was added by d80b2fcbe0a023619e0fc73112f2a02c2662f6ab (v5.13).
bpftool = find_program('bpftool',
'/usr/sbin/bpftool',
required : bpf_compiler == 'gcc',
version : bpf_compiler == 'gcc' ? '>= 7.0.0' : '>= 5.13.0')
if bpftool.found()
bpftool_strip = true
elif bpf_compiler == 'clang'
# We require the 'bpftool gen skeleton' subcommand, it was added by 985ead416df39d6fe8e89580cc1db6aa273e0175 (v5.6).
bpftool = find_program('bpftool',
'/usr/sbin/bpftool',
required : true,
version : '>= 5.6.0')
endif
# We use `llvm-strip` as a fallback if `bpftool gen object` strip support is not available.
if not bpftool_strip and bpftool.found() and clang_supports_bpf
if not meson.is_cross_build()
llvm_strip_bin = run_command(clang, '--print-prog-name', 'llvm-strip',
check : true).stdout().strip()
else
llvm_strip_bin = 'llvm-strip'
endif
llvm_strip = find_program(llvm_strip_bin,
required : true,
version : '>= 10.0.0')
endif
else
error('clat support was enabled but couldn\'t find a suitable BPF compiler!')
endif
bpf_clang_flags = [
'-std=gnu17',
'-Wno-compare-distinct-pointer-types',
'-Wunused',
'-fno-stack-protector',
'-O2',
'-target',
'bpf',
'-g',
'-c',
]
bpf_gcc_flags = [
'-std=gnu17',
'-Wunused',
'-fno-stack-protector',
'-fno-ssa-phiopt',
'-O2',
'-mcpu=v3',
'-mco-re',
'-gbtf',
'-c',
]
clang_arch_flag = '-D__@0@__'.format(host_machine.cpu_family())
libbpf_include_dir = dependency('libbpf').get_variable(pkgconfig : 'includedir')
# Generate defines that are appropriate to tell the compiler what architecture
# we're compiling for. By default we just map meson's cpu_family to __<cpu_family>__.
# This dictionary contains the exceptions where this doesn't work.
#
# C.f. https://mesonbuild.com/Reference-tables.html#cpu-families
# and src/basic/missing_syscall_def.h.
cpu_arch_defines = {
'ppc' : ['-D__powerpc__', '-D__TARGET_ARCH_powerpc'],
'ppc64' : ['-D__powerpc64__', '-D__TARGET_ARCH_powerpc', '-D_CALL_ELF=2'],
'riscv32' : ['-D__riscv', '-D__riscv_xlen=32', '-D__TARGET_ARCH_riscv'],
'riscv64' : ['-D__riscv', '-D__riscv_xlen=64', '-D__TARGET_ARCH_riscv'],
'x86' : ['-D__i386__', '-D__TARGET_ARCH_x86'],
's390x' : ['-D__s390__', '-D__s390x__', '-D__TARGET_ARCH_s390'],
# For arm, assume hardware fp is available.
'arm' : ['-D__arm__', '-D__ARM_PCS_VFP', '-D__TARGET_ARCH_arm'],
'loongarch64' : ['-D__loongarch__', '-D__loongarch_grlen=64', '-D__TARGET_ARCH_loongarch']
}
bpf_arch_flags = cpu_arch_defines.get(host_machine.cpu_family(),
['-D__@0@__'.format(host_machine.cpu_family())])
if bpf_compiler == 'gcc'
bpf_arch_flags += ['-m' + host_machine.endian() + '-endian']
endif
bpf_o_unstripped_cmd = []
if bpf_compiler == 'clang'
bpf_o_unstripped_cmd += [
clang,
bpf_clang_flags,
bpf_arch_flags,
]
elif bpf_compiler == 'gcc'
bpf_o_unstripped_cmd += [
bpf_gcc,
bpf_gcc_flags,
bpf_arch_flags,
]
endif
bpf_o_unstripped_cmd += ['-I.']
if cc.get_id() == 'gcc' or meson.is_cross_build()
if cc.get_id() != 'gcc'
warning('Cross compiler is not gcc. Guessing the target triplet for bpf likely fails.')
endif
target_triplet_cmd = run_command(cc.cmd_array(), '-print-multiarch', check: false)
else
# clang does not support -print-multiarch (D133170) and its -dump-machine
# does not match multiarch. Query gcc instead.
target_triplet_cmd = run_command('gcc', '-print-multiarch', check: false)
endif
if target_triplet_cmd.returncode() == 0
target_triplet = target_triplet_cmd.stdout().strip()
bpf_o_unstripped_cmd += [
'-isystem',
'/usr/include/@0@'.format(target_triplet)
]
endif
bpf_o_unstripped_cmd += [
'-idirafter',
libbpf_include_dir,
'@INPUT@',
'-o',
'@OUTPUT@'
]
if bpftool_strip
bpf_o_cmd = [
bpftool,
'gen',
'object',
'@OUTPUT@',
'@INPUT@'
]
elif bpf_compiler == 'clang'
bpf_o_cmd = [
llvm_strip,
'-g',
'@INPUT@',
'-o',
'@OUTPUT@'
]
endif
skel_h_cmd = [
bpftool,
'g',
's',
'@INPUT@'
]
clat_bpf_o_unstripped = custom_target(
'clat.bpf.unstripped.o',
input : 'clat.bpf.c',
output : 'clat.bpf.unstripped.o',
command : bpf_o_unstripped_cmd)
clat_bpf_o = custom_target(
'clat.bpf.o',
input : clat_bpf_o_unstripped,
output : 'clat.bpf.o',
command : bpf_o_cmd)
clat_skel_h = custom_target(
'clat.skel.h',
input : clat_bpf_o,
output : 'clat.skel.h',
command : skel_h_cmd,
capture : true)

View file

@ -1524,6 +1524,29 @@ _prop_get_connection_dnssec(NMDevice *self, NMConnection *connection)
NM_SETTING_CONNECTION_DNSSEC_DEFAULT);
}
static NMSettingIp6ConfigClat
_prop_get_ipv6_clat(NMDevice *self, NMConnection *connection)
{
NMSettingIP6Config *s_ip6 = NULL;
NMSettingIp6ConfigClat clat;
if (connection)
s_ip6 = (NMSettingIP6Config *) nm_connection_get_setting_ip6_config(connection);
if (!s_ip6)
return NM_SETTING_IP6_CONFIG_CLAT_NO;
clat = nm_setting_ip6_config_get_clat(s_ip6);
if (clat != NM_SETTING_IP6_CONFIG_CLAT_DEFAULT)
return clat;
return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("ipv6.clat"),
self,
NM_SETTING_IP6_CONFIG_CLAT_NO,
NM_SETTING_IP6_CONFIG_CLAT_YES,
NM_SETTING_IP6_CONFIG_CLAT_NO);
}
static NMMptcpFlags
_prop_get_connection_mptcp_flags(NMDevice *self, NMConnection *connection)
{
@ -1922,26 +1945,48 @@ _prop_get_ipvx_may_fail_cached(NMDevice *self, int addr_family, NMTernary *cache
}
static gboolean
_prop_get_ipv4_dhcp_ipv6_only_preferred(NMDevice *self)
_prop_get_ipv4_dhcp_ipv6_only_preferred(NMDevice *self, gboolean *out_is_auto)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMSettingIP4Config *s_ip4;
NMSettingIP4DhcpIpv6OnlyPreferred ipv6_only;
NM_SET_OUT(out_is_auto, FALSE);
s_ip4 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP4_CONFIG);
if (!s_ip4)
return FALSE;
ipv6_only = nm_setting_ip4_config_get_dhcp_ipv6_only_preferred(s_ip4);
if (ipv6_only != NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_DEFAULT)
if (NM_IN_SET(ipv6_only,
NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_YES,
NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO))
return ipv6_only;
return nm_config_data_get_connection_default_int64(
NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("ipv4.dhcp-ipv6-only-preferred"),
self,
NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO,
NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_YES,
NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO);
if (ipv6_only == NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_DEFAULT) {
ipv6_only = nm_config_data_get_connection_default_int64(
NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("ipv4.dhcp-ipv6-only-preferred"),
self,
NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO,
NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_AUTO,
NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_AUTO);
}
if (NM_IN_SET(ipv6_only,
NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_YES,
NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO))
return ipv6_only;
/* auto */
NM_SET_OUT(out_is_auto, TRUE);
if (nm_streq0(priv->ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)
&& _prop_get_ipv6_clat(self, nm_device_get_applied_connection(self))) {
return NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_YES;
}
return NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_YES;
}
/**
@ -3642,6 +3687,8 @@ nm_device_create_l3_config_data_from_connection(NMDevice *self, NMConnection *co
nm_l3_config_data_set_dnssec(l3cd, _prop_get_connection_dnssec(self, connection));
nm_l3_config_data_set_ip6_privacy(l3cd, _prop_get_ipv6_ip6_privacy(self, connection));
nm_l3_config_data_set_mptcp_flags(l3cd, _prop_get_connection_mptcp_flags(self, connection));
nm_l3_config_data_set_clat(l3cd, _prop_get_ipv6_clat(self, connection));
return l3cd;
}
@ -11776,8 +11823,9 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
gboolean hostname_is_fqdn;
gboolean send_client_id;
guint8 dscp;
gboolean dscp_explicit = FALSE;
gboolean ipv6_only_pref = FALSE;
gboolean dscp_explicit = FALSE;
gboolean ipv6_only_pref = FALSE;
gboolean ipv6_only_pref_auto = FALSE;
client_id = _prop_get_ipv4_dhcp_client_id(self, connection, hwaddr, &send_client_id);
dscp = _prop_get_ipv4_dhcp_dscp(self, &dscp_explicit);
@ -11796,13 +11844,15 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
hostname = nm_setting_ip_config_get_dhcp_hostname(s_ip);
}
if (_prop_get_ipv4_dhcp_ipv6_only_preferred(self)) {
if (_prop_get_ipv4_dhcp_ipv6_only_preferred(self, &ipv6_only_pref_auto)) {
if (nm_streq0(priv->ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
_LOGI_ipdhcp(
addr_family,
"not requesting the \"IPv6-only preferred\" option because IPv6 is disabled");
} else {
_LOGD_ipdhcp(addr_family, "requesting the \"IPv6-only preferred\" option");
_LOGD_ipdhcp(addr_family,
"requesting the \"IPv6-only preferred\" option (%s enabled)",
ipv6_only_pref_auto ? "automatically" : "explicitly");
ipv6_only_pref = TRUE;
}
}

View file

@ -1460,7 +1460,9 @@ nm_dhcp_client_schedule_ipv6_only_restart(NMDhcpClient *self, guint timeout)
nm_assert(!priv->is_stopped);
timeout = NM_MAX(priv->v4.ipv6_only_min_wait, timeout);
_LOGI("received option \"ipv6-only-preferred\": stopping DHCPv4 for %u seconds", timeout);
_LOGI("received option \"ipv6-only-preferred\": stopping DHCPv4 for %u seconds. Set "
"ipv4.dhcp-ipv6-only-preferred=no to force the use of IPv4 on this IPv6-mostly network",
timeout);
nm_dhcp_client_stop(self, FALSE);
nm_clear_g_source_inst(&priv->no_lease_timeout_source);

View file

@ -32,6 +32,15 @@ install_data(
core_plugins = []
subdir('bpf')
base_sources_addon = []
base_deps_addon = []
if enable_clat
base_sources_addon += [clat_skel_h]
base_deps_addon += [libbpf]
endif
libNetworkManagerBase = static_library(
'NetworkManagerBase',
sources: files(
@ -55,13 +64,13 @@ libNetworkManagerBase = static_library(
'nm-l3cfg.c',
'nm-bond-manager.c',
'nm-ip-config.c',
),
) + base_sources_addon,
dependencies: [
core_default_dep,
libnm_core_public_dep,
libsystemd_dep,
libudev_dep,
],
] + base_deps_addon,
)
nm_deps = [

View file

@ -162,6 +162,7 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
int offset;
int hop_limit;
guint32 val;
gboolean pref64_found = FALSE;
/* Router discovery is subject to the following RFC documents:
*
@ -401,6 +402,31 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
}
}
/* PREF64 */
ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_PREF64) {
struct in6_addr pref64_prefix = *ndp_msg_opt_pref64_prefix(msg, offset);
guint8 pref64_length = ndp_msg_opt_pref64_prefix_length(msg, offset);
gint64 expiry_msec =
_nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_pref64_lifetime(msg, offset));
/* libndp should only return lengths defined in RFC 8781 */
nm_assert(NM_IN_SET(pref64_length, 96, 64, 56, 48, 40, 32));
/* Newer RA has more up to date information, prefer it: */
if (!pref64_found) {
pref64_found = TRUE;
rdata->public.pref64.expiry_msec = 0;
}
if (expiry_msec >= rdata->public.pref64.expiry_msec) {
rdata->public.pref64.network = pref64_prefix;
rdata->public.pref64.expiry_msec = expiry_msec;
rdata->public.pref64.plen = pref64_length;
rdata->public.pref64.valid = TRUE;
changed |= NM_NDISC_CONFIG_PREF64;
}
}
nm_ndisc_ra_received(ndisc, now_msec, changed);
return 0;
}

View file

@ -14,6 +14,7 @@ struct _NMNDiscDataInternal {
NMNDiscData public;
GArray *gateways;
GArray *addresses;
GArray *clat_addresses;
GArray *routes;
GArray *dns_servers;
GArray *dns_domains;

View file

@ -109,7 +109,8 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex *multi_idx,
int ifindex,
const NMNDiscData *rdata,
NMSettingIP6ConfigPrivacy ip6_privacy,
NMUtilsIPv6IfaceId *token)
NMUtilsIPv6IfaceId *token,
const char *network_id)
{
nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
guint32 ifa_flags;
@ -211,6 +212,12 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex *multi_idx,
for (i = 0; i < rdata->dns_domains_n; i++)
nm_l3_config_data_add_search(l3cd, AF_INET6, rdata->dns_domains[i].domain);
if (rdata->pref64.valid) {
nm_l3_config_data_set_pref64(l3cd, rdata->pref64.network, rdata->pref64.plen);
} else {
nm_l3_config_data_set_pref64_valid(l3cd, FALSE);
}
nm_l3_config_data_set_ndisc_hop_limit(l3cd, rdata->hop_limit);
nm_l3_config_data_set_ndisc_reachable_time_msec(l3cd, rdata->reachable_time_ms);
nm_l3_config_data_set_ndisc_retrans_timer_msec(l3cd, rdata->retrans_timer_ms);
@ -218,6 +225,8 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex *multi_idx,
nm_l3_config_data_set_ip6_mtu(l3cd, rdata->mtu);
if (token)
nm_l3_config_data_set_ip6_token(l3cd, *token);
if (network_id)
nm_l3_config_data_set_network_id(l3cd, network_id);
return g_steal_pointer(&l3cd);
}
@ -437,7 +446,8 @@ nm_ndisc_emit_config_change(NMNDisc *self, NMNDiscConfigMap changed)
nm_l3cfg_get_ifindex(priv->config.l3cfg),
rdata,
priv->config.ip6_privacy,
priv->iid_is_token ? &priv->iid : NULL);
priv->iid_is_token ? &priv->iid : NULL,
priv->config.network_id);
l3cd = nm_l3_config_data_seal(l3cd);
if (!nm_l3_config_data_equal(priv->l3cd, l3cd))
@ -1524,6 +1534,19 @@ clean_routes(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64
*changed |= NM_NDISC_CONFIG_ROUTES;
}
static void
clean_pref64(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
{
NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
if (!rdata->public.pref64.valid)
return;
if (!expiry_next(now_msec, rdata->public.pref64.expiry_msec, next_msec)) {
rdata->public.pref64.valid = FALSE;
*changed |= NM_NDISC_CONFIG_PREF64;
}
}
static void
clean_dns_servers(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
{
@ -1600,6 +1623,7 @@ check_timestamps(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap changed)
clean_gateways(ndisc, now_msec, &changed, &next_msec);
clean_addresses(ndisc, now_msec, &changed, &next_msec);
clean_routes(ndisc, now_msec, &changed, &next_msec);
clean_pref64(ndisc, now_msec, &changed, &next_msec);
clean_dns_servers(ndisc, now_msec, &changed, &next_msec);
clean_dns_domains(ndisc, now_msec, &changed, &next_msec);

View file

@ -119,6 +119,13 @@ typedef struct _NMNDiscRoute {
bool duplicate : 1;
} NMNDiscRoute;
typedef struct _NMNDiscPref64 {
struct in6_addr network;
gint64 expiry_msec;
guint8 plen;
bool valid : 1;
} NMNDiscPref64;
typedef struct {
struct in6_addr address;
gint64 expiry_msec;
@ -141,6 +148,7 @@ typedef enum {
NM_NDISC_CONFIG_MTU = 1 << 7,
NM_NDISC_CONFIG_REACHABLE_TIME = 1 << 8,
NM_NDISC_CONFIG_RETRANS_TIMER = 1 << 9,
NM_NDISC_CONFIG_PREF64 = 1 << 10,
} NMNDiscConfigMap;
typedef enum {
@ -196,6 +204,8 @@ typedef struct {
const NMNDiscRoute *routes;
const NMNDiscDNSServer *dns_servers;
const NMNDiscDNSDomain *dns_domains;
NMNDiscPref64 pref64;
} NMNDiscData;
/**
@ -282,6 +292,7 @@ struct _NML3ConfigData *nm_ndisc_data_to_l3cd(NMDedupMultiIndex *multi_id
int ifindex,
const NMNDiscData *rdata,
NMSettingIP6ConfigPrivacy ip6_privacy,
NMUtilsIPv6IfaceId *token);
NMUtilsIPv6IfaceId *token,
const char *network_id);
#endif /* __NETWORKMANAGER_NDISC_H__ */

View file

@ -304,6 +304,7 @@ typedef enum {
NM_UTILS_STABLE_TYPE_STABLE_ID = 1,
NM_UTILS_STABLE_TYPE_GENERATED = 2,
NM_UTILS_STABLE_TYPE_RANDOM = 3,
NM_UTILS_STABLE_TYPE_CLAT = 4,
} NMUtilsStableType;
#define NM_UTILS_STABLE_TYPE_NONE ((NMUtilsStableType) - 1)

View file

@ -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;
@ -122,6 +125,7 @@ struct _NML3ConfigData {
NMSettingConnectionDnsOverTls dns_over_tls;
NMSettingConnectionDnssec dnssec;
NMUtilsIPv6IfaceId ip6_token;
NMRefString *network_id;
NML3ConfigDatFlags flags;
@ -167,6 +171,9 @@ struct _NML3ConfigData {
bool routed_dns_4 : 1;
bool routed_dns_6 : 1;
bool clat : 1;
bool pref64_valid : 1;
};
/*****************************************************************************/
@ -519,6 +526,16 @@ nm_l3_config_data_log(const NML3ConfigData *self,
_L("nis-domain: %s", self->nis_domain->str);
}
if (!IS_IPv4 && self->clat) {
_L("clat: yes");
}
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];
@ -603,6 +620,10 @@ nm_l3_config_data_log(const NML3ConfigData *self,
nm_utils_inet6_interface_identifier_to_token(&self->ip6_token, sbuf_addr));
}
if (self->network_id) {
_L("network-id: %s", self->network_id->str);
}
if (self->metered != NM_TERNARY_DEFAULT)
_L("metered: %s", self->metered ? "yes" : "no");
@ -722,6 +743,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);
@ -822,6 +844,7 @@ nm_l3_config_data_unref(const NML3ConfigData *self)
nm_ref_string_unref(mutable->nis_domain);
nm_ref_string_unref(mutable->proxy_pac_url);
nm_ref_string_unref(mutable->proxy_pac_script);
nm_ref_string_unref(mutable->network_id);
nm_g_slice_free(mutable);
}
@ -1957,6 +1980,86 @@ nm_l3_config_data_set_ip6_token(NML3ConfigData *self, NMUtilsIPv6IfaceId ipv6_to
return TRUE;
}
const char *
nm_l3_config_data_get_network_id(const NML3ConfigData *self)
{
nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
return nm_ref_string_get_str(self->network_id);
}
gboolean
nm_l3_config_data_set_network_id(NML3ConfigData *self, const char *value)
{
nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
return nm_ref_string_reset_str(&self->network_id, value);
}
gboolean
nm_l3_config_data_set_clat(NML3ConfigData *self, gboolean val)
{
if (self->clat == val)
return FALSE;
self->clat = val;
return TRUE;
}
gboolean
nm_l3_config_data_get_clat(const NML3ConfigData *self)
{
nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
return self->clat;
}
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)
{
nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
return self->pref64_valid;
}
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)
{
@ -2484,6 +2587,7 @@ nm_l3_config_data_cmp_full(const NML3ConfigData *a,
if (NM_FLAGS_HAS(flags, NM_L3_CONFIG_CMP_FLAGS_OTHER)) {
NM_CMP_DIRECT(a->flags, b->flags);
NM_CMP_DIRECT(a->ip6_token.id, b->ip6_token.id);
NM_CMP_DIRECT_REF_STRING(a->network_id, b->network_id);
NM_CMP_DIRECT(a->mtu, b->mtu);
NM_CMP_DIRECT(a->ip6_mtu, b->ip6_mtu);
NM_CMP_DIRECT_UNSAFE(a->metered, b->metered);
@ -2509,6 +2613,15 @@ 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_UNSAFE(a->clat, b->clat);
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);
}
@ -2524,8 +2637,10 @@ nm_l3_config_data_cmp_full(const NML3ConfigData *a,
/*****************************************************************************/
static const NMPObject *
_data_get_direct_route_for_host(const NML3ConfigData *self, int addr_family, gconstpointer host)
const NMPObject *
nm_l3_config_data_get_direct_route_for_host(const NML3ConfigData *self,
int addr_family,
gconstpointer host)
{
const int IS_IPv4 = NM_IS_IPv4(addr_family);
const NMPObject *best_route_obj = NULL;
@ -2683,7 +2798,7 @@ nm_l3_config_data_add_dependent_onlink_routes(NML3ConfigData *self, int addr_fam
if (NM_FLAGS_HAS(route_src->rx.r_rtm_flags, (unsigned) RTNH_F_ONLINK))
continue;
if (_data_get_direct_route_for_host(self, addr_family, p_gateway))
if (nm_l3_config_data_get_direct_route_for_host(self, addr_family, p_gateway))
continue;
new_route = nmp_object_clone(obj_src, FALSE);
@ -3506,6 +3621,9 @@ nm_l3_config_data_merge(NML3ConfigData *self,
if (self->ip6_token.id == 0)
self->ip6_token.id = src->ip6_token.id;
if (!self->network_id)
self->network_id = nm_ref_string_ref(src->network_id);
self->metered = NM_MAX((NMTernary) self->metered, (NMTernary) src->metered);
if (self->proxy_method == NM_PROXY_CONFIG_METHOD_UNKNOWN)
@ -3567,6 +3685,15 @@ nm_l3_config_data_merge(NML3ConfigData *self,
self->routed_dns_4 = TRUE;
if (src->routed_dns_6)
self->routed_dns_6 = TRUE;
if (src->clat)
self->clat = TRUE;
if (src->pref64_valid) {
self->pref64_prefix = src->pref64_prefix;
self->pref64_plen = src->pref64_plen;
self->pref64_valid = src->pref64_valid;
}
}
NML3ConfigData *

View file

@ -225,6 +225,10 @@ nm_l3_config_data_equal(const NML3ConfigData *a, const NML3ConfigData *b)
/*****************************************************************************/
const NMPObject *nm_l3_config_data_get_direct_route_for_host(const NML3ConfigData *self,
int addr_family,
gconstpointer host);
const NMDedupMultiIdxType *nm_l3_config_data_lookup_index(const NML3ConfigData *self,
NMPObjectType obj_type);
@ -490,6 +494,24 @@ NMUtilsIPv6IfaceId nm_l3_config_data_get_ip6_token(const NML3ConfigData *self);
gboolean nm_l3_config_data_set_ip6_token(NML3ConfigData *self, NMUtilsIPv6IfaceId ipv6_token);
gboolean nm_l3_config_data_set_network_id(NML3ConfigData *self, const char *network_id);
const char *nm_l3_config_data_get_network_id(const NML3ConfigData *self);
gboolean nm_l3_config_data_set_clat(NML3ConfigData *self, gboolean val);
gboolean nm_l3_config_data_get_clat(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 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);

View file

@ -11,6 +11,10 @@
#include <linux/if_ether.h>
#include <linux/rtnetlink.h>
#include <linux/fib_rules.h>
#if 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-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"
#if 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,23 @@ typedef struct _NML3CfgPrivate {
NMIPConfig *ipconfig_x[2];
};
#if HAVE_CLAT
/* The reserved IPv4 address for CLAT in the 192.0.0.0/28 range */
NMNetnsIPReservation *clat_address_4;
/* The IPv6 address for sending and receiving translated packets */
NMPlatformIP6Address clat_address_6;
/* The same addresses as above, but already committed previously */
NMNetnsIPReservation *clat_address_4_committed;
NMPlatformIP6Address clat_address_6_committed;
/* 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 +381,9 @@ typedef struct _NML3CfgPrivate {
bool rp_filter_handled : 1;
bool rp_filter_set : 1;
bool clat_address_6_valid : 1;
bool clat_address_6_committed_valid : 1;
} NML3CfgPrivate;
struct _NML3CfgClass {
@ -4123,6 +4154,10 @@ _l3cfg_update_combined_config(NML3Cfg *self,
guint i;
gboolean merged_changed = FALSE;
gboolean commited_changed = FALSE;
#if HAVE_CLAT
struct in6_addr pref64;
guint32 pref64_plen;
#endif
nm_assert(NM_IS_L3CFG(self));
nm_assert(!out_old || !*out_old);
@ -4220,6 +4255,139 @@ _l3cfg_update_combined_config(NML3Cfg *self,
&hook_data);
}
#if HAVE_CLAT
if (nm_l3_config_data_get_clat(l3cd) && nm_l3_config_data_get_pref64_valid(l3cd)) {
NMPlatformIPXRoute rx;
NMIPAddrTyped best_v6_gateway;
NMDedupMultiIter iter;
const NMPlatformIP6Route *best_v6_route;
const NMPlatformIP6Address *ip6_entry;
struct in6_addr ip6;
const char *network_id;
char buf[512];
/* If we have a valid NAT64 prefix, configure in kernel:
*
* - a CLAT IPv4 address (192.0.0.x)
* - a IPv4 default route via the best IPv6 gateway
*
* We also set clat_address_6 as an additional /64 IPv6 address
* determined according to https://www.rfc-editor.org/rfc/rfc6877#section-6.3 .
* This address is used for sending and receiving translated packets,
* but is not configured in kernel to avoid that it gets used by applications.
* Later in _l3_commit_pref64() we use IPV6_JOIN_ANYCAST to let the kernel
* handle ND for the address.
*/
nm_l3_config_data_get_pref64(l3cd, &pref64, &pref64_plen);
network_id = nm_l3_config_data_get_network_id(l3cd);
if (!self->priv.p->clat_address_6_valid && network_id) {
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_CLAT,
&ip6,
nm_l3cfg_get_ifname(self, TRUE),
network_id,
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,
};
_LOGT("clat: using IPv6 address %s", nm_inet6_ntop(&ip6, buf));
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 committed one */
if (self->priv.p->clat_address_4 != self->priv.p->clat_address_4_committed) {
nm_clear_pointer(&self->priv.p->clat_address_4,
nm_netns_ip_reservation_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_reservation_get(self->priv.netns,
NM_NETNS_IP_RESERVATION_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);
}
_LOGT("clat: route %s",
nm_platform_ip4_route_to_string(&rx.r4, buf, sizeof(buf)));
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 +4448,18 @@ _l3cfg_update_combined_config(NML3Cfg *self,
if (nm_l3_config_data_equal(l3cd, self->priv.p->combined_l3cd_merged))
goto out;
#if HAVE_CLAT
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_committed) {
nm_clear_pointer(&self->priv.p->clat_address_4, nm_netns_ip_reservation_release);
} else {
self->priv.p->clat_address_4 = NULL;
}
}
#endif /* HAVE_CLAT */
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 +5559,221 @@ _l3_commit_one(NML3Cfg *self,
_failedobj_handle_routes(self, addr_family, routes_failed);
}
#if HAVE_CLAT
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);
}
bpf_tc_hook_destroy(&hook);
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_config clat_config;
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) {
_LOGT("clat: attaching the BPF program");
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("clat: failed to open the BPF program: %s", buf);
clat_bpf__destroy(self->priv.p->clat_bpf);
self->priv.p->clat_bpf = NULL;
return;
}
err = clat_bpf__load(self->priv.p->clat_bpf);
if (err) {
libbpf_strerror(err, buf, sizeof(buf));
_LOGW("clat: failed to load the BPF program: %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("clat: failed to create the 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("clat: couldn't find the 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("clat: couldn't find the 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("clat: failed to attach ingress to 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("clat: failed to attach ingress to 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;
}
/* Pass configuration to the BPF program */
memset(&clat_config, 0, sizeof(clat_config));
clat_config.local_v4.s_addr = self->priv.p->clat_address_4->addr;
clat_config.local_v6 = self->priv.p->clat_address_6.address;
clat_config.pref64 = *l3cd_pref64;
clat_config.pref64_len = l3cd_pref64_plen;
self->priv.p->clat_bpf->bss->config = clat_config;
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("clat: couldn't create the socket: %s", 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 < 0) {
_LOGW("clat: couldn't bind the socket: %s", nm_strerror_native(errno));
}
}
v6_changed =
(self->priv.p->clat_address_6_valid != self->priv.p->clat_address_6_committed_valid)
|| (self->priv.p->clat_address_6_valid && self->priv.p->clat_address_6_committed_valid
&& memcmp(&self->priv.p->clat_address_6.address,
&self->priv.p->clat_address_6_committed.address,
sizeof(self->priv.p->clat_address_6_committed.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_committed_valid) {
mreq.ipv6mr_multiaddr = self->priv.p->clat_address_6_committed.address;
err = setsockopt(self->priv.p->clat_socket,
SOL_IPV6,
IPV6_LEAVE_ANYCAST,
&mreq,
sizeof(mreq));
if (err < 0) {
_LOGW("clat: couldn't leave the anycast group: %s", 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 < 0) {
_LOGW("clat: couldn't join the anycast group: %s", nm_strerror_native(errno));
}
}
}
} else {
if (self->priv.p->clat_bpf) {
_l3_clat_destroy(self);
}
/* Committed 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. Committed 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_committed) {
nm_clear_pointer(&self->priv.p->clat_address_4, nm_netns_ip_reservation_release);
} else {
self->priv.p->clat_address_4 = NULL;
}
}
/* Record the new state */
if (self->priv.p->clat_address_4_committed != self->priv.p->clat_address_4) {
nm_clear_pointer(&self->priv.p->clat_address_4_committed, nm_netns_ip_reservation_release);
self->priv.p->clat_address_4_committed = self->priv.p->clat_address_4;
}
self->priv.p->clat_address_6_committed = self->priv.p->clat_address_6;
self->priv.p->clat_address_6_committed_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 +5856,10 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle)
_l3_acd_data_process_changes(self);
#if 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 +6241,10 @@ nm_l3cfg_init(NML3Cfg *self)
{
self->priv.p = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_L3CFG, NML3CfgPrivate);
#if HAVE_CLAT
self->priv.p->clat_socket = -1;
#endif /* HAVE_CLAT */
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);
@ -5950,6 +6353,14 @@ finalize(GObject *object)
if (changed)
nmp_global_tracker_sync_mptcp_addrs(self->priv.global_tracker, FALSE);
#if HAVE_CLAT
self->priv.p->clat_address_4_committed = NULL;
nm_clear_pointer(&self->priv.p->clat_address_4, nm_netns_ip_reservation_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);

View file

@ -588,6 +588,14 @@ static const IPReservationTypeDesc ip_reservation_types[_NM_NETNS_IP_RESERVATION
.num_addrs = 256,
.allow_reuse = TRUE,
},
[NM_NETNS_IP_RESERVATION_TYPE_CLAT] =
{
.name = "clat",
.start_addr = 0xc0000000, /* 192.0.0.0 */
.prefix_len = 32,
.num_addrs = 8,
.allow_reuse = FALSE,
},
};
NMNetnsIPReservation *

View file

@ -43,6 +43,7 @@ NML3Cfg *nm_netns_l3cfg_acquire(NMNetns *netns, int ifindex);
typedef enum {
NM_NETNS_IP_RESERVATION_TYPE_SHARED4,
NM_NETNS_IP_RESERVATION_TYPE_CLAT,
_NM_NETNS_IP_RESERVATION_TYPE_NUM,
} NMNetnsIPReservationType;

View file

@ -3598,6 +3598,7 @@ do_write_construct(NMConnection *connection,
} else
route_ignore = FALSE;
/* Unsupported properties */
if ((s_ip4 = nm_connection_get_setting_ip4_config(connection))) {
if (nm_setting_ip_config_get_dhcp_dscp(s_ip4)) {
set_error_unsupported(error,
@ -3618,6 +3619,17 @@ do_write_construct(NMConnection *connection,
}
}
if ((s_ip6 = nm_connection_get_setting_ip6_config(connection))) {
if (nm_setting_ip6_config_get_clat(NM_SETTING_IP6_CONFIG(s_ip6))
!= NM_SETTING_IP6_CONFIG_CLAT_DEFAULT) {
set_error_unsupported(error,
connection,
NM_SETTING_IP6_CONFIG_SETTING_NAME "." NM_SETTING_IP6_CONFIG_CLAT,
FALSE);
return FALSE;
}
}
write_ip4_setting(connection,
ifcfg,
!route_ignore && route_path_is_svformat ? &route_content_svformat : NULL,

View file

@ -53,6 +53,44 @@ test_ip_reservation_shared4(void)
}
}
static void
test_ip_reservation_clat(void)
{
gs_unref_object NMPlatform *platform = NULL;
gs_unref_object NMNetns *netns = NULL;
NMNetnsIPReservation *res[8];
NMNetnsIPReservation *res1;
char buf[NM_INET_ADDRSTRLEN];
guint i;
platform = g_object_ref(NM_PLATFORM_GET);
netns = nm_netns_new(platform);
/* Allocate addresses from 192.0.0.0 to 192.0.0.7 */
for (i = 0; i < 8; i++) {
res[i] = nm_netns_ip_reservation_get(netns, NM_NETNS_IP_RESERVATION_TYPE_CLAT);
g_snprintf(buf, sizeof(buf), "192.0.0.%u", i);
nmtst_assert_ip4_address(res[i]->addr, buf);
g_assert_cmpint(res[i]->_ref_count, ==, 1);
}
/* Release an address and get it back */
nm_netns_ip_reservation_release(res[2]);
res[2] = nm_netns_ip_reservation_get(netns, NM_NETNS_IP_RESERVATION_TYPE_CLAT);
nmtst_assert_ip4_address(res[2]->addr, "192.0.0.2");
/* No reuse */
NMTST_EXPECT_NM_ERROR("netns[*]: clat: ran out of IP addresses");
res1 = nm_netns_ip_reservation_get(netns, NM_NETNS_IP_RESERVATION_TYPE_CLAT);
g_test_assert_expected_messages();
g_assert_null(res1);
/* Release all */
for (i = 0; i < 8; i++) {
nm_netns_ip_reservation_release(res[i]);
}
}
/*****************************************************************************/
NMTST_DEFINE();
@ -64,6 +102,7 @@ main(int argc, char **argv)
nm_linux_platform_setup();
g_test_add_func("/netns/ip_reservation/shared4", test_ip_reservation_shared4);
g_test_add_func("/netns/ip_reservation/clat", test_ip_reservation_clat);
return g_test_run();
}

View file

@ -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;

View file

@ -2087,9 +2087,17 @@ global:
libnm_1_56_0 {
global:
nm_dns_server_validate;
nm_setting_connection_dnssec_get_type;
nm_setting_connection_get_dnssec;
nm_setting_gsm_get_device_uid;
nm_setting_connection_get_dnssec;
nm_setting_connection_dnssec_get_type;
nm_utils_copy_cert_as_user;
nm_vpn_plugin_info_supports_safe_private_file_access;
} libnm_1_54_0;
libnm_1_58_0 {
global:
nm_setting_ip6_config_clat_get_type;
nm_setting_ip6_config_get_clat;
} libnm_1_56_0;

View file

@ -1819,6 +1819,10 @@
dbus-type="i"
gprop-type="NMTernary"
/>
<property name="clat"
dbus-type="i"
gprop-type="gint"
/>
<property name="dad-timeout"
dbus-type="i"
gprop-type="gint"

View file

@ -1353,18 +1353,25 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
/**
* NMSettingIP4Config:dhcp-ipv6-only-preferred
*
* Controls the "IPv6-Only Preferred" DHCPv4 option (RFC 8925).
* Controls the "IPv6-Only Preferred" DHCPv4 option (option 108 - RFC 8925).
*
* When set to %NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_YES, the host adds the
* option to the parameter request list; if the DHCP server sends the option back,
* the host stops the DHCP client for the time interval specified in the option.
*
* Enable this feature if the host supports an IPv6-only mode, i.e. either all
* applications are IPv6-only capable or there is a form of 464XLAT deployed.
* applications are IPv6-only capable or there is a form of CLAT (464XLAT)
* deployed.
*
* If set to %NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_AUTO, the option is
* automatically turned on when the IPv6 method is "auto" and the connection
* enables CLAT. If these two conditions are met, the host can operate in
* IPv6-only mode and therefore it is safe to disable DHCPv4 if the network
* also supports it.
*
* When set to %NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_DEFAULT, the actual value
* is looked up in the global configuration; if not specified, it defaults to
* %NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO.
* %NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_AUTO.
*
* If the connection has IPv6 method set to "disabled", this property does not
* have effect and the "IPv6-Only Preferred" option is always disabled.

View file

@ -47,7 +47,8 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_IP6_PRIVACY,
PROP_DHCP_DUID,
PROP_RA_TIMEOUT,
PROP_MTU,
PROP_DHCP_PD_HINT, );
PROP_DHCP_PD_HINT,
PROP_CLAT, );
typedef struct {
NMSettingIPConfigPrivate parent;
@ -60,6 +61,7 @@ typedef struct {
gint32 temp_preferred_lifetime;
gint32 addr_gen_mode;
gint32 ra_timeout;
gint32 clat;
guint32 mtu;
} NMSettingIP6ConfigPrivate;
@ -252,6 +254,24 @@ nm_setting_ip6_config_get_mtu(NMSettingIP6Config *setting)
return NM_SETTING_IP6_CONFIG_GET_PRIVATE(setting)->mtu;
}
/**
* nm_setting_ip6_config_get_clat:
* @setting: the #NMSettingIP6Config
*
* Returns the value in the #NMSettingIP6Config:clat property.
*
* Returns: the CLAT property value
*
* Since: 1.58
*/
NMSettingIp6ConfigClat
nm_setting_ip6_config_get_clat(NMSettingIP6Config *setting)
{
g_return_val_if_fail(NM_IS_SETTING_IP6_CONFIG(setting), NM_SETTING_IP6_CONFIG_CLAT_DEFAULT);
return NM_SETTING_IP6_CONFIG_GET_PRIVATE(setting)->clat;
}
static gboolean
verify(NMSetting *setting, NMConnection *connection, GError **error)
{
@ -1339,6 +1359,35 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
_set_string_fcn_dhcp_pd_hint,
.direct_string_allow_empty = TRUE);
/**
* NMSettingIP6Config:clat
*
* Controls the CLAT (Customer-side translator) functionality. CLAT is used to implement the
* client part of 464XLAT (RFC 6877), an architecture that provides IPv4 connectivity to hosts
* on IPv6-only networks.
*
* Setting %NM_SETTING_IP6_CONFIG_CLAT_YES or %NM_SETTING_IP6_CONFIG_CLAT_NO respectively enables
* or disables CLAT. When enabled, NetworkManager discovers the NAT64 prefix via Router
* Advertisement; if the prefix is found, it installs a BPF program to perform the stateless
* translation of packets betweeen IPv4 and IPv6.
*
* When set to %NM_SETTING_IP6_CONFIG_CLAT_DEFAULT, the actual value is looked up in the global
* configuration; if not specified it defaults to %NM_SETTING_IP6_CONFIG_CLAT_NO. In the future
* the fallback value may change to %NM_SETTING_IP6_CONFIG_CLAT_YES.
*
* Since: 1.56
*/
_nm_setting_property_define_direct_enum(properties_override,
obj_properties,
NM_SETTING_IP6_CONFIG_CLAT,
PROP_CLAT,
NM_TYPE_SETTING_IP6_CONFIG_CLAT,
NM_SETTING_IP6_CONFIG_CLAT_DEFAULT,
NM_SETTING_PARAM_NONE,
NULL,
NMSettingIP6ConfigPrivate,
clat);
/* IP6-specific property overrides */
/* ---dbus---

View file

@ -109,6 +109,8 @@ typedef enum {
* @NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_DEFAULT: use the global default value
* @NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO: the option is disabled
* @NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_YES: the option is enabled
* @NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_AUTO: the option is enabled when
* the IPv6 method is "auto" and CLAT is enabled. Since: 1.58
*
* #NMSettingIP4DhcpIpv6OnlyPreferred values specify if the "IPv6-Only Preferred"
* option (RFC 8925) for DHCPv4 is enabled.
@ -119,6 +121,7 @@ typedef enum {
NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_DEFAULT = -1,
NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO = 0,
NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_YES = 1,
NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_AUTO = 2,
} NMSettingIP4DhcpIpv6OnlyPreferred;
typedef struct _NMSettingIP4ConfigClass NMSettingIP4ConfigClass;

View file

@ -46,6 +46,8 @@ G_BEGIN_DECLS
#define NM_SETTING_IP6_CONFIG_DHCP_PD_HINT "dhcp-pd-hint"
#define NM_SETTING_IP6_CONFIG_CLAT "clat"
/**
* NM_SETTING_IP6_CONFIG_METHOD_IGNORE:
*
@ -153,6 +155,25 @@ typedef enum {
NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT = 3,
} NMSettingIP6ConfigAddrGenMode;
/**
* NMSettingIp6ConfigClat:
* @NM_SETTING_IP6_CONFIG_CLAT_DEFAULT: use the global default value
* @NM_SETTING_IP6_CONFIG_CLAT_NO: disable CLAT
* @NM_SETTING_IP6_CONFIG_CLAT_YES: enable CLAT
*
* #NMSettingIP6ConfigClat values specify if CLAT (Customer-side translator)
* is enabled or not. CLAT is used to implement the client part of 464XLAT
* (RFC 6877), an architecture that provides IPv4 connectivity to hosts on
* IPv6-only networks.
*
* Since: 1.58
*/
typedef enum {
NM_SETTING_IP6_CONFIG_CLAT_DEFAULT = -1,
NM_SETTING_IP6_CONFIG_CLAT_NO = 0,
NM_SETTING_IP6_CONFIG_CLAT_YES = 1,
} NMSettingIp6ConfigClat;
typedef struct _NMSettingIP6ConfigClass NMSettingIP6ConfigClass;
GType nm_setting_ip6_config_get_type(void);
@ -176,6 +197,8 @@ NM_AVAILABLE_IN_1_40
guint32 nm_setting_ip6_config_get_mtu(NMSettingIP6Config *setting);
NM_AVAILABLE_IN_1_44
const char *nm_setting_ip6_config_get_dhcp_pd_hint(NMSettingIP6Config *setting);
NM_AVAILABLE_IN_1_58
NMSettingIp6ConfigClat nm_setting_ip6_config_get_clat(NMSettingIP6Config *setting);
G_END_DECLS

View file

@ -6913,6 +6913,9 @@ static const NMMetaPropertyInfo *const property_infos_IP6_CONFIG[] = {
PROPERTY_INFO (NM_SETTING_IP6_CONFIG_DHCP_PD_HINT, DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_PD_HINT,
.property_type = &_pt_gobject_string,
),
PROPERTY_INFO (NM_SETTING_IP6_CONFIG_CLAT, DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_CLAT,
.property_type = &_pt_gobject_enum,
),
PROPERTY_INFO (NM_SETTING_IP6_CONFIG_DHCP_DUID, DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_DUID,
.property_type = &_pt_gobject_string,
),

View file

@ -193,7 +193,7 @@
#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_HOSTNAME N_("If the \"dhcp-send-hostname\" property is TRUE, then the specified name will be sent to the DHCP server when acquiring a lease. This property and \"dhcp-fqdn\" are mutually exclusive and cannot be set at the same time.")
#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_HOSTNAME_FLAGS N_("Flags for the DHCP hostname and FQDN. Currently, this property only includes flags to control the FQDN flags set in the DHCP FQDN option. Supported FQDN flags are \"fqdn-serv-update\" (0x1), \"fqdn-encoded\" (0x2) and \"fqdn-no-update\" (0x4). When no FQDN flag is set and \"fqdn-clear-flags\" (0x8) is set, the DHCP FQDN option will contain no flag. Otherwise, if no FQDN flag is set and \"fqdn-clear-flags\" (0x8) is not set, the standard FQDN flags are set in the request: \"fqdn-serv-update\" (0x1), \"fqdn-encoded\" (0x2) for IPv4 and \"fqdn-serv-update\" (0x1) for IPv6. When this property is set to the default value \"none\" (0x0), a global default is looked up in NetworkManager configuration. If that value is unset or also \"none\" (0x0), then the standard FQDN flags described above are sent in the DHCP requests.")
#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_IAID N_("A string containing the \"Identity Association Identifier\" (IAID) used by the DHCP client. The string can be a 32-bit number (either decimal, hexadecimal or as colon separated hexadecimal numbers). Alternatively it can be set to the special values \"mac\", \"perm-mac\", \"ifname\" or \"stable\". When set to \"mac\" (or \"perm-mac\"), the last 4 bytes of the current (or permanent) MAC address are used as IAID. When set to \"ifname\", the IAID is computed by hashing the interface name. The special value \"stable\" can be used to generate an IAID based on the stable-id (see connection.stable-id), a per-host key and the interface name. When the property is unset, the value from global configuration is used; if no global default is set then the IAID is assumed to be \"ifname\". For DHCPv4, the IAID is only used with \"ipv4.dhcp-client-id\" values \"duid\" and \"ipv6-duid\" to generate the client-id. For DHCPv6, note that at the moment this property is only supported by the \"internal\" DHCPv6 plugin. The \"dhclient\" DHCPv6 plugin always derives the IAID from the MAC address. The actually used DHCPv6 IAID for a currently activated interface is exposed in the lease information of the device.")
#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_IPV6_ONLY_PREFERRED N_("Controls the \"IPv6-Only Preferred\" DHCPv4 option (RFC 8925). When set to \"yes\" (1), the host adds the option to the parameter request list; if the DHCP server sends the option back, the host stops the DHCP client for the time interval specified in the option. Enable this feature if the host supports an IPv6-only mode, i.e. either all applications are IPv6-only capable or there is a form of 464XLAT deployed. When set to \"default\" (-1), the actual value is looked up in the global configuration; if not specified, it defaults to \"no\" (0). If the connection has IPv6 method set to \"disabled\", this property does not have effect and the \"IPv6-Only Preferred\" option is always disabled.")
#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_IPV6_ONLY_PREFERRED N_("Controls the \"IPv6-Only Preferred\" DHCPv4 option (option 108 - RFC 8925). When set to \"yes\" (1), the host adds the option to the parameter request list; if the DHCP server sends the option back, the host stops the DHCP client for the time interval specified in the option. Enable this feature if the host supports an IPv6-only mode, i.e. either all applications are IPv6-only capable or there is a form of CLAT (464XLAT) deployed. If set to \"auto\" (2), the option is automatically turned on when the IPv6 method is \"auto\" and the connection enables CLAT. If these two conditions are met, the host can operate in IPv6-only mode and therefore it is safe to disable DHCPv4 if the network also supports it. When set to \"default\" (-1), the actual value is looked up in the global configuration; if not specified, it defaults to \"auto\" (2). If the connection has IPv6 method set to \"disabled\", this property does not have effect and the \"IPv6-Only Preferred\" option is always disabled.")
#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_REJECT_SERVERS N_("Array of servers from which DHCP offers must be rejected. This property is useful to avoid getting a lease from misconfigured or rogue servers. For DHCPv4, each element must be an IPv4 address, optionally followed by a slash and a prefix length (e.g. \"192.168.122.0/24\"). This property is currently not implemented for DHCPv6.")
#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_SEND_HOSTNAME N_("Since 1.52 this property is deprecated and is only used as fallback value for dhcp-send-hostname if it's set to 'default'. This is only done to avoid breaking existing configurations, the new property should be used from now on.")
#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_SEND_HOSTNAME_V2 N_("If TRUE, a hostname is sent to the DHCP server when acquiring a lease. Some DHCP servers use this hostname to update DNS databases, essentially providing a static hostname for the computer. If the dhcp-hostname property is NULL and this property is TRUE, the current persistent hostname of the computer is sent. The default value is default (-1). In this case the global value from NetworkManager configuration is looked up. If it's not set, the value from dhcp-send-hostname-deprecated, which defaults to TRUE, is used for backwards compatibility. In the future this will change and, in absence of a global default, it will always fallback to TRUE.")
@ -224,6 +224,7 @@
#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE N_("Configure method for creating the IPv6 interface identifier of addresses with RFC4862 IPv6 Stateless Address Autoconfiguration and Link Local addresses. The permitted values are: \"eui64\" (0), \"stable-privacy\" (1), \"default\" (3) or \"default-or-eui64\" (2). If the property is set to \"eui64\", the addresses will be generated using the interface token derived from hardware address. This makes the host part of the address to stay constant, making it possible to track the host's presence when it changes networks. The address changes when the interface hardware is replaced. If a duplicate address is detected, there is also no fallback to generate another address. When configured, the \"ipv6.token\" is used instead of the MAC address to generate addresses for stateless autoconfiguration. If the property is set to \"stable-privacy\", the interface identifier is generated as specified by RFC7217. This works by hashing a host specific key (see NetworkManager(8) manual), the interface name, the connection's \"connection.stable-id\" property and the address prefix. This improves privacy by making it harder to use the address to track the host's presence and the address is stable when the network interface hardware is replaced. The special values \"default\" and \"default-or-eui64\" will fallback to the global connection default as documented in the NetworkManager.conf(5) manual. If the global default is not specified, the fallback value is \"stable-privacy\" or \"eui64\", respectively. If not specified, when creating a new profile the default is \"default\". Note that this setting is distinct from the Privacy Extensions as configured by \"ip6-privacy\" property and it does not affect the temporary addresses configured with this option.")
#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_ADDRESSES N_("A list of IPv6 addresses and their prefix length. Multiple addresses can be separated by comma. For example \"2001:db8:85a3::8a2e:370:7334/64, 2001:db8:85a3::5/64\". The addresses are listed in decreasing priority, meaning the first address will be the primary address. This can make a difference with IPv6 source address selection (RFC 6724, section 5).")
#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_AUTO_ROUTE_EXT_GW N_("VPN connections will default to add the route automatically unless this setting is set to FALSE. For other connection types, adding such an automatic route is currently not supported and setting this to TRUE has no effect.")
#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_CLAT N_("Controls the CLAT (Customer-side translator) functionality. CLAT is used to implement the client part of 464XLAT (RFC 6877), an architecture that provides IPv4 connectivity to hosts on IPv6-only networks. Setting \"yes\" (1) or \"no\" (0) respectively enables or disables CLAT. When enabled, NetworkManager discovers the NAT64 prefix via Router Advertisement; if the prefix is found, it installs a BPF program to perform the stateless translation of packets betweeen IPv4 and IPv6. When set to \"default\" (-1), the actual value is looked up in the global configuration; if not specified it defaults to \"no\" (0). In the future the fallback value may change to \"yes\" (1).")
#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DAD_TIMEOUT N_("Maximum timeout in milliseconds used to check for the presence of duplicate IP addresses on the network. If an address conflict is detected, the activation will fail. The property is currently implemented only for IPv4. A zero value means that no duplicate address detection is performed, -1 means the default value (either the value configured globally in NetworkManger.conf or 200ms). A value greater than zero is a timeout in milliseconds. Note that the time intervals are subject to randomization as per RFC 5227 and so the actual duration can be between half and the full time specified in this property.")
#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_DSCP N_("Specifies the value for the DSCP field (traffic class) of the IP header. When empty, the global default value is used; if no global default is specified, it is assumed to be \"CS0\". Allowed values are: \"CS0\", \"CS4\" and \"CS6\". The property is currently valid only for IPv4, and it is supported only by the \"internal\" DHCP plugin.")
#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_DUID N_("A string containing the DHCPv6 Unique Identifier (DUID) used by the dhcp client to identify itself to DHCPv6 servers (RFC 3315). The DUID is carried in the Client Identifier option. If the property is a hex string ('aa:bb:cc') it is interpreted as a binary DUID and filled as an opaque value in the Client Identifier option. The special value \"lease\" will retrieve the DUID previously used from the lease file belonging to the connection. If no DUID is found and \"dhclient\" is the configured dhcp client, the DUID is searched in the system-wide dhclient lease file. If still no DUID is found, or another dhcp client is used, a global and permanent DUID-UUID (RFC 6355) will be generated based on the machine-id. The special values \"llt\" and \"ll\" will generate a DUID of type LLT or LL (see RFC 3315) based on the current MAC address of the device. In order to try providing a stable DUID-LLT, the time field will contain a constant timestamp that is used globally (for all profiles) and persisted to disk. The special values \"stable-llt\", \"stable-ll\" and \"stable-uuid\" will generate a DUID of the corresponding type, derived from the connection's stable-id and a per-host unique key. You may want to include the \"${DEVICE}\" or \"${MAC}\" specifier in the stable-id, in case this profile gets activated on multiple devices. So, the link-layer address of \"stable-ll\" and \"stable-llt\" will be a generated address derived from the stable id. The DUID-LLT time value in the \"stable-llt\" option will be picked among a static timespan of three years (the upper bound of the interval is the same constant timestamp used in \"llt\"). When the property is unset, the global value provided for \"ipv6.dhcp-duid\" is used. If no global value is provided, the default \"lease\" value is assumed.")

View file

@ -1452,9 +1452,9 @@
nmcli-description="The Vendor Class Identifier DHCP option (60). Special characters in the data string may be escaped using C-style escapes, nevertheless this property cannot contain nul bytes. If the per-profile value is unspecified (the default), a global connection default gets consulted. If still unspecified, the DHCP option is not sent to the server."
format="string" />
<property name="dhcp-ipv6-only-preferred"
nmcli-description="Controls the &quot;IPv6-Only Preferred&quot; DHCPv4 option (RFC 8925). When set to &quot;yes&quot; (1), the host adds the option to the parameter request list; if the DHCP server sends the option back, the host stops the DHCP client for the time interval specified in the option. Enable this feature if the host supports an IPv6-only mode, i.e. either all applications are IPv6-only capable or there is a form of 464XLAT deployed. When set to &quot;default&quot; (-1), the actual value is looked up in the global configuration; if not specified, it defaults to &quot;no&quot; (0). If the connection has IPv6 method set to &quot;disabled&quot;, this property does not have effect and the &quot;IPv6-Only Preferred&quot; option is always disabled."
nmcli-description="Controls the &quot;IPv6-Only Preferred&quot; DHCPv4 option (option 108 - RFC 8925). When set to &quot;yes&quot; (1), the host adds the option to the parameter request list; if the DHCP server sends the option back, the host stops the DHCP client for the time interval specified in the option. Enable this feature if the host supports an IPv6-only mode, i.e. either all applications are IPv6-only capable or there is a form of CLAT (464XLAT) deployed. If set to &quot;auto&quot; (2), the option is automatically turned on when the IPv6 method is &quot;auto&quot; and the connection enables CLAT. If these two conditions are met, the host can operate in IPv6-only mode and therefore it is safe to disable DHCPv4 if the network also supports it. When set to &quot;default&quot; (-1), the actual value is looked up in the global configuration; if not specified, it defaults to &quot;auto&quot; (2). If the connection has IPv6 method set to &quot;disabled&quot;, this property does not have effect and the &quot;IPv6-Only Preferred&quot; option is always disabled."
format="choice (NMSettingIP4DhcpIpv6OnlyPreferred)"
values="default (-1), no (0), yes (1)" />
values="default (-1), no (0), yes (1), auto (2)" />
<property name="link-local"
nmcli-description="Enable and disable the IPv4 link-local configuration independently of the ipv4.method configuration. This allows a link-local address (169.254.x.y/16) to be obtained in addition to other addresses, such as those manually configured or obtained from a DHCP server. When set to &quot;auto&quot;, the value is dependent on &quot;ipv4.method&quot;. When set to &quot;default&quot;, it honors the global connection default, before falling back to &quot;auto&quot;. Note that if &quot;ipv4.method&quot; is &quot;disabled&quot;, then link local addressing is always disabled too. The default is &quot;default&quot;. Since 1.52, when set to &quot;fallback&quot;, a link-local address is obtained if no other IPv4 address is set."
format="choice (NMSettingIP4LinkLocal)"
@ -1578,6 +1578,10 @@
<property name="dhcp-pd-hint"
nmcli-description="A IPv6 address followed by a slash and a prefix length. If set, the value is sent to the DHCPv6 server as hint indicating the prefix delegation (IA_PD) we want to receive. To only hint a prefix length without prefix, set the address part to the zero address (for example &quot;::/60&quot;)."
format="string" />
<property name="clat"
nmcli-description="Controls the CLAT (Customer-side translator) functionality. CLAT is used to implement the client part of 464XLAT (RFC 6877), an architecture that provides IPv4 connectivity to hosts on IPv6-only networks. Setting &quot;yes&quot; (1) or &quot;no&quot; (0) respectively enables or disables CLAT. When enabled, NetworkManager discovers the NAT64 prefix via Router Advertisement; if the prefix is found, it installs a BPF program to perform the stateless translation of packets betweeen IPv4 and IPv6. When set to &quot;default&quot; (-1), the actual value is looked up in the global configuration; if not specified it defaults to &quot;no&quot; (0). In the future the fallback value may change to &quot;yes&quot; (1)."
format="choice (NMSettingIp6ConfigClat)"
values="default (-1), no (0), yes (1)" />
<property name="dhcp-duid"
nmcli-description="A string containing the DHCPv6 Unique Identifier (DUID) used by the dhcp client to identify itself to DHCPv6 servers (RFC 3315). The DUID is carried in the Client Identifier option. If the property is a hex string (&apos;aa:bb:cc&apos;) it is interpreted as a binary DUID and filled as an opaque value in the Client Identifier option. The special value &quot;lease&quot; will retrieve the DUID previously used from the lease file belonging to the connection. If no DUID is found and &quot;dhclient&quot; is the configured dhcp client, the DUID is searched in the system-wide dhclient lease file. If still no DUID is found, or another dhcp client is used, a global and permanent DUID-UUID (RFC 6355) will be generated based on the machine-id. The special values &quot;llt&quot; and &quot;ll&quot; will generate a DUID of type LLT or LL (see RFC 3315) based on the current MAC address of the device. In order to try providing a stable DUID-LLT, the time field will contain a constant timestamp that is used globally (for all profiles) and persisted to disk. The special values &quot;stable-llt&quot;, &quot;stable-ll&quot; and &quot;stable-uuid&quot; will generate a DUID of the corresponding type, derived from the connection&apos;s stable-id and a per-host unique key. You may want to include the &quot;${DEVICE}&quot; or &quot;${MAC}&quot; specifier in the stable-id, in case this profile gets activated on multiple devices. So, the link-layer address of &quot;stable-ll&quot; and &quot;stable-llt&quot; will be a generated address derived from the stable id. The DUID-LLT time value in the &quot;stable-llt&quot; option will be picked among a static timespan of three years (the upper bound of the interval is the same constant timestamp used in &quot;llt&quot;). When the property is unset, the global value provided for &quot;ipv6.dhcp-duid&quot; is used. If no global value is provided, the default &quot;lease&quot; value is assumed."
format="string" />

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff