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:
Beniamino Galvani 2026-01-24 09:32:53 +00:00
commit e311df0c75
39 changed files with 3120 additions and 614 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

@ -40,6 +40,10 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE!
* New <Select...> button in nmtui that allows users to chose from list of
available devices when creating connection profiles for physical interfaces
(Ethernet, Wi-Fi, etc.).
* 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 ("yes" or "auto") in the connection profile.
=============================================
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

@ -105,7 +105,12 @@ Release: __RELEASE_VERSION__%{?dist}
%else
%bcond_with polkit_noauth_group
%endif
%ifarch %{ix86}
# there is no bpftool in i686
%bcond_with clat
%else
%bcond_without clat
%endif
###############################################################################
%global dbus_version 1.9.18
@ -183,6 +188,10 @@ Requires: dbus >= %{dbus_version}
Requires: glib2 >= %{glib2_version}
Requires: %{name}-libnm%{?_isa} = %{epoch}:%{version}-%{release}
%if %{with clat}
Requires: libbpf
%endif
%if 0%{?rhel} == 8
# Older libndp versions use select() (rh#1933041). On well known distros,
# choose a version that has the necessary fix.
@ -230,6 +239,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
@ -284,6 +294,10 @@ BuildRequires: firewalld-filesystem
BuildRequires: iproute
BuildRequires: iproute-tc
BuildRequires: libnvme-devel >= 1.5
%if %{with clat}
BuildRequires: libbpf-devel
BuildRequires: bpftool
%endif
Provides: %{name}-dispatcher%{?_isa} = %{epoch}:%{version}-%{release}
@ -612,6 +626,11 @@ Preferably use nmcli instead.
%else
-Diwd=false \
%endif
%if %{with clat}
-Dclat=true \
%else
-Dclat=false \
%endif
%if %{with bluetooth}
-Dbluez5_dun=true \
%else

View file

@ -954,6 +954,10 @@ ipv6.ip6-privacy=0
<term><varname>ipv4.forwarding</varname></term>
<listitem><para>Whether to configure IPv4 sysctl interface-specific forwarding. When enabled, the interface will act as a router to forward the IPv4 packet from one interface to another. If left unspecified, "auto" is used, so NetworkManager sets the IPv4 forwarding if any shared connection is active, or it will use the kernel default value otherwise. The "ipv4.forwarding" property is ignored when "ipv4.method" is set to "shared", because forwarding is always enabled in this case. The accepted values are: 0: disabled, 1: enabled, 2: auto, 3: ignored (leave the forwarding unchanged).</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>ipv4.clat</varname></term>
<listitem><para>If left unspecified, defaults to "no".</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>ipv4.routed-dns</varname></term>
</varlistentry>
@ -972,7 +976,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>

View file

@ -288,7 +288,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())
@ -514,6 +514,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')
@ -1181,5 +1189,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')

1116
src/core/bpf/clat.bpf.c Normal file

File diff suppressed because it is too large Load diff

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

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

@ -0,0 +1,239 @@
# 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',
'-Wunused',
'-Wimplicit-fallthrough',
'-Wno-compare-distinct-pointer-types',
'-fno-stack-protector',
'-O2',
'-target',
'bpf',
'-g',
'-c',
]
bpf_gcc_flags = [
'-std=gnu17',
'-Wunused',
'-Wimplicit-fallthrough',
'-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

@ -906,10 +906,11 @@ static void concheck_update_state(NMDevice *self,
static void sriov_op_cb(GError *error, gpointer user_data);
static void device_ifindex_changed_cb(NMManager *manager, NMDevice *device_changed, NMDevice *self);
static gboolean device_link_changed(gpointer user_data);
static gboolean _get_maybe_ipv6_disabled(NMDevice *self);
static void deactivate_ready(NMDevice *self, NMDeviceStateReason reason);
static void carrier_disconnected_action_cancel(NMDevice *self);
static gboolean device_link_changed(gpointer user_data);
static gboolean _get_maybe_ipv6_disabled(NMDevice *self);
static void deactivate_ready(NMDevice *self, NMDeviceStateReason reason);
static void carrier_disconnected_action_cancel(NMDevice *self);
static const char *nm_device_get_effective_ip_config_method(NMDevice *self, int addr_family);
/*****************************************************************************/
@ -1523,6 +1524,40 @@ _prop_get_connection_dnssec(NMDevice *self, NMConnection *connection)
NM_SETTING_CONNECTION_DNSSEC_DEFAULT);
}
static NMSettingIp4ConfigClat
_prop_get_ipv4_clat(NMDevice *self)
{
NMSettingIP4Config *s_ip4 = NULL;
NMSettingIp4ConfigClat clat;
const char *method;
s_ip4 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP4_CONFIG);
if (!s_ip4)
return NM_SETTING_IP4_CONFIG_CLAT_NO;
method = nm_device_get_effective_ip_config_method(self, AF_INET);
if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
return NM_SETTING_IP4_CONFIG_CLAT_NO;
clat = nm_setting_ip4_config_get_clat(s_ip4);
if (clat == NM_SETTING_IP4_CONFIG_CLAT_DEFAULT) {
clat = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("ipv4.clat"),
self,
NM_SETTING_IP4_CONFIG_CLAT_NO,
NM_SETTING_IP4_CONFIG_CLAT_FORCE,
NM_SETTING_IP4_CONFIG_CLAT_NO);
}
if (clat == NM_SETTING_IP4_CONFIG_CLAT_AUTO
&& !nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
/* clat=auto enables CLAT only with method=auto */
clat = NM_SETTING_IP4_CONFIG_CLAT_NO;
}
return clat;
}
static NMMptcpFlags
_prop_get_connection_mptcp_flags(NMDevice *self, NMConnection *connection)
{
@ -1921,26 +1956,43 @@ _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)
{
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)
return ipv6_only;
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);
}
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 (NM_IN_SET(ipv6_only,
NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_YES,
NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO))
return ipv6_only == NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_YES;
/* auto */
NM_SET_OUT(out_is_auto, TRUE);
if (nm_streq0(nm_device_get_effective_ip_config_method(self, AF_INET6),
NM_SETTING_IP6_CONFIG_METHOD_AUTO)
&& _prop_get_ipv4_clat(self) != NM_SETTING_IP4_CONFIG_CLAT_NO) {
return TRUE;
}
return FALSE;
}
/**
@ -3641,6 +3693,7 @@ 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));
return l3cd;
}
@ -4923,7 +4976,7 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N
if (state >= NM_DEVICE_STATE_IP_CONFIG && state < NM_DEVICE_STATE_DEACTIVATING) {
/* FIXME(l3cfg): MTU handling should be moved to l3cfg. */
if (l3cd)
priv->ip6_mtu = nm_l3_config_data_get_ip6_mtu(l3cd);
priv->ip6_mtu = nm_l3_config_data_get_ip6_mtu_ra(l3cd);
_commit_mtu(self);
}
_dev_ipll4_check_fallback(self, l3cd);
@ -11467,6 +11520,8 @@ _dev_ipmanual_start(NMDevice *self)
if (_prop_get_ipvx_routed_dns(self, AF_INET6) == NM_SETTING_IP_CONFIG_ROUTED_DNS_YES) {
nm_l3_config_data_set_routed_dns(l3cd, AF_INET6, TRUE);
}
nm_l3_config_data_set_clat(l3cd, _prop_get_ipv4_clat(self));
}
if (!l3cd) {
@ -11775,8 +11830,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);
@ -11795,13 +11851,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,13 +212,21 @@ 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);
nm_l3_config_data_set_ip6_mtu(l3cd, rdata->mtu);
nm_l3_config_data_set_ip6_mtu_ra(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,8 @@ struct _NML3ConfigData {
NMSettingConnectionDnsOverTls dns_over_tls;
NMSettingConnectionDnssec dnssec;
NMUtilsIPv6IfaceId ip6_token;
NMSettingIp4ConfigClat clat;
NMRefString *network_id;
NML3ConfigDatFlags flags;
@ -130,7 +135,8 @@ struct _NML3ConfigData {
int ndisc_hop_limit_val;
guint32 mtu;
guint32 ip6_mtu;
guint32 ip6_mtu_static; /* IPv6 MTU from the connection profile */
guint32 ip6_mtu_ra; /* IPv6 MTU from Router Advertisement */
guint32 ndisc_reachable_time_msec_val;
guint32 ndisc_retrans_timer_msec_val;
@ -167,6 +173,8 @@ struct _NML3ConfigData {
bool routed_dns_4 : 1;
bool routed_dns_6 : 1;
bool pref64_valid : 1;
};
/*****************************************************************************/
@ -390,8 +398,11 @@ nm_l3_config_data_log(const NML3ConfigData *self,
: "",
!self->is_sealed ? ", not-sealed" : "");
if (self->mtu != 0 || self->ip6_mtu != 0) {
_L("mtu: %u, ip6-mtu: %u", self->mtu, self->ip6_mtu);
if (self->mtu != 0 || self->ip6_mtu_static != 0 || self->ip6_mtu_ra != 0) {
_L("mtu: %u, ip6-mtu-static: %u, ip6-mtu-ra %u",
self->mtu,
self->ip6_mtu_static,
self->ip6_mtu_ra);
}
for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
@ -519,6 +530,19 @@ nm_l3_config_data_log(const NML3ConfigData *self,
_L("nis-domain: %s", self->nis_domain->str);
}
if (!IS_IPv4) {
if (self->clat == NM_SETTING_IP4_CONFIG_CLAT_AUTO)
_L("clat: auto");
else if (self->clat == NM_SETTING_IP4_CONFIG_CLAT_FORCE)
_L("clat: force");
}
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 +627,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");
@ -709,6 +737,7 @@ nm_l3_config_data_new(NMDedupMultiIndex *multi_idx, int ifindex, NMIPConfigSourc
.flags = NM_L3_CONFIG_DAT_FLAGS_NONE,
.metered = NM_TERNARY_DEFAULT,
.proxy_browser_only = NM_TERNARY_DEFAULT,
.clat = NM_SETTING_IP4_CONFIG_CLAT_NO,
.proxy_method = NM_PROXY_CONFIG_METHOD_UNKNOWN,
.route_table_sync_4 = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE,
.route_table_sync_6 = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE,
@ -722,6 +751,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 +852,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);
}
@ -1890,22 +1921,42 @@ nm_l3_config_data_set_mtu(NML3ConfigData *self, guint32 mtu)
}
guint32
nm_l3_config_data_get_ip6_mtu(const NML3ConfigData *self)
nm_l3_config_data_get_ip6_mtu_static(const NML3ConfigData *self)
{
nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
return self->ip6_mtu;
return self->ip6_mtu_static;
}
gboolean
nm_l3_config_data_set_ip6_mtu(NML3ConfigData *self, guint32 ip6_mtu)
nm_l3_config_data_set_ip6_mtu_static(NML3ConfigData *self, guint32 ip6_mtu)
{
nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
if (self->ip6_mtu == ip6_mtu)
if (self->ip6_mtu_static == ip6_mtu)
return FALSE;
self->ip6_mtu = ip6_mtu;
self->ip6_mtu_static = ip6_mtu;
return TRUE;
}
guint32
nm_l3_config_data_get_ip6_mtu_ra(const NML3ConfigData *self)
{
nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
return self->ip6_mtu_ra;
}
gboolean
nm_l3_config_data_set_ip6_mtu_ra(NML3ConfigData *self, guint32 ip6_mtu)
{
nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
if (self->ip6_mtu_ra == ip6_mtu)
return FALSE;
self->ip6_mtu_ra = ip6_mtu;
return TRUE;
}
@ -1957,6 +2008,92 @@ 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, NMSettingIp4ConfigClat val)
{
nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
nm_assert(NM_IN_SET(val,
NM_SETTING_IP4_CONFIG_CLAT_NO,
NM_SETTING_IP4_CONFIG_CLAT_FORCE,
NM_SETTING_IP4_CONFIG_CLAT_AUTO));
if (self->clat == val)
return FALSE;
self->clat = val;
return TRUE;
}
NMSettingIp4ConfigClat
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,8 +2621,10 @@ 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(a->ip6_mtu_static, b->ip6_mtu_static);
NM_CMP_DIRECT(a->ip6_mtu_ra, b->ip6_mtu_ra);
NM_CMP_DIRECT_UNSAFE(a->metered, b->metered);
NM_CMP_DIRECT_UNSAFE(a->proxy_browser_only, b->proxy_browser_only);
NM_CMP_DIRECT_UNSAFE(a->proxy_method, b->proxy_method);
@ -2509,6 +2648,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 +2672,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 +2833,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);
@ -3030,6 +3180,9 @@ _init_from_connection_ip(NML3ConfigData *self, int addr_family, NMConnection *co
nm_l3_config_data_set_ip6_privacy(
self,
nm_setting_ip6_config_get_ip6_privacy(NM_SETTING_IP6_CONFIG(s_ip)));
nm_l3_config_data_set_ip6_mtu_static(
self,
nm_setting_ip6_config_get_mtu(NM_SETTING_IP6_CONFIG(s_ip)));
}
}
@ -3506,6 +3659,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)
@ -3544,8 +3700,11 @@ nm_l3_config_data_merge(NML3ConfigData *self,
if (self->mtu == 0u)
self->mtu = src->mtu;
if (self->ip6_mtu == 0u)
self->ip6_mtu = src->ip6_mtu;
if (self->ip6_mtu_static == 0u)
self->ip6_mtu_static = src->ip6_mtu_static;
if (self->ip6_mtu_ra == 0u)
self->ip6_mtu_ra = src->ip6_mtu_ra;
if (NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_CLONE)) {
_nm_unused nm_auto_unref_dhcplease NMDhcpLease *dhcp_lease_6 =
@ -3567,6 +3726,20 @@ nm_l3_config_data_merge(NML3ConfigData *self,
self->routed_dns_4 = TRUE;
if (src->routed_dns_6)
self->routed_dns_6 = TRUE;
if (self->clat == NM_SETTING_IP4_CONFIG_CLAT_NO) {
/* 'no' always loses to 'force' and 'auto' */
self->clat = src->clat;
} else if (src->clat == NM_SETTING_IP4_CONFIG_CLAT_FORCE) {
/* 'force' always takes precedence */
self->clat = src->clat;
}
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

@ -5,6 +5,7 @@
#include "libnm-glib-aux/nm-dedup-multi.h"
#include "nm-setting-connection.h"
#include "nm-setting-ip4-config.h"
#include "nm-setting-ip6-config.h"
#include "libnm-platform/nm-platform.h"
#include "libnm-platform/nmp-object.h"
@ -225,6 +226,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);
@ -482,14 +487,36 @@ guint32 nm_l3_config_data_get_mtu(const NML3ConfigData *self);
gboolean nm_l3_config_data_set_mtu(NML3ConfigData *self, guint32 mtu);
guint32 nm_l3_config_data_get_ip6_mtu(const NML3ConfigData *self);
guint32 nm_l3_config_data_get_ip6_mtu_static(const NML3ConfigData *self);
gboolean nm_l3_config_data_set_ip6_mtu(NML3ConfigData *self, guint32 ip6_mtu);
gboolean nm_l3_config_data_set_ip6_mtu_static(NML3ConfigData *self, guint32 ip6_mtu);
guint32 nm_l3_config_data_get_ip6_mtu_ra(const NML3ConfigData *self);
gboolean nm_l3_config_data_set_ip6_mtu_ra(NML3ConfigData *self, guint32 ip6_mtu);
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, NMSettingIp4ConfigClat val);
NMSettingIp4ConfigClat 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,24 @@ 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;
struct bpf_link *clat_ingress_link;
struct bpf_link *clat_egress_link;
int clat_socket;
#endif /* HAVE_CLAT */
/* Whether we earlier configured MPTCP endpoints for the interface. */
union {
struct {
@ -353,6 +382,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 +4155,13 @@ _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;
gboolean clat_enabled = FALSE;
const NMPlatformIP4Route *ip4_route;
NMDedupMultiIter iter;
#endif
nm_assert(NM_IS_L3CFG(self));
nm_assert(!out_old || !*out_old);
@ -4220,6 +4259,212 @@ _l3cfg_update_combined_config(NML3Cfg *self,
&hook_data);
}
#if HAVE_CLAT
switch (nm_l3_config_data_get_clat(l3cd)) {
case NM_SETTING_IP4_CONFIG_CLAT_FORCE:
clat_enabled = TRUE;
break;
case NM_SETTING_IP4_CONFIG_CLAT_NO:
clat_enabled = FALSE;
break;
case NM_SETTING_IP4_CONFIG_CLAT_AUTO:
clat_enabled = TRUE;
/* disable if there is a native IPv4 gateway */
nm_l3_config_data_iter_ip4_route_for_each (&iter, l3cd, &ip4_route) {
if (ip4_route->network == INADDR_ANY && ip4_route->plen == 0
&& ip4_route->gateway != INADDR_ANY)
clat_enabled = FALSE;
break;
}
break;
case NM_SETTING_IP4_CONFIG_CLAT_DEFAULT:
nm_assert_not_reached();
clat_enabled = TRUE;
break;
}
if (clat_enabled && nm_l3_config_data_get_pref64_valid(l3cd)) {
NMPlatformIPXRoute rx;
NMIPAddrTyped best_v6_gateway;
const NMPlatformIP6Route *best_v6_route;
const NMPlatformIP6Address *ip6_entry;
struct in6_addr ip6;
const char *network_id;
char buf[512];
guint32 route4_metric = NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4;
/* 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);
}
{
const NMPlatformIP4Route *r4;
guint32 metric = 0;
guint32 penalty = 0;
/* Find the IPv4 metric for the CLAT default route.
* If there is another non-CLAT default route on the device, use the
* same metric + 1, so that native connectivity is always preferred.
* Otherwise, use the metric from the connection profile.
*/
r4 = NMP_OBJECT_CAST_IP4_ROUTE(
nm_l3_config_data_get_best_default_route(l3cd, AF_INET));
if (r4) {
route4_metric = nm_add_clamped_u32(r4->metric, 1u);
} else {
for (i = 0; i < l3_config_datas_len; i++) {
const L3ConfigData *l3cd_data = l3_config_datas_arr[i];
if (l3cd_data->default_route_metric_4
!= NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4) {
metric = l3cd_data->default_route_metric_4;
}
if (l3cd_data->default_route_penalty_4 != 0) {
penalty = l3cd_data->default_route_penalty_4;
}
}
route4_metric = nm_add_clamped_u32(metric, penalty);
}
}
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) {
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,
};
const NMPlatformLink *pllink;
guint mtu = 0;
guint val = 0;
best_v6_gateway.addr_family = AF_INET6;
best_v6_gateway.addr.addr6 = best_v6_route->gateway;
/* Determine the IPv6 MTU of the interface. Unfortunately,
* the logic to set the MTU is in NMDevice and here we need
* some duplication to find the actual value.
* TODO: move the MTU handling into l3cfg. */
/* Get the link MTU */
pllink = nm_l3cfg_get_pllink(self, TRUE);
if (pllink)
mtu = pllink->mtu;
if (mtu == 0)
mtu = 1500;
/* Update it with the IPv6 MTU value from the connection
* or from RA */
val = nm_l3_config_data_get_ip6_mtu_static(l3cd);
if (val == 0) {
val = nm_l3_config_data_get_ip6_mtu_ra(l3cd);
}
if (val != 0 && val < mtu) {
mtu = val;
}
if (mtu < 1280)
mtu = 1280;
/* Leave 20 additional bytes for the ipv4 -> ipv6 header translation,
* 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,
.metric = route4_metric,
.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 +4525,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 +5636,174 @@ _l3_commit_one(NML3Cfg *self,
_failedobj_handle_routes(self, addr_family, routes_failed);
}
#if HAVE_CLAT
static void
_l3_clat_destroy(NML3Cfg *self)
{
char buf[100];
int err;
if (self->priv.p->clat_ingress_link) {
err = bpf_link__destroy(self->priv.p->clat_ingress_link);
if (err != 0) {
libbpf_strerror(err, buf, sizeof(buf));
_LOGD("clat: failed to destroy the ingress link");
}
self->priv.p->clat_ingress_link = NULL;
}
if (self->priv.p->clat_egress_link) {
err = bpf_link__destroy(self->priv.p->clat_egress_link);
if (err != 0) {
libbpf_strerror(err, buf, sizeof(buf));
_LOGD("clat: failed to destroy the egress link");
}
self->priv.p->clat_egress_link = NULL;
}
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;
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_and_load();
if (!self->priv.p->clat_bpf) {
libbpf_strerror(errno, buf, sizeof(buf));
_LOGW("clat: failed to open and load the BPF program: %s", buf);
return;
}
self->priv.p->clat_ingress_link =
bpf_program__attach_tcx(self->priv.p->clat_bpf->progs.clat_ingress,
self->priv.ifindex,
NULL);
if (!self->priv.p->clat_ingress_link) {
libbpf_strerror(errno, buf, sizeof(buf));
_LOGW("clat: failed to attach the ingress program: %s", buf);
return;
}
self->priv.p->clat_egress_link =
bpf_program__attach_tcx(self->priv.p->clat_bpf->progs.clat_egress,
self->priv.ifindex,
NULL);
if (!self->priv.p->clat_egress_link) {
libbpf_strerror(errno, buf, sizeof(buf));
_LOGW("clat: failed to attach the egress program: %s", buf);
return;
}
_LOGT("clat: program attached successfully");
}
/* 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 +5886,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 +6271,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 +6383,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

@ -574,8 +574,8 @@ notify_watcher:
typedef struct {
const char *name;
guint32 start_addr; /* host byte order */
guint prefix_len;
guint num_addrs;
guint range_plen;
guint addr_plen;
gboolean allow_reuse;
} IPReservationTypeDesc;
@ -583,11 +583,19 @@ static const IPReservationTypeDesc ip_reservation_types[_NM_NETNS_IP_RESERVATION
[NM_NETNS_IP_RESERVATION_TYPE_SHARED4] =
{
.name = "shared-ip4",
.start_addr = 0x0a2a0001, /* 10.42.0.1 */
.prefix_len = 24,
.num_addrs = 256,
.start_addr = 0x0a2a0001, /* 10.42.{0-255}.1/24 */
.range_plen = 16,
.addr_plen = 24,
.allow_reuse = TRUE,
},
[NM_NETNS_IP_RESERVATION_TYPE_CLAT] =
{
.name = "clat",
.start_addr = 0xc0000005, /* 192.0.0.{5-7,0-4}/32 */
.range_plen = 29,
.addr_plen = 32,
.allow_reuse = FALSE,
},
};
NMNetnsIPReservation *
@ -613,13 +621,23 @@ nm_netns_ip_reservation_get(NMNetns *self, NMNetnsIPReservationType type)
g_object_ref(self);
} else {
guint32 count;
guint32 base_network;
guint32 host_mask;
guint32 increment;
nm_assert(g_hash_table_size(*table) > 0);
nm_assert(desc->prefix_len > 0 && desc->prefix_len <= 32);
nm_assert(desc->range_plen < 32);
nm_assert(desc->addr_plen > 0 && desc->addr_plen <= 32);
nm_assert(desc->addr_plen > desc->range_plen);
base_network = desc->start_addr & ~(0xFFFFFFFFu >> desc->range_plen);
host_mask = 0xFFFFFFFFu >> desc->range_plen;
increment = 1 << (32 - desc->addr_plen);
count = 0u;
for (;;) {
addr = htonl(desc->start_addr + (count << (32 - desc->prefix_len)));
addr = htonl(base_network
+ ((base_network + (desc->start_addr + count * increment)) & host_mask));
res = g_hash_table_lookup(*table, &addr);
if (!res)
@ -627,7 +645,7 @@ nm_netns_ip_reservation_get(NMNetns *self, NMNetnsIPReservationType type)
count++;
if (count >= desc->num_addrs) {
if (count >= 1 << (desc->addr_plen - desc->range_plen)) {
if (!desc->allow_reuse) {
_LOGE("%s: ran out of IP addresses", desc->name);
return NULL;
@ -637,12 +655,12 @@ nm_netns_ip_reservation_get(NMNetns *self, NMNetnsIPReservationType type)
_LOGE("%s: ran out of IP addresses. Reuse %s/%u",
desc->name,
nm_inet4_ntop(res->addr, buf),
desc->prefix_len);
desc->addr_plen);
} else {
_LOGD("%s: reserved IP address %s/%u (duplicate)",
desc->name,
nm_inet4_ntop(res->addr, buf),
desc->prefix_len);
desc->addr_plen);
}
res->_ref_count++;
return res;
@ -663,7 +681,7 @@ nm_netns_ip_reservation_get(NMNetns *self, NMNetnsIPReservationType type)
_LOGD("%s: reserved IP address %s/%u",
desc->name,
nm_inet4_ntop(res->addr, buf),
desc->prefix_len);
desc->addr_plen);
return res;
}
@ -695,7 +713,7 @@ nm_netns_ip_reservation_release(NMNetnsIPReservation *res)
_LOGD("%s: release IP address reservation %s/%u (%d more references held)",
desc->name,
nm_inet4_ntop(res->addr, buf),
desc->prefix_len,
desc->addr_plen,
res->_ref_count);
return;
}
@ -706,7 +724,7 @@ nm_netns_ip_reservation_release(NMNetnsIPReservation *res)
_LOGD("%s: release IP address reservation %s/%u",
desc->name,
nm_inet4_ntop(res->addr, buf),
desc->prefix_len);
desc->addr_plen);
if (g_hash_table_size(*table) == 0) {
nm_clear_pointer(table, g_hash_table_unref);

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

@ -3600,6 +3600,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,14 @@ do_write_construct(NMConnection *connection,
FALSE);
return FALSE;
}
if (nm_setting_ip4_config_get_clat(NM_SETTING_IP4_CONFIG(s_ip4))
!= NM_SETTING_IP4_CONFIG_CLAT_DEFAULT) {
set_error_unsupported(error,
connection,
NM_SETTING_IP4_CONFIG_SETTING_NAME "." NM_SETTING_IP4_CONFIG_CLAT,
FALSE);
return FALSE;
}
}
write_ip4_setting(connection,

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 192.0.0.{5,6,7,0,1,2,3,4} */
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 + 5) % 8);
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.7");
/* 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

@ -2106,6 +2106,8 @@ global:
libnm_1_58_0 {
global:
nm_setting_ip4_config_clat_get_type;
nm_setting_ip4_config_get_clat;
nm_utils_wifi_6ghz_freqs;
nm_utils_wifi_freq_to_band;
nm_wifi_band_get_type;

View file

@ -1652,6 +1652,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

@ -40,7 +40,8 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_DHCP_CLIENT_ID,
PROP_DHCP_FQDN,
PROP_DHCP_VENDOR_CLASS_IDENTIFIER,
PROP_LINK_LOCAL,
PROP_DHCP_IPV6_ONLY_PREFERRED, );
PROP_DHCP_IPV6_ONLY_PREFERRED,
PROP_CLAT, );
typedef struct {
NMSettingIPConfigPrivate parent;
@ -50,6 +51,7 @@ typedef struct {
char *dhcp_vendor_class_identifier;
gint32 link_local;
gint32 dhcp_ipv6_only_preferred;
gint32 clat;
} NMSettingIP4ConfigPrivate;
/**
@ -168,6 +170,24 @@ nm_setting_ip4_config_get_dhcp_ipv6_only_preferred(NMSettingIP4Config *setting)
return NM_SETTING_IP4_CONFIG_GET_PRIVATE(setting)->dhcp_ipv6_only_preferred;
}
/**
* nm_setting_ip4_config_get_clat:
* @setting: the #NMSettingIP4Config
*
* Returns the value in the #NMSettingIP4Config:clat property.
*
* Returns: the CLAT property value
*
* Since: 1.58
*/
NMSettingIp4ConfigClat
nm_setting_ip4_config_get_clat(NMSettingIP4Config *setting)
{
g_return_val_if_fail(NM_IS_SETTING_IP4_CONFIG(setting), NM_SETTING_IP4_CONFIG_CLAT_DEFAULT);
return NM_SETTING_IP4_CONFIG_GET_PRIVATE(setting)->clat;
}
static gboolean
verify(NMSetting *setting, NMConnection *connection, GError **error)
{
@ -186,12 +206,15 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
if (!strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
if (nm_setting_ip_config_get_num_addresses(s_ip) == 0
&& nm_setting_ip_config_get_num_routes(s_ip) == 0) {
g_set_error(error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_MISSING_PROPERTY,
_("method '%s' requires at least an address or a route"),
method);
&& nm_setting_ip_config_get_num_routes(s_ip) == 0
&& nm_setting_ip4_config_get_clat(NM_SETTING_IP4_CONFIG(s_ip))
!= NM_SETTING_IP4_CONFIG_CLAT_FORCE) {
g_set_error(
error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_MISSING_PROPERTY,
_("method '%s' requires at least an address, a route, or CLAT set to 'force'"),
method);
g_prefix_error(error,
"%s.%s: ",
NM_SETTING_IP4_CONFIG_SETTING_NAME,
@ -1353,18 +1376,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
* profile has ipv4.clat set to "yes" or "auto". If these two conditions are
* met, the host can operate in IPv6-only mode and therefore it is safe to
* disable DHCPv4 when 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.
@ -1382,6 +1412,39 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
NMSettingIP4ConfigPrivate,
dhcp_ipv6_only_preferred);
/**
* NMSettingIP4Config: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.
*
* When CLAT is enabled, NetworkManager discovers the NAT64 prefix from IPv6 Router Advertisements;
* if a NAT64 prefix is announced, NetworkManager installs a BPF program to perform the stateless
* translation of packets between IPv4 and IPv6.
*
* Setting %NM_SETTING_IP4_CONFIG_CLAT_NO completely disables CLAT. %NM_SETTING_IP4_CONFIG_CLAT_AUTO
* enables CLAT only when the IPv4 method is 'auto' and the device doesn't have a native IPv4 gateway.
* %NM_SETTING_IP4_CONFIG_CLAT_FORCE enables CLAT even if the IPv4 method is not 'auto' and even if
* the device has a native IPv4 gateway.
*
* When set to %NM_SETTING_IP4_CONFIG_CLAT_DEFAULT, the actual value is looked up in the global
* configuration; if not specified it defaults to %NM_SETTING_IP4_CONFIG_CLAT_NO. In the future the
* default fall back value will change to %NM_SETTING_IP4_CONFIG_CLAT_AUTO.
*
* Since: 1.58
*/
_nm_setting_property_define_direct_enum(properties_override,
obj_properties,
NM_SETTING_IP4_CONFIG_CLAT,
PROP_CLAT,
NM_TYPE_SETTING_IP4_CONFIG_CLAT,
NM_SETTING_IP4_CONFIG_CLAT_DEFAULT,
NM_SETTING_PARAM_NONE,
NULL,
NMSettingIP4ConfigPrivate,
clat);
g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
_nm_setting_class_commit(setting_class,

View file

@ -4094,6 +4094,7 @@ test_connection_diff_a_only(void)
{NM_SETTING_IP_CONFIG_DHCP_REJECT_SERVERS, NM_SETTING_DIFF_RESULT_IN_A},
{NM_SETTING_IP4_CONFIG_LINK_LOCAL, NM_SETTING_DIFF_RESULT_IN_A},
{NM_SETTING_IP4_CONFIG_DHCP_IPV6_ONLY_PREFERRED, NM_SETTING_DIFF_RESULT_IN_A},
{NM_SETTING_IP4_CONFIG_CLAT, NM_SETTING_DIFF_RESULT_IN_A},
{NM_SETTING_IP_CONFIG_AUTO_ROUTE_EXT_GW, NM_SETTING_DIFF_RESULT_IN_A},
{NM_SETTING_IP_CONFIG_REPLACE_LOCAL_RULE, NM_SETTING_DIFF_RESULT_IN_A},
{NM_SETTING_IP_CONFIG_DHCP_SEND_RELEASE, NM_SETTING_DIFF_RESULT_IN_A},

View file

@ -34,6 +34,7 @@ G_BEGIN_DECLS
#define NM_SETTING_IP4_CONFIG_DHCP_VENDOR_CLASS_IDENTIFIER "dhcp-vendor-class-identifier"
#define NM_SETTING_IP4_CONFIG_DHCP_IPV6_ONLY_PREFERRED "dhcp-ipv6-only-preferred"
#define NM_SETTING_IP4_CONFIG_LINK_LOCAL "link-local"
#define NM_SETTING_IP4_CONFIG_CLAT "clat"
/**
* NM_SETTING_IP4_CONFIG_METHOD_AUTO:
@ -109,6 +110,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,8 +122,32 @@ 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;
/**
* NMSettingIp4ConfigClat:
* @NM_SETTING_IP4_CONFIG_CLAT_DEFAULT: use the global default value
* @NM_SETTING_IP4_CONFIG_CLAT_NO: disable CLAT
* @NM_SETTING_IP4_CONFIG_CLAT_AUTO: enable CLAT only when the IPv4 method
* is 'auto' and the device doesn't have a native IPv4 gateway.
* @NM_SETTING_IP4_CONFIG_CLAT_FORCE: enable CLAT even with IPv4 methods
* other than 'auto' and even if the device has a native IPv4 gateway.
*
* #NMSettingIP4ConfigClat 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_IP4_CONFIG_CLAT_DEFAULT = -1,
NM_SETTING_IP4_CONFIG_CLAT_NO = 0,
NM_SETTING_IP4_CONFIG_CLAT_AUTO = 1,
NM_SETTING_IP4_CONFIG_CLAT_FORCE = 2,
} NMSettingIp4ConfigClat;
typedef struct _NMSettingIP4ConfigClass NMSettingIP4ConfigClass;
GType nm_setting_ip4_config_get_type(void);
@ -141,6 +168,9 @@ NM_AVAILABLE_IN_1_52
NMSettingIP4DhcpIpv6OnlyPreferred
nm_setting_ip4_config_get_dhcp_ipv6_only_preferred(NMSettingIP4Config *setting);
NM_AVAILABLE_IN_1_58
NMSettingIp4ConfigClat nm_setting_ip4_config_get_clat(NMSettingIP4Config *setting);
G_END_DECLS
#endif /* __NM_SETTING_IP4_CONFIG_H__ */

View file

@ -6601,6 +6601,9 @@ static const NMMetaPropertyInfo *const property_infos_IP4_CONFIG[] = {
PROPERTY_INFO_WITH_DESC (NM_SETTING_IP4_CONFIG_DHCP_IPV6_ONLY_PREFERRED,
.property_type = &_pt_gobject_enum,
),
PROPERTY_INFO (NM_SETTING_IP4_CONFIG_CLAT, DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_CLAT,
.property_type = &_pt_gobject_enum,
),
PROPERTY_INFO_WITH_DESC (NM_SETTING_IP4_CONFIG_LINK_LOCAL,
.property_type = &_pt_gobject_enum,
.property_typ_data = DEFINE_PROPERTY_TYP_DATA (

View file

@ -186,6 +186,7 @@
#define DESCRIBE_DOC_NM_SETTING_INFINIBAND_TRANSPORT_MODE N_("The IP-over-InfiniBand transport mode. Either \"datagram\" or \"connected\".")
#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_ADDRESSES N_("A list of IPv4 addresses and their prefix length. Multiple addresses can be separated by comma. For example \"192.168.1.5/24, 10.1.0.5/24\". The addresses are listed in decreasing priority, meaning the first address will be the primary address.")
#define DESCRIBE_DOC_NM_SETTING_IP4_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_IP4_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. When CLAT is enabled, NetworkManager discovers the NAT64 prefix from IPv6 Router Advertisements; if a NAT64 prefix is announced, NetworkManager installs a BPF program to perform the stateless translation of packets between IPv4 and IPv6. Setting \"no\" (0) completely disables CLAT. \"auto\" (1) enables CLAT only when the IPv4 method is 'auto' and the device doesn't have a native IPv4 gateway. \"force\" (2) enables CLAT even if the IPv4 method is not 'auto' and even if the device has a native IPv4 gateway. 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 default fall back value will change to \"auto\" (1).")
#define DESCRIBE_DOC_NM_SETTING_IP4_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_IP4_CONFIG_DHCP_CLIENT_ID N_("A string sent to the DHCP server to identify the local machine which the DHCP server may use to customize the DHCP lease and options. When the property is a hex string ('aa:bb:cc') it is interpreted as a binary client ID, in which case the first byte is assumed to be the 'type' field as per RFC 2132 section 9.14 and the remaining bytes may be an hardware address (e.g. '01:xx:xx:xx:xx:xx:xx' where 1 is the Ethernet ARP type and the rest is a MAC address). If the property is not a hex string it is considered as a non-hardware-address client ID and the 'type' field is set to 0. The special values \"mac\" and \"perm-mac\" are supported, which use the current or permanent MAC address of the device to generate a client identifier with type ethernet (01). Currently, these options only work for ethernet type of links. The special value \"ipv6-duid\" uses the DUID from \"ipv6.dhcp-duid\" property as an RFC4361-compliant client identifier. As IAID it uses \"ipv4.dhcp-iaid\" and falls back to \"ipv6.dhcp-iaid\" if unset. The special value \"duid\" generates a RFC4361-compliant client identifier based on \"ipv4.dhcp-iaid\" and uses a DUID generated by hashing /etc/machine-id. The special value \"stable\" is supported to generate a type 0 client identifier based on the stable-id (see connection.stable-id) and a per-host key. If you set the stable-id, you may want to include the \"${DEVICE}\" or \"${MAC}\" specifier to get a per-device key. The special value \"none\" prevents any client identifier from being sent. Note that this is normally not recommended. If unset, a globally configured default from NetworkManager.conf is used. If still unset, the default depends on the DHCP plugin. The internal dhcp client will default to \"mac\" and the dhclient plugin will try to use one from its config file if present, or won't sent any client-id otherwise.")
#define DESCRIBE_DOC_NM_SETTING_IP4_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.")
@ -193,7 +194,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 profile has ipv4.clat set to \"yes\" or \"auto\". If these two conditions are met, the host can operate in IPv6-only mode and therefore it is safe to disable DHCPv4 when 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.")

View file

@ -1452,9 +1452,13 @@
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 profile has ipv4.clat set to &quot;yes&quot; or &quot;auto&quot;. If these two conditions are met, the host can operate in IPv6-only mode and therefore it is safe to disable DHCPv4 when 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="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. When CLAT is enabled, NetworkManager discovers the NAT64 prefix from IPv6 Router Advertisements; if a NAT64 prefix is announced, NetworkManager installs a BPF program to perform the stateless translation of packets between IPv4 and IPv6. Setting &quot;no&quot; (0) completely disables CLAT. &quot;auto&quot; (1) enables CLAT only when the IPv4 method is &apos;auto&apos; and the device doesn&apos;t have a native IPv4 gateway. &quot;force&quot; (2) enables CLAT even if the IPv4 method is not &apos;auto&apos; and even if the device has a native IPv4 gateway. 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 default fall back value will change to &quot;auto&quot; (1)."
format="choice (NMSettingIp4ConfigClat)"
values="default (-1), no (0), auto (1), force (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)"

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff