Commit graph

17923 commits

Author SHA1 Message Date
Beniamino Galvani
912026b269 l3cfg: use the tcx attachment for the clat program
The TCX attachment type was added in kernel 6.6 (October 2023) and it
replaces the Traffic Control (TC) BPF attachment, providing better
usability. Convert the l3cfg code to use it.
2026-01-14 11:44:11 +01:00
Beniamino Galvani
534d32cf81 ipv4: improve logging for ipv4.dhcp-ipv6-only-preferred 2026-01-13 18:41:13 +01:00
Beniamino Galvani
1c32775005 ipv4: enable by default ipv4.dhcp-ipv6-only-preferred when CLAT is on
When CLAT is enabled, we want to also enable and honor by default DHCP
option 108 (IPv6-only preferred), so that the host can avoid
requesting an IPv4 address and go IPv6-only.
2026-01-13 18:41:11 +01:00
Beniamino Galvani
e249d725fe bpf: clat: improve debug messages 2026-01-13 18:41:10 +01:00
Beniamino Galvani
5d93f782d3 bpf: clat: translate inner headers of incoming ICMPv6 errors
ICMPv6 error messages contain a copy of the original packet that
caused the error. In a 464XLAT deployment, this inner packet is an
IPv6 packet (as translated by the PLAT), while the local host expects
to see the original IPv4 packet it generated.

Without translation, the local host can't match the error to an active
socket. This breaks functionality like Path MTU Discovery (PMTUD),
traceroute, and error reporting for connected UDP sockets.

This commit implements the translation of the inner headers from IPv6
to IPv4 for incoming ICMPv6 errors.

Some implementation notes:

 - this only handles incoming ICMPv6; outgoing ICMPv4 is not yet
   implemented, but it seems less important.

 - the program uses different functions for rewriting the outer and
   inner header. I tried using recursion but the verifier didn't seem
   to like it.

 - after rewriting the inner headers, the ICMP checksum is
   incrementally updated based on difference of all the individual
   modifications done to the inner headers. This has the advantage
   that all the operations are fixed-size. But probably it would be
   easier and faster to just calculate the checksum from scratch.
2026-01-13 18:41:07 +01:00
Beniamino Galvani
2c07267baf clat: support all pref64 lengths
Support all the prefix lengths defined in RFC 6052.
2026-01-13 18:41:05 +01:00
Beniamino Galvani
102b04d0ea clat: pass the configuration as a BPF global variable
The program only needs to know the local IPv4 address, the local IPv6
address and the PREF64. There is no need to create multiple maps for
that, just pass a global configuration struct containing those 3
fields.
2026-01-13 18:41:04 +01:00
Beniamino Galvani
cf7bdeaadb bpf: clat: improve the code style and consistency
Improve the code style and consistency of some functions:

- declare only one variable per line
- add "const" keyword to read-only function arguments
- remove unneeded function arguments
- rename variables holding headers on the stack with the "_buf"
  suffix
2026-01-13 18:41:03 +01:00
Beniamino Galvani
2e751865ea bpf: clat: rework to avoid pointer arithmetic
Avoid using pointer arithmetic in the BPF program, so that it requires
only CAP_BPF and not CAP_PERFMON. In this context "pointer arithmetic"
means adding a variable value to a packet pointer. This means that the
program no longer tries to parse variable-size headers (IPv4 options,
IPv6 extension headers). Those were already not supported before. It
also doesn't parse VLAN tags, but there should be no need for that. If
we use fixed offset, we can avoid using the parsing helpers from
libxdp.
2026-01-13 18:41:02 +01:00
Beniamino Galvani
161dc7774e bpf: clat: remove commented code
The rewrite of IPv6 header inside a ICMP error needs to be
implemented. Remove the unused comments for now.
2026-01-13 18:41:01 +01:00
Beniamino Galvani
2fb47693d3 bpf: clat: fix error handling for IPv6 packets
There are 3 possible results from clat_translate_v6():

 1. the packet didn't match the CLAT IPv6 address and must be
   accepted;

 2. the packet matches but it is invalid and so it must be dropped;

 3. the packet matches and it is valid; clat_handle_v6() should
    translate the packet to IPv4;

Before, the function returned TC_ACT_SHOT for both 2 and 3. Therefore,
clat_handle_v6() tried to rewrite also invalid packets.

Fix that by returning TC_ACT_UNSPEC for valid packets, meaning that
there isn't a final verdict yet.
2026-01-13 18:41:00 +01:00
Beniamino Galvani
b251fdca9b bpf: clat: don't explicitly inline functions
BPF handles function calls fine these days. Only leave the inline
qualifier on very small functions like csum_fold_helper().
2026-01-13 18:40:59 +01:00
Beniamino Galvani
51690d5f48 bpf: clat: use the right endian-conversion function
bpf_ntohl() is more correct because the field is in network byte
order; but there is no actual change in behavior.
2026-01-13 18:40:59 +01:00
Beniamino Galvani
7c89e7f19b bpf: clat: fix translation of ICMPv6 Parameter Problem
According to RFC 6145 5.2, the pointer should be set for code 0, not
1.
2026-01-13 18:40:58 +01:00
Beniamino Galvani
db85e4bf22 bpf: clat: add missing "break" statements 2026-01-13 18:40:57 +01:00
Beniamino Galvani
61f97032c4 bpf: clat: remove unused includes 2026-01-13 18:40:56 +01:00
Beniamino Galvani
46aff88d5f bpf: clat: remove unused variables 2026-01-13 18:40:55 +01:00
Beniamino Galvani
af439a4e5c bpf: clat: fix other verifier errors
When copying the IPv6 addresses via a direct assignement, the compiler
generates 32-bit operations that the verifier doesn't like:

>   237: (61) r3 = *(u32 *)(r8 +76)       ; frame1: R3_w=pkt(r=0) R8=ctx()
>   ; .saddr = ip6h->saddr, @ clat.bpf.c:124
>   238: (63) *(u32 *)(r10 -64) = r3
>   invalid size of register spill

Use explicit memcpy() for those.

Also, check the packet length before accessing the ICMPv6 header.
2026-01-13 18:40:54 +01:00
Beniamino Galvani
cee2be7d37 bpf: clat: avoid 32-bit register spills when access skb->data
The verifier reports this error when accessing skb->data:

  ; void *data     = (void *)(unsigned long long)skb->data; @ clat.bpf.c:625
  (61) r2 = *(u32 *)(r1 +76)       ; frame1: R1=ctx() R2_w=pkt(r=0)
  (63) *(u32 *)(r10 -120) = r2
  invalid size of register spill

Apparently it's trying to spill only 32 bits from the register to the
stack, which is invalid. A similar problem was reported here:
https://github.com/cilium/cilium/pull/25336

Add some macros using inline asm to fix the problem. With this change
now the compiler properly generates 64-bit spills.

 ; src/core/bpf/clat.bpf.c:625
-;     void *data     = (void *)(unsigned long long)skb->data;
+;     void *data     = SKB_DATA(skb);
      137:      61 12 4c 00 00 00 00 00 w2 = *(u32 *)(r1 + 0x4c)
-     138:      63 2a 88 ff 00 00 00 00 *(u32 *)(r10 - 0x78) = w2
+     138:      7b 2a 88 ff 00 00 00 00 *(u64 *)(r10 - 0x78) = r2
2026-01-13 18:40:54 +01:00
Beniamino Galvani
17a5a828ba core: honor the ipv6.clat property 2026-01-13 18:40:52 +01:00
Beniamino Galvani
b5c98c81de libnm,nmcli: add a new ipv6.clat property 2026-01-13 18:40:46 +01:00
Beniamino Galvani
2fe97bea4d ndisc: add support for PREF64 option (fixes) 2026-01-09 17:31:09 +01:00
Mary Strodl
0b3a7ca9d0 ndisc: add support for PREF64 option 2026-01-09 17:31:08 +01:00
Beniamino Galvani
b31d3e1eae Add support for CLAT to l3cfg (fixes) 2026-01-09 17:31:08 +01:00
Mary Strodl
79a2b5599f Add support for CLAT to l3cfg 2026-01-09 17:31:08 +01:00
Beniamino Galvani
011eed3c10 Add CLAT BPF program and build machinery (fixes) 2026-01-09 17:31:07 +01:00
Mary Strodl
e25492ec36 Add CLAT BPF program and build machinery 2026-01-09 17:31:07 +01:00
Mary Strodl
266c09be81 l3-config-data: make get_direct_route_for_host public 2026-01-09 17:31:06 +01:00
Mary Strodl
269474c382 clat: propagate network_id down to l3cfg 2026-01-09 17:31:06 +01:00
Beniamino Galvani
e4dba7a214 netns: add a CLAT IP reservation type
This will be used to obtain an IPv4 address to be used for the CLAT
(464XLAT).

Based on a patch by Mary Strodl <ipadlover8322@gmail.com>.
2026-01-09 17:31:06 +01:00
Beniamino Galvani
72f0600cf3 netns: allow defining a ip reservation that wraps around
The current implementation returns IP addresses obtained by adding a
counter to a base address. For CLAT we want to return all the 8
addresses in the 192.0.0.0/29 range, but not starting from 192.0.0.0
because that looks more like a network address. Slightly tweak the
algorithm so that addresses can wrap around.
2026-01-09 17:31:06 +01:00
Beniamino Galvani
a2fe2f6300 supplicant: properly validate blobs
The purpose of the validation is to check that we pass to the
supplicant a configuration that it can understand. For certificates
and keys we enforce a maximum length of 64KiB; that means that the
value of the property we send (i.e. the file path or the blob id) can
be at most 64KiB. Instead we wrongly checked the size of the blob
data.

Fix the validation. Also, enforce a maximum blob size of 32MiB.

Fixes: e85cc46d0b ('core: pass certificates as blobs to supplicant for private connections')
2026-01-09 17:31:05 +01:00
Beniamino Galvani
018e96e97b core: limit the result from the helper to 32MiB 2026-01-09 17:31:05 +01:00
Beniamino Galvani
de8d74aa08 device: use the internal ping implementation
Currently NetworkManager depends on the external ping binary to
perform the reachability check on IP addresses. This means that the NM
daemon package must depend on another package. On Fedora the iputils
package is 800KiB.

Implement the same functionality natively so that we can drop such
dependency.
2026-01-07 09:49:32 +01:00
Beniamino Galvani
90ea2ddb90 core: introduce nm_utils_ping_host()
Introduce a function that pings a given host. It opens a "ping socket"
(IPPROTO_ICMP), binds it to the given ifindex, connects it to the
remote address, and keep sending ICMP echo-request packets until it
receives a reply or the optional timeout is reached. By using this
kind of socket, the kernel automatically sets the ICMP ID on outgoing
packets and matches incoming packets by the same ID.
2026-01-07 09:48:00 +01:00
Beniamino Galvani
427a7cf257 nmcli: start the agent only after updating the connection
When connecting to a wifi network and providing the password on the
command line, nmcli first looks if there is a compatible connection to
reuse. If there is not, it creates and activates a new one via a
single call to AddAndActivate().

If there is a compatible connection, nmcli first calls Update() on it
to set the new password and then Activate() to bring it up. Before
that, it registers a secret agent that can prompt for a new password
in case of authentication failure.

However, as soon as nmcli registers a secret agent, NM tries to
activate again the connection if it was blocked due to a previous
authentication failure. This connection attempt is going to fail
because it still uses the old password, as new one hasn't been set via
Update().

Change the order of operations to register the agent after Update()
and before Activate().

Reproducer:

 nmcli device wifi connect SSID password BAD_PASSWORD
 nmcli device wifi connect SSID password GOOD_PASSWORD

Fixes: c8ff1b30fb ('nmcli/dev: use secret agent for nmcli d [wifi] connect')
2025-12-17 10:55:51 +01:00
Beniamino Galvani
3a4e18e302 nmcli: fix "device wifi connect" command with existing connection
Executing this command twice, or when a connection profile already
exists for the SSID:

  nmcli device wifi connect $SSID password $PASSWORD

returns error:

  Error: 802-11-wireless-security.key-mgmt: property is missing.

When setting the password nmcli was wiping the existing wireless
security setting.

Fixes: c8ff1b30fb ('nmcli/dev: use secret agent for nmcli d [wifi] connect')

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/1688
2025-12-17 10:55:50 +01:00
Íñigo Huguet
599cc1ed1d std-aux: use _nm_strerror_r
The function strerror_r returns an int per POSIX spec, but GNU version
returns char *. Using it fails the compilation in Alpine, so use
_nm_strerror_r instead that handles both cases.

Fixes: 41e28b900f ('daemon-helper: add read-file-as-user')
2025-12-12 15:07:53 +01:00
Beniamino Galvani
1a52bbe7c9 libnm: add function to copy a certificate or key as user
Add a new public function nm_utils_copy_cert_as_user() to libnm. It
reads a certificate or key file on behalf of the given user and writes
it to a directory in /run/NetworkManager. It is useful for VPN plugins
that run as root and need to verify that the user owning the
connection (the one listed in the connection.permissions property) can
access the file.
2025-12-12 12:43:15 +01:00
Beniamino Galvani
57eb4a5bc6 vpn: check that plugin supports private connections
Only allow private VPN connections if the VPN plugin declares the
supports-safe-private-file-access capability. Also check that the
private connection doesn't have more than one owner.
2025-12-12 12:42:01 +01:00
Beniamino Galvani
10db4baeb6 vpn: add nm_vpn_plugin_info_supports_safe_private_file_access()
The new API indicates that the VPN plugin supports reading files
(certificates, keys) of private connections in a safe way
(i.e. checking user permissions), or that it doesn't need to read any
file from disk.
2025-12-12 12:41:28 +01:00
Beniamino Galvani
8d8edda3f4 core,libnm-core: introduce property flag for certificate and keys
If we add a new property in the future and it references a certificate
or key stored on disk, we need to also implement the logic to verify
the access to the file for private connections.

Add a new property flag NM_SETTING_PARAM_CERT_KEY_FILE to existing
certificate and key properties, so that it's easier to see that they
need special treatment. Also add some assertions to verify that the
properties with the flag are handled properly.

While at it, move the enumeration of private-files to the settings.
2025-12-12 12:38:50 +01:00
Beniamino Galvani
e85cc46d0b core: pass certificates as blobs to supplicant for private connections
In case of private connections, the device has already read the
certificates and keys content from disk, validating that the owner of
the connection has access to them. Pass those files as blobs to the
supplicant so that it doesn't have to read them again from the
filesystem, creating the opportunity for TOCTOU bugs.
2025-12-12 12:38:50 +01:00
Beniamino Galvani
a1928b4459 device: read private files in stage2
During stage2 (prepare) of an activation, check if the connection is
private and if it contains any certificate/key path. If so, start
reading the files and delay stage2. Once done, store the files'
content into priv->private_files.table and continue the activation.
2025-12-12 12:38:49 +01:00
Beniamino Galvani
9703305122 core: add functions to read private files of connections
Add function nm_utils_read_private_files(). It can be used to read a
list of paths as the given user. It spawns the daemon-helper to read
each path and returns asynchronously a hash table containing the files
content.

Also add nm_utils_get_connection_private_files_paths() to return a
list of file paths referenced in a connection. The function currently
returns only 802.1x file paths for certificates and keys.
2025-12-12 12:38:49 +01:00
Beniamino Galvani
932b85f7e7 supplicant: rename variables
Rename uid to to blob_id, and con_uid to con_uuid.
2025-12-12 12:38:49 +01:00
Beniamino Galvani
4e26403c4a core: support returning binary output from the daemon helper
The full output of the daemon helper is added to a NMStrBuf, without
interpreting it as a string (that is, without stopping at the first
NUL character).

However, when we retrieve the content from the NMStrBuf we assume it's
a string. This is fine for certain commands that expect a string
output, but it's not for other commands as the read-file-as-user one.

Add a new argument to nm_utils_spawn_helper() to specify whether the
output is binary or not. Also have different finish functions
depending on the return type.
2025-12-12 12:38:49 +01:00
Beniamino Galvani
bd2484d1a9 supplicant: remove blobs before adding new ones
When connecting, we add the blobs to the Interface object of the
supplicant. Those blobs are not removed on disconnect and so when we
try to add blobs with the same id, the supplicant returns an error.

Make sure we start from a clean slate on each connection attempt, by
deleting all existing blobs. Probably we should also delete the added
blobs on disconnect, but that's left for a future improvement.
2025-12-12 12:38:48 +01:00
Beniamino Galvani
41e28b900f daemon-helper: add read-file-as-user
Add a new command to read the content of a file after switching to the
given user. This command can be used to enforce Unix filesystem
permissions when accessing a file on behalf of a user.
2025-12-12 12:38:48 +01:00
Beniamino Galvani
6c1e04fc61 helpers: move helper programs to the same directory
Create a new 'nm-helpers' directory for all the helper programs, to
avoid having too many subdirs in the src directory.
2025-12-12 12:38:48 +01:00