Compare commits

...

82 commits
main ... 1.52.2

Author SHA1 Message Date
Íñigo Huguet
57a409441b release: bump version to 1.52.2 2025-12-12 20:15:51 +01:00
Íñigo Huguet
22d3b80a77 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')
(cherry picked from commit 599cc1ed1d)
(cherry picked from commit ea759ccf3a)
(cherry picked from commit c8384fd528)
2025-12-12 15:41:44 +01:00
Íñigo Huguet
45835b5128 merge: branch 'nm-1-52-issue1809'
[nm-1-52] CVE-2025-9615: avoid that non-admin user using other users' certificates

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2327
2025-12-12 13:03:19 +00:00
Íñigo Huguet
376210ec37 nm-version: set API_VERSION with MICRO+1 (temporary)
In the past, stable branches used odd micro numbers as development micro
version. Because of that, NM_API_VERSION was defined with MICRO+1 so we
don't get warnings during development.

As we stopped using odd micro=devel it is wrong to set MICRO+1 on odd
releases. Final users of 1.52.3 has NM_API_VERSION 1.52.4.

However, during development we need to have MICRO+1. For example, if we
are working on top of 1.52.3 towards the next 1.52.4, we define new
symbols with NM_AVAILABLE_IN_1_52_4. Because of that, we get compilation
failures until we finally bump to 1.52.4, just before the release. The
CI remains red until then, potentially missing many bugs.

For now, just set MICRO+1 all the time. It is wrong, but it was wrong
half of the time anyway, and at least we'll have a green CI until we
implement a definitive solution.

(cherry picked from commit 13bfa44ceb)
2025-12-12 13:35:53 +01:00
Beniamino Galvani
bb0d29a8f1 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.

(cherry picked from commit 1a52bbe7c9)
(cherry picked from commit 3d85bace3d)
(cherry picked from commit 4587832735)
2025-12-12 13:35:39 +01:00
Beniamino Galvani
29d190ef95 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.

(cherry picked from commit 10db4baeb6)
(cherry picked from commit 8437e14758)
(cherry picked from commit 15346f1a4f)
2025-12-12 13:35:05 +01:00
Íñigo Huguet
425fd0d031 libnm: introduce NM_VERSION_1_52_2 2025-12-12 12:09:50 +01:00
Beniamino Galvani
b5c19e47ba 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.

(cherry picked from commit acbfae5e05)
(cherry picked from commit e3c27f2a22)
(cherry picked from commit 9bc4d62680)
2025-12-10 11:28:00 +01:00
Beniamino Galvani
598e266179 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.

(cherry picked from commit 36ea70c099)
(cherry picked from commit aac5b80fca)
(cherry picked from commit f08ee617b9)
2025-12-10 11:27:58 +01:00
Beniamino Galvani
3e096c087d 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.

(cherry picked from commit 98e6dbdf21)
(cherry picked from commit a417df3484)
(cherry picked from commit b8f8731636)
2025-12-10 11:26:24 +01:00
Beniamino Galvani
46b8bc0f1c 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.

(cherry picked from commit de4eb64253)
(cherry picked from commit 9432822f34)
(cherry picked from commit 399d7be771)
2025-12-10 11:26:23 +01:00
Beniamino Galvani
7252fb5d8f supplicant: rename variables
Rename uid to to blob_id, and con_uid to con_uuid.

(cherry picked from commit 586f7700b8)
(cherry picked from commit a17f51fe15)
(cherry picked from commit b7926872e1)
2025-12-10 11:26:23 +01:00
Beniamino Galvani
70efbbd5a5 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.

(cherry picked from commit 1d90d50fc6)
(cherry picked from commit 59df5fc93f)
(cherry picked from commit 7acf70dfb9)
2025-12-10 11:26:23 +01:00
Beniamino Galvani
5c3b7e029b 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.

(cherry picked from commit 0093bbd950)
(cherry picked from commit ce3ebf6d3e)
(cherry picked from commit 4f3597448d)
2025-12-10 11:26:23 +01:00
Beniamino Galvani
5ad3a752b9 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.

(cherry picked from commit 285457a5f8)
(cherry picked from commit 022b992846)
(cherry picked from commit 310887be71)
2025-12-10 11:26:22 +01:00
Beniamino Galvani
4074f9e2c6 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.

(cherry picked from commit 3d76d12eee)
(cherry picked from commit afa6fc951b)
(cherry picked from commit d1776c5394)
2025-12-10 11:26:22 +01:00
Beniamino Galvani
d1a1db43b8 libnm-core, core: add permission helpers
Add utility functions to get the number of users and the first user
from the connection.permissions property of a connection.

(cherry picked from commit 59543620dc)
(cherry picked from commit 2fc662cc71)
(cherry picked from commit abdf3385d6)
2025-12-10 11:26:22 +01:00
Íñigo Huguet
554e3a3e83 NEWS: update 2025-10-15 09:56:15 +02:00
Jan Vaclav
737000860e device: add support for reapplying the sriov.vfs property
Adds support for reapplying the `sriov.vfs` property. Note this
does not include `num_vfs`, as the configuration needs to be reset
and reconfigured from scratch in that case.

Previously, if an existing VF is modified (e.g. if we change the `trust`
flag), we reset all VF configurations, and started from scratch. But in
some cases, this is unnecessarily disruptive.

Resolves: https://issues.redhat.com/browse/RHEL-95844
(cherry picked from commit 4ba3ffee67)
(cherry picked from commit 6f454c98a9)
2025-10-15 09:53:12 +02:00
Jan Vaclav
8676995903 device: extract sriov platform vf generation to separate function
(cherry picked from commit 588a69cd1b)
(cherry picked from commit b2d8f60c49)
2025-10-15 09:52:11 +02:00
Íñigo Huguet
daf43f3732 merge: branch 'ih/ovs-external-ports'
ovs: don't remove unrelated external ports

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2296

(cherry picked from commit e6a31264c1)

(cherry picked from commit c0048e0d26)
2025-10-15 09:27:59 +02:00
Íñigo Huguet
0a9bc10076 man: ovs: document known limitation when removing ifaces and ports
Document a known limitation that we delete bridges and ports from ovsdb
when we remove their last NM-owned attached port or interface, even if
other externally added ports or interfaces exist.

(cherry picked from commit 86ea2c5963)
(cherry picked from commit 6f9ba5fcc5)
2025-10-15 09:27:59 +02:00
Íñigo Huguet
a2cad8cb10 ovs: don't remove unrelated external ports
The commit linked below introduced a bug that caused that OVS ports
added externally to NM are always deleted when we delete any OVS
interface. It affects to all externally added ports, including those
that are not related to the deleted interface and even those in
different OVS bridges.

Fix it by only modifying ports and bridges that are ascendants of the
deleted interface, leaving everything else untouched.

Note that bridges and ports still need to have at least one NM-managed
interface, otherwise they will also be purged. For example, an NM-owned
OVS bridge with 2 ports+iface, one NM-owned and one external: if we
delete the NM-owned iface, both ports and the bridge will be deleted.
For now, this is a known limitation that is not being fixed here.

Fixes: 476c89b6f2 ('ovs: only keep bridges and ports with NM interfaces attached')
(cherry picked from commit 93491d76ec)
(cherry picked from commit 8326cc32d6)
2025-10-15 09:27:59 +02:00
Beniamino Galvani
4e240c493d merge: branch 'bg/ovs-bridge-ext-port'
ovs: only keep bridges and ports with NM interfaces attached

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2193

(cherry picked from commit e8e0d43f04)
2025-10-15 09:27:55 +02:00
Beniamino Galvani
db2f88bc73 ovs: only keep bridges and ports with NM interfaces attached
If a OVS bridge created via NM has a port created externally, when the
bridge connections goes down then NM detaches the NM-created
port. However, it finds that the bridge still has a port (the external
one) and so it doesn't remove the bridge from ovsdb.

This is a problem, because it means that an explicity deactivation of
the bridge leaves the bridge up. To fix this, only track the number of
port in the bridge actually created by NM. Also, leave alone bridges
not created by NM.

(cherry picked from commit 476c89b6f2)
2025-10-15 09:27:54 +02:00
Beniamino Galvani
1877c8b464 ovs: slightly improve _delete_interface()
Add comments, and move variables inside the block where they are used.

(cherry picked from commit 78a4e5cf3b)
2025-10-15 09:27:54 +02:00
Íñigo Huguet
2f6597a673 merge: branch 'ih/update-distros-1.52'
[nm-1-52] ci: update distros

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2273
2025-09-08 13:50:59 +00:00
Íñigo Huguet
bd9886963e [nm-1-52] ci: update distros 2025-09-08 13:06:31 +02:00
Jan Tojnar
507c0d625c meson: Fix docs generation with PyGObject 3.52
PyGObject 3.52 switched from gobject-introspection’s libgirepository 1.0
to glib’s libgirepository 2.0. As a result, the Python script would
no longer be able to find the `GIRepository` 2.0 typelib:

    (process:1944): GLib-GIRepository-DEBUG: 15:25:14.521: Ignoring GIRepository-2.0.typelib because this libgirepository corresponds to GIRepository-3.0.typelib

We could update the script to support both versions of the typelib
but it is not really necessary. It was only used to add extra directories
from `$LD_LIBRARY_PATH` and the CLI argument to repository’s library path
but libgirepository already supports using `LD_LIBRARY_PATH` directly:
https://docs.gtk.org/girepository/method.Repository.prepend_library_path.html

(cherry picked from commit 12eff9a7fd)
2025-09-08 13:06:13 +02:00
Beniamino Galvani
95d2da5da9 device: ensure that sw devices are unrealized after connection deletion
When a software device becomes deactivated, we check whether it can
be unrealized (= deleted in kernel), by calling function
delete_on_deactivate_check_and_schedule().

The function returns without doing anything if there is a new
activation enqueued on the device (priv->queued_act_request), because
in that case the device will be reused for the next activation.

This commit fixes a problem seen in NMCI test
@ovs_delete_connecting_interface: sometimes the device is not
unrealized after deleting the connection. That happens because if the
queued activation fails, we never try again to unrealize the device.

Fix that by calling delete_on_deactivate_check_and_schedule() when
there is a failure starting the queued activation.

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2258
(cherry picked from commit 0b03614b68)
(cherry picked from commit 1f23bb18ad)
2025-08-27 13:13:47 +02:00
Beniamino Galvani
0135379ac4 device: accept changes to the bond-port.vlans during reapply
Commit c5d1e35f99 ('device: support reapplying bridge-port VLANs')
didn't update can_reapply_change() to accept the "bridge-port.vlans"
property during a reapply. So, it was only possible to change the
bridge port VLANs by updating the "bridge.vlan-default-pvid" property
and doing a reapply. Fix that.

Fixes: c5d1e35f99 ('device: support reapplying bridge-port VLANs')
(cherry picked from commit 261fa8db33)
(cherry picked from commit c647c060d6)
2025-08-12 14:14:13 +02:00
Beniamino Galvani
1489f9d0e3 bridge: fix reapplying port VLANs
If the bridge default-pvid is zero, it means that the default PVID is
disabled. That is, the bridge PVID is not propagated to ports.

Currently NM tries to merge the existing bridge VLANs on the port with
the default PVID from the bridge, even when the PVID is zero. This
causes an error when setting the new VLAN list in the kernel, because
it rejects VLAN zero.

Skip the merge of the default PVID when zero.

Fixes: c5d1e35f99 ('device: support reapplying bridge-port VLANs')
(cherry picked from commit bf79fbd678)
(cherry picked from commit 956f9ba365)
2025-08-12 14:10:32 +02:00
Jan Vaclav
826e37b175 release: bump version to 1.52.1 2025-07-08 12:21:52 +02:00
Jan Vaclav
2ee4ba9034 NEWS: update for 1.52.1 release 2025-07-08 11:19:58 +02:00
Beniamino Galvani
7106daf1a3 ovs: set the tun interface up before stage3
When using the netdev datapath, we wait that the tun link appears, we
call nm_device_set_ip_ifindex() (which also brings the link up) and
then we check that the link is ready, i.e. that udev has announced the
link and the MAC address is correct. After that, we schedule stage3
(ip-config).

In this, there is a race condition that occurs sometimes in NMCI test
ovs_datapath_type_netdev_with_cloned_mac. In rare conditions,
nm_device_set_ip_ifindex() bring the interface up but then ovs-vswitch
changes again the flags of the interface without IFF_UP. The result is
that the interface stays down, breaking communications.

To fix this, we need to always call nm_device_bring_up() after the tun
device is ready. The problem is that we can't do it in
_netdev_tun_link_cb() because that function is already invoked
synchronously from platform code.

Instead, simplify the handling of the netdev datapath. Every
"link-changed" event from platform is handled by
_netdev_tun_link_cb(), which always schedule a delayed function
_netdev_tun_link_cb_in_idle(). This function just assigns the
ip-ifindex to the device if missing, and starts stage3 if the link is
ready. While doing so, it also bring the interface up.

Fixes: 99a6c6eda6 ('ovs, dpdk: fix creating ovs-interface when the ovs-bridge is netdev')

https://issues.redhat.com/browse/RHEL-17358

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2218
(cherry picked from commit 46e0d2b4e4)
(cherry picked from commit dd0ca122e3)
2025-06-27 10:33:57 +02:00
Beniamino Galvani
3dfd364502 connectivity: fix compiler warning when building without concheck
Fix the following:

../src/core/nm-connectivity.c:958:1: warning: ‘check_platform_config’ defined but not used [-Wunused-function]
  958 | check_platform_config(NMConnectivity *self,
      | ^~~~~~~~~~~~~~~~~~~~~

Fixes: 91d447df19 ('device: don't start connectivity check on unconfigured devices')

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2224
(cherry picked from commit 1253cbad5a)
(cherry picked from commit c1d94d7081)
2025-06-27 10:33:52 +02:00
Beniamino Galvani
35f11cd6e1 ip-config: fix crash in DNS options evaluation
Fixes: 58287cbcc0 ('core: rework IP configuration in NetworkManager using layer 3 configuration')
(cherry picked from commit c1350f40bd)
2025-06-27 10:33:45 +02:00
Beniamino Galvani
ff9032ff9d device: update the external-down unmanaged flag on port attach/release
A device has the "external-down" unmanaged flag when:

  !is-created-by-nm AND (!is-up OR (!has-address AND !is-controller))

When the "is-up" or the "has-address" conditions change, we properly update
the unmanaged flag by calling _dev_unmanaged_check_external_down() in
_dev_l3_cfg_notify_cb(PLATFORM_CHANGE_ON_IDLE).

The "is-controller" condition changes when another link indicates the
current device as controller. We currently don't update the unmanaged flag
when that happens and so it's possible that the device stays unmanaged even
if it has a port. This can be easily reproduced by running this commands:

    ip link add veth0 type veth peer name veth1
    ip link add vrf0 type vrf table 10
    ip link set vrf0 up
    ip link set veth0 master vrf0

Sometimes, the device shows as "unmanaged" instead of "connected
(externally)".

Fix this by re-evaluating the "external-down" unmanaged flags on the
controller when a port is attached or detached.

Fixes: c3586ce01a ('device: consider a device with slaves configured')

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2209
(cherry picked from commit fd3eccfb16)
2025-06-27 10:33:41 +02:00
Íñigo Huguet
37e72927d7 core: virtual devices can be available without a parent set
When calling to nm_device_is_available, the device types that requires a
parent like VLAN or MACVLAN checks that their parent exists.

nm_device_is_available is a function to check if the device is available
to activate a connection, so it makes sense that if the parent is not
present it can't be activated.

However, this is wrong for 2 reasons:
1. Most of they are virtual devices that might be unrealized when
   checking its availability. If they're unrealized, their parent hasn't
   been set yet.
2. Even if they're realized, their current parent might not be the one
   that is defined in the connection that is being activated.

This is causing that unrealized devices are not being activated as ports
because nm_manager_get_best_device_for_connection thinks that they are
not available.

Get rid of these checks for the parent in the is_available callbacks.

Fixes: ba86c208e0 ('Revert "core: prevent the activation of unavailable OVS interfaces only"')
Fixes: 774badb151 ('core: prevent the activation of unavailable devices')
(cherry picked from commit 94595332c4)
2025-06-27 10:33:23 +02:00
Íñigo Huguet
e059388f05 merge: branch 'ih/initrd-fix-bond-ip6'
nm-initrd-generator: fix IPv6 with square brackets in bond options

Closes #1755 and #1731

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2197

(cherry picked from commit 34255b2692)
2025-06-27 10:29:41 +02:00
Íñigo Huguet
399b08aab6 nm-initrd-generator: fix ignored prefix for IPv6 address with brackets
When defining an IPv6 address with square brackets and prefix, like
[dead::beef]/64, the prefix was silently ignored. The address was
accepted only accidentally, because get_word replaced ']' with '\0' so
it resulted in a valid IPv6 address string, but without the prefix.

The previous commit has fixed get_word with better logic to handle the
square brackets, uncovering this issue.

Fix it by explicitly splitting IP addresses and prefixes in
reader_parse_ip so we get a valid address and prefix.

Also, use a prefix different to 64 in the test test_if_ip6_manual. 64 is
the default one, making that the test passed despite the defined prefix
was actually ignored.

Fixes: ecc074b2f8 ('initrd: add command line parser')
(cherry picked from commit 6f6bb17a28)
2025-06-27 10:29:39 +02:00
Íñigo Huguet
693e1e9742 nm-initrd-generator: fix IPv6 with square brackets in bond options
If any bond option contains an IPv6 address it needs to be enclosed with
[]. Otherwise the ':' separators from the IP address can be confused
with the ':' separators from the 'bond=' cmdline arguments.

However, the square brackets were ignored:
    $ nm-initrd-generator -s "bond=bond0:eth0,eth1:ns_ip6_target=[FC08::789:1:0:0:3]"
    NetworkManager-Message: 08:46:55.114: <warn>  [1745498815.1146] cmdline-reader: Ignoring invalid bond option: "ns_ip6_target" = "[FC08": '[FC08' is not a valid IPv6 address for 'ns_ip6_target' option
    NetworkManager-Message: 08:46:55.114: <warn>  [1745498815.1148] cmdline-reader: Ignoring extra: '789:1:0:0:3]'.

The opening '[' was only being considered if it was the first character
in `get_word`. Fix it and consider it if it's in the middle too.

If the brackets are used first and last, directly remove them as it is what
most callers expect. However, if it's in the middle there is no reasonable
way to remove them, so don't do it. Instead, the caller will have to consider
this possibility when processing the content.

Fixes: ecc074b2f8 ('initrd: add command line parser')
Fixes https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/1755

(cherry picked from commit aeaf8ca23c)
2025-06-27 10:29:39 +02:00
Beniamino Galvani
6d3ff1c30b man: fix syntax to match on connection-id
Fixes: 604c611cd0 ('core: add nm_utils_connection_match_spec_list()')

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/1746
(cherry picked from commit b571e2be5a)
2025-06-27 10:29:16 +02:00
Beniamino Galvani
b83f847b4d libnm-core: set ovs-dpdk and ovs-patch as non-base settings
Settings "ovs-dpdk" and "ovs-patch" are currently marked with priority
NM_SETTING_PRIORITY_HW_BASE, which makes them "base" settings. This
means that they can be used as connection type, for example via "nmcli
connection add type ovs-dpdk ...".

This is wrong, as both settings can only belong to a connection of
type "ovs-interface". Decrease their priority and make them non-base
settings.

The problem was spotted when trying to add a ovs-patch connection via
nmcli:

  # nmcli connection add type ovs-patch ifname p con-name q ovs-patch.peer r controller s port-type ovs-port
  Warning: controller='s' doesn't refer to any existing profile.

  (process:4580): nm-CRITICAL **: 10:15:42.807: file ../src/libnm-core-impl/nm-connection.c: line 1682 (_normalize_ovs_interface_type): should not be reached

  (process:4580): nm-WARNING **: 10:15:42.807: connection did not verify after normalization: ??

  (process:4580): nm-CRITICAL **: 10:15:42.807: file ../src/libnm-core-impl/nm-connection.c: line 2170 (_connection_normalize): should not be reached
  Error: Failed to add 'q' connection: ovs-interface.type: A connection with 'ovs-patch' setting must be of connection.type "ovs-interface" but is "ovs-patch"

Fixes: d0ec501163 ('cli: assert that valid_parts are set for base types')

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2178
(cherry picked from commit 14106431fb)
2025-06-27 10:29:01 +02:00
Beniamino Galvani
2172c5ff5a libnm: fix memory leak when parsing IP configuration
When the dictionary contains keys "address" and "uri", the first value
is leaked.

  ==4730== 14 bytes in 1 blocks are definitely lost in loss record 51 of 1,755
  ==4730==    at 0x4841866: malloc (vg_replace_malloc.c:446)
  ==4730==    by 0x4CC5CB9: g_malloc (gmem.c:100)
  ==4730==    by 0x4CDF518: g_strdup (gstrfuncs.c:323)
  ==4730==    by 0x496A6B8: g_strdup_inline (gstrfuncs.h:321)
  ==4730==    by 0x496A6B8: nm_inet_ntop_dup (nm-inet-utils.h:355)
  ==4730==    by 0x496A95B: nm_inet_parse_str (nm-inet-utils.c:539)
  ==4730==    by 0x48AF3A3: _notify_update_prop_nameservers (nm-ip-config.c:179)

Fixes: 4422b14704 ('core, libnm: support per-connection DNS URIs')

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2184
(cherry picked from commit 37d8945b13)
2025-06-27 10:28:43 +02:00
Friedrich Altheide
59be16b91a device: renew dhcp lease only if carrier was down
Make sure nm_device_update_dynamic_ip_setup is called every time a carrier was down before and the link is now up again.
Previously the dhcp lease was not renewed if the carrier went down and then up again quickly enough.
This led to cases where an old IP was retained even though the device was connected to a different network with a different DHCP server.

This commit introduces device_link_carrier_changed_down

Fixes: d6429d3ddb ('device: ensure DHCP is restarted every time the link goes up')
(cherry picked from commit 163c2574d8)
2025-06-27 10:26:51 +02:00
Íñigo Huguet
051a4a27a2 merge: branch 'ih/coverity'
Fix some defects detected by Coverity

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2174

(cherry picked from commit 95b9b4b678)
2025-06-27 10:25:52 +02:00
Íñigo Huguet
a1e1dd2978 n-dhcp4: fix resource leaks
The function n_dhcp4_c_connection_send_request does not release or take
ownership of its request argument. Because of that, setting it to NULL
in the caller prevents the auto-cleanup of the variable to be executed,
causing a resource leak. Fix it.

Fixes: e23b3c9c3a ('Squashed 'shared/n-dhcp4/' content from commit fb1d43449')
Fixes: 243cc433fb ('n-dhcp4: add new client probe function to send RELEASE message')
(cherry picked from commit 9edfc0438c)
2025-06-27 10:25:51 +02:00
Íñigo Huguet
d19068c9e3 core: fix use after free in ping operations
Detected by coverity, the ping_op pointers are used after being freed in
cleanup_ping_operations. Although calling to g_list_remove is probably
safe because it only needs the value of the pointer, not to dereference
it, better to follow best practices. One of the use after free was
actually an error because we dereference ping_op->log_domain.

Fixes: 658aef0fa1 ('connection: Support connection.ip-ping-addresses')
(cherry picked from commit ae7de5b353)
2025-06-27 10:25:51 +02:00
Íñigo Huguet
ca39902cee bond-slb: initialize dest hw address in GARP packets
Detected by Coverity:
    1. NetworkManager-1.53.1/src/core/nm-bond-manager.c:885:5: var_decl: Declaring variable "data" without initializer.
    7. NetworkManager-1.53.1/src/core/nm-bond-manager.c:948:13: uninit_use_in_call: Using uninitialized value "data". Field "data.d_hw_addr" is uninitialized when calling "sendto".
       946|               unaligned_write_ne32(data.s_ip_addr, tmp_addr);
       947|               unaligned_write_ne32(data.d_ip_addr, tmp_addr);
       948|->             if (sendto(sockfd, &data, sizeof(data), 0, (struct sockaddr *) &addr, sizeof(addr)) < 0)
       949|                   return FALSE;
       950|           }

Fixes: 3f2f922dd9 ('bonding: send ARP announcement on bonding-slb link/carrier down')
(cherry picked from commit 42edb37499)
2025-06-27 10:25:51 +02:00
Beniamino Galvani
e766920e0b vpn: fix logging message
Fixes: c4a7d6a06f ('vpn: honor the ipvX.method connection property')
(cherry picked from commit ecce8fa461)
2025-06-27 10:25:21 +02:00
Beniamino Galvani
9e640cb751 release: fix validation of gitlab token
If the token is wrong or expired, the command still returns
success. Check the content of the reply instead.

Fixes: f05192ada8 ('release.sh: release to freedesktop.org, not to GNOME')

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2148
(cherry picked from commit 481afec6ea)
2025-06-27 10:24:25 +02:00
Beniamino Galvani
75bb71fddf libnm: fix GObject introspection annotations for NMSecretAgentOld
Add some missing "(nullable)" annotations to parameters of the
callback functions in NMSecretAgentOld. Otherwise, PyGObject complains
that those parameters cannot be NULL when implementing a secret agent.

Fixes: d595f7843e ('libnm: add libnm/libnm-core (part 1)')
(cherry picked from commit c01168f4f9)
2025-06-27 10:24:13 +02:00
Íñigo Huguet
c3ed106d63 libnm: macvlan: specify that promiscuous affects to the parent
Fixes: https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/1732
Fixes: 4d0192e661 ('libnm-core: add NMSettingMacvlan')
(cherry picked from commit 1fdcfb7abe)
2025-06-27 10:23:59 +02:00
Beniamino Galvani
9c74fa8e36 device: remove the prefix-delegation IP configuration on cleanup
When a device in IPv6 shared mode obtains a prefix, it adds a new l3cd
of type L3_CONFIG_DATA_TYPE_PD_6 for that prefix. However, that l3cd
is never removed later and so the address lingers on the interface
even after the connection goes down. Remove the l3cd on cleanup.

(cherry picked from commit 4a8bedcd89)
2025-06-27 10:04:39 +02:00
Wen Liang
2fe69da6fd merge: branch 'wl/nm-1-52'
core: ovs: fix NULL pointer dereference in ovsdb read timeout callback

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2204
2025-05-14 17:18:35 +00:00
Íñigo Huguet
9ec498f321 core: ovs: fix NULL pointer dereference in ovsdb read timeout callback
Fixes: f7d321c6d6 ('ovsdb: add watchdog for unparsable JSON data in socket')
(cherry picked from commit dc9bf255ee)
2025-05-14 07:59:44 -04:00
Jan Vaclav
15a68c6a9b merge: branch 'jv/wg-nftables'
wireguard: add connmark and filtering firewall rules

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2158

(cherry picked from commit 5e17b52810)
2025-05-12 13:38:38 +02:00
Jan Vaclav
ae420a8dd6 firewall/utils: replace ipv4 iptables macro with ipxtables macro
(cherry picked from commit 2106251e46)
2025-05-12 13:38:38 +02:00
Jan Vaclav
3f2c0869dc firewall/utils: remove _share prefix from iptables_get_name
It's no longer used just for shared mode.

(cherry picked from commit 18d5b7d641)
2025-05-12 13:38:38 +02:00
Jan Vaclav
4d0223f8a4 firewall/wireguard: drop packets received to wrong interface
If we receive a packet sent to the WG interface's address,
but it does not come from the WG tunnel, let's assume something
is broken and drop the packet.

This is also inspired by wg-quick firewall rules:
https://git.zx2c4.com/wireguard-tools/tree/src/wg-quick/linux.bash?id=17c78d31c27a3c311a2ff42a881057753c6ef2a4#n221

(cherry picked from commit a769c17af7)
2025-05-12 13:38:38 +02:00
Jan Vaclav
2afcebe0c7 wireguard: add firewall rules to copy mark
When a WG connection is connecting to an IPv6 endpoint, configures a
default route, and firewalld is active with IPv6_rpfilter=yes, it never
handshakes and doesn't pass traffic. This is because firewalld has a
IPv6 reverse path filter which is discarding these packets.

Thus, we add some firewall rules whenever a WG connection is brought up
that ensure the conntrack mark and packet mark are copied over.
These rules are largely inspired by wg-quick:

https://git.zx2c4.com/wireguard-tools/tree/src/wg-quick/linux.bash?id=17c78d31c27a3c311a2ff42a881057753c6ef2a4#n221
(cherry picked from commit db557908a2)
2025-05-12 13:38:38 +02:00
Jan Vaclav
57321f78c9 build: add path definition for ip6tables
(cherry picked from commit 0f469b30ad)
2025-05-12 13:38:38 +02:00
Jan Vaclav
ff853203d9 firewall/utils: move logs from sharing to firewall domain
(cherry picked from commit 10c2892d57)
2025-05-12 13:38:38 +02:00
Jan Vaclav
e77a1df6e7 firewall/utils: fix ntf -> nft typo
Fixes: 4badc1f33a ('firewall: fix signalling timeout error reason from _fw_nft_call()')
(cherry picked from commit e39e119636)
2025-05-12 13:38:38 +02:00
Beniamino Galvani
6f480d9494 ovs: allow reapplying ovs-bridge and ovs-port properties
Allow reapplying the following properties:

 - ovs-bridge.fail-mode
 - ovs-bridge.mcast-snooping-enable
 - ovs-bridge.rstp-enable
 - ovs-bridge.stp-enable
 - ovs-port.bond-downdelay
 - ovs-port.bond-mode
 - ovs-port.bond-updelay
 - ovs-port.lacp
 - ovs-port.tag
 - ovs-port.trunks
 - ovs-port.vlan-mode

(cherry picked from commit 4f577d677f)
2025-05-09 16:45:50 +02:00
Vladimír Beneš
e568648717 merge: branch 'vb/dnsconfd_oci_backports'
backport oci chnages to nm-1-52

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2188
2025-04-11 13:16:40 +00:00
Lubomir Rintel
0c5e37f34d test-client: add test for OCI VNIC on VM
Tests that we create a connection for disconnected device.

(cherry picked from commit cb7157f552)
2025-04-11 14:45:08 +02:00
Lubomir Rintel
4321cca3e8 test-client: split up _mock_devices()
The new VM OCI VNIC tests will need to create the devices, but leave one
disconnected.

(cherry picked from commit c42322bd0d)
2025-04-11 14:45:03 +02:00
Lubomir Rintel
629d85e4da test-client: expand MAC addresses from variable
Less literals, more expanded variables. Makes it a little easier to read
for me.

(cherry picked from commit 39db524d94)
2025-04-11 14:44:58 +02:00
Lubomir Rintel
a212c9452e test/nm-service: default the devices to DISCONNETED not UNAVAILABLE
It is a little odd that client tests connect "UNAVAILABLE" devices, and
the devices return to "DISCONNECTED" after deactivation.

It differs from what happens in reality, and some client tools
(hey nm-cloud-setup) can break when they rightly assume that the
device is not ready for activation when it's "UNAVAILABLE" not
"DISCONNECTED".

(cherry picked from commit 79b1877c02)
2025-04-11 14:44:53 +02:00
Lubomir Rintel
8371b4733e cloud-setup: configure disconnected wired devices on OCI
On OCI VMs (virtual machines, as opposed to BM -- bare metal), the VNICs
don't get their addresses via DHCP and need us to get the address from
the metadata and apply it.

https://issues.redhat.com/browse/NMT-1432
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2180
(cherry picked from commit 548e27ef5f)
2025-04-11 14:44:48 +02:00
Lubomir Rintel
06e35f165f cloud-setup: add a chicken bit variable for creation of new connections
Make it possible to opt in or out of the behavior of creating
connections for disconnected devices. It's not clear why such policy was
in place, and the feature might come useful outside OCI.

Let's add an (undocumented) knob to configure the behavior. We might
remove it (and perhaps make the behaviour default everywhere), or
document and keep it if it turns out we need to use it.

(cherry picked from commit 0540b3c9bc)
2025-04-11 14:44:43 +02:00
Lubomir Rintel
e933dfaf72 cloud-setup: factor out creation of new connection
We're going to create connections on wired devices for OCI VM VNICs, and
they're going to also need the same user setting. Factor it out.

(cherry picked from commit 93960639e8)
2025-04-11 14:44:37 +02:00
Lubomir Rintel
22f3cdd8ab cloud-setup: add device argument to nmcs_add_and_activate()
This will allow us to add & complete connections for existing devices,
such as VNICs on OCI VMs.

(cherry picked from commit 9895540a24)
2025-04-11 14:44:32 +02:00
Lubomir Rintel
07d549eb5a cloud-setup: split up _nmc_skip_connection_by_type()
Split _nmc_skip_connection_by_type() so that we can get a little
more finely grained error reporting.

(cherry picked from commit 961be7d971)
2025-04-11 14:44:27 +02:00
Wen Liang
3dee98acdc merge: branch 'wl/nm-1-52'
core: fail early if we cannot get current FEC value

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2181
2025-04-07 13:31:15 +00:00
Íñigo Huguet
094a542546 core: optimize hash table search in _ethtool_fec_set
Break the loop as soon as we've found the value.

Fixes: 19bed3121f ('ethtool: support Forward Error Correction(fec)')
(cherry picked from commit 245f0e0b35)
2025-04-07 08:10:47 -04:00
Íñigo Huguet
b7e34f225a core: fail early if we cannot get current FEC value
If we cannot get current FEC value probably we won't be able to set it a
few lines later. Also, if it fails to set, we try to use the value of
the old one that we tried to retrieve without success. In that case, the
variable old_fec_mode would be uninitialized. Fix it by returning early
if we cannot get the current value.

Fixes: 19bed3121f ('ethtool: support Forward Error Correction(fec)')
(cherry picked from commit cbdd0d9cca)
2025-04-07 08:10:39 -04:00
Beniamino Galvani
1ace58c0c2 merge: branch 'dnsconfd_plugin_fixes'
dns: Fix invalid memory access on Dnsconfd DBUS error

Closes #1738

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2161

(cherry picked from commit a0ff8d20f0)
2025-03-24 09:15:39 +01:00
Tomas Korbar
873adc4dc0 dns: Refactor changing of Dnsconfd plugin state
(cherry picked from commit 7ba27f7a13)
2025-03-24 09:15:39 +01:00
Tomas Korbar
de4f4e870d dns: Fix invalid memory access on Dnsconfd DBUS error
DBus errors were not properly handled after DBus calls and
that caused SIGSEGV. Now they are checked.

Fixes #1738
Fixes: b8714e86e4 ('dns: introduce configuration_serial support to the dnsconfd plugin')

(cherry picked from commit 4ad20787bb)
2025-03-24 09:15:39 +01:00
95 changed files with 4515 additions and 2560 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-77ec3d923fd6'
CENTOS_TAG: 'tag-7a677f4838e1'
DEBIAN_TAG: 'tag-ecad19904683'
FEDORA_TAG: 'tag-7a677f4838e1'
UBUNTU_TAG: 'tag-ecad19904683'
ALPINE_TAG: 'tag-672dcdb2e2bf'
CENTOS_TAG: 'tag-d7d348d344cf'
DEBIAN_TAG: 'tag-217545cfdeb1'
FEDORA_TAG: 'tag-d7d348d344cf'
UBUNTU_TAG: 'tag-217545cfdeb1'
ALPINE_EXEC: 'bash .gitlab-ci/alpine-install.sh'
CENTOS_EXEC: 'bash .gitlab-ci/fedora-install.sh'
@ -102,7 +102,61 @@ variables:
# Build a container for each distribution + version. The ci-templates
# will re-use the containers if the tag doesn't change.
tier1:fedora:41@prep:
tier1:fedora:42@prep:
extends:
- .fdo.container-build@fedora
stage: prep
variables:
GIT_STRATEGY: none
FDO_DISTRIBUTION_VERSION: '42'
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
FDO_DISTRIBUTION_EXEC: $FEDORA_EXEC
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule' || $SCHEDULED_PIPELINE_NAME == "weekly"
tier2:ubuntu:25.04@prep:
extends:
- .fdo.container-build@ubuntu
stage: prep
variables:
GIT_STRATEGY: none
FDO_DISTRIBUTION_VERSION: '25.04'
FDO_DISTRIBUTION_TAG: $UBUNTU_TAG
FDO_DISTRIBUTION_EXEC: $UBUNTU_EXEC
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
when: manual
allow_failure: true
tier2:debian:13@prep:
extends:
- .fdo.container-build@debian
stage: prep
variables:
GIT_STRATEGY: none
FDO_DISTRIBUTION_VERSION: '13'
FDO_DISTRIBUTION_TAG: $DEBIAN_TAG
FDO_DISTRIBUTION_EXEC: $DEBIAN_EXEC
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
when: manual
allow_failure: true
tier2:alpine:3.22@prep:
extends:
- .fdo.container-build@alpine
stage: prep
variables:
GIT_STRATEGY: none
FDO_DISTRIBUTION_VERSION: '3.22'
FDO_DISTRIBUTION_TAG: $ALPINE_TAG
FDO_DISTRIBUTION_EXEC: $ALPINE_EXEC
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
when: manual
allow_failure: true
tier3:fedora:41@prep:
extends:
- .fdo.container-build@fedora
stage: prep
@ -111,116 +165,6 @@ tier1:fedora:41@prep:
FDO_DISTRIBUTION_VERSION: '41'
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
FDO_DISTRIBUTION_EXEC: $FEDORA_EXEC
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule' || $SCHEDULED_PIPELINE_NAME == "weekly"
tier2:fedora:rawhide@prep:
extends:
- .fdo.container-build@fedora
stage: prep
variables:
GIT_STRATEGY: none
FDO_DISTRIBUTION_VERSION: 'rawhide'
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
FDO_DISTRIBUTION_EXEC: $FEDORA_EXEC
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
when: manual
allow_failure: true
tier2:centos:stream9@prep:
extends:
- .fdo.container-build@centos
stage: prep
variables:
GIT_STRATEGY: none
FDO_DISTRIBUTION_VERSION: 'stream9'
FDO_DISTRIBUTION_TAG: $CENTOS_TAG
FDO_DISTRIBUTION_EXEC: $CENTOS_EXEC
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
when: manual
allow_failure: true
tier2:ubuntu:devel@prep:
extends:
- .fdo.container-build@ubuntu
stage: prep
variables:
GIT_STRATEGY: none
FDO_DISTRIBUTION_VERSION: 'devel'
FDO_DISTRIBUTION_TAG: $UBUNTU_TAG
FDO_DISTRIBUTION_EXEC: $UBUNTU_EXEC
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
when: manual
allow_failure: true
tier2:debian:testing@prep:
extends:
- .fdo.container-build@debian
stage: prep
variables:
GIT_STRATEGY: none
FDO_DISTRIBUTION_VERSION: 'testing'
FDO_DISTRIBUTION_TAG: $DEBIAN_TAG
FDO_DISTRIBUTION_EXEC: $DEBIAN_EXEC
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
when: manual
allow_failure: true
tier2:debian:sid@prep:
extends:
- .fdo.container-build@debian
stage: prep
variables:
GIT_STRATEGY: none
FDO_DISTRIBUTION_VERSION: 'sid'
FDO_DISTRIBUTION_TAG: $DEBIAN_TAG
FDO_DISTRIBUTION_EXEC: $DEBIAN_EXEC
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
when: manual
allow_failure: true
tier2:alpine:edge@prep:
extends:
- .fdo.container-build@alpine
stage: prep
variables:
GIT_STRATEGY: none
FDO_DISTRIBUTION_VERSION: 'edge'
FDO_DISTRIBUTION_TAG: $ALPINE_TAG
FDO_DISTRIBUTION_EXEC: $ALPINE_EXEC
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
when: manual
allow_failure: true
tier3:fedora:40@prep:
extends:
- .fdo.container-build@fedora
stage: prep
variables:
GIT_STRATEGY: none
FDO_DISTRIBUTION_VERSION: '40'
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
FDO_DISTRIBUTION_EXEC: $FEDORA_EXEC
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
when: manual
allow_failure: true
tier3:ubuntu:24.10@prep:
extends:
- .fdo.container-build@ubuntu
stage: prep
variables:
GIT_STRATEGY: none
FDO_DISTRIBUTION_VERSION: '24.10'
FDO_DISTRIBUTION_TAG: $UBUNTU_TAG
FDO_DISTRIBUTION_EXEC: $UBUNTU_EXEC
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
when: manual
@ -254,20 +198,6 @@ tier3:ubuntu:22.04@prep:
when: manual
allow_failure: true
tier3:ubuntu:20.04@prep:
extends:
- .fdo.container-build@ubuntu
stage: prep
variables:
GIT_STRATEGY: none
FDO_DISTRIBUTION_VERSION: '20.04'
FDO_DISTRIBUTION_TAG: $UBUNTU_TAG
FDO_DISTRIBUTION_EXEC: $UBUNTU_EXEC
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
when: manual
allow_failure: true
tier3:debian:12@prep:
extends:
- .fdo.container-build@debian
@ -324,15 +254,29 @@ tier3:alpine:3.19@prep:
when: manual
allow_failure: true
tier3:alpine:3.18@prep:
tier3:centos:stream10@prep:
extends:
- .fdo.container-build@alpine
- .fdo.container-build@centos
stage: prep
variables:
GIT_STRATEGY: none
FDO_DISTRIBUTION_VERSION: '3.18'
FDO_DISTRIBUTION_TAG: $ALPINE_TAG
FDO_DISTRIBUTION_EXEC: $ALPINE_EXEC
FDO_DISTRIBUTION_VERSION: 'stream10'
FDO_DISTRIBUTION_TAG: $CENTOS_TAG
FDO_DISTRIBUTION_EXEC: $CENTOS_EXEC
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
when: manual
allow_failure: true
tier3:centos:stream9@prep:
extends:
- .fdo.container-build@centos
stage: prep
variables:
GIT_STRATEGY: none
FDO_DISTRIBUTION_VERSION: 'stream9'
FDO_DISTRIBUTION_TAG: $CENTOS_TAG
FDO_DISTRIBUTION_EXEC: $CENTOS_EXEC
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
when: manual
@ -354,7 +298,7 @@ tier3:alpine:3.18@prep:
dependencies: []
t_fedora:41:
t_fedora:42:
extends:
- .build@template
- .fdo.distribution-image@fedora
@ -370,122 +314,66 @@ t_fedora:41:
- tarball
- subtree
variables:
FDO_DISTRIBUTION_VERSION: '41'
FDO_DISTRIBUTION_VERSION: '42'
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
needs:
- "tier1:fedora:41@prep"
- "tier1:fedora:42@prep"
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
t_fedora:rawhide:
extends:
- .build@template
- .fdo.distribution-image@fedora
- .nm_artifacts_debug
stage: tier2
variables:
FDO_DISTRIBUTION_VERSION: 'rawhide'
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
needs:
- "tier2:fedora:rawhide@prep"
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
t_centos:stream9:
extends:
- .build@template
- .fdo.distribution-image@centos
- .nm_artifacts_debug
stage: tier2
variables:
FDO_DISTRIBUTION_VERSION: 'stream9'
FDO_DISTRIBUTION_TAG: $CENTOS_TAG
needs:
- "tier2:centos:stream9@prep"
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
t_ubuntu:devel:
t_ubuntu:25.04:
extends:
- .build@template
- .fdo.distribution-image@ubuntu
- .nm_artifacts_debug
stage: tier2
variables:
FDO_DISTRIBUTION_VERSION: 'devel'
FDO_DISTRIBUTION_VERSION: '25.04'
FDO_DISTRIBUTION_TAG: $UBUNTU_TAG
needs:
- "tier2:ubuntu:devel@prep"
- "tier2:ubuntu:25.04@prep"
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
t_debian:testing:
t_debian:13:
extends:
- .build@template
- .fdo.distribution-image@debian
- .nm_artifacts_debug
stage: tier2
variables:
FDO_DISTRIBUTION_VERSION: 'testing'
FDO_DISTRIBUTION_VERSION: '13'
FDO_DISTRIBUTION_TAG: $DEBIAN_TAG
needs:
- "tier2:debian:testing@prep"
- "tier2:debian:13@prep"
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
t_debian:sid:
extends:
- .build@template
- .fdo.distribution-image@debian
- .nm_artifacts_debug
stage: tier2
variables:
FDO_DISTRIBUTION_VERSION: 'sid'
FDO_DISTRIBUTION_TAG: $DEBIAN_TAG
needs:
- "tier2:debian:sid@prep"
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
t_alpine:edge:
t_alpine:3.22:
extends:
- .build@template
- .fdo.distribution-image@alpine
- .nm_artifacts_debug
stage: tier2
variables:
FDO_DISTRIBUTION_VERSION: 'edge'
FDO_DISTRIBUTION_VERSION: '3.22'
FDO_DISTRIBUTION_TAG: $ALPINE_TAG
needs:
- "tier2:alpine:edge@prep"
- "tier2:alpine:3.22@prep"
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
t_fedora:40:
t_fedora:41:
extends:
- .build@template
- .fdo.distribution-image@fedora
- .nm_artifacts_debug
stage: tier3
variables:
FDO_DISTRIBUTION_VERSION: '40'
FDO_DISTRIBUTION_VERSION: '41'
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
needs:
- "tier3:fedora:40@prep"
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
t_ubuntu:24.10:
extends:
- .build@template
- .fdo.distribution-image@ubuntu
- .nm_artifacts_debug
stage: tier3
variables:
FDO_DISTRIBUTION_VERSION: '24.10'
FDO_DISTRIBUTION_TAG: $UBUNTU_TAG
needs:
- "tier3:ubuntu:24.10@prep"
- "tier3:fedora:41@prep"
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
@ -517,20 +405,6 @@ t_ubuntu:22.04:
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
t_ubuntu:20.04:
extends:
- .build@template
- .fdo.distribution-image@ubuntu
- .nm_artifacts_debug
stage: tier3
variables:
FDO_DISTRIBUTION_VERSION: '20.04'
FDO_DISTRIBUTION_TAG: $UBUNTU_TAG
needs:
- "tier3:ubuntu:20.04@prep"
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
t_debian:12:
extends:
- .build@template
@ -587,17 +461,31 @@ t_alpine:3.19:
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
t_alpine:3.18:
t_centos:stream10:
extends:
- .build@template
- .fdo.distribution-image@alpine
- .fdo.distribution-image@centos
- .nm_artifacts_debug
stage: tier3
variables:
FDO_DISTRIBUTION_VERSION: '3.18'
FDO_DISTRIBUTION_TAG: $ALPINE_TAG
FDO_DISTRIBUTION_VERSION: 'stream10'
FDO_DISTRIBUTION_TAG: $CENTOS_TAG
needs:
- "tier3:alpine:3.18@prep"
- "tier3:centos:stream10@prep"
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
t_centos:stream9:
extends:
- .build@template
- .fdo.distribution-image@centos
- .nm_artifacts_debug
stage: tier3
variables:
FDO_DISTRIBUTION_VERSION: 'stream9'
FDO_DISTRIBUTION_TAG: $CENTOS_TAG
needs:
- "tier3:centos:stream9@prep"
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
@ -611,10 +499,10 @@ check-patch:
extends:
- .fdo.distribution-image@fedora
variables:
FDO_DISTRIBUTION_VERSION: '41'
FDO_DISTRIBUTION_VERSION: '42'
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
needs:
- "tier1:fedora:41@prep"
- "tier1:fedora:42@prep"
rules:
- if: $CI_PIPELINE_SOURCE != 'schedule'
stage: tier1
@ -626,10 +514,10 @@ check-tree:
extends:
- .fdo.distribution-image@fedora
variables:
FDO_DISTRIBUTION_VERSION: '41'
FDO_DISTRIBUTION_VERSION: '42'
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
needs:
- "tier1:fedora:41@prep"
- "tier1:fedora:42@prep"
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event' && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME != $CI_DEFAULT_BRANCH
allow_failure: true
@ -659,9 +547,9 @@ pages:
when: never
- if: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == 'main'
dependencies:
- "t_fedora:41: [meson+gcc+docs+valgrind]"
- "t_fedora:42: [meson+gcc+docs+valgrind]"
needs:
- "t_fedora:41: [meson+gcc+docs+valgrind]"
- "t_fedora:42: [meson+gcc+docs+valgrind]"
triage:issues:
stage: triage
@ -678,11 +566,11 @@ coverity:
extends:
- .fdo.distribution-image@fedora
variables:
FDO_DISTRIBUTION_VERSION: '41'
FDO_DISTRIBUTION_VERSION: '42'
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
stage: coverity
needs:
- "tier1:fedora:41@prep"
- "tier1:fedora:42@prep"
rules:
- if: $CI_PIPELINE_SOURCE == "schedule" && $SCHEDULED_PIPELINE_NAME == "weekly"
script:

View file

@ -23,45 +23,34 @@ distributions:
- name: fedora
tier: 1
versions:
- '41'
- '42'
# TIER 2: distribution versions that will or might use the current NM version.
# Run when doing a release.
- name: fedora
tier: 2
versions:
- 'rawhide'
- name: centos
tier: 2
versions:
- 'stream9'
- name: ubuntu
tier: 2
versions:
- 'devel'
- '25.04'
- name: debian
tier: 2
versions:
- 'testing'
- 'sid'
- '13'
- name: alpine
tier: 2
versions:
- 'edge'
- '3.22'
# TIER 3: distribution versions not in EOL but don't use the current NM version.
# Run when doing a release, but a failure won't be blocking for the release.
- name: fedora
tier: 3
versions:
- '40'
- '41'
- name: ubuntu
tier: 3
versions:
- '24.10'
- '24.04'
- '22.04'
- '20.04'
- name: debian
tier: 3
versions:
@ -72,4 +61,8 @@ distributions:
- '3.21'
- '3.20'
- '3.19'
- '3.18'
- name: centos
tier: 3
versions:
- 'stream10'
- 'stream9'

View file

@ -13,6 +13,8 @@ if [ $IS_CENTOS = 1 ]; then
CENTOS_VERSION=8
elif grep -q '^VERSION_ID=.*\<9\>' /etc/os-release ; then
CENTOS_VERSION=9
elif grep -q '^VERSION_ID=.*\<10\>' /etc/os-release ; then
CENTOS_VERSION=10
else
exit 1
fi
@ -33,6 +35,11 @@ fi
dnf install -y 'dnf-command(config-manager)'
dnf config-manager --set-enabled crb
curl https://copr.fedorainfracloud.org/coprs/nmstate/nm-build-deps/repo/epel-9/nmstate-nm-build-deps-epel-9.repo > /etc/yum.repos.d/nmstate-nm-build-deps-epel-9.repo
elif [ "$CENTOS_VERSION" = stream10 ]; then
dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm
dnf install -y 'dnf-command(config-manager)'
dnf config-manager --set-enabled crb
curl https://copr.fedorainfracloud.org/coprs/nmstate/nm-build-deps/repo/epel-10/nmstate-nm-build-deps-epel-10.repo > /etc/yum.repos.d/nmstate-nm-build-deps-epel-10.repo
else
exit 1
fi

View file

@ -17,10 +17,17 @@ grep -q '^NAME=.*\(CentOS\)' /etc/os-release && IS_CENTOS=1
grep -q '^NAME=.*\(Fedora\)' /etc/os-release && IS_FEDORA=1
grep -q '^NAME=.*\(Alpine\)' /etc/os-release && IS_ALPINE=1
IS_CENTOS_7=0
if [ $IS_CENTOS = 1 ]; then
if grep -q '^VERSION_ID=.*\<7\>' /etc/os-release ; then
IS_CENTOS_7=1
CENTOS_VER_LINE="$(grep '^VERSION_ID=' /etc/os-release)"
if [[ $CENTOS_VER_LINE =~ ^VERSION_ID=\"?([0-9]+)\"?$ ]]; then
CENTOS_VER="${BASH_REMATCH[1]}"
else
echo "Error detecting CentOS Stream version" >&2
exit 1
fi
if (( $CENTOS_VER >= 10 )); then
export WITH_LIBTEAM=0
fi
fi

25
NEWS
View file

@ -1,3 +1,28 @@
===============================================
NetworkManager-1.52.2
Overview of changes since NetworkManager-1.52.1
===============================================
* Support reapplying the "sriov.vfs" property as long as
"sriov.total-vfs" is not changed.
* For private connections (the ones that specify a user in the
"connection.permissions" property), verify that the user can access
the 802.1X certificates and keys set in the connection.
* Introduce a libnm function that can be used by VPN plugins to check
user permissions on certificate and keys.
=============================================
NetworkManager-1.52.1
Overview of changes since NetworkManager-1.52
=============================================
* Fail early if we cannot get current FEC
(Forward Error Correction) value.
* Allow reapplying ovs-bridge and ovs-port properties.
* When activating a WireGuard connection to an IPv6 endpoint, now
NetworkManager creates firewall rules to ensure that the incoming
packets are not dropped by kernel reverse path filtering.
=============================================
NetworkManager-1.52
Overview of changes since NetworkManager-1.50

View file

@ -67,6 +67,9 @@
/* Define to path of iptables binary */
#mesondefine IPTABLES_PATH
/* Define to path of ip6tables binary */
#mesondefine IP6TABLES_PATH
/* Define to path of nft binary */
#mesondefine NFT_PATH

View file

@ -11,6 +11,7 @@ apk add \
'clang' \
'curl-dev' \
'dbus' \
'dbus-dev' \
'elogind-dev' \
'eudev-dev' \
'gcc' \

View file

@ -597,6 +597,7 @@ Preferably use nmcli instead.
%endif
-Dnft=%{_sbindir}/nft \
-Diptables=%{_sbindir}/iptables \
-Dip6tables=%{_sbindir}/ip6tables \
%if %{with dhclient}
-Ddhclient=%{_sbindir}/dhclient \
%else
@ -889,6 +890,7 @@ fi
%{_libexecdir}/nm-dispatcher
%{_libexecdir}/nm-initrd-generator
%{_libexecdir}/nm-daemon-helper
%{_libexecdir}/nm-libnm-helper
%{_libexecdir}/nm-priv-helper
%dir %{_libdir}/%{name}
%dir %{nmplugindir}

View file

@ -222,6 +222,7 @@ if [[ $NO_DIST != 1 ]]; then
-Dconfig_dhcp_default=internal \
-Dconfig_dns_rc_manager_default=auto \
-Diptables=/usr/sbin/iptables \
-Dip6tables=/usr/sbin/ip6tables \
-Dnft=/usr/bin/nft \
|| die "Error meson setup"

View file

@ -368,6 +368,7 @@ meson setup\
$(args_enable "$P_TEST" --werror) \
-Dnft="${D_SBINDIR}/nft" \
-Diptables="${D_SBINDIR}/iptables" \
-Dip6tables="${D_SBINDIR}/ip6tables" \
-Ddhclient="${D_SBINDIR}/dhclient" \
-Ddhcpcd=no \
-Dconfig_dhcp_default="$P_DHCP_DEFAULT" \

View file

@ -559,9 +559,11 @@ if [[ $GITLAB_TOKEN == "" ]]; then
fi
# This step is not necessary for authentication, we use it only to provide a meaningful error message.
curl --request GET --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.freedesktop.org/api/v4/personal_access_tokens/self" &>/dev/null \
|| die "failed to authenticate at gitlab.freedesktop.org with the private token"
GITLAB_USER_ID=$(curl --request GET --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.freedesktop.org/api/v4/personal_access_tokens/self" 2>/dev/null | jq ".user_id" || true)
if [ -z "$GITLAB_USER_ID" ] || [ "$GITLAB_USER_ID" = "null" ]; then
die "failed to authenticate to gitlab.freedesktop.org with the private token"
fi
do_command git push "$ORIGIN" "${BRANCHES[@]}" || die "failed to to push branches ${BRANCHES[@]} to $ORIGIN"

View file

@ -79,6 +79,14 @@ if [ "$CC" != gcc ]; then
_WITH_CRYPTO=nss
fi
if [ "$WITH_LIBTEAM" != "" ]; then
if _is_true "$WITH_LIBTEAM"; then
_WITH_LIBTEAM="true"
else
_WITH_LIBTEAM="false"
fi
fi
if [ "$WITH_DOCS" != "" ]; then
if _is_true "$WITH_DOCS"; then
_WITH_DOCS="true"

View file

@ -1933,7 +1933,7 @@ interface-name:vboxnet*,except:interface-name:vboxnet2
<literal>"uuid:83037490-1d17-4986-a397-01f1db3a7fc2"</literal></para></listitem>
</varlistentry>
<varlistentry>
<term>id=ID</term>
<term>id:ID</term>
<listitem><para>Match the connection by name.</para></listitem>
</varlistentry>
<varlistentry>

View file

@ -91,6 +91,10 @@
NetworkManager inserts the records for Bridges into OVSDB when a Port is
attached.
</para>
<para>Known limitation: when the last NetworkManager's owned port is removed,
the bridge is removed too, even if there are other externally attached ports.
</para>
</refsect2>
<refsect2>
@ -102,6 +106,10 @@
exist. Ports can also be configured to do VLAN tagging or Bonding.
NetworkManager inserts the records for Ports into OVSDB when an Interface is
attached. Ports must be attached to a Bridge.</para>
<para>Known limitation: when the last NetworkManager's owned interface is removed,
the port is removed too, even if there are other externally attached interfaces.
</para>
</refsect2>
<refsect2>

View file

@ -5,7 +5,7 @@ project(
# NOTE: When incrementing version also add corresponding
# NM_VERSION_x_y_z macros in
# "src/libnm-core-public/nm-version-macros.h.in"
version: '1.52.0',
version: '1.52.2',
license: 'GPL2+',
default_options: [
'buildtype=debugoptimized',
@ -723,6 +723,7 @@ default_paths = ['/sbin', '/usr/sbin']
# 0: cmdline option, 1: paths, 2: fallback
progs = [['iptables', default_paths, '/usr/sbin/iptables'],
['ip6tables', default_paths, '/usr/sbin/ip6tables'],
['nft', default_paths, '/usr/sbin/nft'],
['dnsmasq', default_paths, ''],
['modprobe', default_paths, '/sbin/modprobe']
@ -1125,6 +1126,7 @@ endif
output += '\n'
output += ' jansson: ' + jansson_msg + '\n'
output += ' iptables: ' + config_h.get('IPTABLES_PATH') + '\n'
output += ' ip6tables: ' + config_h.get('IP6TABLES_PATH') + '\n'
output += ' nft: ' + config_h.get('NFT_PATH') + '\n'
output += ' modprobe: ' + config_h.get('MODPROBE_PATH') + '\n'
output += ' modemmanager-1: ' + enable_modem_manager.to_string() + '\n'

View file

@ -6,6 +6,7 @@ option('dbus_conf_dir', type: 'string', value: '', description: 'where D-Bus sys
option('kernel_firmware_dir', type: 'string', value: '/lib/firmware', description: 'where kernel firmware directory is (default is /lib/firmware)')
option('runtime_dir', type: 'string', value: '', description: 'Directory for transient runtime state [default: LOCALSTATEDIR/run or /run]')
option('iptables', type: 'string', value: '', description: 'path to iptables')
option('ip6tables', type: 'string', value: '', description: 'path to ip6tables')
option('nft', type: 'string', value: '', description: 'path to nft')
option('dnsmasq', type: 'string', value: '', description: 'path to dnsmasq')
option('modprobe', type: 'string', value: '', description: 'path to modprobe')

View file

@ -137,14 +137,6 @@ link_changed(NMDevice *device, const NMPlatformLink *pllink)
nm_device_parent_set_ifindex(device, parent);
}
static gboolean
is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
{
if (!nm_device_parent_get_device(device))
return FALSE;
return NM_DEVICE_CLASS(nm_device_6lowpan_parent_class)->is_available(device, flags);
}
static gboolean
complete_connection(NMDevice *device,
NMConnection *connection,
@ -237,7 +229,6 @@ nm_device_6lowpan_class_init(NMDevice6LowpanClass *klass)
device_class->get_generic_capabilities = get_generic_capabilities;
device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
device_class->link_changed = link_changed;
device_class->is_available = is_available;
device_class->parent_changed_notify = parent_changed_notify;
device_class->update_connection = update_connection;
}

View file

@ -735,6 +735,11 @@ merge_bridge_vlan_default_pvid(NMPlatformBridgeVlan *vlans, guint *num_vlans, gu
gboolean has_pvid = FALSE;
guint i;
if (default_pvid == 0) {
/* default_pvid=0 means that the default PVID is disabled. No need to merge it. */
return vlans;
}
for (i = 0; i < *num_vlans; i++) {
if (vlans[i].pvid) {
has_pvid = TRUE;

View file

@ -630,10 +630,17 @@ build_supplicant_config(NMDeviceEthernet *self, GError **error)
mtu = nm_platform_link_get_mtu(nm_device_get_platform(NM_DEVICE(self)),
nm_device_get_ifindex(NM_DEVICE(self)));
config = nm_supplicant_config_new(NM_SUPPL_CAP_MASK_NONE);
config = nm_supplicant_config_new(NM_SUPPL_CAP_MASK_NONE,
nm_utils_get_connection_first_permissions_user(connection));
security = nm_connection_get_setting_802_1x(connection);
if (!nm_supplicant_config_add_setting_8021x(config, security, con_uuid, mtu, TRUE, error)) {
if (!nm_supplicant_config_add_setting_8021x(config,
security,
con_uuid,
mtu,
TRUE,
nm_device_get_private_files(NM_DEVICE(self)),
error)) {
g_prefix_error(error, "802-1x-setting: ");
g_clear_object(&config);
}

View file

@ -221,16 +221,6 @@ get_generic_capabilities(NMDevice *device)
/*****************************************************************************/
static gboolean
is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
{
if (!nm_device_parent_get_device(device))
return FALSE;
return NM_DEVICE_CLASS(nm_device_ipvlan_parent_class)->is_available(device, flags);
}
/*****************************************************************************/
static gboolean
check_connection_compatible(NMDevice *device,
NMConnection *connection,
@ -376,7 +366,6 @@ nm_device_ipvlan_class_init(NMDeviceIpvlanClass *klass)
device_class->check_connection_compatible = check_connection_compatible;
device_class->create_and_realize = create_and_realize;
device_class->get_generic_capabilities = get_generic_capabilities;
device_class->is_available = is_available;
device_class->link_changed = link_changed;
device_class->update_connection = update_connection;

View file

@ -201,7 +201,8 @@ build_supplicant_config(NMDeviceMacsec *self, GError **error)
mtu = nm_platform_link_get_mtu(nm_device_get_platform(NM_DEVICE(self)),
nm_device_get_ifindex(NM_DEVICE(self)));
config = nm_supplicant_config_new(NM_SUPPL_CAP_MASK_NONE);
config = nm_supplicant_config_new(NM_SUPPL_CAP_MASK_NONE,
nm_utils_get_connection_first_permissions_user(connection));
s_macsec = nm_device_get_applied_setting(NM_DEVICE(self), NM_TYPE_SETTING_MACSEC);
@ -227,7 +228,13 @@ build_supplicant_config(NMDeviceMacsec *self, GError **error)
if (nm_setting_macsec_get_mode(s_macsec) == NM_SETTING_MACSEC_MODE_EAP) {
s_8021x = nm_connection_get_setting_802_1x(connection);
if (!nm_supplicant_config_add_setting_8021x(config, s_8021x, con_uuid, mtu, TRUE, error)) {
if (!nm_supplicant_config_add_setting_8021x(config,
s_8021x,
con_uuid,
mtu,
TRUE,
nm_device_get_private_files(NM_DEVICE(self)),
error)) {
g_prefix_error(error, "802-1x-setting: ");
return NULL;
}
@ -683,14 +690,6 @@ get_generic_capabilities(NMDevice *dev)
/******************************************************************/
static gboolean
is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
{
if (!nm_device_parent_get_device(device))
return FALSE;
return NM_DEVICE_CLASS(nm_device_macsec_parent_class)->is_available(device, flags);
}
static gboolean
create_and_realize(NMDevice *device,
NMConnection *connection,
@ -903,7 +902,6 @@ nm_device_macsec_class_init(NMDeviceMacsecClass *klass)
device_class->deactivate = deactivate;
device_class->get_generic_capabilities = get_generic_capabilities;
device_class->link_changed = link_changed;
device_class->is_available = is_available;
device_class->parent_changed_notify = parent_changed_notify;
device_class->state_changed = device_state_changed;
device_class->get_configured_mtu = nm_device_get_configured_mtu_wired_parent;

View file

@ -270,16 +270,6 @@ get_generic_capabilities(NMDevice *device)
/*****************************************************************************/
static gboolean
is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
{
if (!nm_device_parent_get_device(device))
return FALSE;
return NM_DEVICE_CLASS(nm_device_macvlan_parent_class)->is_available(device, flags);
}
/*****************************************************************************/
static gboolean
check_connection_compatible(NMDevice *device,
NMConnection *connection,
@ -508,7 +498,6 @@ nm_device_macvlan_class_init(NMDeviceMacvlanClass *klass)
device_class->create_and_realize = create_and_realize;
device_class->get_generic_capabilities = get_generic_capabilities;
device_class->get_configured_mtu = nm_device_get_configured_mtu_wired_parent;
device_class->is_available = is_available;
device_class->link_changed = link_changed;
device_class->parent_changed_notify = parent_changed_notify;
device_class->update_connection = update_connection;

View file

@ -179,4 +179,6 @@ void nm_device_auth_request(NMDevice *self,
void nm_device_link_properties_set(NMDevice *self, gboolean reapply);
GHashTable *nm_device_get_private_files(NMDevice *self);
#endif /* NM_DEVICE_PRIVATE_H */

View file

@ -235,7 +235,7 @@ resolve_addr_helper_cb(GObject *source, GAsyncResult *result, gpointer user_data
gs_free_error GError *error = NULL;
gs_free char *output = NULL;
output = nm_utils_spawn_helper_finish(result, &error);
output = nm_utils_spawn_helper_finish_string(result, &error);
if (nm_utils_error_is_cancelled(error))
return;
@ -274,6 +274,7 @@ resolve_addr_spawn_helper(ResolveAddrInfo *info, ResolveAddrService services)
nm_inet_ntop(info->addr_family, &info->address, addr_str);
_LOG2D(info, "start lookup via nm-daemon-helper using services: %s", str);
nm_utils_spawn_helper(NM_MAKE_STRV("resolve-address", addr_str, str),
FALSE,
g_task_get_cancellable(info->task),
resolve_addr_helper_cb,
info);

View file

@ -292,16 +292,6 @@ get_generic_capabilities(NMDevice *device)
/*****************************************************************************/
static gboolean
is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
{
if (!nm_device_parent_get_device(device))
return FALSE;
return NM_DEVICE_CLASS(nm_device_vlan_parent_class)->is_available(device, flags);
}
/*****************************************************************************/
static gboolean
check_connection_compatible(NMDevice *device,
NMConnection *connection,
@ -561,7 +551,6 @@ nm_device_vlan_class_init(NMDeviceVlanClass *klass)
device_class->act_stage1_prepare_set_hwaddr_ethernet = TRUE;
device_class->act_stage1_prepare = act_stage1_prepare;
device_class->get_configured_mtu = nm_device_get_configured_mtu_wired_parent;
device_class->is_available = is_available;
device_class->parent_changed_notify = parent_changed_notify;
device_class->check_connection_compatible = check_connection_compatible;

View file

@ -23,6 +23,7 @@
#include "nm-active-connection.h"
#include "nm-act-request.h"
#include "dns/nm-dns-manager.h"
#include "nm-firewall-utils.h"
#define _NMLOG_DEVICE_TYPE NMDeviceWireGuard
#include "nm-device-logging.h"
@ -1207,6 +1208,40 @@ skip:
*out_allowed_ips_data = g_steal_pointer(&allowed_ips);
}
static void
_configure_firewall(NMDeviceWireGuard *self, NMConnection *connection, int addr_family, gboolean up)
{
NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
const char *ip_iface;
NMSettingIPConfig *ip_config;
ip_iface = nm_device_get_ip_iface(NM_DEVICE(self));
nm_assert(ip_iface);
switch (addr_family) {
case AF_INET:
if (!priv->auto_default_route_enabled_4)
return;
ip_config = nm_connection_get_setting_ip4_config(connection);
break;
case AF_INET6:
if (!priv->auto_default_route_enabled_6)
return;
ip_config = nm_connection_get_setting_ip6_config(connection);
break;
default:
nm_assert_not_reached();
}
nm_assert(ip_config);
nm_assert(priv->auto_default_route_fwmark);
nm_firewall_config_set_wg_rule(ip_iface, ip_config, priv->auto_default_route_fwmark, up);
}
/*****************************************************************************/
static void
@ -1300,6 +1335,18 @@ create_and_realize(NMDevice *device,
return TRUE;
}
static void
deactivate(NMDevice *device)
{
NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD(device);
NMConnection *connection = nm_device_get_applied_connection(NM_DEVICE(self));
if (connection) {
_configure_firewall(self, connection, AF_INET, FALSE);
_configure_firewall(self, connection, AF_INET6, FALSE);
}
}
/*****************************************************************************/
static void
@ -1768,6 +1815,10 @@ act_stage3_ip_config(NMDevice *device, int addr_family)
nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL;
l3cd = _get_dev2_ip_config(NM_DEVICE_WIREGUARD(device), addr_family);
_configure_firewall(NM_DEVICE_WIREGUARD(device),
nm_device_get_applied_connection(device),
addr_family,
TRUE);
nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, l3cd);
}
@ -1866,6 +1917,10 @@ reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_ne
if (state >= NM_DEVICE_STATE_CONFIG) {
priv->auto_default_route_refresh = TRUE;
_configure_firewall(self, con_old, AF_INET, FALSE);
_configure_firewall(self, con_old, AF_INET6, FALSE);
link_config(NM_DEVICE_WIREGUARD(device), "reapply", LINK_CONFIG_MODE_REAPPLY, NULL);
}
@ -2018,6 +2073,7 @@ nm_device_wireguard_class_init(NMDeviceWireGuardClass *klass)
device_class->state_changed = device_state_changed;
device_class->create_and_realize = create_and_realize;
device_class->deactivate = deactivate;
device_class->act_stage2_config = act_stage2_config;
device_class->act_stage2_config_also_for_external_or_assume = TRUE;
device_class->act_stage3_ip_config = act_stage3_ip_config;

View file

@ -335,6 +335,12 @@ typedef struct {
int addr_family;
} HostnameResolver;
typedef enum {
PRIVATE_FILES_STATE_UNKNOWN = 0,
PRIVATE_FILES_STATE_READING,
PRIVATE_FILES_STATE_DONE,
} PrivateFilesState;
/*****************************************************************************/
enum {
@ -603,6 +609,7 @@ typedef struct _NMDevicePrivate {
bool is_attached : 1;
bool device_link_carrier_changed_down : 1;
bool device_link_changed_down : 1;
bool concheck_rp_filter_checked : 1;
@ -768,6 +775,13 @@ typedef struct _NMDevicePrivate {
guint64 rx_bytes;
} stats;
struct {
GHashTable *table;
GCancellable *cancellable;
char *user;
PrivateFilesState state;
} private_files;
bool mtu_force_set_done : 1;
bool needs_ip6_subnet : 1;
@ -2759,22 +2773,23 @@ _ethtool_fec_set(NMDevice *self,
g_hash_table_iter_init(&iter, hash);
while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &variant)) {
NMEthtoolID ethtool_id = nm_ethtool_id_get_by_name(name);
if (!nm_ethtool_id_is_fec(ethtool_id))
continue;
if (nm_ethtool_id_is_fec(nm_ethtool_id_get_by_name(name))) {
nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32));
fec_mode = g_variant_get_uint32(variant);
break;
}
}
nm_platform_ethtool_get_fec_mode(platform, ethtool_state->ifindex, &old_fec_mode);
/* The NM_SETTING_ETHTOOL_FEC_MODE_NONE is query only value, hence do nothing. */
if (!fec_mode || fec_mode == NM_SETTING_ETHTOOL_FEC_MODE_NONE) {
return;
}
if (!nm_platform_ethtool_get_fec_mode(platform, ethtool_state->ifindex, &old_fec_mode)) {
_LOGW(LOGD_DEVICE, "ethtool: failure setting FEC %d: cannot get current value", fec_mode);
return;
}
if (!nm_platform_ethtool_set_fec_mode(platform, ethtool_state->ifindex, fec_mode))
_LOGW(LOGD_DEVICE, "ethtool: failure setting FEC %d", fec_mode);
else {
@ -7123,6 +7138,9 @@ nm_device_controller_release_port(NMDevice *self,
NM_UNMANAGED_IS_PORT,
NM_UNMAN_FLAG_OP_FORGET,
NM_DEVICE_STATE_REASON_REMOVED);
/* Once the port is detached, unmanaged-external-down might change */
_dev_unmanaged_check_external_down(self, FALSE, FALSE);
}
/*****************************************************************************/
@ -7558,10 +7576,12 @@ device_link_changed(gpointer user_data)
gboolean carrier_was_up;
gboolean update_unmanaged_specs = FALSE;
gboolean got_hw_addr = FALSE, had_hw_addr;
gboolean carrier_seen_down = priv->device_link_carrier_changed_down;
gboolean seen_down = priv->device_link_changed_down;
priv->device_link_changed_id = 0;
priv->device_link_changed_down = FALSE;
priv->device_link_carrier_changed_down = FALSE;
ifindex = nm_device_get_ifindex(self);
if (ifindex <= 0)
@ -7712,7 +7732,8 @@ device_link_changed(gpointer user_data)
if (priv->state >= NM_DEVICE_STATE_IP_CONFIG && priv->state <= NM_DEVICE_STATE_ACTIVATED
&& !nm_device_managed_type_is_external(self))
nm_device_l3cfg_commit(self, NM_L3_CFG_COMMIT_TYPE_REAPPLY, FALSE);
}
if (priv->carrier && (!carrier_was_up || carrier_seen_down)) {
/* If the device is active without a carrier (probably because it is
* tagged for carrier ignore) ensure that when the carrier appears we
* renew DHCP leases and such.
@ -7803,6 +7824,8 @@ link_changed_cb(NMPlatform *platform,
priv = NM_DEVICE_GET_PRIVATE(self);
if (ifindex == nm_device_get_ifindex(self)) {
if (!(pllink->n_ifi_flags & IFF_LOWER_UP))
priv->device_link_carrier_changed_down = TRUE;
if (!(pllink->n_ifi_flags & IFF_UP))
priv->device_link_changed_down = TRUE;
if (!priv->device_link_changed_id) {
@ -8814,6 +8837,9 @@ nm_device_controller_add_port(NMDevice *self, NMDevice *port, gboolean configure
} else
g_return_val_if_fail(port_priv->controller == self, FALSE);
/* Once the port is attached, unmanaged-external-down might change */
_dev_unmanaged_check_external_down(self, TRUE, FALSE);
nm_device_queue_recheck_assume(self);
nm_device_queue_recheck_assume(port);
@ -10322,6 +10348,43 @@ sriov_params_cb(GError *error, gpointer user_data)
nm_device_activate_schedule_stage1_device_prepare(self, FALSE);
}
static gboolean
sriov_gen_platform_vfs(NMDevice *self,
NMSettingSriov *s_sriov,
NMPlatformVF ***plat_vfs_out,
GError **error)
{
nm_auto_freev NMPlatformVF **plat_vfs = NULL;
guint num;
nm_assert(s_sriov);
nm_assert(plat_vfs_out && !*plat_vfs_out);
num = nm_setting_sriov_get_num_vfs(s_sriov);
plat_vfs = g_new0(NMPlatformVF *, num + 1);
for (int i = 0; i < num; i++) {
NMSriovVF *vf = nm_setting_sriov_get_vf(s_sriov, i);
gs_free_error GError *local = NULL;
plat_vfs[i] = sriov_vf_config_to_platform(self, vf, &local);
if (!plat_vfs[i]) {
g_set_error(error,
local->domain,
local->code,
"VF '%s' is invalid: %s",
nm_utils_sriov_vf_to_str(vf, FALSE, NULL),
local->message);
return FALSE;
}
}
*plat_vfs_out = g_steal_pointer(&plat_vfs);
return TRUE;
}
/*
* activate_stage1_device_prepare
*
@ -10368,10 +10431,7 @@ activate_stage1_device_prepare(NMDevice *self)
if (s_sriov && nm_device_has_capability(self, NM_DEVICE_CAP_SRIOV)) {
nm_auto_freev NMPlatformVF **plat_vfs = NULL;
gs_free_error GError *error = NULL;
NMSriovVF *vf;
NMTernary autoprobe;
guint num;
guint i;
autoprobe = nm_setting_sriov_get_autoprobe_drivers(s_sriov);
if (autoprobe == NM_TERNARY_DEFAULT) {
@ -10384,22 +10444,13 @@ activate_stage1_device_prepare(NMDevice *self)
NM_OPTION_BOOL_TRUE);
}
num = nm_setting_sriov_get_num_vfs(s_sriov);
plat_vfs = g_new0(NMPlatformVF *, num + 1);
for (i = 0; i < num; i++) {
vf = nm_setting_sriov_get_vf(s_sriov, i);
plat_vfs[i] = sriov_vf_config_to_platform(self, vf, &error);
if (!plat_vfs[i]) {
_LOGE(LOGD_DEVICE,
"failed to apply SR-IOV VF '%s': %s",
nm_utils_sriov_vf_to_str(vf, FALSE, NULL),
error->message);
if (!sriov_gen_platform_vfs(self, s_sriov, &plat_vfs, &error)) {
_LOGE(LOGD_DEVICE, "cannot parse the VF list: %s", error->message);
nm_device_state_changed(self,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED);
return;
}
}
/* When changing the number of VFs the kernel can block
* for very long time in the write to sysfs, especially
@ -10705,6 +10756,49 @@ tc_commit(NMDevice *self)
return TRUE;
}
static void
read_private_files_cb(GObject *source_object, GAsyncResult *result, gpointer data)
{
gs_unref_hashtable GHashTable *table = NULL;
gs_free_error GError *error = NULL;
NMDevice *self;
NMDevicePrivate *priv;
table = nm_utils_read_private_files_finish(result, &error);
if (nm_utils_error_is_cancelled(error))
return;
self = NM_DEVICE(data);
priv = NM_DEVICE_GET_PRIVATE(self);
if (error) {
NMConnection *connection = nm_device_get_applied_connection(self);
_LOGW(LOGD_DEVICE,
"could not read files for private connection %s owned by user '%s': %s",
connection ? nm_connection_get_uuid(connection) : NULL,
priv->private_files.user,
error->message);
nm_device_state_changed(self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
return;
}
_LOGD(LOGD_DEVICE, "private files successfully read");
priv->private_files.state = PRIVATE_FILES_STATE_DONE;
priv->private_files.table = g_steal_pointer(&table);
g_clear_pointer(&priv->private_files.user, g_free);
g_clear_object(&priv->private_files.cancellable);
nm_device_activate_schedule_stage2_device_config(self, FALSE);
}
GHashTable *
nm_device_get_private_files(NMDevice *self)
{
return NM_DEVICE_GET_PRIVATE(self)->private_files.table;
}
/*
* activate_stage2_device_config
*
@ -10717,6 +10811,7 @@ activate_stage2_device_config(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDeviceClass *klass = NM_DEVICE_GET_CLASS(self);
NMConnection *applied;
NMActStageReturn ret;
NMSettingWired *s_wired;
gboolean no_firmware = FALSE;
@ -10725,6 +10820,68 @@ activate_stage2_device_config(NMDevice *self)
nm_device_state_changed(self, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
applied = nm_device_get_applied_connection(self);
/* If the connection is private (owned by a specific user), we need to
* verify that the user has permission to access any files specified in
* the connection, such as certificates and keys. We do that by calling
* nm_utils_read_private_files() and saving the file contents in a hash
* table that can be accessed later during the activation. It is important
* to never access the files again to avoid TOCTOU bugs.
*/
switch (priv->private_files.state) {
case PRIVATE_FILES_STATE_UNKNOWN:
{
gs_free const char **paths = NULL;
NMSettingConnection *s_con;
const char *user;
s_con = nm_connection_get_setting_connection(applied);
nm_assert(s_con);
user = _nm_setting_connection_get_first_permissions_user(s_con);
priv->private_files.user = g_strdup(user);
if (!priv->private_files.user) {
priv->private_files.state = PRIVATE_FILES_STATE_DONE;
break;
}
paths = nm_utils_get_connection_private_files_paths(applied);
if (!paths) {
priv->private_files.state = PRIVATE_FILES_STATE_DONE;
break;
}
if (_nm_setting_connection_get_num_permissions_users(s_con) > 1) {
_LOGW(LOGD_DEVICE,
"private connections with multiple users are not allowed to reference "
"certificates and keys on the filesystem. Specify only one user in the "
"connection.permissions property.");
nm_device_state_changed(self,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_CONFIG_FAILED);
return;
}
priv->private_files.state = PRIVATE_FILES_STATE_READING;
priv->private_files.cancellable = g_cancellable_new();
_LOGD(LOGD_DEVICE, "reading private files");
nm_utils_read_private_files(paths,
priv->private_files.user,
priv->private_files.cancellable,
read_private_files_cb,
self);
return;
}
case PRIVATE_FILES_STATE_READING:
/* wait */
return;
case PRIVATE_FILES_STATE_DONE:
/* proceed */
break;
}
if (!nm_device_managed_type_is_external(self)) {
_ethtool_state_set(self);
nm_device_link_properties_set(self, FALSE);
@ -10741,7 +10898,7 @@ activate_stage2_device_config(NMDevice *self)
priv->tc_committed = TRUE;
}
nm_routing_rules_sync(nm_device_get_applied_connection(self),
nm_routing_rules_sync(applied,
NM_TERNARY_TRUE,
klass->get_extra_rules,
self,
@ -13417,6 +13574,8 @@ _dev_ipsharedx_cleanup(NMDevice *self, int addr_family)
nm_clear_l3cd(&priv->ipshared_data_4.v4.l3cd);
_dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_SHARED_4, NULL, FALSE);
} else {
_dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_PD_6, NULL, FALSE);
}
_dev_ipsharedx_set_state(self, addr_family, NM_DEVICE_IP_STATE_NONE);
@ -13988,6 +14147,20 @@ can_reapply_change(NMDevice *self,
return TRUE;
}
if (nm_streq(setting_name, NM_SETTING_BRIDGE_PORT_SETTING_NAME)) {
return nm_device_hash_check_invalid_keys(diffs,
NM_SETTING_BRIDGE_PORT_SETTING_NAME,
error,
NM_SETTING_BRIDGE_PORT_VLANS);
}
if (nm_streq(setting_name, NM_SETTING_SRIOV_SETTING_NAME)) {
return nm_device_hash_check_invalid_keys(diffs,
NM_SETTING_SRIOV_SETTING_NAME,
error,
NM_SETTING_SRIOV_VFS);
}
out_fail:
g_set_error(error,
NM_DEVICE_ERROR,
@ -14163,9 +14336,35 @@ check_and_reapply_connection(NMDevice *self,
nm_device_link_properties_set(self, TRUE);
if (priv->state >= NM_DEVICE_STATE_CONFIG)
if (priv->state >= NM_DEVICE_STATE_CONFIG) {
GHashTable *sriov_diff;
lldp_setup(self, NM_TERNARY_DEFAULT);
sriov_diff = nm_g_hash_table_lookup(diffs, NM_SETTING_SRIOV_SETTING_NAME);
if (sriov_diff && nm_g_hash_table_lookup(sriov_diff, NM_SETTING_SRIOV_VFS)) {
nm_auto_freev NMPlatformVF **plat_vfs = NULL;
NMSettingSriov *s_sriov;
s_sriov = (NMSettingSriov *) nm_connection_get_setting(applied, NM_TYPE_SETTING_SRIOV);
if (s_sriov) {
gs_free_error GError *local = NULL;
if (!sriov_gen_platform_vfs(self, s_sriov, &plat_vfs, &local)
|| !nm_platform_link_set_sriov_vfs(nm_device_get_platform(self),
priv->ifindex,
(const NMPlatformVF *const *) plat_vfs)) {
_LOGE(LOGD_DEVICE,
"failed to reapply SRIOV VFs%s%s",
local ? ": " : "",
local ? local->message : "");
}
}
}
}
if (priv->state >= NM_DEVICE_STATE_IP_CONFIG) {
/* Allow reapply of MTU */
priv->mtu_source = NM_DEVICE_MTU_SOURCE_NONE;
@ -14619,13 +14818,15 @@ impl_device_delete(NMDBusObject *obj,
NULL);
}
static void
static gboolean
_device_activate(NMDevice *self, NMActRequest *req)
{
NMConnection *connection;
g_return_if_fail(NM_IS_DEVICE(self));
g_return_if_fail(NM_IS_ACT_REQUEST(req));
/* Returns TRUE on success, FALSE if the activation request could not be started */
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
g_return_val_if_fail(NM_IS_ACT_REQUEST(req), FALSE);
nm_assert(nm_device_is_real(self));
/* Ensure the activation request is still valid; the controller may have
@ -14633,7 +14834,7 @@ _device_activate(NMDevice *self, NMActRequest *req)
*/
if (nm_active_connection_get_state(NM_ACTIVE_CONNECTION(req))
>= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING)
return;
return FALSE;
if (!nm_device_get_managed(self, FALSE)) {
/* It's unclear why the device would be unmanaged at this point.
@ -14644,7 +14845,7 @@ _device_activate(NMDevice *self, NMActRequest *req)
nm_active_connection_set_state_fail((NMActiveConnection *) req,
NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN,
NULL);
return;
return FALSE;
}
connection = nm_act_request_get_applied_connection(req);
@ -14660,6 +14861,8 @@ _device_activate(NMDevice *self, NMActRequest *req)
act_request_set(self, req);
nm_device_activate_schedule_stage1_device_prepare(self, FALSE);
return TRUE;
}
static void
@ -14679,7 +14882,9 @@ _carrier_wait_check_queued_act_request(NMDevice *self)
_LOGD(LOGD_DEVICE, "Activate queued activation request as we now have carrier");
queued_req = g_steal_pointer(&priv->queued_act_request);
_device_activate(self, queued_req);
if (!_device_activate(self, queued_req)) {
delete_on_deactivate_check_and_schedule(self);
}
}
}
@ -15085,8 +15290,8 @@ respawn_ping_cb(gpointer user_data)
nm_clear_g_source_inst(&ping_op->watch);
if (!spawn_ping_for_operation(self, ping_op)) {
cleanup_ping_operation(ping_op);
priv->ping_operations = g_list_remove(priv->ping_operations, ping_op);
cleanup_ping_operation(ping_op);
if (g_list_length(priv->ping_operations) == 0) {
ip_check_pre_up(self);
@ -15129,7 +15334,6 @@ ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data)
if (success) {
if (ping_op->ping_addresses_require_all) {
cleanup_ping_operation(ping_op);
priv->ping_operations = g_list_remove(priv->ping_operations, ping_op);
if (g_list_length(priv->ping_operations) == 0) {
_LOGD(ping_op->log_domain,
@ -15139,6 +15343,7 @@ ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data)
nm_clear_g_source_inst(&priv->ping_timeout);
ip_check_pre_up(self);
}
cleanup_ping_operation(ping_op);
} else {
nm_assert(priv->ping_operations);
@ -16907,6 +17112,12 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
if (klass->deactivate)
klass->deactivate(self);
/* Clean up private files */
nm_clear_g_cancellable(&priv->private_files.cancellable);
g_clear_pointer(&priv->private_files.table, g_hash_table_unref);
g_clear_pointer(&priv->private_files.user, g_free);
priv->private_files.state = PRIVATE_FILES_STATE_UNKNOWN;
ifindex = nm_device_get_ip_ifindex(self);
if (cleanup_type == CLEANUP_TYPE_DECONFIGURE) {
@ -17442,7 +17653,8 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
gs_unref_object NMActRequest *queued_req = NULL;
queued_req = g_steal_pointer(&priv->queued_act_request);
_device_activate(self, queued_req);
if (!_device_activate(self, queued_req))
delete_on_deactivate_check_and_schedule(self);
}
break;
case NM_DEVICE_STATE_ACTIVATED:

View file

@ -135,13 +135,36 @@ nm_device_ovs_reapply_connection(NMDevice *self, NMConnection *con_old, NMConnec
nm_ovsdb_set_reapply(nm_ovsdb_get(),
device_type,
nm_device_get_ip_iface(self),
nm_connection_get_uuid(con_new),
nm_simple_connection_new_clone(con_new),
_nm_connection_get_setting(con_old, NM_TYPE_SETTING_OVS_EXTERNAL_IDS),
_nm_connection_get_setting(con_new, NM_TYPE_SETTING_OVS_EXTERNAL_IDS),
_nm_connection_get_setting(con_old, NM_TYPE_SETTING_OVS_OTHER_CONFIG),
_nm_connection_get_setting(con_new, NM_TYPE_SETTING_OVS_OTHER_CONFIG));
}
static gboolean
can_reapply_change(NMDevice *device,
const char *setting_name,
NMSetting *s_old,
NMSetting *s_new,
GHashTable *diffs,
GError **error)
{
NMDeviceClass *device_class = NM_DEVICE_CLASS(nm_device_ovs_bridge_parent_class);
if (nm_streq(setting_name, NM_SETTING_OVS_BRIDGE_SETTING_NAME)) {
return nm_device_hash_check_invalid_keys(diffs,
NM_SETTING_OVS_BRIDGE_SETTING_NAME,
error,
NM_SETTING_OVS_BRIDGE_FAIL_MODE,
NM_SETTING_OVS_BRIDGE_MCAST_SNOOPING_ENABLE,
NM_SETTING_OVS_BRIDGE_RSTP_ENABLE,
NM_SETTING_OVS_BRIDGE_STP_ENABLE);
}
return device_class->can_reapply_change(device, setting_name, s_old, s_new, diffs, error);
}
/*****************************************************************************/
static void
@ -180,6 +203,7 @@ nm_device_ovs_bridge_class_init(NMDeviceOvsBridgeClass *klass)
device_class->ready_for_ip_config = ready_for_ip_config;
device_class->attach_port = attach_port;
device_class->detach_port = detach_port;
device_class->can_reapply_change = can_reapply_change;
device_class->can_reapply_change_ovs_external_ids = TRUE;
device_class->reapply_connection = nm_device_ovs_reapply_connection;
}

View file

@ -26,16 +26,18 @@ typedef struct {
NMOvsdb *ovsdb;
struct {
/* The source for the idle handler to set the TUN ifindex */
GSource *tun_set_ifindex_idle_source;
/* The signal id for the TUN link-changed event */
gulong tun_link_signal_id;
/* The idle handler source for the TUN link-changed event */
GSource *tun_link_idle_source;
/* The ifindex for the TUN link-changed event */
int tun_ifindex;
/* The cloned MAC to set */
char *cloned_mac;
/* The id for the signal watching the TUN link to appear/change */
gulong tun_link_signal_id;
/* The TUN ifindex to set in the idle handler */
int tun_ifindex;
/* Whether we have determined the cloned MAC */
bool cloned_mac_evaluated : 1;
/* Whether we are waiting for the kernel link */
bool waiting : 1;
} wait_link;
@ -261,39 +263,33 @@ ready_for_ip_config(NMDevice *device, gboolean is_manual)
}
static gboolean
_set_ip_ifindex_tun(gpointer user_data)
_netdev_tun_link_cb_in_idle(gpointer user_data)
{
NMDevice *device = user_data;
NMDeviceOvsInterface *self = NM_DEVICE_OVS_INTERFACE(device);
NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
if (nm_device_get_ip_ifindex(device) <= 0) {
_LOGT(LOGD_CORE,
"ovs-wait-link: setting ip-ifindex %d from tun interface",
"ovs-wait-link: setting ip-ifindex %d from tun link",
priv->wait_link.tun_ifindex);
nm_clear_g_source_inst(&priv->wait_link.tun_set_ifindex_idle_source);
nm_device_set_ip_ifindex(device, priv->wait_link.tun_ifindex);
}
if (check_waiting_for_link(device, "set-ip-ifindex-tun")) {
/* If the link is not ready, it means the MAC is not set yet. We don't have
* a convenient way to monitor for ip-ifindex changes other than listening
* for platform events again.*/
nm_assert(!priv->wait_link.tun_link_signal_id);
priv->wait_link.tun_link_signal_id = g_signal_connect(nm_device_get_platform(device),
NM_PLATFORM_SIGNAL_LINK_CHANGED,
G_CALLBACK(_netdev_tun_link_cb),
self);
if (check_waiting_for_link(device, "tun-link-changed")) {
nm_clear_g_source_inst(&priv->wait_link.tun_link_idle_source);
return G_SOURCE_CONTINUE;
}
_LOGT(LOGD_CORE, "tun link is ready");
_LOGT(LOGD_CORE, "ovs-wait-link: tun link is ready");
nm_device_link_properties_set(device, FALSE);
nm_device_bring_up(device);
nm_device_devip_set_state(device, AF_INET, NM_DEVICE_IP_STATE_PENDING, NULL);
nm_device_devip_set_state(device, AF_INET6, NM_DEVICE_IP_STATE_PENDING, NULL);
nm_device_activate_schedule_stage3_ip_config(device, FALSE);
nm_clear_g_signal_handler(nm_device_get_platform(device), &priv->wait_link.tun_link_signal_id);
nm_clear_g_source_inst(&priv->wait_link.tun_link_idle_source);
return G_SOURCE_CONTINUE;
}
@ -309,40 +305,28 @@ _netdev_tun_link_cb(NMPlatform *platform,
const NMPlatformSignalChangeType change_type = change_type_i;
NMDeviceOvsInterface *self = NM_DEVICE_OVS_INTERFACE(device);
NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
int ip_ifindex;
/* This is the handler for the link-changed platform events. It is triggered for all
* link changes. Keep only the ones matching our device. */
if (!NM_IN_SET(change_type, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_SIGNAL_CHANGED))
return;
if (pllink->type != NM_LINK_TYPE_TUN || !nm_streq0(pllink->name, nm_device_get_iface(device)))
return;
ip_ifindex = nm_device_get_ip_ifindex(device);
if (ip_ifindex > 0) {
/* When we have an ifindex, we are only waiting for the MAC to settle */
if (change_type != NM_PLATFORM_SIGNAL_CHANGED)
return;
if (!check_waiting_for_link(device, "tun-link-changed")) {
_LOGT(LOGD_CORE, "ovs-wait-link: tun link is ready, cloned MAC is set");
nm_clear_g_signal_handler(platform, &priv->wait_link.tun_link_signal_id);
nm_device_link_properties_set(device, FALSE);
nm_device_devip_set_state(device, AF_INET, NM_DEVICE_IP_STATE_PENDING, NULL);
nm_device_devip_set_state(device, AF_INET6, NM_DEVICE_IP_STATE_PENDING, NULL);
nm_device_activate_schedule_stage3_ip_config(device, FALSE);
}
return;
}
/* No ip-ifindex on the device, set it when the link appears */
if (change_type != NM_PLATFORM_SIGNAL_ADDED)
return;
_LOGT(LOGD_CORE,
"ovs-wait-link: found matching tun interface, schedule set-ip-ifindex(%d)",
"ovs-wait-link: got platform event \'%s\' for ifindex %d, scheduling idle handler",
change_type == NM_PLATFORM_SIGNAL_ADDED ? "added" : "changed",
ifindex);
nm_clear_g_signal_handler(platform, &priv->wait_link.tun_link_signal_id);
/* The handler is invoked by the platform synchronously in the netlink receive loop.
* We can't perform other platform operations (like bringing the interface up) since
* the code there is not re-entrant. Schedule an idle handler. */
nm_clear_g_source_inst(&priv->wait_link.tun_link_idle_source);
priv->wait_link.tun_link_idle_source =
nm_g_idle_add_source(_netdev_tun_link_cb_in_idle, device);
priv->wait_link.tun_ifindex = ifindex;
priv->wait_link.tun_set_ifindex_idle_source = nm_g_idle_add_source(_set_ip_ifindex_tun, device);
return;
}
static gboolean
@ -464,7 +448,7 @@ act_stage3_ip_config(NMDevice *device, int addr_family)
nm_device_activate_schedule_stage3_ip_config(device, TRUE);
return;
}
nm_clear_g_source_inst(&priv->wait_link.tun_set_ifindex_idle_source);
nm_clear_g_source_inst(&priv->wait_link.tun_link_idle_source);
nm_clear_g_signal_handler(nm_device_get_platform(device), &priv->wait_link.tun_link_signal_id);
nm_device_link_properties_set(device, FALSE);
@ -488,7 +472,7 @@ deactivate(NMDevice *device)
priv->wait_link.cloned_mac_evaluated = FALSE;
nm_clear_g_free(&priv->wait_link.cloned_mac);
nm_clear_g_signal_handler(nm_device_get_platform(device), &priv->wait_link.tun_link_signal_id);
nm_clear_g_source_inst(&priv->wait_link.tun_set_ifindex_idle_source);
nm_clear_g_source_inst(&priv->wait_link.tun_link_idle_source);
}
typedef struct {
@ -581,7 +565,7 @@ deactivate_async(NMDevice *device,
_LOGT(LOGD_CORE, "deactivate: start async");
nm_clear_g_signal_handler(nm_device_get_platform(device), &priv->wait_link.tun_link_signal_id);
nm_clear_g_source_inst(&priv->wait_link.tun_set_ifindex_idle_source);
nm_clear_g_source_inst(&priv->wait_link.tun_link_idle_source);
priv->wait_link.tun_ifindex = -1;
priv->wait_link.cloned_mac_evaluated = FALSE;
nm_clear_g_free(&priv->wait_link.cloned_mac);
@ -682,7 +666,7 @@ dispose(GObject *object)
nm_assert(!priv->wait_link.waiting);
nm_assert(priv->wait_link.tun_link_signal_id == 0);
nm_assert(!priv->wait_link.tun_set_ifindex_idle_source);
nm_assert(!priv->wait_link.tun_link_idle_source);
if (priv->ovsdb) {
g_signal_handlers_disconnect_by_func(priv->ovsdb, G_CALLBACK(ovsdb_ready), self);

View file

@ -256,6 +256,32 @@ detach_port(NMDevice *device,
return ret;
}
static gboolean
can_reapply_change(NMDevice *device,
const char *setting_name,
NMSetting *s_old,
NMSetting *s_new,
GHashTable *diffs,
GError **error)
{
NMDeviceClass *device_class = NM_DEVICE_CLASS(nm_device_ovs_port_parent_class);
if (nm_streq(setting_name, NM_SETTING_OVS_PORT_SETTING_NAME)) {
return nm_device_hash_check_invalid_keys(diffs,
NM_SETTING_OVS_PORT_SETTING_NAME,
error,
NM_SETTING_OVS_PORT_TAG,
NM_SETTING_OVS_PORT_VLAN_MODE,
NM_SETTING_OVS_PORT_BOND_UPDELAY,
NM_SETTING_OVS_PORT_BOND_DOWNDELAY,
NM_SETTING_OVS_PORT_LACP,
NM_SETTING_OVS_PORT_BOND_MODE,
NM_SETTING_OVS_PORT_TRUNKS);
}
return device_class->can_reapply_change(device, setting_name, s_old, s_new, diffs, error);
}
/*****************************************************************************/
static void
@ -293,6 +319,7 @@ nm_device_ovs_port_class_init(NMDeviceOvsPortClass *klass)
device_class->ready_for_ip_config = ready_for_ip_config;
device_class->attach_port = attach_port;
device_class->detach_port = detach_port;
device_class->can_reapply_change = can_reapply_change;
device_class->can_reapply_change_ovs_external_ids = TRUE;
device_class->reapply_connection = nm_device_ovs_reapply_connection;
}

View file

@ -103,7 +103,7 @@ typedef union {
struct {
NMDeviceType device_type;
char *ifname;
char *connection_uuid;
NMConnection *connection;
GHashTable *external_ids_old;
GHashTable *external_ids_new;
GHashTable *other_config_old;
@ -244,22 +244,19 @@ static void cleanup_check_ready(NMOvsdb *self);
#define OVSDB_METHOD_PAYLOAD_SET_REAPPLY(xdevice_type, \
xifname, \
xconnection_uuid, \
xconnection, \
xexternal_ids_old, \
xexternal_ids_new, \
xother_config_old, \
xother_config_new) \
(&((const OvsdbMethodPayload) { \
.set_reapply = \
{ \
.device_type = xdevice_type, \
.set_reapply = {.device_type = xdevice_type, \
.ifname = (char *) NM_CONSTCAST(char, (xifname)), \
.connection_uuid = (char *) NM_CONSTCAST(char, (xconnection_uuid)), \
.connection = (xconnection), \
.external_ids_old = (xexternal_ids_old), \
.external_ids_new = (xexternal_ids_new), \
.other_config_old = (xother_config_old), \
.other_config_new = (xother_config_new), \
}, \
.other_config_new = (xother_config_new)}, \
}))
/*****************************************************************************/
@ -316,7 +313,7 @@ _call_complete(OvsdbMethodCall *call, json_t *response, GError *error)
break;
case OVSDB_SET_REAPPLY:
nm_clear_g_free(&call->payload.set_reapply.ifname);
nm_clear_g_free(&call->payload.set_reapply.connection_uuid);
nm_clear_g_object(&call->payload.set_reapply.connection);
nm_clear_pointer(&call->payload.set_reapply.external_ids_old, g_hash_table_destroy);
nm_clear_pointer(&call->payload.set_reapply.external_ids_new, g_hash_table_destroy);
nm_clear_pointer(&call->payload.set_reapply.other_config_old, g_hash_table_destroy);
@ -478,7 +475,7 @@ ovsdb_call_method(NMOvsdb *self,
case OVSDB_SET_REAPPLY:
call->payload.set_reapply.device_type = payload->set_reapply.device_type;
call->payload.set_reapply.ifname = g_strdup(payload->set_reapply.ifname);
call->payload.set_reapply.connection_uuid = g_strdup(payload->set_reapply.connection_uuid);
call->payload.set_reapply.connection = payload->set_reapply.connection;
call->payload.set_reapply.external_ids_old =
nm_g_hash_table_ref(payload->set_reapply.external_ids_old);
call->payload.set_reapply.external_ids_new =
@ -488,8 +485,8 @@ ovsdb_call_method(NMOvsdb *self,
call->payload.set_reapply.other_config_new =
nm_g_hash_table_ref(payload->set_reapply.other_config_new);
_LOGT_call(call,
"new: set external-ids/other-config con-uuid=%s, interface=%s",
call->payload.set_reapply.connection_uuid,
"new: reapply con-uuid=%s, interface=%s",
nm_connection_get_uuid(payload->set_reapply.connection),
call->payload.set_reapply.ifname);
break;
}
@ -976,65 +973,79 @@ _insert_interface(json_t *params,
"rowInterface"));
}
/**
* _insert_port:
*
* Returns an commands that adds new port from a given connection.
*/
static void
_insert_port(json_t *params, NMConnection *port, json_t *new_interfaces)
ovsdb_row_set_string_or_null(json_t *row, const char *key, const char *str)
{
/* ovsdb represents a NULL string (no value) as an empty set */
if (str) {
json_object_set_new(row, key, json_string(str));
} else {
json_object_set_new(row, key, json_pack("[s, []]", "set"));
}
}
static json_t *
create_port_row_object(NMConnection *connection)
{
NMSettingOvsPort *s_ovs_port;
const char *vlan_mode = NULL;
json_t *trunks = NULL;
guint tag = 0;
const char *lacp = NULL;
const char *bond_mode = NULL;
guint bond_updelay = 0;
guint bond_downdelay = 0;
json_t *row;
const char *s;
guint u;
s_ovs_port = nm_connection_get_setting_ovs_port(port);
s_ovs_port = nm_connection_get_setting_ovs_port(connection);
nm_assert(s_ovs_port);
row = json_object();
if (s_ovs_port) {
s = nm_setting_ovs_port_get_vlan_mode(s_ovs_port);
ovsdb_row_set_string_or_null(row, "vlan_mode", s);
u = nm_setting_ovs_port_get_tag(s_ovs_port);
json_object_set_new(row, "tag", u != 0 ? json_integer(u) : json_pack("[s, []]", "set"));
u = nm_setting_ovs_port_get_bond_updelay(s_ovs_port);
json_object_set_new(row, "bond_updelay", json_integer(u));
u = nm_setting_ovs_port_get_bond_downdelay(s_ovs_port);
json_object_set_new(row, "bond_downdelay", json_integer(u));
s = nm_setting_ovs_port_get_lacp(s_ovs_port);
ovsdb_row_set_string_or_null(row, "lacp", s);
s = nm_setting_ovs_port_get_bond_mode(s_ovs_port);
ovsdb_row_set_string_or_null(row, "bond_mode", s);
{
const GPtrArray *ranges;
guint i;
json_t *trunks = json_array();
guint64 start;
guint64 end;
vlan_mode = nm_setting_ovs_port_get_vlan_mode(s_ovs_port);
tag = nm_setting_ovs_port_get_tag(s_ovs_port);
lacp = nm_setting_ovs_port_get_lacp(s_ovs_port);
bond_mode = nm_setting_ovs_port_get_bond_mode(s_ovs_port);
bond_updelay = nm_setting_ovs_port_get_bond_updelay(s_ovs_port);
bond_downdelay = nm_setting_ovs_port_get_bond_downdelay(s_ovs_port);
guint i;
ranges = _nm_setting_ovs_port_get_trunks_arr(s_ovs_port);
for (i = 0; i < ranges->len; i++) {
if (!trunks)
trunks = json_array();
nm_range_get_range(ranges->pdata[i], &start, &end);
for (; start <= end; start++)
json_array_append_new(trunks, json_integer(start));
}
json_object_set_new(row, "trunks", json_pack("[s, o]", "set", trunks));
}
if (vlan_mode)
json_object_set_new(row, "vlan_mode", json_string(vlan_mode));
if (tag)
json_object_set_new(row, "tag", json_integer(tag));
if (trunks)
json_object_set_new(row, "trunks", json_pack("[s, o]", "set", trunks));
if (lacp)
json_object_set_new(row, "lacp", json_string(lacp));
if (bond_mode)
json_object_set_new(row, "bond_mode", json_string(bond_mode));
if (bond_updelay)
json_object_set_new(row, "bond_updelay", json_integer(bond_updelay));
if (bond_downdelay)
json_object_set_new(row, "bond_downdelay", json_integer(bond_downdelay));
return row;
}
/**
* _insert_port:
*
* Returns a command that adds new port from a given connection.
*/
static void
_insert_port(json_t *params, NMConnection *port, json_t *new_interfaces)
{
json_t *row;
row = create_port_row_object(port);
json_object_set_new(row, "name", json_string(nm_connection_get_interface_name(port)));
json_object_set_new(row, "interfaces", json_pack("[s, O]", "set", new_interfaces));
@ -1058,10 +1069,50 @@ _insert_port(json_t *params, NMConnection *port, json_t *new_interfaces)
"rowPort"));
}
static json_t *
create_bridge_row_object(NMConnection *connection, gboolean is_reapply)
{
NMSettingOvsBridge *s_ovs_bridge;
json_t *row;
gboolean b;
const char *s;
s_ovs_bridge = nm_connection_get_setting_ovs_bridge(connection);
nm_assert(s_ovs_bridge);
row = json_object();
b = nm_setting_ovs_bridge_get_mcast_snooping_enable(s_ovs_bridge);
json_object_set_new(row, "mcast_snooping_enable", json_boolean(b));
b = nm_setting_ovs_bridge_get_rstp_enable(s_ovs_bridge);
json_object_set_new(row, "rstp_enable", json_boolean(b));
b = nm_setting_ovs_bridge_get_stp_enable(s_ovs_bridge);
json_object_set_new(row, "stp_enable", json_boolean(b));
s = nm_setting_ovs_bridge_get_fail_mode(s_ovs_bridge);
ovsdb_row_set_string_or_null(row, "fail_mode", s);
if (!is_reapply) {
/* The datapath type can't be reapplied because after changing it,
* ovs removes the existing ovs-interface and creates a tun one (or
* vice-versa). */
s = nm_setting_ovs_bridge_get_datapath_type(s_ovs_bridge);
if (s) {
/* Cannot use ovsdb_row_set_string_or_null() here as the column
* is a set and must not be empty. */
json_object_set_new(row, "datapath_type", json_string(s));
}
}
return row;
}
/**
* _insert_bridge:
*
* Returns an commands that adds new bridge from a given connection.
* Returns a command that adds new bridge from a given connection.
*/
static void
_insert_bridge(json_t *params,
@ -1070,36 +1121,9 @@ _insert_bridge(json_t *params,
json_t *new_ports,
const char *cloned_mac)
{
NMSettingOvsBridge *s_ovs_bridge;
const char *fail_mode = NULL;
gboolean mcast_snooping_enable = FALSE;
gboolean rstp_enable = FALSE;
gboolean stp_enable = FALSE;
const char *datapath_type = NULL;
json_t *row;
s_ovs_bridge = nm_connection_get_setting_ovs_bridge(bridge);
row = json_object();
if (s_ovs_bridge) {
fail_mode = nm_setting_ovs_bridge_get_fail_mode(s_ovs_bridge);
mcast_snooping_enable = nm_setting_ovs_bridge_get_mcast_snooping_enable(s_ovs_bridge);
rstp_enable = nm_setting_ovs_bridge_get_rstp_enable(s_ovs_bridge);
stp_enable = nm_setting_ovs_bridge_get_stp_enable(s_ovs_bridge);
datapath_type = nm_setting_ovs_bridge_get_datapath_type(s_ovs_bridge);
}
if (fail_mode)
json_object_set_new(row, "fail_mode", json_string(fail_mode));
if (mcast_snooping_enable)
json_object_set_new(row, "mcast_snooping_enable", json_boolean(mcast_snooping_enable));
if (rstp_enable)
json_object_set_new(row, "rstp_enable", json_boolean(rstp_enable));
if (stp_enable)
json_object_set_new(row, "stp_enable", json_boolean(stp_enable));
if (datapath_type)
json_object_set_new(row, "datapath_type", json_string(datapath_type));
row = create_bridge_row_object(bridge, FALSE);
json_object_set_new(row, "name", json_string(nm_connection_get_interface_name(bridge)));
json_object_set_new(row, "ports", json_pack("[s, O]", "set", new_ports));
@ -1350,88 +1374,115 @@ _delete_interface(NMOvsdb *self, json_t *params, const char *ifname)
nm_auto_decref_json json_t *bridges = NULL;
nm_auto_decref_json json_t *new_bridges = NULL;
gboolean bridges_changed;
gboolean ports_changed;
gboolean interfaces_changed;
int pi;
int ii;
bridges = json_array();
new_bridges = json_array();
bridges_changed = FALSE;
/* Loop over all bridges */
g_hash_table_iter_init(&iter, priv->bridges);
while (g_hash_table_iter_next(&iter, (gpointer) &ovs_bridge, NULL)) {
nm_auto_decref_json json_t *ports = NULL;
nm_auto_decref_json json_t *new_ports = NULL;
guint num_nm_ports = 0;
gboolean ports_changed = FALSE;
int pi;
ports = json_array();
new_ports = json_array();
ports_changed = FALSE;
/* Add the bridge UUID to the list of known bridges for the "expect" condition */
json_array_append_new(bridges, json_pack("[s,s]", "uuid", ovs_bridge->bridge_uuid));
if (!ovs_bridge->connection_uuid) {
/* Externally created, don't touch it */
json_array_append_new(new_bridges, json_pack("[s,s]", "uuid", ovs_bridge->bridge_uuid));
continue;
}
/* Loop over all bridge's ports */
for (pi = 0; pi < ovs_bridge->ports->len; pi++) {
nm_auto_decref_json json_t *interfaces = NULL;
nm_auto_decref_json json_t *new_interfaces = NULL;
guint num_nm_interfaces = 0;
gboolean interfaces_changed = FALSE;
int ii;
interfaces = json_array();
new_interfaces = json_array();
port_uuid = g_ptr_array_index(ovs_bridge->ports, pi);
ovs_port = g_hash_table_lookup(priv->ports, &port_uuid);
/* Add the port UUID to the list of known bridge port for the "expect" condition */
json_array_append_new(ports, json_pack("[s,s]", "uuid", port_uuid));
interfaces_changed = FALSE;
if (!ovs_port) {
/* This would be a violation of ovsdb's reference integrity (a bug). */
_LOGW("Unknown port '%s' in bridge '%s'", port_uuid, ovs_bridge->bridge_uuid);
continue;
}
/* Loop over all port's interfaces */
for (ii = 0; ii < ovs_port->interfaces->len; ii++) {
interface_uuid = g_ptr_array_index(ovs_port->interfaces, ii);
ovs_interface = g_hash_table_lookup(priv->interfaces, &interface_uuid);
/* Add the interface UUID to the list of known port interfaces for the "expect" condition */
json_array_append_new(interfaces, json_pack("[s,s]", "uuid", interface_uuid));
if (ovs_interface) {
if (nm_streq(ovs_interface->name, ifname)) {
/* skip the interface */
/* We are deleting this interface, don't count it */
interfaces_changed = TRUE;
continue;
}
if (ovs_interface->connection_uuid)
num_nm_interfaces++;
} else {
/* This would be a violation of ovsdb's reference integrity (a bug). */
_LOGW("Unknown interface '%s' in port '%s'", interface_uuid, port_uuid);
}
/* Add the interface to the list of new interfaces to set on the port */
json_array_append_new(new_interfaces, json_pack("[s,s]", "uuid", interface_uuid));
}
if (json_array_size(new_interfaces) == 0) {
if (interfaces_changed && num_nm_interfaces == 0) {
/* We are deleting the last nm-interface of this port. Don't add it to "new_ports"
* and set ports_changed=TRUE, so that it will be deleted. */
ports_changed = TRUE;
} else {
/* Keep this port: it's still alive, or it's unrelated to the deleted interface */
json_array_append_new(new_ports, json_pack("[s,s]", "uuid", port_uuid));
if (ovs_port->connection_uuid)
num_nm_ports++;
if (interfaces_changed) {
/* This port is still alive, but an interface needs to be deleted from it */
_expect_port_interfaces(params, ovs_port->name, interfaces);
_set_port_interfaces(params, ovs_port->name, new_interfaces);
}
json_array_append_new(new_ports, json_pack("[s,s]", "uuid", port_uuid));
}
}
if (json_array_size(new_ports) == 0) {
if (ports_changed && num_nm_ports == 0) {
/* We are deleting the last nm-port of this bridge. Don't add it to "new_bridges"
* and set bridges_changed=TRUE, so that it will be deleted. */
bridges_changed = TRUE;
} else {
/* Keep this bridge: it's still alive, or it's unrelated to the deleted interface */
json_array_append_new(new_bridges, json_pack("[s,s]", "uuid", ovs_bridge->bridge_uuid));
if (ports_changed) {
/* This bridge is still alive, but a port needs to be deleted from it */
_expect_bridge_ports(params, ovs_bridge->name, ports);
_set_bridge_ports(params, ovs_bridge->name, new_ports);
}
json_array_append_new(new_bridges, json_pack("[s,s]", "uuid", ovs_bridge->bridge_uuid));
}
}
if (bridges_changed) {
/* A bridge needs to be deleted */
_expect_ovs_bridges(params, priv->db_uuid, bridges);
_set_ovs_bridges(params, priv->db_uuid, new_bridges);
}
@ -1541,13 +1592,47 @@ ovsdb_next_command(NMOvsdb *self)
break;
case OVSDB_SET_REAPPLY:
{
NMConnection *connection;
json_t *mutations;
json_t *row;
const char *table;
connection = call->payload.set_reapply.connection;
table = _device_type_to_table(call->payload.set_reapply.device_type);
/* Reapply device properties */
switch (call->payload.set_reapply.device_type) {
case NM_DEVICE_TYPE_OVS_BRIDGE:
row = create_bridge_row_object(connection, TRUE);
break;
case NM_DEVICE_TYPE_OVS_PORT:
row = create_port_row_object(connection);
break;
default:
row = NULL;
break;
}
if (row) {
json_array_append_new(params,
json_pack("{s:s, s:s, s:o, s:[[s, s, s]]}",
"op",
"update",
"table",
table,
"row",
row,
"where",
"name",
"==",
call->payload.set_reapply.ifname));
}
/* Reapply external-ids and other-config */
mutations = json_array();
_j_create_strv_array_update(mutations,
STRDICT_TYPE_EXTERNAL_IDS,
call->payload.set_reapply.connection_uuid,
nm_connection_get_uuid(connection),
call->payload.set_reapply.external_ids_old,
call->payload.set_reapply.external_ids_new);
_j_create_strv_array_update(mutations,
@ -1556,13 +1641,12 @@ ovsdb_next_command(NMOvsdb *self)
call->payload.set_reapply.other_config_old,
call->payload.set_reapply.other_config_new);
json_array_append_new(
params,
json_array_append_new(params,
json_pack("{s:s, s:s, s:o, s:[[s, s, s]]}",
"op",
"mutate",
"table",
_device_type_to_table(call->payload.set_reapply.device_type),
table,
"mutations",
mutations,
"where",
@ -2411,7 +2495,7 @@ again:
* content is broken (_json_read_msg() cannot extract any data) and
* we disconnect. */
priv->input_timeout_source =
nm_g_timeout_add_seconds_source(5, _ovsdb_read_input_timeout_cb, NULL);
nm_g_timeout_add_seconds_source(5, _ovsdb_read_input_timeout_cb, self);
}
return;
}
@ -2975,7 +3059,7 @@ void
nm_ovsdb_set_reapply(NMOvsdb *self,
NMDeviceType device_type,
const char *ifname,
const char *connection_uuid,
NMConnection *connection_take,
NMSettingOvsExternalIDs *s_external_ids_old,
NMSettingOvsExternalIDs *s_external_ids_new,
NMSettingOvsOtherConfig *s_other_config_old,
@ -2986,6 +3070,11 @@ nm_ovsdb_set_reapply(NMOvsdb *self,
gs_unref_hashtable GHashTable *other_config_old = NULL;
gs_unref_hashtable GHashTable *other_config_new = NULL;
nm_assert(NM_IN_SET(device_type,
NM_DEVICE_TYPE_OVS_BRIDGE,
NM_DEVICE_TYPE_OVS_PORT,
NM_DEVICE_TYPE_OVS_INTERFACE));
external_ids_old =
s_external_ids_old
? nm_strdict_clone(_nm_setting_ovs_external_ids_get_data(s_external_ids_old))
@ -3011,7 +3100,7 @@ nm_ovsdb_set_reapply(NMOvsdb *self,
OVSDB_SET_REAPPLY,
OVSDB_METHOD_PAYLOAD_SET_REAPPLY(device_type,
ifname,
connection_uuid,
connection_take,
external_ids_old,
external_ids_new,
other_config_old,

View file

@ -50,7 +50,7 @@ void nm_ovsdb_set_interface_mtu(NMOvsdb *self,
void nm_ovsdb_set_reapply(NMOvsdb *self,
NMDeviceType device_type,
const char *ifname,
const char *connection_uuid,
NMConnection *connection_take,
NMSettingOvsExternalIDs *s_external_ids_old,
NMSettingOvsExternalIDs *s_external_ids_new,
NMSettingOvsOtherConfig *s_other_config_old,

View file

@ -2935,7 +2935,8 @@ build_supplicant_config(NMDeviceWifi *self,
s_wireless = nm_connection_get_setting_wireless(connection);
g_return_val_if_fail(s_wireless != NULL, NULL);
config = nm_supplicant_config_new(nm_supplicant_interface_get_capabilities(priv->sup_iface));
config = nm_supplicant_config_new(nm_supplicant_interface_get_capabilities(priv->sup_iface),
nm_utils_get_connection_first_permissions_user(connection));
/* Warn if AP mode may not be supported */
if (nm_streq0(nm_setting_wireless_get_mode(s_wireless), NM_SETTING_WIRELESS_MODE_AP)
@ -3011,6 +3012,7 @@ build_supplicant_config(NMDeviceWifi *self,
mtu,
pmf,
fils,
nm_device_get_private_files(NM_DEVICE(self)),
error)) {
g_prefix_error(error, "802-11-wireless-security: ");
goto error;

View file

@ -71,6 +71,15 @@ typedef enum {
/*****************************************************************************/
static void
dnsconfd_change_plugin_state(NMDnsDnsconfd *self, DnsconfdPluginState new_state)
{
NMDnsDnsconfdPrivate *priv = NM_DNS_DNSCONFD_GET_PRIVATE(self);
priv->plugin_state = new_state;
_nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self));
}
static void
dnsconfd_serial_changed(NMDnsDnsconfd *self, guint new_serial)
{
@ -78,12 +87,10 @@ dnsconfd_serial_changed(NMDnsDnsconfd *self, guint new_serial)
priv->present_configuration_serial = new_serial;
if (priv->plugin_state == DNSCONFD_PLUGIN_WAIT_SERIAL
&& priv->awaited_configuration_serial == new_serial) {
priv->plugin_state = DNSCONFD_PLUGIN_IDLE;
dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_IDLE);
/* Update finished, serials match */
_LOGT("serials match, update finished");
}
_nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self));
}
static void
@ -132,6 +139,12 @@ dnsconfd_serial_retrieval_done(GObject *source_object, GAsyncResult *res, gpoint
self = user_data;
priv = NM_DNS_DNSCONFD_GET_PRIVATE(self);
if (!response) {
_LOGW("dnsconfd serial retrieval failed: %s", error->message);
dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_IDLE);
return;
}
nm_clear_g_cancellable(&priv->serial_cancellable);
g_variant_get(response, "(v)", &new_serial_variant);
@ -201,8 +214,11 @@ dnsconfd_update_done(GObject *source_object, GAsyncResult *res, gpointer user_da
nm_clear_g_cancellable(&priv->update_cancellable);
if (!response)
if (!response) {
_LOGW("dnsconfd update failed: %s", error->message);
dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_IDLE);
return;
}
/* By using &s we will get pointer to char data contained
* in variant and thus no freing of dnsconfd_message is required */
@ -210,8 +226,7 @@ dnsconfd_update_done(GObject *source_object, GAsyncResult *res, gpointer user_da
if (!awaited_serial) {
_LOGW("dnsconfd refused update: %s", dnsconfd_message);
priv->plugin_state = DNSCONFD_PLUGIN_IDLE;
_nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self));
dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_IDLE);
return;
}
@ -220,14 +235,12 @@ dnsconfd_update_done(GObject *source_object, GAsyncResult *res, gpointer user_da
if (priv->awaited_configuration_serial == priv->present_configuration_serial) {
/* Serials match, update finished */
priv->plugin_state = DNSCONFD_PLUGIN_IDLE;
dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_IDLE);
_LOGT("after update serials match");
} else {
priv->plugin_state = DNSCONFD_PLUGIN_WAIT_SERIAL;
dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_WAIT_SERIAL);
_LOGT("after update serials don't match, waiting");
}
_nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self));
}
static gboolean
@ -478,8 +491,7 @@ name_owner_changed(NMDnsDnsconfd *self, const char *name_owner)
|| priv->plugin_state == DNSCONFD_PLUGIN_WAIT_SERIAL) {
/* We were waiting for either serial or confirmation of update and name
* disappeared, thus we need to retransmit */
priv->plugin_state = DNSCONFD_PLUGIN_WAIT_CONNECT;
_nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self));
dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_WAIT_CONNECT);
}
return;
}
@ -490,15 +502,13 @@ name_owner_changed(NMDnsDnsconfd *self, const char *name_owner)
if (!subscribe_serial(self)) {
/* This means that in time between new name and subscribe serial call
* we lost the name again thus wait again */
priv->plugin_state = DNSCONFD_PLUGIN_WAIT_CONNECT;
dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_WAIT_CONNECT);
_LOGT("subscription failed, waiting to connect");
} else {
priv->plugin_state = DNSCONFD_PLUGIN_WAIT_UPDATE_DONE;
dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_WAIT_UPDATE_DONE);
_LOGT("sending update and waiting for its finish");
send_dnsconfd_update(self);
}
_nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self));
}
static void
@ -695,18 +705,16 @@ update(NMDnsPlugin *plugin,
/* We need to consider only whether we are connected, because newer update call
* overrides the old one */
if (all_connected == CONNECTION_FAIL) {
priv->plugin_state = DNSCONFD_PLUGIN_IDLE;
dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_IDLE);
_LOGT("failed to connect");
} else if (all_connected == CONNECTION_WAIT) {
priv->plugin_state = DNSCONFD_PLUGIN_WAIT_CONNECT;
dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_WAIT_CONNECT);
_LOGT("not connected, waiting to connect");
} else {
priv->plugin_state = DNSCONFD_PLUGIN_WAIT_UPDATE_DONE;
dnsconfd_change_plugin_state(self, DNSCONFD_PLUGIN_WAIT_UPDATE_DONE);
_LOGT("connected, waiting for update to finish");
}
_nm_dns_plugin_update_pending_maybe_changed(plugin);
if (all_connected == CONNECTION_FAIL) {
nm_utils_error_set(error,
NM_UTILS_ERROR_UNKNOWN,

View file

@ -882,7 +882,7 @@ nm_bond_manager_send_arp(int bond_ifindex,
.sll_protocol = htons(ETH_P_ARP),
.sll_ifindex = bond_ifindex,
};
ARPPacket data;
ARPPacket data = {0};
const guint8 *hwaddr;
gsize hwaddrlen = 0;
nm_auto_close int sockfd = -1;
@ -940,6 +940,7 @@ nm_bond_manager_send_arp(int bond_ifindex,
data.op = htons(ARP_OP_GARP);
memcpy(data.s_addr, hwaddr, hwaddrlen);
memcpy(data.s_hw_addr, hwaddr, hwaddrlen);
memset(data.d_hw_addr, 0xff, ETH_ALEN);
for (int i = 0; i < addrs_len; i++) {
const in_addr_t tmp_addr = addrs_array[i];

View file

@ -25,6 +25,8 @@
#define HEADER_STATUS_ONLINE "X-NetworkManager-Status: online\r\n"
#define SD_RESOLVED_DNS ((guint64) (1LL << 0))
/*****************************************************************************/
static NM_UTILS_LOOKUP_STR_DEFINE(_state_to_string,
@ -950,9 +952,6 @@ systemd_resolved_resolve_cb(GObject *object, GAsyncResult *res, gpointer user_da
do_curl_request(cb_data, nm_str_buf_get_str(&strbuf_hosts));
}
#endif
#define SD_RESOLVED_DNS ((guint64) (1LL << 0))
static NMConnectivityState
check_platform_config(NMConnectivity *self,
@ -1013,6 +1012,7 @@ check_platform_config(NMConnectivity *self,
NM_SET_OUT(reason, NULL);
return NM_CONNECTIVITY_UNKNOWN;
}
#endif
NMConnectivityCheckHandle *
nm_connectivity_check_start(NMConnectivity *self,

View file

@ -5011,6 +5011,7 @@ typedef struct {
int child_stdin;
int child_stdout;
int child_stderr;
gboolean binary_output;
GSource *input_source;
GSource *output_source;
GSource *error_source;
@ -5090,9 +5091,17 @@ helper_complete(HelperInfo *info, GError *error)
}
nm_clear_g_cancellable_disconnect(g_task_get_cancellable(info->task), &info->cancellable_id);
if (info->binary_output) {
g_task_return_pointer(
info->task,
g_bytes_new(nm_str_buf_get_str_unsafe(&info->in_buffer), info->in_buffer.len),
(GDestroyNotify) (g_bytes_unref));
} else {
g_task_return_pointer(info->task,
nm_str_buf_finalize(&info->in_buffer, NULL) ?: g_new0(char, 1),
g_free);
}
helper_info_free(info);
}
@ -5235,6 +5244,7 @@ helper_cancelled(GObject *object, gpointer user_data)
void
nm_utils_spawn_helper(const char *const *args,
gboolean binary_output,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer cb_data)
@ -5251,8 +5261,13 @@ nm_utils_spawn_helper(const char *const *args,
info = g_new(HelperInfo, 1);
*info = (HelperInfo) {
.task = nm_g_task_new(NULL, cancellable, nm_utils_spawn_helper, callback, cb_data),
.binary_output = binary_output,
};
/* Store if the caller requested binary output so that we can check later
* that the right result function is called. */
g_task_set_task_data(info->task, GINT_TO_POINTER(binary_output), NULL);
if (!g_spawn_async_with_pipes("/",
(char **) NM_MAKE_STRV(LIBEXECDIR "/nm-daemon-helper"),
(char **) NM_MAKE_STRV(),
@ -5363,11 +5378,25 @@ nm_utils_spawn_helper(const char *const *args,
}
char *
nm_utils_spawn_helper_finish(GAsyncResult *result, GError **error)
nm_utils_spawn_helper_finish_string(GAsyncResult *result, GError **error)
{
GTask *task = G_TASK(result);
nm_assert(nm_g_task_is_valid(result, NULL, nm_utils_spawn_helper));
/* Check binary_output */
nm_assert(GPOINTER_TO_INT(g_task_get_task_data(task)) == FALSE);
return g_task_propagate_pointer(task, error);
}
GBytes *
nm_utils_spawn_helper_finish_binary(GAsyncResult *result, GError **error)
{
GTask *task = G_TASK(result);
nm_assert(nm_g_task_is_valid(result, NULL, nm_utils_spawn_helper));
/* Check binary_output */
nm_assert(GPOINTER_TO_INT(g_task_get_task_data(task)) == TRUE);
return g_task_propagate_pointer(task, error);
}
@ -5474,3 +5503,185 @@ nm_utils_shorten_hostname(const char *hostname, char **shortened)
*shortened = g_steal_pointer(&s);
return TRUE;
}
const char *
nm_utils_get_connection_first_permissions_user(NMConnection *connection)
{
NMSettingConnection *s_con;
s_con = nm_connection_get_setting_connection(connection);
nm_assert(s_con);
return _nm_setting_connection_get_first_permissions_user(s_con);
}
/*****************************************************************************/
const char **
nm_utils_get_connection_private_files_paths(NMConnection *connection)
{
GPtrArray *files;
gs_free NMSetting **settings = NULL;
guint num_settings;
guint i;
files = g_ptr_array_new();
settings = nm_connection_get_settings(connection, &num_settings);
for (i = 0; i < num_settings; i++) {
_nm_setting_get_private_files(settings[i], files);
}
g_ptr_array_add(files, NULL);
return (const char **) g_ptr_array_free(files, files->len == 1);
}
typedef struct _ReadInfo ReadInfo;
typedef struct {
char *path;
ReadInfo *read_info;
} FileInfo;
struct _ReadInfo {
GTask *task;
GHashTable *table;
GPtrArray *file_infos; /* of FileInfo */
GError *first_error;
guint num_pending;
};
static void
read_file_helper_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
FileInfo *file_info = user_data;
ReadInfo *read_info = file_info->read_info;
gs_unref_bytes GBytes *output = NULL;
gs_free_error GError *error = NULL;
output = nm_utils_spawn_helper_finish_binary(result, &error);
nm_assert(read_info->num_pending > 0);
read_info->num_pending--;
if (nm_utils_error_is_cancelled(error)) {
/* nop */
} else if (error) {
nm_log_dbg(LOGD_CORE,
"read-private-files: failed to read file '%s': %s",
file_info->path,
error->message);
if (!read_info->first_error) {
/* @error just says "helper process exited with status X".
* Return a more human-friendly one. */
read_info->first_error = g_error_new(NM_UTILS_ERROR,
NM_UTILS_ERROR_UNKNOWN,
"error reading file '%s'",
file_info->path);
}
} else {
nm_log_dbg(LOGD_SUPPLICANT,
"read-private-files: successfully read file '%s'",
file_info->path);
/* Store the file contents in the hash table */
if (!read_info->table) {
read_info->table = g_hash_table_new_full(nm_str_hash,
g_str_equal,
g_free,
(GDestroyNotify) g_bytes_unref);
}
g_hash_table_insert(read_info->table,
g_steal_pointer(&file_info->path),
g_steal_pointer(&output));
}
g_clear_pointer(&file_info->path, g_free);
/* If all operations are completed, return */
if (read_info->num_pending == 0) {
if (read_info->first_error) {
g_task_return_error(read_info->task, g_steal_pointer(&read_info->first_error));
} else {
g_task_return_pointer(read_info->task,
g_steal_pointer(&read_info->table),
(GDestroyNotify) g_hash_table_unref);
}
if (read_info->table)
g_hash_table_unref(read_info->table);
if (read_info->file_infos)
g_ptr_array_unref(read_info->file_infos);
g_object_unref(read_info->task);
g_free(read_info);
}
}
/**
* nm_utils_read_private_files:
* @paths: array of file paths to be read
* @user: name of the user to impersonate when reading the files
* @cancellable: cancellable to cancel the operation
* @callback: callback to invoke on completion
* @cb_data: data for @callback
*
* Reads the given list of files @paths on behalf of user @user. Invokes
* @callback asynchronously on completion. The callback must use
* nm_utils_read_private_files_finish() to obtain the result.
*/
void
nm_utils_read_private_files(const char *const *paths,
const char *user,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer cb_data)
{
ReadInfo *read_info;
FileInfo *file_info;
guint i;
g_return_if_fail(paths && paths[0]);
g_return_if_fail(cancellable);
g_return_if_fail(callback);
g_return_if_fail(cb_data);
read_info = g_new(ReadInfo, 1);
*read_info = (ReadInfo) {
.task = nm_g_task_new(NULL, cancellable, nm_utils_read_private_files, callback, cb_data),
.file_infos = g_ptr_array_new_with_free_func(g_free),
};
for (i = 0; paths[i]; i++) {
file_info = g_new(FileInfo, 1);
*file_info = (FileInfo) {
.path = g_strdup(paths[i]),
.read_info = read_info,
};
g_ptr_array_add(read_info->file_infos, file_info);
read_info->num_pending++;
nm_utils_spawn_helper(NM_MAKE_STRV("read-file-as-user", user, paths[i]),
TRUE,
cancellable,
read_file_helper_cb,
file_info);
}
}
/**
* nm_utils_read_private_files_finish:
* @result: the GAsyncResult
* @error: on return, the error
*
* Returns the files read by nm_utils_read_private_files(). The return value
* is a hash table {char * -> GBytes *}. Free it with g_hash_table_unref().
*/
GHashTable *
nm_utils_read_private_files_finish(GAsyncResult *result, GError **error)
{
GTask *task = G_TASK(result);
nm_assert(nm_g_task_is_valid(result, NULL, nm_utils_read_private_files));
return g_task_propagate_pointer(task, error);
}

View file

@ -478,11 +478,13 @@ guint8 nm_wifi_utils_level_to_quality(int val);
/*****************************************************************************/
void nm_utils_spawn_helper(const char *const *args,
gboolean binary_output,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer cb_data);
char *nm_utils_spawn_helper_finish(GAsyncResult *result, GError **error);
char *nm_utils_spawn_helper_finish_string(GAsyncResult *result, GError **error);
GBytes *nm_utils_spawn_helper_finish_binary(GAsyncResult *result, GError **error);
/*****************************************************************************/
@ -490,4 +492,19 @@ uid_t nm_utils_get_nm_uid(void);
gid_t nm_utils_get_nm_gid(void);
/*****************************************************************************/
const char *nm_utils_get_connection_first_permissions_user(NMConnection *connection);
/*****************************************************************************/
const char **nm_utils_get_connection_private_files_paths(NMConnection *connection);
void nm_utils_read_private_files(const char *const *paths,
const char *user,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer cb_data);
GHashTable *nm_utils_read_private_files_finish(GAsyncResult *result, GError **error);
#endif /* __NM_CORE_UTILS_H__ */

View file

@ -8,6 +8,7 @@
#include "nm-firewall-utils.h"
#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
#include "libnm-glib-aux/nm-str-buf.h"
#include "libnm-glib-aux/nm-io-utils.h"
#include "libnm-platform/nm-platform.h"
@ -127,7 +128,7 @@ _share_iptables_subnet_to_str(char buf[static _SHARE_IPTABLES_SUBNET_TO_STR
}
static char *
_share_iptables_get_name(gboolean is_iptables_chain, const char *prefix, const char *ip_iface)
_iptables_get_name(gboolean is_iptables_chain, const char *prefix, const char *ip_iface)
{
NMStrBuf strbuf = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_40, FALSE);
gsize ip_iface_len;
@ -179,13 +180,13 @@ _share_iptables_get_name(gboolean is_iptables_chain, const char *prefix, const c
/*****************************************************************************/
static gboolean
_share_iptables_call_v(const char *const *argv)
_iptables_call_v(const char *const *argv)
{
gs_free_error GError *error = NULL;
gs_free char *argv_str = NULL;
int status;
nm_log_dbg(LOGD_SHARING, "iptables: %s", (argv_str = g_strjoinv(" ", (char **) argv)));
nm_log_dbg(LOGD_FIREWALL, "iptables: %s", (argv_str = g_strjoinv(" ", (char **) argv)));
if (!g_spawn_sync("/",
(char **) argv,
@ -197,7 +198,7 @@ _share_iptables_call_v(const char *const *argv)
NULL,
&status,
&error)) {
nm_log_warn(LOGD_SHARING,
nm_log_warn(LOGD_FIREWALL,
"iptables: error executing command %s: %s",
argv[0],
error->message);
@ -205,20 +206,24 @@ _share_iptables_call_v(const char *const *argv)
}
if (!g_spawn_check_exit_status(status, &error)) {
nm_log_warn(LOGD_SHARING, "iptables: command %s failed: %s", argv[0], error->message);
nm_log_warn(LOGD_FIREWALL, "iptables: command %s failed: %s", argv[0], error->message);
return FALSE;
}
return TRUE;
}
#define _share_iptables_call(...) \
_share_iptables_call_v(NM_MAKE_STRV("" IPTABLES_PATH "", "--wait", "2", __VA_ARGS__))
#define _ipxtables_call(family, ...) \
_iptables_call_v( \
NM_MAKE_STRV((family == AF_INET ? "" IPTABLES_PATH "" : "" IP6TABLES_PATH ""), \
"--wait", \
"2", \
__VA_ARGS__))
static gboolean
_share_iptables_chain_op(const char *table, const char *chain, const char *op)
{
return _share_iptables_call("--table", table, op, chain);
return _ipxtables_call(AF_INET, "--table", table, op, chain);
}
static gboolean
@ -244,10 +249,11 @@ _share_iptables_set_masquerade_sync(gboolean up, const char *ip_iface, in_addr_t
char str_subnet[_SHARE_IPTABLES_SUBNET_TO_STR_LEN];
gs_free char *comment_name = NULL;
comment_name = _share_iptables_get_name(FALSE, "nm-shared", ip_iface);
comment_name = _iptables_get_name(FALSE, "nm-shared", ip_iface);
_share_iptables_subnet_to_str(str_subnet, addr, plen);
_share_iptables_call("--table",
_ipxtables_call(AF_INET,
"--table",
"nat",
up ? "--insert" : "--delete",
"POSTROUTING",
@ -297,7 +303,8 @@ _share_iptables_set_shared_chains_add(const char *chain_input,
_share_iptables_chain_add("filter", chain_input);
for (i = 0; i < (int) G_N_ELEMENTS(input_params); i++) {
_share_iptables_call("--table",
_ipxtables_call(AF_INET,
"--table",
"filter",
"--append",
chain_input,
@ -311,7 +318,8 @@ _share_iptables_set_shared_chains_add(const char *chain_input,
_share_iptables_chain_add("filter", chain_forward);
_share_iptables_call("--table",
_ipxtables_call(AF_INET,
"--table",
"filter",
"--append",
chain_forward,
@ -325,7 +333,8 @@ _share_iptables_set_shared_chains_add(const char *chain_input,
"ESTABLISHED,RELATED",
"--jump",
"ACCEPT");
_share_iptables_call("--table",
_ipxtables_call(AF_INET,
"--table",
"filter",
"--append",
chain_forward,
@ -335,7 +344,8 @@ _share_iptables_set_shared_chains_add(const char *chain_input,
ip_iface,
"--jump",
"ACCEPT");
_share_iptables_call("--table",
_ipxtables_call(AF_INET,
"--table",
"filter",
"--append",
chain_forward,
@ -345,7 +355,8 @@ _share_iptables_set_shared_chains_add(const char *chain_input,
ip_iface,
"--jump",
"ACCEPT");
_share_iptables_call("--table",
_ipxtables_call(AF_INET,
"--table",
"filter",
"--append",
chain_forward,
@ -353,7 +364,8 @@ _share_iptables_set_shared_chains_add(const char *chain_input,
ip_iface,
"--jump",
"REJECT");
_share_iptables_call("--table",
_ipxtables_call(AF_INET,
"--table",
"filter",
"--append",
chain_forward,
@ -377,14 +389,15 @@ _share_iptables_set_shared_sync(gboolean up, const char *ip_iface, in_addr_t add
gs_free char *chain_input = NULL;
gs_free char *chain_forward = NULL;
comment_name = _share_iptables_get_name(FALSE, "nm-shared", ip_iface);
chain_input = _share_iptables_get_name(TRUE, "nm-sh-in", ip_iface);
chain_forward = _share_iptables_get_name(TRUE, "nm-sh-fw", ip_iface);
comment_name = _iptables_get_name(FALSE, "nm-shared", ip_iface);
chain_input = _iptables_get_name(TRUE, "nm-sh-in", ip_iface);
chain_forward = _iptables_get_name(TRUE, "nm-sh-fw", ip_iface);
if (up)
_share_iptables_set_shared_chains_add(chain_input, chain_forward, ip_iface, addr, plen);
_share_iptables_call("--table",
_ipxtables_call(AF_INET,
"--table",
"filter",
up ? "--insert" : "--delete",
"INPUT",
@ -397,7 +410,8 @@ _share_iptables_set_shared_sync(gboolean up, const char *ip_iface, in_addr_t add
"--comment",
comment_name);
_share_iptables_call("--table",
_ipxtables_call(AF_INET,
"--table",
"filter",
up ? "--insert" : "--delete",
"FORWARD",
@ -460,19 +474,19 @@ _fw_nft_call_communicate_cb(GObject *source, GAsyncResult *result, gpointer user
/* on any error, the process might still be running. We need to abort it in
* the background... */
if (!nm_utils_error_is_cancelled(error)) {
nm_log_dbg(LOGD_SHARING,
nm_log_dbg(LOGD_FIREWALL,
"firewall: nft[%s]: communication failed: %s. Kill process",
call_data->identifier,
error->message);
} else if (!call_data->timeout_source) {
nm_log_dbg(LOGD_SHARING,
"firewall: ntf[%s]: communication timed out. Kill process",
nm_log_dbg(LOGD_FIREWALL,
"firewall: nft[%s]: communication timed out. Kill process",
call_data->identifier);
nm_clear_error(&error);
nm_utils_error_set(&error, NM_UTILS_ERROR_UNKNOWN, "timeout communicating with nft");
} else {
nm_log_dbg(LOGD_SHARING,
"firewall: ntf[%s]: communication cancelled. Kill process",
nm_log_dbg(LOGD_FIREWALL,
"firewall: nft[%s]: communication cancelled. Kill process",
call_data->identifier);
}
@ -485,7 +499,7 @@ _fw_nft_call_communicate_cb(GObject *source, GAsyncResult *result, gpointer user
nm_g_subprocess_terminate_in_background(call_data->subprocess, 200);
}
} else if (g_subprocess_get_successful(call_data->subprocess)) {
nm_log_dbg(LOGD_SHARING, "firewall: nft[%s]: command successful", call_data->identifier);
nm_log_dbg(LOGD_FIREWALL, "firewall: nft[%s]: command successful", call_data->identifier);
} else {
char buf[NM_UTILS_GET_PROCESS_EXIT_STATUS_BUF_LEN];
gs_free char *ss_stdout = NULL;
@ -498,7 +512,7 @@ _fw_nft_call_communicate_cb(GObject *source, GAsyncResult *result, gpointer user
nm_utils_get_process_exit_status_desc_buf(status, buf, sizeof(buf));
nm_log_warn(LOGD_SHARING,
nm_log_warn(LOGD_FIREWALL,
"firewall: nft[%s]: command %s:%s%s%s%s%s%s%s",
call_data->identifier,
buf,
@ -534,7 +548,7 @@ _fw_nft_call_cancelled_cb(GCancellable *cancellable, gpointer user_data)
if (call_data->cancellable_id == 0)
return;
nm_log_dbg(LOGD_SHARING, "firewall: nft[%s]: operation cancelled", call_data->identifier);
nm_log_dbg(LOGD_FIREWALL, "firewall: nft[%s]: operation cancelled", call_data->identifier);
nm_clear_g_signal_handler(g_task_get_cancellable(call_data->task), &call_data->cancellable_id);
nm_clear_g_cancellable(&call_data->intern_cancellable);
@ -546,7 +560,7 @@ _fw_nft_call_timeout_cb(gpointer user_data)
FwNftCallData *call_data = user_data;
nm_clear_g_source_inst(&call_data->timeout_source);
nm_log_dbg(LOGD_SHARING,
nm_log_dbg(LOGD_FIREWALL,
"firewall: nft[%s]: cancel operation after timeout",
call_data->identifier);
@ -573,7 +587,7 @@ nm_firewall_nft_call(GBytes *stdin_buf,
.timeout_source = NULL,
};
nm_log_trace(LOGD_SHARING,
nm_log_trace(LOGD_FIREWALL,
"firewall: nft: call command: [ '%s' ]",
nm_utils_buf_utf8safe_escape_bytes(stdin_buf,
NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
@ -585,7 +599,7 @@ nm_firewall_nft_call(GBytes *stdin_buf,
call_data,
NULL);
if (call_data->cancellable_id == 0) {
nm_log_dbg(LOGD_SHARING, "firewall: nft: already cancelled");
nm_log_dbg(LOGD_FIREWALL, "firewall: nft: already cancelled");
nm_utils_error_set_cancelled(&error, FALSE, NULL);
_fw_nft_call_data_free(call_data, g_steal_pointer(&error));
return;
@ -602,14 +616,14 @@ nm_firewall_nft_call(GBytes *stdin_buf,
&error);
if (!call_data->subprocess) {
nm_log_dbg(LOGD_SHARING, "firewall: nft: spawning nft failed: %s", error->message);
nm_log_dbg(LOGD_FIREWALL, "firewall: nft: spawning nft failed: %s", error->message);
_fw_nft_call_data_free(call_data, g_steal_pointer(&error));
return;
}
call_data->identifier = g_strdup(g_subprocess_get_identifier(call_data->subprocess));
nm_log_dbg(LOGD_SHARING, "firewall: nft[%s]: communicate with nft", call_data->identifier);
nm_log_dbg(LOGD_FIREWALL, "firewall: nft[%s]: communicate with nft", call_data->identifier);
nm_shutdown_wait_obj_register_object(call_data->task, "nft-call");
@ -691,7 +705,7 @@ _fw_nft_set_shared_construct(gboolean up, const char *ip_iface, in_addr_t addr,
gs_free char *table_name = NULL;
char str_subnet[_SHARE_IPTABLES_SUBNET_TO_STR_LEN];
table_name = _share_iptables_get_name(FALSE, "nm-shared", ip_iface);
table_name = _iptables_get_name(FALSE, "nm-shared", ip_iface);
_share_iptables_subnet_to_str(str_subnet, addr, plen);
@ -756,6 +770,141 @@ _fw_nft_set_shared_construct(gboolean up, const char *ip_iface, in_addr_t addr,
return nm_str_buf_finalize_to_gbytes(&strbuf);
}
static GBytes *
_fw_nft_wg_default_construct(const char *ip_iface,
NMSettingIPConfig *ip_config,
int fwmark,
gboolean up)
{
nm_auto_str_buf NMStrBuf strbuf = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE);
gs_free char *table_name = NULL;
const char *family_str;
table_name = _iptables_get_name(FALSE, "nm-wg", ip_iface);
family_str = nm_setting_ip_config_get_addr_family(ip_config) == AF_INET ? "ip" : "ip6";
_fw_nft_append_cmd_table(&strbuf, family_str, table_name, up);
if (up) {
guint n_addresses = nm_setting_ip_config_get_num_addresses(ip_config);
if (n_addresses) {
_append(&strbuf, "add chain %s %s preraw {", family_str, table_name);
for (guint i = 0; i < n_addresses; i++) {
NMIPAddress *addr = nm_setting_ip_config_get_address(ip_config, i);
_append(&strbuf,
" iifname != \"%s\" "
" %s daddr %s "
" fib saddr type != local "
"drop;",
ip_iface,
family_str,
nm_ip_address_get_address(addr));
}
_append(&strbuf, "};");
}
_append(&strbuf,
"add chain %s %s premangle {"
" type filter hook prerouting priority mangle; policy accept; "
" meta l4proto udp meta mark set ct mark; "
"};",
family_str,
table_name);
_append(&strbuf,
"add chain %s %s postmangle {"
" type filter hook postrouting priority mangle; policy accept; "
" meta l4proto udp mark 0x%08x ct mark set meta mark; "
"};",
family_str,
table_name,
fwmark);
}
return nm_str_buf_finalize_to_gbytes(&strbuf);
}
static void
_fw_iptables_wg_configure(const char *ip_iface,
NMSettingIPConfig *ip_config,
int fwmark,
gboolean up)
{
gs_free char *comment_name = NULL;
char fwmark_str[11];
int family = nm_setting_ip_config_get_addr_family(ip_config);
guint n_addresses = nm_setting_ip_config_get_num_addresses(ip_config);
comment_name = _iptables_get_name(FALSE, "nm-wg", ip_iface);
g_snprintf(fwmark_str, sizeof(fwmark_str), "%" G_GUINT32_FORMAT, fwmark);
nm_assert(strlen(fwmark_str) > 0);
for (guint i = 0; i < n_addresses; i++) {
NMIPAddress *addr = nm_setting_ip_config_get_address(ip_config, i);
_ipxtables_call(family,
"--table",
"raw",
up ? "--insert" : "--delete",
"PREROUTING",
"!",
"--in-interface",
ip_iface,
"--destination",
nm_ip_address_get_address(addr),
"--match",
"addrtype",
"!",
"--src-type",
"LOCAL",
"-j",
"DROP",
"-m",
"comment",
"--comment",
comment_name);
}
_ipxtables_call(family,
"--table",
"mangle",
up ? "--insert" : "--delete",
"POSTROUTING",
"--match",
"mark",
"--mark",
fwmark_str,
"--protocol",
"udp",
"--jump",
"CONNMARK",
"--save-mark",
"-m",
"comment",
"--comment",
comment_name);
_ipxtables_call(family,
"--table",
"mangle",
up ? "--insert" : "--delete",
"PREROUTING",
"--protocol",
"udp",
"--jump",
"CONNMARK",
"--restore-mark",
"-m",
"comment",
"--comment",
comment_name);
}
/*****************************************************************************/
GBytes *
@ -1046,6 +1195,31 @@ nm_firewall_config_free(NMFirewallConfig *self)
}
/*****************************************************************************/
void
nm_firewall_config_set_wg_rule(const char *ifname,
NMSettingIPConfig *ip_config,
int fwmark,
gboolean up)
{
switch (nm_firewall_utils_get_backend()) {
case NM_FIREWALL_BACKEND_NFTABLES:
{
gs_unref_bytes GBytes *stdin_buf = NULL;
stdin_buf = _fw_nft_wg_default_construct(ifname, ip_config, fwmark, up);
_fw_nft_call_sync(stdin_buf, NULL);
break;
}
case NM_FIREWALL_BACKEND_IPTABLES:
_fw_iptables_wg_configure(ifname, ip_config, fwmark, up);
break;
case NM_FIREWALL_BACKEND_NONE:
break;
default:
nm_assert_not_reached();
break;
}
}
void
nm_firewall_config_apply_sync(NMFirewallConfig *self, gboolean up)
@ -1124,7 +1298,7 @@ again:
if (!g_atomic_int_compare_and_exchange(&backend, NM_FIREWALL_BACKEND_UNKNOWN, b))
goto again;
nm_log_dbg(LOGD_SHARING,
nm_log_dbg(LOGD_FIREWALL,
"firewall: use %s backend%s%s%s%s%s%s%s",
FirewallBackends[b - 1].name,
NM_PRINT_FMT_QUOTED(FirewallBackends[b - 1].path,

View file

@ -24,6 +24,11 @@ NMFirewallConfig *nm_firewall_config_new_shared(const char *ip_iface, in_addr_t
void nm_firewall_config_free(NMFirewallConfig *self);
void nm_firewall_config_set_wg_rule(const char *ifname,
NMSettingIPConfig *ip_config,
int fwmark,
gboolean up);
void nm_firewall_config_apply_sync(NMFirewallConfig *self, gboolean up);
/*****************************************************************************/

View file

@ -826,7 +826,7 @@ _handle_l3cd_changed(NMIPConfig *self, const NML3ConfigData *l3cd)
if (v_i != v_i_old)
changed_params[n_changed_params++] = obj_properties_ip[PROP_IP_DNS_PRIORITY];
strarr_old = nm_l3_config_data_get_dns_options(l3cd_old, addr_family, &len);
strarr_old = nm_l3_config_data_get_dns_options(l3cd_old, addr_family, &len_old);
strarr = nm_l3_config_data_get_dns_options(priv->l3cd, addr_family, &len);
if (!nm_strv_equal_n(strarr, len, strarr_old, len_old))
changed_params[n_changed_params++] = obj_properties_ip[PROP_IP_DNS_OPTIONS];

View file

@ -30,6 +30,7 @@ typedef struct {
typedef struct {
GHashTable *config;
GHashTable *blobs;
char *private_user;
NMSupplCapMask capabilities;
guint32 ap_scan;
bool fast_required : 1;
@ -60,7 +61,7 @@ _get_capability(NMSupplicantConfigPrivate *priv, NMSupplCapType type)
}
NMSupplicantConfig *
nm_supplicant_config_new(NMSupplCapMask capabilities)
nm_supplicant_config_new(NMSupplCapMask capabilities, const char *private_user)
{
NMSupplicantConfigPrivate *priv;
NMSupplicantConfig *self;
@ -69,6 +70,7 @@ nm_supplicant_config_new(NMSupplCapMask capabilities)
priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
priv->capabilities = capabilities;
priv->private_user = g_strdup(private_user);
return self;
}
@ -258,19 +260,19 @@ static gboolean
nm_supplicant_config_add_blob_for_connection(NMSupplicantConfig *self,
GBytes *field,
const char *name,
const char *con_uid,
const char *con_uuid,
GError **error)
{
if (field && g_bytes_get_size(field)) {
gs_free char *uid = NULL;
gs_free char *blob_id = NULL;
char *p;
uid = g_strdup_printf("%s-%s", con_uid, name);
for (p = uid; *p; p++) {
blob_id = g_strdup_printf("%s-%s", con_uuid, name);
for (p = blob_id; *p; p++) {
if (*p == '/')
*p = '-';
}
if (!nm_supplicant_config_add_blob(self, name, field, uid, error))
if (!nm_supplicant_config_add_blob(self, name, field, blob_id, error))
return FALSE;
}
return TRUE;
@ -283,6 +285,7 @@ nm_supplicant_config_finalize(GObject *object)
g_hash_table_destroy(priv->config);
nm_clear_pointer(&priv->blobs, g_hash_table_destroy);
nm_clear_pointer(&priv->private_user, g_free);
G_OBJECT_CLASS(nm_supplicant_config_parent_class)->finalize(object);
}
@ -930,6 +933,7 @@ nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig
guint32 mtu,
NMSettingWirelessSecurityPmf pmf,
NMSettingWirelessSecurityFils fils,
GHashTable *files,
GError **error)
{
NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
@ -1284,6 +1288,7 @@ nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig
con_uuid,
mtu,
FALSE,
files,
error))
return FALSE;
}
@ -1365,6 +1370,7 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
const char *con_uuid,
guint32 mtu,
gboolean wired,
GHashTable *files,
GError **error)
{
NMSupplicantConfigPrivate *priv;
@ -1594,24 +1600,21 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
}
/* CA certificate */
path = NULL;
bytes = NULL;
if (ca_cert_override) {
if (!add_string_val(self, ca_cert_override, "ca_cert", FALSE, NULL, error))
return FALSE;
/* This is a build-time-configured system-wide file path, no need to pass
* it as a blob */
path = ca_cert_override;
} else {
switch (nm_setting_802_1x_get_ca_cert_scheme(setting)) {
case NM_SETTING_802_1X_CK_SCHEME_BLOB:
bytes = nm_setting_802_1x_get_ca_cert_blob(setting);
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"ca_cert",
con_uuid,
error))
return FALSE;
break;
case NM_SETTING_802_1X_CK_SCHEME_PATH:
path = nm_setting_802_1x_get_ca_cert_path(setting);
if (!add_string_val(self, path, "ca_cert", FALSE, NULL, error))
return FALSE;
if (priv->private_user)
bytes = nm_g_hash_table_lookup(files, path);
break;
case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
if (!add_pkcs11_uri_with_pin(self,
@ -1627,26 +1630,32 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
break;
}
}
if (bytes) {
if (!nm_supplicant_config_add_blob_for_connection(self, bytes, "ca_cert", con_uuid, error))
return FALSE;
} else if (path) {
/* Private connections cannot use paths other than the system CA store */
g_return_val_if_fail(ca_cert_override || !priv->private_user, FALSE);
if (!add_string_val(self, path, "ca_cert", FALSE, NULL, error))
return FALSE;
}
/* Phase 2 CA certificate */
path = NULL;
bytes = NULL;
if (ca_cert_override) {
if (!add_string_val(self, ca_cert_override, "ca_cert2", FALSE, NULL, error))
return FALSE;
/* This is a build-time-configured system-wide file path, no need to pass
* it as a blob */
path = ca_cert_override;
} else {
switch (nm_setting_802_1x_get_phase2_ca_cert_scheme(setting)) {
case NM_SETTING_802_1X_CK_SCHEME_BLOB:
bytes = nm_setting_802_1x_get_phase2_ca_cert_blob(setting);
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"ca_cert2",
con_uuid,
error))
return FALSE;
break;
case NM_SETTING_802_1X_CK_SCHEME_PATH:
path = nm_setting_802_1x_get_phase2_ca_cert_path(setting);
if (!add_string_val(self, path, "ca_cert2", FALSE, NULL, error))
return FALSE;
if (priv->private_user)
bytes = nm_g_hash_table_lookup(files, path);
break;
case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
if (!add_pkcs11_uri_with_pin(
@ -1663,6 +1672,15 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
break;
}
}
if (bytes) {
if (!nm_supplicant_config_add_blob_for_connection(self, bytes, "ca_cert2", con_uuid, error))
return FALSE;
} else if (path) {
/* Private connections cannot use paths other than the system CA store */
g_return_val_if_fail(ca_cert_override || !priv->private_user, FALSE);
if (!add_string_val(self, path, "ca_cert2", FALSE, NULL, error))
return FALSE;
}
/* Subject match */
value = nm_setting_802_1x_get_subject_match(setting);
@ -1714,21 +1732,17 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
/* Private key */
added = FALSE;
path = NULL;
bytes = NULL;
switch (nm_setting_802_1x_get_private_key_scheme(setting)) {
case NM_SETTING_802_1X_CK_SCHEME_BLOB:
bytes = nm_setting_802_1x_get_private_key_blob(setting);
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"private_key",
con_uuid,
error))
return FALSE;
added = TRUE;
break;
case NM_SETTING_802_1X_CK_SCHEME_PATH:
path = nm_setting_802_1x_get_private_key_path(setting);
if (!add_string_val(self, path, "private_key", FALSE, NULL, error))
return FALSE;
if (priv->private_user)
bytes = nm_g_hash_table_lookup(files, path);
added = TRUE;
break;
case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
@ -1745,6 +1759,19 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
default:
break;
}
if (bytes) {
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"private_key",
con_uuid,
error))
return FALSE;
} else if (path) {
/* Private connections cannot use paths */
g_return_val_if_fail(!priv->private_user, FALSE);
if (!add_string_val(self, path, "private_key", FALSE, NULL, error))
return FALSE;
}
if (added) {
NMSetting8021xCKFormat format;
@ -1768,20 +1795,16 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
/* Only add the client cert if the private key is not PKCS#12, as
* wpa_supplicant configuration directs us to do.
*/
path = NULL;
bytes = NULL;
switch (nm_setting_802_1x_get_client_cert_scheme(setting)) {
case NM_SETTING_802_1X_CK_SCHEME_BLOB:
bytes = nm_setting_802_1x_get_client_cert_blob(setting);
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"client_cert",
con_uuid,
error))
return FALSE;
break;
case NM_SETTING_802_1X_CK_SCHEME_PATH:
path = nm_setting_802_1x_get_client_cert_path(setting);
if (!add_string_val(self, path, "client_cert", FALSE, NULL, error))
return FALSE;
if (priv->private_user)
bytes = nm_g_hash_table_lookup(files, path);
break;
case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
if (!add_pkcs11_uri_with_pin(
@ -1797,26 +1820,35 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
default:
break;
}
if (bytes) {
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"client_cert",
con_uuid,
error))
return FALSE;
} else if (path) {
/* Private connections cannot use paths */
g_return_val_if_fail(!priv->private_user, FALSE);
if (!add_string_val(self, path, "client_cert", FALSE, NULL, error))
return FALSE;
}
}
}
/* Phase 2 private key */
added = FALSE;
path = NULL;
bytes = NULL;
switch (nm_setting_802_1x_get_phase2_private_key_scheme(setting)) {
case NM_SETTING_802_1X_CK_SCHEME_BLOB:
bytes = nm_setting_802_1x_get_phase2_private_key_blob(setting);
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"private_key2",
con_uuid,
error))
return FALSE;
added = TRUE;
break;
case NM_SETTING_802_1X_CK_SCHEME_PATH:
path = nm_setting_802_1x_get_phase2_private_key_path(setting);
if (!add_string_val(self, path, "private_key2", FALSE, NULL, error))
return FALSE;
if (priv->private_user)
bytes = nm_g_hash_table_lookup(files, path);
added = TRUE;
break;
case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
@ -1834,6 +1866,19 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
default:
break;
}
if (bytes) {
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"private_key2",
con_uuid,
error))
return FALSE;
} else if (path) {
/* Private connections cannot use paths */
g_return_val_if_fail(!priv->private_user, FALSE);
if (!add_string_val(self, path, "private_key2", FALSE, NULL, error))
return FALSE;
}
if (added) {
NMSetting8021xCKFormat format;
@ -1857,20 +1902,16 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
/* Only add the client cert if the private key is not PKCS#12, as
* wpa_supplicant configuration directs us to do.
*/
path = NULL;
bytes = NULL;
switch (nm_setting_802_1x_get_phase2_client_cert_scheme(setting)) {
case NM_SETTING_802_1X_CK_SCHEME_BLOB:
bytes = nm_setting_802_1x_get_phase2_client_cert_blob(setting);
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"client_cert2",
con_uuid,
error))
return FALSE;
break;
case NM_SETTING_802_1X_CK_SCHEME_PATH:
path = nm_setting_802_1x_get_phase2_client_cert_path(setting);
if (!add_string_val(self, path, "client_cert2", FALSE, NULL, error))
return FALSE;
if (priv->private_user)
bytes = nm_g_hash_table_lookup(files, path);
break;
case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
if (!add_pkcs11_uri_with_pin(
@ -1886,6 +1927,19 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
default:
break;
}
if (bytes) {
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"client_cert2",
con_uuid,
error))
return FALSE;
} else if (path) {
/* Private connections cannot use paths */
g_return_val_if_fail(!priv->private_user, FALSE);
if (!add_string_val(self, path, "client_cert2", FALSE, NULL, error))
return FALSE;
}
}
}

View file

@ -29,7 +29,7 @@ typedef struct _NMSupplicantConfigClass NMSupplicantConfigClass;
GType nm_supplicant_config_get_type(void);
NMSupplicantConfig *nm_supplicant_config_new(NMSupplCapMask capabilities);
NMSupplicantConfig *nm_supplicant_config_new(NMSupplCapMask capabilities, const char *private_user);
guint32 nm_supplicant_config_get_ap_scan(NMSupplicantConfig *self);
@ -57,6 +57,7 @@ gboolean nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig
guint32 mtu,
NMSettingWirelessSecurityPmf pmf,
NMSettingWirelessSecurityFils fils,
GHashTable *files,
GError **error);
gboolean nm_supplicant_config_add_no_security(NMSupplicantConfig *self, GError **error);
@ -66,6 +67,7 @@ gboolean nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
const char *con_uuid,
guint32 mtu,
gboolean wired,
GHashTable *files,
GError **error);
gboolean nm_supplicant_config_add_setting_macsec(NMSupplicantConfig *self,

View file

@ -46,6 +46,7 @@ typedef struct {
gpointer user_data;
guint fail_on_idle_id;
guint blobs_left;
guint remove_blobs_left;
guint calls_left;
struct _AddNetworkData *add_network_data;
} AssocData;
@ -2264,6 +2265,7 @@ assoc_add_blob_cb(GObject *source, GAsyncResult *result, gpointer user_data)
return;
}
nm_assert(priv->assoc_data->blobs_left > 0);
priv->assoc_data->blobs_left--;
_LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: blob added (%u left)",
NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
@ -2272,6 +2274,148 @@ assoc_add_blob_cb(GObject *source, GAsyncResult *result, gpointer user_data)
assoc_call_select_network(self);
}
static void
assoc_add_blobs(NMSupplicantInterface *self)
{
NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self);
GHashTable *blobs;
GHashTableIter iter;
const char *blob_name;
GBytes *blob_data;
blobs = nm_supplicant_config_get_blobs(priv->assoc_data->cfg);
priv->assoc_data->blobs_left = nm_g_hash_table_size(blobs);
_LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: need to add %u blobs",
NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
priv->assoc_data->blobs_left);
if (priv->assoc_data->blobs_left == 0) {
assoc_call_select_network(self);
return;
}
g_hash_table_iter_init(&iter, blobs);
while (g_hash_table_iter_next(&iter, (gpointer) &blob_name, (gpointer) &blob_data)) {
_LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: adding blob '%s'",
NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
blob_name);
_dbus_connection_call(
self,
NM_WPAS_DBUS_IFACE_INTERFACE,
"AddBlob",
g_variant_new("(s@ay)", blob_name, nm_g_bytes_to_variant_ay(blob_data)),
G_VARIANT_TYPE("()"),
G_DBUS_CALL_FLAGS_NONE,
DBUS_TIMEOUT_MSEC,
priv->assoc_data->cancellable,
assoc_add_blob_cb,
self);
}
}
static void
assoc_remove_blob_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
NMSupplicantInterface *self;
NMSupplicantInterfacePrivate *priv;
gs_free_error GError *error = NULL;
gs_unref_variant GVariant *res = NULL;
res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error);
if (nm_utils_error_is_cancelled(error))
return;
self = NM_SUPPLICANT_INTERFACE(user_data);
priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self);
/* We don't consider a failure fatal. The new association might be able
* to proceed even with the existing blobs, if they don't conflict with new
* ones. */
nm_assert(priv->assoc_data->remove_blobs_left > 0);
priv->assoc_data->remove_blobs_left--;
if (error) {
g_dbus_error_strip_remote_error(error);
_LOGD("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: failed to delete blob: %s",
NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
error->message);
} else {
_LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: blob removed (%u left)",
NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
priv->assoc_data->remove_blobs_left);
}
if (priv->assoc_data->remove_blobs_left == 0)
assoc_add_blobs(self);
}
static void
assoc_get_blobs_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
NMSupplicantInterface *self;
NMSupplicantInterfacePrivate *priv;
gs_free_error GError *error = NULL;
gs_unref_variant GVariant *res = NULL;
gs_unref_variant GVariant *value = NULL;
GVariantIter iter;
const char *blob_name;
GVariant *blob_data;
res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error);
if (nm_utils_error_is_cancelled(error))
return;
self = NM_SUPPLICANT_INTERFACE(user_data);
priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self);
if (error) {
_LOGD("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: failed to get blob list: %s",
NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
error->message);
assoc_add_blobs(self);
return;
}
g_variant_get(res, "(v)", &value);
/* While the "Blobs" property is documented as type "as", it is actually "a{say}" */
if (!value || !g_variant_is_of_type(value, G_VARIANT_TYPE("a{say}"))) {
_LOGD("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: failed to get blob list: wrong return type %s",
NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
value ? g_variant_get_type_string(value) : "NULL");
assoc_add_blobs(self);
return;
}
g_variant_iter_init(&iter, value);
priv->assoc_data->remove_blobs_left = g_variant_iter_n_children(&iter);
_LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: need to delete %u blobs",
NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
priv->assoc_data->remove_blobs_left);
if (priv->assoc_data->remove_blobs_left == 0) {
assoc_add_blobs(self);
} else {
while (g_variant_iter_loop(&iter, "{&s@ay}", &blob_name, &blob_data)) {
_LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: removing blob '%s'",
NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
blob_name);
_dbus_connection_call(self,
NM_WPAS_DBUS_IFACE_INTERFACE,
"RemoveBlob",
g_variant_new("(s)", blob_name),
G_VARIANT_TYPE("()"),
G_DBUS_CALL_FLAGS_NONE,
DBUS_TIMEOUT_MSEC,
priv->assoc_data->cancellable,
assoc_remove_blob_cb,
self);
}
}
}
static void
assoc_add_network_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
@ -2281,10 +2425,6 @@ assoc_add_network_cb(GObject *source, GAsyncResult *result, gpointer user_data)
NMSupplicantInterfacePrivate *priv;
gs_unref_variant GVariant *res = NULL;
gs_free_error GError *error = NULL;
GHashTable *blobs;
GHashTableIter iter;
const char *blob_name;
GBytes *blob_data;
nm_auto_ref_string NMRefString *name_owner = NULL;
nm_auto_ref_string NMRefString *object_path = NULL;
@ -2336,34 +2476,21 @@ assoc_add_network_cb(GObject *source, GAsyncResult *result, gpointer user_data)
nm_assert(!priv->net_path);
g_variant_get(res, "(o)", &priv->net_path);
/* Send blobs first; otherwise jump to selecting the network */
blobs = nm_supplicant_config_get_blobs(priv->assoc_data->cfg);
priv->assoc_data->blobs_left = blobs ? g_hash_table_size(blobs) : 0u;
_LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: network added (%s) (%u blobs left)",
_LOGT("assoc[" NM_HASH_OBFUSCATE_PTR_FMT "]: network added (%s)",
NM_HASH_OBFUSCATE_PTR(priv->assoc_data),
priv->net_path,
priv->assoc_data->blobs_left);
priv->net_path);
if (priv->assoc_data->blobs_left == 0) {
assoc_call_select_network(self);
return;
}
g_hash_table_iter_init(&iter, blobs);
while (g_hash_table_iter_next(&iter, (gpointer) &blob_name, (gpointer) &blob_data)) {
_dbus_connection_call(
self,
NM_WPAS_DBUS_IFACE_INTERFACE,
"AddBlob",
g_variant_new("(s@ay)", blob_name, nm_g_bytes_to_variant_ay(blob_data)),
G_VARIANT_TYPE("()"),
/* Delete any existing blobs before adding new ones */
_dbus_connection_call(self,
DBUS_INTERFACE_PROPERTIES,
"Get",
g_variant_new("(ss)", NM_WPAS_DBUS_IFACE_INTERFACE, "Blobs"),
G_VARIANT_TYPE("(v)"),
G_DBUS_CALL_FLAGS_NONE,
DBUS_TIMEOUT_MSEC,
priv->assoc_data->cancellable,
assoc_add_blob_cb,
assoc_get_blobs_cb,
self);
}
}
static void

View file

@ -98,7 +98,8 @@ build_supplicant_config(NMConnection *connection,
NMSetting8021x *s_8021x;
gboolean success;
config = nm_supplicant_config_new(capabilities);
config = nm_supplicant_config_new(capabilities,
nm_utils_get_connection_first_permissions_user(connection));
s_wifi = nm_connection_get_setting_wireless(connection);
g_assert(s_wifi);
@ -120,6 +121,7 @@ build_supplicant_config(NMConnection *connection,
mtu,
pmf,
fils,
NULL,
&error);
} else {
success = nm_supplicant_config_add_no_security(config, &error);

View file

@ -36,6 +36,7 @@ test_config_h(void)
G_STMT_END
ABSOLUTE_PATH(IPTABLES_PATH);
ABSOLUTE_PATH(IP6TABLES_PATH);
ABSOLUTE_PATH(NFT_PATH);
}

View file

@ -1899,7 +1899,7 @@ _dbus_signal_config_cb(NMVpnConnection *self, GVariant *dict)
_LOGD("config: reply received (IPv4:%s(%s), IPv6:%s(%s))",
priv->ip_data_4.enabled ? "on" : "off",
priv->ip_data_4.method_auto ? "auto" : "disabled",
priv->ip_data_4.enabled ? "on" : "off",
priv->ip_data_6.enabled ? "on" : "off",
priv->ip_data_6.method_auto ? "auto" : "disabled");
if (!priv->ip_data_4.method_auto)

View file

@ -2048,3 +2048,9 @@ global:
nm_setting_ethtool_fec_mode_get_type;
nm_ethtool_optname_is_fec;
} libnm_1_50_0;
libnm_1_52_2 {
global:
nm_utils_copy_cert_as_user;
nm_vpn_plugin_info_supports_safe_private_file_access;
} libnm_1_52_0;

View file

@ -209,7 +209,6 @@ if enable_introspection
'LD_LIBRARY_PATH=' + ld_library_path,
python_path,
gen_gir_cmd,
'--lib-path', meson.current_build_dir(),
'--gir', libnm_gir[0],
'--output', '@OUTPUT@',
'--target', name

View file

@ -180,6 +180,7 @@ _notify_update_prop_nameservers(NMClient *client,
goto next;
nameserver = g_steal_pointer(&val_str);
} else if (nm_streq(key, "uri")) {
g_free(nameserver);
nameserver = g_variant_dup_string(val, NULL);
}
next:

View file

@ -5,6 +5,7 @@ test_units = [
'test-nm-client',
'test-remote-settings-client',
'test-secret-agent',
'test-copy-cert-as-user'
]
foreach test_unit: test_units
@ -37,12 +38,15 @@ foreach test_unit: test_units
],
)
# test-copy-cert-as-user is a manual test, don't run it automatically
if test_unit != 'test-copy-cert-as-user'
test(
'src/libnm-client-impl/tests/' + test_unit,
test_script,
timeout: 90,
args: test_args + [exe.full_path()],
)
endif
endforeach
if enable_introspection

View file

@ -0,0 +1,32 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* This is a program to manually test the
* nm_utils_copy_cert_as_user() libnm function.
*/
#include "libnm-client-impl/nm-default-libnm.h"
#include "nm-utils.h"
int
main(int argc, char **argv)
{
gs_free_error GError *error = NULL;
gs_free char *filename = NULL;
if (argc != 3) {
g_printerr("Usage: %s <FILE> <USER>\n", argv[0]);
return 1;
}
filename = nm_utils_copy_cert_as_user(argv[1], argv[2], &error);
if (!filename) {
g_printerr("Error: %s\n", error->message);
return 1;
}
g_print("%s\n", filename);
return 0;
}

View file

@ -93,6 +93,8 @@ def syms_from_ver(verfile):
# hardcode it.
c_syms["nm_ethtool_optname_is_feature"] = "1.20"
c_syms["nm_setting_bond_port_get_prio"] = "1.44"
c_syms["nm_utils_copy_cert_as_user"] = "1.56"
c_syms["nm_vpn_plugin_info_supports_safe_private_file_access"] = "1.56"
return c_syms

View file

@ -39,13 +39,13 @@ typedef struct {
* note that this object will be unrefed after the callback has returned, use
* g_object_ref()/g_object_unref() if you want to use this object after the callback
* has returned
* @secrets: the #GVariant of type %NM_VARIANT_TYPE_CONNECTION containing the requested
* @secrets: (nullable): the #GVariant of type %NM_VARIANT_TYPE_CONNECTION containing the requested
* secrets (as created by nm_connection_to_dbus() for example). Each key in @secrets
* should be the name of a #NMSetting object (like "802-11-wireless-security")
* and each value should be an %NM_VARIANT_TYPE_SETTING variant. The sub-dicts
* map string:value, where the string is the setting property name (like "psk")
* and the value is the secret
* @error: if the secrets request failed, give a descriptive error here
* @error: (nullable): if the secrets request failed, give a descriptive error here
* @user_data: caller-specific data to be passed to the function
*
* Called as a result of a request by NM to retrieve secrets. When the
@ -90,7 +90,7 @@ typedef void (*NMSecretAgentOldGetSecretsFunc)(NMSecretAgentOld *agent,
* note that this object will be unrefed after the callback has returned, use
* g_object_ref()/g_object_unref() if you want to use this object after the callback
* has returned
* @error: if the saving secrets failed, give a descriptive error here
* @error: (nullable): if the saving secrets failed, give a descriptive error here
* @user_data: caller-specific data to be passed to the function
*
* Called as a result of a request by NM to save secrets. When the
@ -109,7 +109,7 @@ typedef void (*NMSecretAgentOldSaveSecretsFunc)(NMSecretAgentOld *agent,
* note that this object will be unrefed after the callback has returned, use
* g_object_ref()/g_object_unref() if you want to use this object after the callback
* has returned
* @error: if the deleting secrets failed, give a descriptive error here
* @error: (nullable): if the deleting secrets failed, give a descriptive error here
* @user_data: caller-specific data to be passed to the function
*
* Called as a result of a request by NM to delete secrets. When the

View file

@ -431,7 +431,7 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = {
[NM_META_SETTING_TYPE_OVS_DPDK] =
{
.meta_type = NM_META_SETTING_TYPE_OVS_DPDK,
.setting_priority = NM_SETTING_PRIORITY_HW_BASE,
.setting_priority = NM_SETTING_PRIORITY_AUX,
.setting_name = NM_SETTING_OVS_DPDK_SETTING_NAME,
.get_setting_gtype = nm_setting_ovs_dpdk_get_type,
},
@ -459,7 +459,7 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = {
[NM_META_SETTING_TYPE_OVS_PATCH] =
{
.meta_type = NM_META_SETTING_TYPE_OVS_PATCH,
.setting_priority = NM_SETTING_PRIORITY_HW_BASE,
.setting_priority = NM_SETTING_PRIORITY_AUX,
.setting_name = NM_SETTING_OVS_PATCH_SETTING_NAME,
.get_setting_gtype = nm_setting_ovs_patch_get_type,
},
@ -656,9 +656,7 @@ const NMMetaSettingType nm_meta_setting_types_by_priority[] = {
NM_META_SETTING_TYPE_MACSEC,
NM_META_SETTING_TYPE_MACVLAN,
NM_META_SETTING_TYPE_OVS_BRIDGE,
NM_META_SETTING_TYPE_OVS_DPDK,
NM_META_SETTING_TYPE_OVS_INTERFACE,
NM_META_SETTING_TYPE_OVS_PATCH,
NM_META_SETTING_TYPE_OVS_PORT,
NM_META_SETTING_TYPE_TEAM,
NM_META_SETTING_TYPE_TUN,
@ -688,8 +686,10 @@ const NMMetaSettingType nm_meta_setting_types_by_priority[] = {
NM_META_SETTING_TYPE_ETHTOOL,
NM_META_SETTING_TYPE_LINK,
NM_META_SETTING_TYPE_MATCH,
NM_META_SETTING_TYPE_OVS_DPDK,
NM_META_SETTING_TYPE_OVS_EXTERNAL_IDS,
NM_META_SETTING_TYPE_OVS_OTHER_CONFIG,
NM_META_SETTING_TYPE_OVS_PATCH,
NM_META_SETTING_TYPE_PPP,
NM_META_SETTING_TYPE_PPPOE,
NM_META_SETTING_TYPE_TEAM_PORT,

View file

@ -3133,6 +3133,86 @@ need_secrets(NMSetting *setting, gboolean check_rerequest)
/*****************************************************************************/
static void
get_private_files(NMSetting *setting, GPtrArray *files)
{
const struct {
const char *property;
NMSetting8021xCKScheme (*get_scheme_func)(NMSetting8021x *);
const char *(*get_path_func)(NMSetting8021x *);
} cert_props[] = {
{NM_SETTING_802_1X_CA_CERT,
nm_setting_802_1x_get_ca_cert_scheme,
nm_setting_802_1x_get_ca_cert_path},
{NM_SETTING_802_1X_CLIENT_CERT,
nm_setting_802_1x_get_client_cert_scheme,
nm_setting_802_1x_get_client_cert_path},
{NM_SETTING_802_1X_PRIVATE_KEY,
nm_setting_802_1x_get_private_key_scheme,
nm_setting_802_1x_get_private_key_path},
{NM_SETTING_802_1X_PHASE2_CA_CERT,
nm_setting_802_1x_get_phase2_ca_cert_scheme,
nm_setting_802_1x_get_phase2_ca_cert_path},
{NM_SETTING_802_1X_PHASE2_CLIENT_CERT,
nm_setting_802_1x_get_phase2_client_cert_scheme,
nm_setting_802_1x_get_phase2_client_cert_path},
{NM_SETTING_802_1X_PHASE2_PRIVATE_KEY,
nm_setting_802_1x_get_phase2_private_key_scheme,
nm_setting_802_1x_get_phase2_private_key_path},
};
NMSetting8021x *s_8021x = NM_SETTING_802_1X(setting);
const char *path;
guint i;
if (NM_MORE_ASSERT_ONCE(5)) {
GObjectClass *klass;
gs_free GParamSpec **properties = NULL;
guint n_properties;
gboolean found;
guint j;
/* Check that all the properties in the setting with flag CERT_KEY_FILE
* are listed in the table, and vice versa. */
klass = G_OBJECT_GET_CLASS(setting);
properties = g_object_class_list_properties(klass, &n_properties);
for (i = 0; i < n_properties; i++) {
if (!(properties[i]->flags & NM_SETTING_PARAM_CERT_KEY_FILE))
continue;
found = FALSE;
for (j = 0; j < G_N_ELEMENTS(cert_props); j++) {
if (nm_streq0(properties[i]->name, cert_props[j].property)) {
found = TRUE;
break;
}
}
nm_assert(found);
}
for (i = 0; i < G_N_ELEMENTS(cert_props); i++) {
GParamSpec *prop;
prop = g_object_class_find_property(klass, cert_props[i].property);
nm_assert(prop);
nm_assert(prop->flags & NM_SETTING_PARAM_CERT_KEY_FILE);
}
}
for (i = 0; i < G_N_ELEMENTS(cert_props); i++) {
if (cert_props[i].get_scheme_func(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH) {
path = cert_props[i].get_path_func(s_8021x);
if (path) {
g_ptr_array_add(files, (gpointer) path);
}
}
}
}
/*****************************************************************************/
static void
get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
@ -3225,6 +3305,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
setting_class->verify = verify;
setting_class->need_secrets = need_secrets;
setting_class->get_private_files = get_private_files;
/**
* NMSetting8021x:eap:
@ -3359,7 +3440,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
obj_properties,
NM_SETTING_802_1X_CA_CERT,
PROP_CA_CERT,
NM_SETTING_PARAM_NONE,
NM_SETTING_PARAM_CERT_KEY_FILE,
NMSetting8021xPrivate,
ca_cert);
@ -3556,7 +3637,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
obj_properties,
NM_SETTING_802_1X_CLIENT_CERT,
PROP_CLIENT_CERT,
NM_SETTING_PARAM_NONE,
NM_SETTING_PARAM_CERT_KEY_FILE,
NMSetting8021xPrivate,
client_cert);
@ -3803,7 +3884,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
obj_properties,
NM_SETTING_802_1X_PHASE2_CA_CERT,
PROP_PHASE2_CA_CERT,
NM_SETTING_PARAM_NONE,
NM_SETTING_PARAM_CERT_KEY_FILE,
NMSetting8021xPrivate,
phase2_ca_cert);
@ -4006,7 +4087,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
obj_properties,
NM_SETTING_802_1X_PHASE2_CLIENT_CERT,
PROP_PHASE2_CLIENT_CERT,
NM_SETTING_PARAM_NONE,
NM_SETTING_PARAM_CERT_KEY_FILE,
NMSetting8021xPrivate,
phase2_client_cert);
@ -4175,7 +4256,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
obj_properties,
NM_SETTING_802_1X_PRIVATE_KEY,
PROP_PRIVATE_KEY,
NM_SETTING_PARAM_NONE,
NM_SETTING_PARAM_CERT_KEY_FILE,
NMSetting8021xPrivate,
private_key);
@ -4276,7 +4357,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
obj_properties,
NM_SETTING_802_1X_PHASE2_PRIVATE_KEY,
PROP_PHASE2_PRIVATE_KEY,
NM_SETTING_PARAM_NONE,
NM_SETTING_PARAM_CERT_KEY_FILE,
NMSetting8021xPrivate,
phase2_private_key);

View file

@ -431,6 +431,47 @@ nm_setting_connection_permissions_user_allowed_by_uid(NMSettingConnection *setti
return _permissions_user_allowed(setting, NULL, uid);
}
guint
_nm_setting_connection_get_num_permissions_users(NMSettingConnection *setting)
{
NMSettingConnectionPrivate *priv;
guint i;
guint count = 0;
nm_assert(NM_IS_SETTING_CONNECTION(setting));
priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting);
for (i = 0; priv->permissions && i < priv->permissions->len; i++) {
const Permission *permission = &nm_g_array_index(priv->permissions, Permission, i);
if (permission->ptype == PERM_TYPE_USER) {
count++;
}
}
return count;
}
const char *
_nm_setting_connection_get_first_permissions_user(NMSettingConnection *setting)
{
NMSettingConnectionPrivate *priv;
guint i;
nm_assert(NM_IS_SETTING_CONNECTION(setting));
priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting);
for (i = 0; priv->permissions && i < priv->permissions->len; i++) {
const Permission *permission = &nm_g_array_index(priv->permissions, Permission, i);
if (permission->ptype == PERM_TYPE_USER) {
return permission->item;
}
}
return NULL;
}
/**
* nm_setting_connection_add_permission:
* @setting: the #NMSettingConnection

View file

@ -251,7 +251,7 @@ nm_setting_macvlan_class_init(NMSettingMacvlanClass *klass)
/**
* NMSettingMacvlan:promiscuous:
*
* Whether the interface should be put in promiscuous mode.
* Whether the parent interface should be put in promiscuous mode (true by default).
*
* Since: 1.2
**/

View file

@ -154,6 +154,11 @@ struct _NMSettingClass {
guint /* NMSettingParseFlags */ parse_flags,
GError **error);
/* returns a list of certificate/key files referenced in the connection.
* When the connection is private, we need to verify that the owner of
* the connection has access to them. */
void (*get_private_files)(NMSetting *setting, GPtrArray *files);
const struct _NMMetaSettingInfo *setting_info;
};
@ -333,6 +338,11 @@ struct _NMRange {
*/
#define NM_SETTING_PARAM_TO_DBUS_IGNORE_FLAGS (1 << (7 + G_PARAM_USER_SHIFT))
/* The property can refer to a certificate or key stored on disk. As such,
* special care is needed when accessing the file for private connections.
*/
#define NM_SETTING_PARAM_CERT_KEY_FILE (1 << (8 + G_PARAM_USER_SHIFT))
extern const NMSettInfoPropertType nm_sett_info_propert_type_setting_name;
extern const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_interface_name;
extern const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_ignore_i;
@ -858,9 +868,10 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
{ \
GParamSpec *_param_spec; \
\
G_STATIC_ASSERT(!NM_FLAGS_ANY((param_flags), \
G_STATIC_ASSERT( \
!NM_FLAGS_ANY((param_flags), \
~(NM_SETTING_PARAM_SECRET | NM_SETTING_PARAM_INFERRABLE \
| NM_SETTING_PARAM_FUZZY_IGNORE))); \
| NM_SETTING_PARAM_FUZZY_IGNORE | NM_SETTING_PARAM_CERT_KEY_FILE))); \
\
_param_spec = g_param_spec_boxed("" prop_name "", \
"", \

View file

@ -2262,6 +2262,34 @@ init_from_dbus(NMSetting *setting,
return TRUE;
}
static void
get_private_files(NMSetting *setting, GPtrArray *files)
{
if (NM_MORE_ASSERTS) {
GParamSpec **properties;
guint n_properties;
int i;
properties = g_object_class_list_properties(G_OBJECT_GET_CLASS(setting), &n_properties);
for (i = 0; i < n_properties; i++) {
if (properties[i]->flags & NM_SETTING_PARAM_CERT_KEY_FILE) {
/* Certificates and keys needs special handling, see setting 802.1X */
nm_assert_not_reached();
}
}
g_free(properties);
}
}
void
_nm_setting_get_private_files(NMSetting *setting, GPtrArray *files)
{
g_return_if_fail(NM_IS_SETTING(setting));
g_return_if_fail(files);
NM_SETTING_GET_CLASS(setting)->get_private_files(setting, files);
}
/**
* nm_setting_get_dbus_property_type:
* @setting: an #NMSetting
@ -4672,6 +4700,7 @@ nm_setting_class_init(NMSettingClass *setting_class)
setting_class->enumerate_values = enumerate_values;
setting_class->aggregate = aggregate;
setting_class->init_from_dbus = init_from_dbus;
setting_class->get_private_files = get_private_files;
/**
* NMSetting:name:

View file

@ -17,6 +17,7 @@
#include <linux/pkt_sched.h>
#include <linux/if_infiniband.h>
#include "libnm-glib-aux/nm-io-utils.h"
#include "libnm-glib-aux/nm-uuid.h"
#include "libnm-glib-aux/nm-json-aux.h"
#include "libnm-glib-aux/nm-str-buf.h"
@ -6195,3 +6196,257 @@ nm_utils_ensure_gtypes(void)
for (meta_type = 0; meta_type < _NM_META_SETTING_TYPE_NUM; meta_type++)
nm_meta_setting_infos[meta_type].get_setting_gtype();
}
/*****************************************************************************/
typedef struct {
GPid pid;
GSource *child_watch_source;
GMainLoop *loop;
GError *error;
int child_stdout;
int child_stderr;
GSource *output_source;
GSource *error_source;
NMStrBuf output_buffer;
NMStrBuf error_buffer;
} HelperInfo;
static void
helper_complete(HelperInfo *info, GError *error_take)
{
if (error_take) {
if (!info->error)
info->error = error_take;
else
g_error_free(error_take);
}
if (info->output_source || info->error_source || info->pid != -1) {
/* Wait that the pipe is closed and process has terminated */
return;
}
if (info->error && info->error_buffer.len > 0) {
/* Prefer the message from stderr as it's more informative */
g_error_free(info->error);
info->error = g_error_new(NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_FAILED,
"%s",
nm_str_buf_get_str(&info->error_buffer));
}
g_main_loop_quit(info->loop);
}
static gboolean
helper_have_err_data(int fd, GIOCondition condition, gpointer user_data)
{
HelperInfo *info = user_data;
gssize n_read;
GError *error = NULL;
n_read = nm_utils_fd_read(fd, &info->error_buffer);
if (n_read > 0)
return G_SOURCE_CONTINUE;
nm_clear_g_source_inst(&info->error_source);
nm_clear_fd(&info->child_stderr);
if (n_read < 0) {
error = g_error_new(NM_UTILS_ERROR,
NM_UTILS_ERROR_UNKNOWN,
"read from process returned %d (%s)",
(int) -n_read,
nm_strerror_native((int) -n_read));
}
helper_complete(info, error);
return G_SOURCE_CONTINUE;
}
static gboolean
helper_have_data(int fd, GIOCondition condition, gpointer user_data)
{
HelperInfo *info = user_data;
gssize n_read;
GError *error = NULL;
n_read = nm_utils_fd_read(fd, &info->output_buffer);
if (n_read > 0)
return G_SOURCE_CONTINUE;
nm_clear_g_source_inst(&info->output_source);
nm_clear_fd(&info->child_stdout);
if (n_read < 0) {
error = g_error_new(NM_UTILS_ERROR,
NM_UTILS_ERROR_UNKNOWN,
"read from process returned %d (%s)",
(int) -n_read,
nm_strerror_native((int) -n_read));
}
helper_complete(info, error);
return G_SOURCE_CONTINUE;
}
static void
helper_child_terminated(GPid pid, int status, gpointer user_data)
{
HelperInfo *info = user_data;
gs_free char *status_desc = NULL;
GError *error = NULL;
info->pid = -1;
nm_clear_g_source_inst(&info->child_watch_source);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
if (!status_desc)
status_desc = nm_utils_get_process_exit_status_desc(status);
error =
g_error_new(NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "helper process %s", status_desc);
}
helper_complete(info, error);
}
#define RUN_CERT_DIR NMRUNDIR "/cert"
/**
* nm_utils_copy_cert_as_user:
* @filename: the file name of the certificate or key to copy
* @user: the user to impersonate when reading the file
* @error: (nullable): return location for a #GError, or %NULL
*
* Reads @filename on behalf of user @user and writes the
* content to a new file in /run/NetworkManager/cert/.
* The new file has permission 600 and is owned by root.
*
* This function 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.
*
* Returns: (transfer full): the name of the new temporary file. Or %NULL
* if an error occurred, including when the given user can't access the
* file.
*
* Since: 1.56, 1.52.2
*/
char *
nm_utils_copy_cert_as_user(const char *filename, const char *user, GError **error)
{
gs_unref_bytes GBytes *bytes = NULL;
char dst_path[] = RUN_CERT_DIR "/XXXXXX";
HelperInfo info = {
.child_stdout = -1,
.child_stderr = -1,
};
GMainContext *context;
int fd = -1;
g_return_val_if_fail(filename, NULL);
g_return_val_if_fail(user, NULL);
g_return_val_if_fail(!error || !*error, NULL);
if (geteuid() != 0) {
g_set_error_literal(error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("This function needs to be called by root"));
return NULL;
}
if (!g_spawn_async_with_pipes(
"/",
(char **)
NM_MAKE_STRV(LIBEXECDIR "/nm-libnm-helper", "read-file-as-user", filename, user),
(char **) NM_MAKE_STRV(),
G_SPAWN_CLOEXEC_PIPES | G_SPAWN_DO_NOT_REAP_CHILD,
NULL,
NULL,
&info.pid,
NULL,
&info.child_stdout,
&info.child_stderr,
error)) {
return NULL;
}
context = g_main_context_new();
info.loop = g_main_loop_new(context, FALSE);
/* Watch process */
info.child_watch_source = nm_g_child_watch_source_new(info.pid,
G_PRIORITY_DEFAULT,
helper_child_terminated,
&info,
NULL);
g_source_attach(info.child_watch_source, context);
/* Watch stdout */
info.output_buffer = NM_STR_BUF_INIT(0, FALSE);
info.output_source = nm_g_unix_fd_source_new(info.child_stdout,
G_IO_IN | G_IO_ERR | G_IO_HUP,
G_PRIORITY_DEFAULT,
helper_have_data,
&info,
NULL);
g_source_attach(info.output_source, context);
/* Watch stderr */
info.error_buffer = NM_STR_BUF_INIT(0, FALSE);
info.error_source = nm_g_unix_fd_source_new(info.child_stderr,
G_IO_IN | G_IO_ERR | G_IO_HUP,
G_PRIORITY_DEFAULT,
helper_have_err_data,
&info,
NULL);
g_source_attach(info.error_source, context);
/* Wait termination */
g_main_loop_run(info.loop);
g_clear_pointer(&info.loop, g_main_loop_unref);
g_clear_pointer(&context, g_main_context_unref);
if (info.error) {
nm_str_buf_destroy(&info.output_buffer);
nm_str_buf_destroy(&info.error_buffer);
g_propagate_error(error, g_steal_pointer(&info.error));
return NULL;
}
/* Write the data to a new file */
bytes = g_bytes_new(nm_str_buf_get_str_unsafe(&info.output_buffer), info.output_buffer.len);
nm_str_buf_destroy(&info.output_buffer);
nm_str_buf_destroy(&info.error_buffer);
mkdir(RUN_CERT_DIR, 0600);
fd = mkstemp(dst_path);
if (fd < 0) {
g_set_error_literal(error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("Failure creating the temporary file"));
return NULL;
}
nm_close(fd);
if (!nm_utils_file_set_contents(dst_path,
g_bytes_get_data(bytes, NULL),
g_bytes_get_size(bytes),
0600,
NULL,
NULL,
error)) {
return NULL;
}
return g_strdup(dst_path);
}

View file

@ -913,6 +913,29 @@ nm_vpn_plugin_info_supports_multiple(NMVpnPluginInfo *self)
return _nm_utils_ascii_str_to_bool(s, FALSE);
}
/**
* nm_vpn_plugin_info_supports_safe_private_file_access:
* @self: plugin info instance
*
* Returns: %TRUE if the service supports reading files (certificates, keys) of
* private connections in a safe way (i.e. checking user permissions), or
if the service doesn't need to read any file from disk.
*
* Since: 1.56, 1.52.2
*/
gboolean
nm_vpn_plugin_info_supports_safe_private_file_access(NMVpnPluginInfo *self)
{
const char *s;
g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), FALSE);
s = nm_vpn_plugin_info_lookup_property(self,
NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION,
"supports-safe-private-file-access");
return _nm_utils_ascii_str_to_bool(s, FALSE);
}
/**
* nm_vpn_plugin_info_get_aliases:
* @self: plugin info instance

View file

@ -1186,4 +1186,11 @@ gboolean nm_connection_need_secrets_for_rerequest(NMConnection *connection);
const GPtrArray *_nm_setting_ovs_port_get_trunks_arr(NMSettingOvsPort *self);
/*****************************************************************************/
guint _nm_setting_connection_get_num_permissions_users(NMSettingConnection *setting);
const char *_nm_setting_connection_get_first_permissions_user(NMSettingConnection *setting);
void _nm_setting_get_private_files(NMSetting *setting, GPtrArray *files);
#endif

View file

@ -261,6 +261,9 @@ nm_utils_base64secret_decode(const char *base64_key, gsize required_key_len, gui
NM_AVAILABLE_IN_1_42
void nm_utils_ensure_gtypes(void);
NM_AVAILABLE_IN_1_52_2
char *nm_utils_copy_cert_as_user(const char *filename, const char *user, GError **error);
G_END_DECLS
#endif /* __NM_UTILS_H__ */

View file

@ -77,6 +77,7 @@
#define NM_VERSION_1_48 (NM_ENCODE_VERSION(1, 48, 0))
#define NM_VERSION_1_50 (NM_ENCODE_VERSION(1, 50, 0))
#define NM_VERSION_1_52 (NM_ENCODE_VERSION(1, 52, 0))
#define NM_VERSION_1_52_2 (NM_ENCODE_VERSION(1, 52, 2))
/* For releases, NM_API_VERSION is equal to NM_VERSION.
*
@ -87,8 +88,8 @@
* version is the future one. */
#define NM_API_VERSION \
(((NM_MINOR_VERSION % 2) == 1) \
? NM_ENCODE_VERSION (NM_MAJOR_VERSION, NM_MINOR_VERSION + 1, 0 ) \
: NM_ENCODE_VERSION (NM_MAJOR_VERSION, NM_MINOR_VERSION , ((NM_MICRO_VERSION + 1) / 2) * 2))
? NM_ENCODE_VERSION(NM_MAJOR_VERSION, NM_MINOR_VERSION + 1, 0) \
: NM_ENCODE_VERSION(NM_MAJOR_VERSION, NM_MINOR_VERSION, NM_MICRO_VERSION + 1))
/* deprecated. */
#define NM_VERSION_CUR_STABLE NM_API_VERSION

View file

@ -425,6 +425,12 @@
#define NM_AVAILABLE_IN_1_52
#endif
#if NM_VERSION_MAX_ALLOWED < NM_VERSION_1_52_2
#define NM_AVAILABLE_IN_1_52_2 G_UNAVAILABLE(1, 52.2)
#else
#define NM_AVAILABLE_IN_1_52_2
#endif
/*
* Synchronous API for calling D-Bus in libnm is deprecated. See
* https://networkmanager.dev/docs/libnm/latest/usage.html#sync-api

View file

@ -64,6 +64,8 @@ NM_AVAILABLE_IN_1_4
gboolean nm_vpn_plugin_info_supports_hints(NMVpnPluginInfo *self);
NM_AVAILABLE_IN_1_42
gboolean nm_vpn_plugin_info_supports_multiple(NMVpnPluginInfo *self);
NM_AVAILABLE_IN_1_52_2
gboolean nm_vpn_plugin_info_supports_safe_private_file_access(NMVpnPluginInfo *self);
NM_AVAILABLE_IN_1_4
const char *const *nm_vpn_plugin_info_get_aliases(NMVpnPluginInfo *self);
NM_AVAILABLE_IN_1_2

View file

@ -4,10 +4,14 @@
#include "nm-std-utils.h"
#include <stdint.h>
#include <assert.h>
#include <fcntl.h>
#include <grp.h>
#include <limits.h>
#include <net/if.h>
#include <pwd.h>
#include <stdint.h>
#include <sys/types.h>
/*****************************************************************************/
@ -95,6 +99,114 @@ out_huge:
/*****************************************************************************/
bool
nm_utils_set_effective_user(const char *user, char *errbuf, size_t errbuf_len)
{
struct passwd *pwentry;
int errsv;
char error[1024];
errno = 0;
pwentry = getpwnam(user);
if (!pwentry) {
errsv = errno;
if (errsv == 0) {
snprintf(errbuf, errbuf_len, "user not found");
} else {
snprintf(errbuf,
errbuf_len,
"error getting user entry: %d (%s)\n",
errsv,
_nm_strerror_r(errsv, error, sizeof(error)));
}
return false;
}
if (setgid(pwentry->pw_gid) != 0) {
errsv = errno;
snprintf(errbuf,
errbuf_len,
"failed to change group to %u: %d (%s)\n",
pwentry->pw_gid,
errsv,
_nm_strerror_r(errsv, error, sizeof(error)));
return false;
}
if (initgroups(user, pwentry->pw_gid) != 0) {
errsv = errno;
snprintf(errbuf,
errbuf_len,
"failed to reset supplementary group list to %u: %d (%s)\n",
pwentry->pw_gid,
errsv,
_nm_strerror_r(errsv, error, sizeof(error)));
return false;
}
if (setuid(pwentry->pw_uid) != 0) {
errsv = errno;
snprintf(errbuf,
errbuf_len,
"failed to change user to %u: %d (%s)\n",
pwentry->pw_uid,
errsv,
_nm_strerror_r(errsv, error, sizeof(error)));
return false;
}
return true;
}
/*****************************************************************************/
bool
nm_utils_read_file_to_stdout(const char *filename, char *errbuf, size_t errbuf_len)
{
nm_auto_close int fd = -1;
char buffer[4096];
char error[1024];
ssize_t bytes_read;
int errsv;
fd = open(filename, O_RDONLY);
if (fd == -1) {
errsv = errno;
snprintf(errbuf,
errbuf_len,
"error opening the file: %d (%s)",
errsv,
_nm_strerror_r(errsv, error, sizeof(error)));
return false;
}
while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) {
if (fwrite(buffer, 1, bytes_read, stdout) != (size_t) bytes_read) {
errsv = errno;
snprintf(errbuf,
errbuf_len,
"error writing to stdout: %d (%s)",
errsv,
_nm_strerror_r(errsv, error, sizeof(error)));
return false;
}
}
if (bytes_read < 0) {
errsv = errno;
snprintf(errbuf,
errbuf_len,
"error reading the file: %d (%s)",
errsv,
_nm_strerror_r(errsv, error, sizeof(error)));
return false;
}
return true;
}
/*****************************************************************************/
/**
* _nm_strerror_r:
* @errsv: the errno passed to strerror_r()

View file

@ -37,4 +37,8 @@ size_t nm_utils_get_next_realloc_size(bool true_realloc, size_t requested);
const char *_nm_strerror_r(int errsv, char *buf, size_t buf_size);
bool nm_utils_set_effective_user(const char *user, char *errbuf, size_t errbuf_size);
bool nm_utils_read_file_to_stdout(const char *filename, char *errbuf, size_t errbuf_len);
#endif /* __NM_STD_UTILS_H__ */

View file

@ -431,7 +431,7 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = {
[NM_META_SETTING_TYPE_OVS_DPDK] =
{
.meta_type = NM_META_SETTING_TYPE_OVS_DPDK,
.setting_priority = NM_SETTING_PRIORITY_HW_BASE,
.setting_priority = NM_SETTING_PRIORITY_AUX,
.setting_name = NM_SETTING_OVS_DPDK_SETTING_NAME,
.get_setting_gtype = nm_setting_ovs_dpdk_get_type,
},
@ -459,7 +459,7 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = {
[NM_META_SETTING_TYPE_OVS_PATCH] =
{
.meta_type = NM_META_SETTING_TYPE_OVS_PATCH,
.setting_priority = NM_SETTING_PRIORITY_HW_BASE,
.setting_priority = NM_SETTING_PRIORITY_AUX,
.setting_name = NM_SETTING_OVS_PATCH_SETTING_NAME,
.get_setting_gtype = nm_setting_ovs_patch_get_type,
},
@ -656,9 +656,7 @@ const NMMetaSettingType nm_meta_setting_types_by_priority[] = {
NM_META_SETTING_TYPE_MACSEC,
NM_META_SETTING_TYPE_MACVLAN,
NM_META_SETTING_TYPE_OVS_BRIDGE,
NM_META_SETTING_TYPE_OVS_DPDK,
NM_META_SETTING_TYPE_OVS_INTERFACE,
NM_META_SETTING_TYPE_OVS_PATCH,
NM_META_SETTING_TYPE_OVS_PORT,
NM_META_SETTING_TYPE_TEAM,
NM_META_SETTING_TYPE_TUN,
@ -688,8 +686,10 @@ const NMMetaSettingType nm_meta_setting_types_by_priority[] = {
NM_META_SETTING_TYPE_ETHTOOL,
NM_META_SETTING_TYPE_LINK,
NM_META_SETTING_TYPE_MATCH,
NM_META_SETTING_TYPE_OVS_DPDK,
NM_META_SETTING_TYPE_OVS_EXTERNAL_IDS,
NM_META_SETTING_TYPE_OVS_OTHER_CONFIG,
NM_META_SETTING_TYPE_OVS_PATCH,
NM_META_SETTING_TYPE_PPP,
NM_META_SETTING_TYPE_PPPOE,
NM_META_SETTING_TYPE_TEAM_PORT,

View file

@ -9133,12 +9133,7 @@ const NMMetaSettingInfoEditor nm_meta_setting_infos_editor[] = {
NM_META_SETTING_VALID_PART_ITEM (WIRED, FALSE),
),
),
SETTING_INFO (OVS_DPDK,
.valid_parts = NM_META_SETTING_VALID_PARTS (
NM_META_SETTING_VALID_PART_ITEM (CONNECTION, TRUE),
NM_META_SETTING_VALID_PART_ITEM (OVS_DPDK, TRUE),
),
),
SETTING_INFO (OVS_DPDK),
SETTING_INFO_EMPTY (OVS_OTHER_CONFIG),
SETTING_INFO_EMPTY (OVS_EXTERNAL_IDS),
SETTING_INFO (OVS_INTERFACE,
@ -9153,12 +9148,7 @@ const NMMetaSettingInfoEditor nm_meta_setting_infos_editor[] = {
NM_META_SETTING_VALID_PART_ITEM (ETHTOOL, FALSE),
),
),
SETTING_INFO (OVS_PATCH,
.valid_parts = NM_META_SETTING_VALID_PARTS (
NM_META_SETTING_VALID_PART_ITEM (CONNECTION, TRUE),
NM_META_SETTING_VALID_PART_ITEM (OVS_PATCH, TRUE),
),
),
SETTING_INFO (OVS_PATCH),
SETTING_INFO (OVS_PORT,
.valid_parts = NM_META_SETTING_VALID_PARTS (
NM_META_SETTING_VALID_PART_ITEM (CONNECTION, TRUE),

View file

@ -286,7 +286,7 @@
#define DESCRIBE_DOC_NM_SETTING_MACSEC_VALIDATION N_("Specifies the validation mode for incoming frames.")
#define DESCRIBE_DOC_NM_SETTING_MACVLAN_MODE N_("The macvlan mode, which specifies the communication mechanism between multiple macvlans on the same lower device.")
#define DESCRIBE_DOC_NM_SETTING_MACVLAN_PARENT N_("If given, specifies the parent interface name or parent connection UUID from which this MAC-VLAN interface should be created. If this property is not specified, the connection must contain an \"802-3-ethernet\" setting with a \"mac-address\" property.")
#define DESCRIBE_DOC_NM_SETTING_MACVLAN_PROMISCUOUS N_("Whether the interface should be put in promiscuous mode.")
#define DESCRIBE_DOC_NM_SETTING_MACVLAN_PROMISCUOUS N_("Whether the parent interface should be put in promiscuous mode (true by default).")
#define DESCRIBE_DOC_NM_SETTING_MACVLAN_TAP N_("Whether the interface should be a MACVTAP.")
#define DESCRIBE_DOC_NM_SETTING_MATCH_DRIVER N_("A list of driver names to match. Each element is a shell wildcard pattern. See NMSettingMatch:interface-name for how special characters '|', '&', '!' and '\\' are used for optional and mandatory matches and inverting the pattern.")
#define DESCRIBE_DOC_NM_SETTING_MATCH_INTERFACE_NAME N_("A list of interface names to match. Each element is a shell wildcard pattern. An element can be prefixed with a pipe symbol (|) or an ampersand (&). The former means that the element is optional and the latter means that it is mandatory. If there are any optional elements, than the match evaluates to true if at least one of the optional element matches (logical OR). If there are any mandatory elements, then they all must match (logical AND). By default, an element is optional. This means that an element \"foo\" behaves the same as \"|foo\". An element can also be inverted with exclamation mark (!) between the pipe symbol (or the ampersand) and before the pattern. Note that \"!foo\" is a shortcut for the mandatory match \"&!foo\". Finally, a backslash can be used at the beginning of the element (after the optional special characters) to escape the start of the pattern. For example, \"&\\!a\" is an mandatory match for literally \"!a\".")

View file

@ -103,8 +103,7 @@ if enable_nmtui
endif
subdir('nmcli')
subdir('nm-dispatcher')
subdir('nm-priv-helper')
subdir('nm-daemon-helper')
subdir('nm-helpers')
subdir('nm-online')
if enable_nmtui
subdir('nmtui')

View file

@ -1140,8 +1140,6 @@ int n_dhcp4_client_probe_transition_decline(NDhcp4ClientProbe *probe, NDhcp4Inco
r = n_dhcp4_c_connection_send_request(&probe->connection, request, ns_now);
if (r)
return r;
else
request = NULL; /* consumed */
n_dhcp4_client_lease_unlink(probe->current_lease);
probe->current_lease = n_dhcp4_client_lease_unref(probe->current_lease);
@ -1346,7 +1344,6 @@ int n_dhcp4_client_probe_release(NDhcp4ClientProbe *probe) {
probe->state = N_DHCP4_CLIENT_PROBE_STATE_INIT;
n_dhcp4_client_lease_unlink(probe->current_lease);
request_out = NULL;
return 0;
}

View file

@ -387,17 +387,6 @@ _nmc_skip_connection_by_user_data(NMConnection *connection)
return FALSE;
}
static gboolean
_nmc_skip_connection_by_type(NMConnection *connection, const char *connection_type)
{
if (!nm_streq0(nm_connection_get_connection_type(connection), connection_type))
return TRUE;
if (!nm_connection_get_setting_ip4_config(connection))
return TRUE;
return FALSE;
}
static void
_nmc_mangle_connection(NMDevice *device,
NMConnection *connection,
@ -618,8 +607,14 @@ try_again:
return any_changes;
}
if (_nmc_skip_connection_by_type(applied_connection, connection_type)) {
_LOGD("config device %s: device has no suitable applied connection. Skip", hwaddr);
if (!nm_streq0(nm_connection_get_connection_type(applied_connection), connection_type)) {
_LOGD("config device %s: skip applied connection due to type mismatch", hwaddr);
return any_changes;
}
if (!nm_connection_get_setting_ip4_config(applied_connection)) {
_LOGD("config device %s: skip applied connection due to missing IPv4 configuration",
hwaddr);
return any_changes;
}
@ -696,13 +691,34 @@ try_again:
return TRUE;
}
static NMConnection *
_new_connection(void)
{
NMConnection *connection;
NMSetting *s_user;
connection = nm_simple_connection_new();
s_user = nm_setting_user_new();
nm_connection_add_setting(connection, s_user);
nm_setting_user_set_data(NM_SETTING_USER(s_user),
"org.freedesktop.NetworkManager.origin",
"nm-cloud-setup",
NULL);
return connection;
}
static gboolean
_config_ethernet(SigTermData *sigterm_data,
const NMCSProviderGetConfigIfaceData *config_data,
NMClient *nmc,
const NMCSProviderGetConfigResult *result)
const NMCSProviderGetConfigResult *result,
gboolean allow_new_connections)
{
gs_unref_object NMDevice *device = NULL;
gs_unref_object NMConnection *connection = NULL;
gs_unref_object NMActiveConnection *active_connection = NULL;
gs_free_error GError *error = NULL;
device = nm_g_object_ref(
_nmc_get_device_by_hwaddr(nmc, NM_TYPE_DEVICE_ETHERNET, config_data->hwaddr));
@ -711,12 +727,52 @@ _config_ethernet(SigTermData *sigterm_data,
return FALSE;
}
if (allow_new_connections && nm_device_get_state(device) == NM_DEVICE_STATE_DISCONNECTED) {
connection = _new_connection();
nm_connection_add_setting(connection,
g_object_new(NM_TYPE_SETTING_CONNECTION,
NM_SETTING_CONNECTION_TYPE,
NM_SETTING_WIRED_SETTING_NAME,
NULL));
nm_connection_add_setting(connection,
g_object_new(NM_TYPE_SETTING_IP4_CONFIG,
NM_SETTING_IP_CONFIG_METHOD,
NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
NULL));
nm_connection_add_setting(connection,
g_object_new(NM_TYPE_SETTING_WIRED,
NM_SETTING_WIRED_MAC_ADDRESS,
config_data->hwaddr,
NULL));
_nmc_mangle_connection(device, connection, result, config_data, NULL, NULL);
active_connection = nmcs_add_and_activate(nmc, NULL, device, connection, &error);
if (!active_connection) {
if (!nm_utils_error_is_cancelled(error)) {
_LOGD("config device %s: failure to activate connection: %s",
nm_device_get_iface(NM_DEVICE(device)),
error->message);
}
return FALSE;
}
_LOGD("config device %s: connection \"%s\" (%s) created",
nm_device_get_iface(NM_DEVICE(device)),
nm_active_connection_get_id(active_connection),
nm_active_connection_get_uuid(active_connection));
return TRUE;
} else {
return _config_existing(sigterm_data,
config_data,
nmc,
result,
NM_SETTING_WIRED_SETTING_NAME,
device);
}
}
static gboolean
@ -738,9 +794,8 @@ _oci_new_vlan_dev(SigTermData *sigterm_data,
const char *wired_mac_addr = NULL;
const NMUtilsNamedValue *map = NULL;
const char *ip4_config_method;
NMSetting *s_user;
connection = nm_simple_connection_new();
connection = _new_connection();
macvlan_name = g_strdup_printf("macvlan%ld", config_data->iface_idx);
connection_id = g_strdup_printf("%s%ld", connection_type, config_data->iface_idx);
@ -811,13 +866,6 @@ _oci_new_vlan_dev(SigTermData *sigterm_data,
hwaddr,
NULL));
s_user = nm_setting_user_new();
nm_connection_add_setting(connection, s_user);
nm_setting_user_set_data(NM_SETTING_USER(s_user),
"org.freedesktop.NetworkManager.origin",
"nm-cloud-setup",
NULL);
_nmc_mangle_connection(NULL, connection, result, config_data, NULL, NULL);
_LOGD("config device %s: creating %s connection for VLAN %d on %s...",
@ -826,7 +874,7 @@ _oci_new_vlan_dev(SigTermData *sigterm_data,
config_data->priv.oci.vlan_tag,
parent_hwaddr);
active_connection = nmcs_add_and_activate(nmc, NULL, connection, &error);
active_connection = nmcs_add_and_activate(nmc, NULL, NULL, connection, &error);
if (active_connection == NULL) {
if (!nm_utils_error_is_cancelled(error)) {
_LOGD("config device %s: failure to activate connection: %s", hwaddr, error->message);
@ -877,6 +925,7 @@ _config_one(SigTermData *sigterm_data,
guint idx)
{
const NMCSProviderGetConfigIfaceData *config_data = result->iface_datas_arr[idx];
gboolean allow_new_connections;
gboolean any_changes;
g_main_context_iteration(NULL, FALSE);
@ -899,7 +948,13 @@ _config_one(SigTermData *sigterm_data,
return FALSE;
}
if (NMCS_IS_PROVIDER_OCI(provider) && config_data->priv.oci.vlan_tag != 0) {
/* Default on on OCI, with an environment variable serving as a chicken bit. */
allow_new_connections =
_nm_utils_ascii_str_to_bool(g_getenv(NMCS_ENV_NM_CLOUD_SETUP_ALLOW_NEW_CONN),
NMCS_IS_PROVIDER_OCI(provider));
if (allow_new_connections && NMCS_IS_PROVIDER_OCI(provider)
&& config_data->priv.oci.vlan_tag != 0) {
if (config_data->priv.oci.parent_hwaddr == NULL) {
_LOGW("config device %s: has vlan id %d but no parent device",
config_data->hwaddr,
@ -924,7 +979,8 @@ _config_one(SigTermData *sigterm_data,
config_data->hwaddr);
} else {
any_changes = _config_ethernet(sigterm_data, config_data, nmc, result);
any_changes =
_config_ethernet(sigterm_data, config_data, nmc, result, allow_new_connections);
}
return any_changes;

View file

@ -637,6 +637,7 @@ _nmcs_add_and_activate_cb(GObject *source, GAsyncResult *result, gpointer user_d
NMActiveConnection *
nmcs_add_and_activate(NMClient *client,
GCancellable *sigterm_cancellable,
NMDevice *device,
NMConnection *connection,
GError **error)
{
@ -648,7 +649,7 @@ nmcs_add_and_activate(NMClient *client,
nm_client_add_and_activate_connection_async(client,
connection,
NULL,
device,
NULL,
sigterm_cancellable,
_nmcs_add_and_activate_cb,

View file

@ -23,6 +23,7 @@
#define NMCS_ENV_NM_CLOUD_SETUP_GCP_HOST "NM_CLOUD_SETUP_GCP_HOST"
#define NMCS_ENV_NM_CLOUD_SETUP_OCI_HOST "NM_CLOUD_SETUP_OCI_HOST"
#define NMCS_ENV_NM_CLOUD_SETUP_MAP_INTERFACES "NM_CLOUD_SETUP_MAP_INTERFACES"
#define NMCS_ENV_NM_CLOUD_SETUP_ALLOW_NEW_CONN "NM_CLOUD_SETUP_ALLOW_NEW_CONN"
/*****************************************************************************/
@ -155,6 +156,7 @@ NMConnection *nmcs_device_get_applied_connection(NMDevice *device,
NMActiveConnection *nmcs_add_and_activate(NMClient *client,
GCancellable *sigterm_cancellable,
NMDevice *device,
NMConnection *connection,
GError **error);

View file

@ -1,11 +0,0 @@
nm-daemon-helper
================
A internal helper application that is spawned by NetworkManager
to perform certain actions.
Currently all it does is doing a reverse DNS lookup, which
cannot be done by NetworkManager because the operation requires
to reconfigure the libc resolver (which is a process-wide operation).
This is not directly useful to the user.

View file

@ -1,15 +0,0 @@
executable(
'nm-daemon-helper',
'nm-daemon-helper.c',
include_directories : [
src_inc,
top_inc,
],
link_with: [
libnm_std_aux,
],
link_args: ldflags_linker_script_binary,
link_depends: linker_script_binary,
install: true,
install_dir: nm_libexecdir,
)

View file

@ -1,5 +1,32 @@
nm-helpers
==========
This directory contains stand-alone helper programs used by various
components.
nm-daemon-helper
----------------
A internal helper application that is spawned by NetworkManager to
perform certain actions which can't be done in the daemon.
Currently it's used to do a reverse DNS lookup after reconfiguring the
libc resolver (which is a process-wide operation), and to read files
on behalf of unprivileged users (which requires a seteuid that affects
all the threads of the process).
This is not directly useful to the user.
nm-libnm-helper
---------------
A internal helper application that is spawned by libnm to perform
certain actions without impacting the calling process.
This is not directly useful to the user.
nm-priv-helper
==============
--------------
This is a D-Bus activatable, exit-on-idle service, which
provides an internal API to NetworkManager daemon.

View file

@ -1,5 +1,45 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# nm-daemon-helper
executable(
'nm-daemon-helper',
'nm-daemon-helper.c',
include_directories : [
src_inc,
top_inc,
],
link_with: [
libnm_std_aux,
],
link_args: ldflags_linker_script_binary,
link_depends: linker_script_binary,
install: true,
install_dir: nm_libexecdir,
)
# nm-libnm-helper
executable(
'nm-libnm-helper',
['nm-libnm-helper.c'],
include_directories : [
src_inc,
top_inc,
],
dependencies: [
glib_dep,
],
link_with: [
libnm_glib_aux,
libnm_std_aux,
],
install: true,
install_dir: nm_libexecdir,
)
# nm-priv-helper
configure_file(
input: 'org.freedesktop.nm_priv_helper.service.in',
output: '@BASENAME@',

View file

@ -137,6 +137,37 @@ cmd_resolve_address(void)
return RETURN_ERROR;
}
static int
cmd_read_file_as_user(void)
{
nm_auto_free char *user = NULL;
nm_auto_free char *filename = NULL;
char error[1024];
user = read_arg();
if (!user)
return RETURN_INVALID_ARGS;
filename = read_arg();
if (!filename)
return RETURN_INVALID_ARGS;
if (more_args())
return RETURN_INVALID_ARGS;
if (!nm_utils_set_effective_user(user, error, sizeof(error))) {
fprintf(stderr, "Failed to set effective user '%s': %s", user, error);
return RETURN_ERROR;
}
if (!nm_utils_read_file_to_stdout(filename, error, sizeof(error))) {
fprintf(stderr, "Failed to read file '%s' as user '%s': %s", filename, user, error);
return RETURN_ERROR;
}
return RETURN_SUCCESS;
}
int
main(int argc, char **argv)
{
@ -150,6 +181,8 @@ main(int argc, char **argv)
return cmd_version();
if (nm_streq(cmd, "resolve-address"))
return cmd_resolve_address();
if (nm_streq(cmd, "read-file-as-user"))
return cmd_read_file_as_user();
return RETURN_INVALID_CMD;
}

View file

@ -0,0 +1,45 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "libnm-std-aux/nm-default-std.h"
#include <stdio.h>
enum {
RETURN_SUCCESS = 0,
RETURN_INVALID_CMD = 1,
RETURN_INVALID_ARGS = 2,
RETURN_ERROR = 3,
};
static int
read_file_as_user(const char *filename, const char *user)
{
char error[1024];
if (!nm_utils_set_effective_user(user, error, sizeof(error))) {
fprintf(stderr, "Failed to set effective user '%s': %s", user, error);
return RETURN_ERROR;
}
if (!nm_utils_read_file_to_stdout(filename, error, sizeof(error))) {
fprintf(stderr, "Failed to read file '%s' as user '%s': %s", filename, user, error);
return RETURN_ERROR;
}
return RETURN_SUCCESS;
}
int
main(int argc, char **argv)
{
if (argc <= 1)
return RETURN_INVALID_CMD;
if (nm_streq(argv[1], "read-file-as-user")) {
if (argc != 4)
return RETURN_INVALID_ARGS;
return read_file_as_user(argv[2], argv[3]);
}
return RETURN_INVALID_CMD;
}

View file

@ -299,33 +299,44 @@ get_word(char **argument, const char separator)
{
char *word;
int nest = 0;
char *last_ch;
char *first_close = NULL;
if (*argument == NULL)
return NULL;
if (**argument == '[') {
nest++;
(*argument)++;
}
word = *argument;
word = last_ch = *argument;
while (**argument != '\0') {
if (nest && **argument == ']') {
**argument = '\0';
(*argument)++;
nest--;
continue;
}
if (nest == 0 && **argument == separator) {
**argument = '\0';
(*argument)++;
break;
}
if (**argument == '[') {
nest++;
} else if (nest && **argument == ']') {
nest--;
if (!first_close && nest == 0)
first_close = *argument;
}
last_ch = *argument;
(*argument)++;
}
/* If the word is surrounded with the nesting symbols [], strip them so we return
* the inner content only.
* If there were nesting symbols but embracing only part of the inner content, don't
* remove them. Example:
* Remove [] in get_word("[fc08::1]:other_token", ":")
* Don't remove [] in get_word("ip6=[fc08::1]:other_token", ":")
*/
if (*word == '[' && *last_ch == ']' && last_ch == first_close) {
word++;
*last_ch = '\0';
}
return *word ? word : NULL;
}
@ -533,7 +544,7 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
NMSettingConnection *s_con;
NMSettingIPConfig *s_ip4 = NULL, *s_ip6 = NULL;
gs_unref_hashtable GHashTable *ibft = NULL;
const char *tmp;
char *tmp;
const char *tmp2;
const char *tmp3;
const char *kind;
@ -578,15 +589,25 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
kind = tmp3;
} else {
/* <client-IP>:[<peer>]:<gateway-IP>:<netmask>:<client_hostname>:<kind> */
client_ip = tmp;
/* note: split here address and prefix to normalize IPs defined as
* [dead::beef]/64. Latter parsing would fail due to the '[]'. */
client_ip = get_word(&tmp, '/');
if (client_ip) {
client_ip_family = get_ip_address_family(client_ip, TRUE);
client_ip_family = get_ip_address_family(client_ip, FALSE);
if (client_ip_family == AF_UNSPEC) {
_LOGW(LOGD_CORE, "Invalid IP address '%s'.", client_ip);
return;
}
}
if (!nm_str_is_empty(tmp)) {
gboolean is_ipv4 = client_ip_family == AF_INET;
client_ip_prefix = _nm_utils_ascii_str_to_int64(tmp, 10, 0, is_ipv4 ? 32 : 128, -1);
}
peer = tmp2;
gateway_ip = get_word(&argument, ':');
netmask = get_word(&argument, ':');
@ -661,11 +682,7 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
NMIPAddress *address = NULL;
NMIPAddr addr;
if (nm_inet_parse_with_prefix_bin(client_ip_family,
client_ip,
NULL,
&addr,
client_ip_prefix == -1 ? &client_ip_prefix : NULL)) {
if (nm_inet_parse_bin(client_ip_family, client_ip, NULL, &addr)) {
if (client_ip_prefix == -1) {
switch (client_ip_family) {
case AF_INET:
@ -906,13 +923,24 @@ reader_parse_controller(Reader *reader,
opts = get_word(&argument, ':');
while (opts && *opts) {
gs_free_error GError *error = NULL;
char *opt;
char *tmp;
const char *opt_name;
char *opt;
const char *opt_value;
nm_auto_unref_ptrarray GPtrArray *opt_values = g_ptr_array_new();
gs_free char *opt_normalized = NULL;
opt_name = get_word(&opts, '=');
opt = get_word(&opts, ',');
opt_name = get_word(&opt, '=');
if (!_nm_setting_bond_validate_option(opt_name, opt, &error)) {
/* Normalize: convert ';' to ',' and remove '[]' from IPv6 addresses */
tmp = opt;
while ((opt_value = get_word(&tmp, ';')))
g_ptr_array_add(opt_values, (gpointer) opt_value);
g_ptr_array_add(opt_values, NULL);
opt_normalized = g_strjoinv(",", (char **) opt_values->pdata);
if (!_nm_setting_bond_validate_option(opt_name, opt_normalized, &error)) {
_LOGW(LOGD_CORE,
"Ignoring invalid bond option: %s%s%s = %s%s%s: %s",
NM_PRINT_FMT_QUOTE_STRING(opt_name),
@ -920,7 +948,7 @@ reader_parse_controller(Reader *reader,
error->message);
continue;
}
nm_setting_bond_add_option(s_bond, opt_name, opt);
nm_setting_bond_add_option(s_bond, opt_name, opt_normalized);
}
mtu = get_word(&argument, ':');

View file

@ -597,7 +597,7 @@ static void
test_if_ip6_manual(void)
{
gs_unref_hashtable GHashTable *connections = NULL;
const char *const *ARGV = NM_MAKE_STRV("ip=[2001:0db8::02]/64::[2001:0db8::01]::"
const char *const *ARGV = NM_MAKE_STRV("ip=[2001:0db8::02]/56::[2001:0db8::01]::"
"hostname0.example.com:eth4::[2001:0db8::53]");
NMConnection *connection;
NMSettingIPConfig *s_ip4;
@ -633,7 +633,7 @@ test_if_ip6_manual(void)
ip_addr = nm_setting_ip_config_get_address(s_ip6, 0);
g_assert(ip_addr);
g_assert_cmpstr(nm_ip_address_get_address(ip_addr), ==, "2001:db8::2");
g_assert_cmpint(nm_ip_address_get_prefix(ip_addr), ==, 64);
g_assert_cmpint(nm_ip_address_get_prefix(ip_addr), ==, 56);
g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip6), ==, "2001:db8::1");
g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, NULL);
}
@ -975,8 +975,8 @@ static void
test_bond(void)
{
gs_unref_hashtable GHashTable *connections = NULL;
const char *const *ARGV = NM_MAKE_STRV("rd.route=192.0.2.53::bong0",
"bond=bong0:eth0,eth1:mode=balance-rr:9000",
const char *const *ARGV = NM_MAKE_STRV("rd.route=192.0.2.53::bond0",
"bond=bond0:eth0,eth1:mode=balance-rr:9000",
"nameserver=203.0.113.53");
NMConnection *connection;
NMSettingConnection *s_con;
@ -990,12 +990,12 @@ test_bond(void)
connections = _parse_cons(ARGV);
g_assert_cmpint(g_hash_table_size(connections), ==, 3);
connection = g_hash_table_lookup(connections, "bong0");
connection = g_hash_table_lookup(connections, "bond0");
nmtst_assert_connection_verifies_without_normalization(connection);
g_assert_cmpstr(nm_connection_get_connection_type(connection),
==,
NM_SETTING_BOND_SETTING_NAME);
g_assert_cmpstr(nm_connection_get_id(connection), ==, "bong0");
g_assert_cmpstr(nm_connection_get_id(connection), ==, "bond0");
controller_uuid = nm_connection_get_uuid(connection);
g_assert(controller_uuid);
@ -1162,6 +1162,118 @@ test_bond_ip(void)
NM_CONNECTION_MULTI_CONNECT_SINGLE);
}
static void
test_bond_ip6_option(void)
{
/* Test that IPv6 addresses within [] are parsed fine in different positions */
gs_unref_hashtable GHashTable *connections = NULL;
const char *const *ARGV =
NM_MAKE_STRV("bond=bond0:eth0,eth1:arp_interval=100,ns_ip6_target=[fc08::1]",
"bond=bond1:eth2,eth3:arp_interval=100,ns_ip6_target=[fc08::1]:9000",
"bond=bond2:eth4,eth5:ns_ip6_target=[fc08::1],arp_interval=100");
NMConnection *connection;
NMSettingBond *s_bond;
connections = _parse_cons(ARGV);
g_assert_cmpint(g_hash_table_size(connections), ==, 9);
connection = g_hash_table_lookup(connections, "bond0");
nmtst_assert_connection_verifies_without_normalization(connection);
s_bond = nm_connection_get_setting_bond(connection);
g_assert(s_bond);
g_assert_cmpint(nm_setting_bond_get_num_options(s_bond), ==, 3);
g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, "ns_ip6_target"), ==, "fc08::1");
connection = g_hash_table_lookup(connections, "bond1");
nmtst_assert_connection_verifies_without_normalization(connection);
s_bond = nm_connection_get_setting_bond(connection);
g_assert(s_bond);
g_assert_cmpint(nm_setting_bond_get_num_options(s_bond), ==, 3);
g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, "ns_ip6_target"), ==, "fc08::1");
connection = g_hash_table_lookup(connections, "bond2");
nmtst_assert_connection_verifies_without_normalization(connection);
s_bond = nm_connection_get_setting_bond(connection);
g_assert(s_bond);
g_assert_cmpint(nm_setting_bond_get_num_options(s_bond), ==, 3);
g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, "ns_ip6_target"), ==, "fc08::1");
}
static void
test_bond_multi_values_option(void)
{
/* Test that semicolon-separated multi-valued options are parsed fine in different positions */
gs_unref_hashtable GHashTable *connections = NULL;
const char *const *ARGV =
NM_MAKE_STRV("bond=bond0:eth0,eth1:arp_interval=100,ns_ip6_target=[fc08::1];[fc08::2]",
"bond=bond1:eth2,eth3:arp_interval=100,ns_ip6_target=[fc08::1];[fc08::2]:9000",
"bond=bond2:eth4,eth5:ns_ip6_target=[fc08::1];[fc08::2],arp_interval=100",
"bond=bond3:eth6,eth7:arp_interval=100,arp_ip_target=10.0.0.1;10.0.0.2",
"bond=bond4:eth8,eth9:arp_interval=100,arp_ip_target=10.0.0.1;10.0.0.2:9000",
"bond=bond5:eth10,eth11:arp_ip_target=10.0.0.1;10.0.0.2,arp_interval=100");
NMConnection *connection;
NMSettingBond *s_bond;
connections = _parse_cons(ARGV);
g_assert_cmpint(g_hash_table_size(connections), ==, 18);
connection = g_hash_table_lookup(connections, "bond0");
nmtst_assert_connection_verifies_without_normalization(connection);
s_bond = nm_connection_get_setting_bond(connection);
g_assert(s_bond);
g_assert_cmpint(nm_setting_bond_get_num_options(s_bond), ==, 3);
g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, "ns_ip6_target"),
==,
"fc08::1,fc08::2");
connection = g_hash_table_lookup(connections, "bond1");
nmtst_assert_connection_verifies_without_normalization(connection);
s_bond = nm_connection_get_setting_bond(connection);
g_assert(s_bond);
g_assert_cmpint(nm_setting_bond_get_num_options(s_bond), ==, 3);
g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, "ns_ip6_target"),
==,
"fc08::1,fc08::2");
connection = g_hash_table_lookup(connections, "bond2");
nmtst_assert_connection_verifies_without_normalization(connection);
s_bond = nm_connection_get_setting_bond(connection);
g_assert(s_bond);
g_assert_cmpint(nm_setting_bond_get_num_options(s_bond), ==, 3);
g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, "ns_ip6_target"),
==,
"fc08::1,fc08::2");
connection = g_hash_table_lookup(connections, "bond3");
nmtst_assert_connection_verifies_without_normalization(connection);
s_bond = nm_connection_get_setting_bond(connection);
g_assert(s_bond);
g_assert_cmpint(nm_setting_bond_get_num_options(s_bond), ==, 3);
g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, "arp_ip_target"),
==,
"10.0.0.1,10.0.0.2");
connection = g_hash_table_lookup(connections, "bond4");
nmtst_assert_connection_verifies_without_normalization(connection);
s_bond = nm_connection_get_setting_bond(connection);
g_assert(s_bond);
g_assert_cmpint(nm_setting_bond_get_num_options(s_bond), ==, 3);
g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, "arp_ip_target"),
==,
"10.0.0.1,10.0.0.2");
connection = g_hash_table_lookup(connections, "bond5");
nmtst_assert_connection_verifies_without_normalization(connection);
s_bond = nm_connection_get_setting_bond(connection);
g_assert(s_bond);
g_assert_cmpint(nm_setting_bond_get_num_options(s_bond), ==, 3);
g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, "arp_ip_target"),
==,
"10.0.0.1,10.0.0.2");
}
static void
test_bond_default(void)
{
@ -2701,6 +2813,8 @@ main(int argc, char **argv)
g_test_add_func("/initrd/cmdline/bootdev", test_bootdev);
g_test_add_func("/initrd/cmdline/bond", test_bond);
g_test_add_func("/initrd/cmdline/bond/ip", test_bond_ip);
g_test_add_func("/initrd/cmdline/bond/ip6-option", test_bond_ip6_option);
g_test_add_func("/initrd/cmdline/bond/multi-values-option", test_bond_multi_values_option);
g_test_add_func("/initrd/cmdline/bond/default", test_bond_default);
g_test_add_func("/initrd/cmdline/team", test_team);
g_test_add_func("/initrd/cmdline/vlan", test_vlan);

View file

@ -629,7 +629,7 @@
alias="type"
nmcli-description="Base type of the connection. For hardware-dependent connections, should contain the setting name of the hardware-type specific setting (ie, &quot;802-3-ethernet&quot; or &quot;802-11-wireless&quot; or &quot;bluetooth&quot;, etc), and for non-hardware dependent connections like VPN or otherwise, should contain the setting name of that setting type (ie, &quot;vpn&quot; or &quot;bridge&quot;, etc)."
format="string"
values="6lowpan, 802-11-olpc-mesh, 802-11-wireless, 802-3-ethernet, adsl, bluetooth, bond, bridge, cdma, dummy, generic, gsm, hsr, infiniband, ip-tunnel, ipvlan, loopback, macsec, macvlan, ovs-bridge, ovs-dpdk, ovs-interface, ovs-patch, ovs-port, pppoe, team, tun, veth, vlan, vpn, vrf, vxlan, wifi-p2p, wimax, wireguard, wpan" />
values="6lowpan, 802-11-olpc-mesh, 802-11-wireless, 802-3-ethernet, adsl, bluetooth, bond, bridge, cdma, dummy, generic, gsm, hsr, infiniband, ip-tunnel, ipvlan, loopback, macsec, macvlan, ovs-bridge, ovs-interface, ovs-port, pppoe, team, tun, veth, vlan, vpn, vrf, vxlan, wifi-p2p, wimax, wireguard, wpan" />
<property name="interface-name"
alias="ifname"
nmcli-description="The name of the network interface this connection is bound to. If not set, then the connection can be attached to any interface of the appropriate type (subject to restrictions imposed by other settings). For software devices this specifies the name of the created device. For connection types where interface names cannot easily be made persistent (e.g. mobile broadband or USB Ethernet), this property should not be used. Setting this property restricts the interfaces a connection can be used with, and if interface names change or are reordered the connection may be applied to the wrong interface."
@ -1698,7 +1698,7 @@
format="choice (NMSettingMacvlanMode)"
values="vepa (1), bridge (2), private (3), passthru (4), source (5)" />
<property name="promiscuous"
nmcli-description="Whether the interface should be put in promiscuous mode."
nmcli-description="Whether the parent interface should be put in promiscuous mode (true by default)."
format="boolean"
values="true/yes/on, false/no/off" />
<property name="tap"

View file

@ -1,87 +1,89 @@
size: 376
size: 382
location: src/tests/client/test-client.py:test_002()/1
cmd: $NMCLI d
lang: C
returncode: 0
stdout: 258 bytes
stdout: 264 bytes
>>>
DEVICE TYPE STATE CONNECTION
eth0 ethernet unavailable --
eth1 ethernet unavailable --
wlan0 wifi unavailable --
wlan1 wifi unavailable --
wlan1 wifi unavailable --
eth0 ethernet disconnected --
eth1 ethernet disconnected --
wlan0 wifi disconnected --
wlan1 wifi disconnected --
wlan1 wifi disconnected --
<<<
size: 391
size: 390
location: src/tests/client/test-client.py:test_002()/2
cmd: $NMCLI d
lang: pl_PL.UTF-8
returncode: 0
stdout: 263 bytes
stdout: 262 bytes
>>>
DEVICE TYPE STATE CONNECTION
eth0 ethernet niedostępne --
eth1 ethernet niedostępne --
wlan0 wifi niedostępne --
wlan1 wifi niedostępne --
wlan1 wifi niedostępne --
eth0 ethernet rozłączono --
eth1 ethernet rozłączono --
wlan0 wifi rozłączono --
wlan1 wifi rozłączono --
wlan1 wifi rozłączono --
<<<
size: 977
size: 983
location: src/tests/client/test-client.py:test_002()/3
cmd: $NMCLI -f all d
lang: C
returncode: 0
stdout: 852 bytes
stdout: 858 bytes
>>>
DEVICE TYPE STATE IP4-CONNECTIVITY IP6-CONNECTIVITY DBUS-PATH CONNECTION CON-UUID CON-PATH
eth0 ethernet unavailable unknown unknown /org/freedesktop/NetworkManager/Devices/1 -- -- --
eth1 ethernet unavailable unknown unknown /org/freedesktop/NetworkManager/Devices/2 -- -- --
wlan0 wifi unavailable unknown unknown /org/freedesktop/NetworkManager/Devices/3 -- -- --
wlan1 wifi unavailable unknown unknown /org/freedesktop/NetworkManager/Devices/4 -- -- --
wlan1 wifi unavailable unknown unknown /org/freedesktop/NetworkManager/Devices/5 -- -- --
eth0 ethernet disconnected unknown unknown /org/freedesktop/NetworkManager/Devices/1 -- -- --
eth1 ethernet disconnected unknown unknown /org/freedesktop/NetworkManager/Devices/2 -- -- --
wlan0 wifi disconnected unknown unknown /org/freedesktop/NetworkManager/Devices/3 -- -- --
wlan1 wifi disconnected unknown unknown /org/freedesktop/NetworkManager/Devices/4 -- -- --
wlan1 wifi disconnected unknown unknown /org/freedesktop/NetworkManager/Devices/5 -- -- --
<<<
size: 992
size: 991
location: src/tests/client/test-client.py:test_002()/4
cmd: $NMCLI -f all d
lang: pl_PL.UTF-8
returncode: 0
stdout: 857 bytes
stdout: 856 bytes
>>>
DEVICE TYPE STATE IP4-CONNECTIVITY IP6-CONNECTIVITY DBUS-PATH CONNECTION CON-UUID CON-PATH
eth0 ethernet niedostępne nieznane nieznane /org/freedesktop/NetworkManager/Devices/1 -- -- --
eth1 ethernet niedostępne nieznane nieznane /org/freedesktop/NetworkManager/Devices/2 -- -- --
wlan0 wifi niedostępne nieznane nieznane /org/freedesktop/NetworkManager/Devices/3 -- -- --
wlan1 wifi niedostępne nieznane nieznane /org/freedesktop/NetworkManager/Devices/4 -- -- --
wlan1 wifi niedostępne nieznane nieznane /org/freedesktop/NetworkManager/Devices/5 -- -- --
eth0 ethernet rozłączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/1 -- -- --
eth1 ethernet rozłączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/2 -- -- --
wlan0 wifi rozłączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/3 -- -- --
wlan1 wifi rozłączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/4 -- -- --
wlan1 wifi rozłączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/5 -- -- --
<<<
size: 738
size: 791
location: src/tests/client/test-client.py:test_002()/5
cmd: $NMCLI
lang: C
returncode: 0
stdout: 621 bytes
stdout: 674 bytes
>>>
eth0: unavailable
eth0: disconnected
"eth0"
1 connection available
ethernet (virtual), 65:2E:D3:9E:0A:0D, hw
eth1: unavailable
eth1: disconnected
"eth1"
1 connection available
ethernet (virtual), 47:D5:6B:65:FD:6A, hw
wlan0: unavailable
wlan0: disconnected
"wlan0"
wifi (virtual), 3D:99:1D:8B:74:4D, hw
wlan1: unavailable
wlan1: disconnected
"wlan1"
wifi (virtual), 99:09:77:FD:FE:1D, hw
wlan1: unavailable
wlan1: disconnected
"wlan1"
wifi (virtual), 5D:30:4A:EC:3F:61, hw
@ -94,30 +96,32 @@ Use "nmcli device show" to get complete information about known devices and
Consult nmcli(1) and nmcli-examples(7) manual pages for complete usage details.
<<<
size: 811
size: 873
location: src/tests/client/test-client.py:test_002()/6
cmd: $NMCLI
lang: pl_PL.UTF-8
returncode: 0
stdout: 684 bytes
stdout: 746 bytes
>>>
eth0: niedostępne
eth0: rozłączono
"eth0"
1 połączenie jest dostępne
ethernet (virtual), 65:2E:D3:9E:0A:0D, sprzęt
eth1: niedostępne
eth1: rozłączono
"eth1"
1 połączenie jest dostępne
ethernet (virtual), 47:D5:6B:65:FD:6A, sprzęt
wlan0: niedostępne
wlan0: rozłączono
"wlan0"
wifi (virtual), 3D:99:1D:8B:74:4D, sprzęt
wlan1: niedostępne
wlan1: rozłączono
"wlan1"
wifi (virtual), 99:09:77:FD:FE:1D, sprzęt
wlan1: niedostępne
wlan1: rozłączono
"wlan1"
wifi (virtual), 5D:30:4A:EC:3F:61, sprzęt

View file

@ -1868,34 +1868,34 @@ GENERAL.ZONE: --
GENERAL.MASTER-PATH: --
<<<
size: 1408
size: 1414
location: src/tests/client/test-client.py:test_003()/43
cmd: $NMCLI -f ALL dev status
lang: C
returncode: 0
stdout: 1272 bytes
stdout: 1278 bytes
>>>
DEVICE TYPE STATE IP4-CONNECTIVITY IP6-CONNECTIVITY DBUS-PATH CONNECTION CON-UUID CON-PATH
eth0 ethernet connected unknown unknown /org/freedesktop/NetworkManager/Devices/1 ethernet UUID-ethernet-REPLACED-REPLACED-REPL /org/freedesktop/NetworkManager/ActiveConnection/1
eth1 ethernet unavailable unknown unknown /org/freedesktop/NetworkManager/Devices/2 -- -- --
wlan0 wifi unavailable unknown unknown /org/freedesktop/NetworkManager/Devices/3 -- -- --
wlan1 wifi unavailable unknown unknown /org/freedesktop/NetworkManager/Devices/4 -- -- --
wlan1 wifi unavailable unknown unknown /org/freedesktop/NetworkManager/Devices/5 -- -- --
eth1 ethernet disconnected unknown unknown /org/freedesktop/NetworkManager/Devices/2 -- -- --
wlan0 wifi disconnected unknown unknown /org/freedesktop/NetworkManager/Devices/3 -- -- --
wlan1 wifi disconnected unknown unknown /org/freedesktop/NetworkManager/Devices/4 -- -- --
wlan1 wifi disconnected unknown unknown /org/freedesktop/NetworkManager/Devices/5 -- -- --
<<<
size: 1424
size: 1422
location: src/tests/client/test-client.py:test_003()/44
cmd: $NMCLI -f ALL dev status
lang: pl_PL.UTF-8
returncode: 0
stdout: 1278 bytes
stdout: 1276 bytes
>>>
DEVICE TYPE STATE IP4-CONNECTIVITY IP6-CONNECTIVITY DBUS-PATH CONNECTION CON-UUID CON-PATH
eth0 ethernet połączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/1 ethernet UUID-ethernet-REPLACED-REPLACED-REPL /org/freedesktop/NetworkManager/ActiveConnection/1
eth1 ethernet niedostępne nieznane nieznane /org/freedesktop/NetworkManager/Devices/2 -- -- --
wlan0 wifi niedostępne nieznane nieznane /org/freedesktop/NetworkManager/Devices/3 -- -- --
wlan1 wifi niedostępne nieznane nieznane /org/freedesktop/NetworkManager/Devices/4 -- -- --
wlan1 wifi niedostępne nieznane nieznane /org/freedesktop/NetworkManager/Devices/5 -- -- --
eth1 ethernet rozłączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/2 -- -- --
wlan0 wifi rozłączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/3 -- -- --
wlan1 wifi rozłączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/4 -- -- --
wlan1 wifi rozłączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/5 -- -- --
<<<
size: 172
@ -2960,34 +2960,34 @@ GENERAL.ZONE: --
GENERAL.MASTER-PATH: --
<<<
size: 1408
size: 1414
location: src/tests/client/test-client.py:test_003()/68
cmd: $NMCLI -f ALL dev status
lang: C
returncode: 0
stdout: 1272 bytes
stdout: 1278 bytes
>>>
DEVICE TYPE STATE IP4-CONNECTIVITY IP6-CONNECTIVITY DBUS-PATH CONNECTION CON-UUID CON-PATH
eth0 ethernet connected unknown unknown /org/freedesktop/NetworkManager/Devices/1 ethernet UUID-ethernet-REPLACED-REPLACED-REPL /org/freedesktop/NetworkManager/ActiveConnection/1
eth1 ethernet connected unknown unknown /org/freedesktop/NetworkManager/Devices/2 ethernet UUID-ethernet-REPLACED-REPLACED-REPL /org/freedesktop/NetworkManager/ActiveConnection/2
wlan0 wifi unavailable unknown unknown /org/freedesktop/NetworkManager/Devices/3 -- -- --
wlan1 wifi unavailable unknown unknown /org/freedesktop/NetworkManager/Devices/4 -- -- --
wlan1 wifi unavailable unknown unknown /org/freedesktop/NetworkManager/Devices/5 -- -- --
wlan0 wifi disconnected unknown unknown /org/freedesktop/NetworkManager/Devices/3 -- -- --
wlan1 wifi disconnected unknown unknown /org/freedesktop/NetworkManager/Devices/4 -- -- --
wlan1 wifi disconnected unknown unknown /org/freedesktop/NetworkManager/Devices/5 -- -- --
<<<
size: 1425
size: 1422
location: src/tests/client/test-client.py:test_003()/69
cmd: $NMCLI -f ALL dev status
lang: pl_PL.UTF-8
returncode: 0
stdout: 1279 bytes
stdout: 1276 bytes
>>>
DEVICE TYPE STATE IP4-CONNECTIVITY IP6-CONNECTIVITY DBUS-PATH CONNECTION CON-UUID CON-PATH
eth0 ethernet połączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/1 ethernet UUID-ethernet-REPLACED-REPLACED-REPL /org/freedesktop/NetworkManager/ActiveConnection/1
eth1 ethernet połączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/2 ethernet UUID-ethernet-REPLACED-REPLACED-REPL /org/freedesktop/NetworkManager/ActiveConnection/2
wlan0 wifi niedostępne nieznane nieznane /org/freedesktop/NetworkManager/Devices/3 -- -- --
wlan1 wifi niedostępne nieznane nieznane /org/freedesktop/NetworkManager/Devices/4 -- -- --
wlan1 wifi niedostępne nieznane nieznane /org/freedesktop/NetworkManager/Devices/5 -- -- --
wlan0 wifi rozłączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/3 -- -- --
wlan1 wifi rozłączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/4 -- -- --
wlan1 wifi rozłączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/5 -- -- --
<<<
size: 172
@ -3298,42 +3298,42 @@ CONNECTIONS.AVAILABLE-CONNECTIONS[2]:UUID-con-xx1-REPLACED-REPLACED-REPLA | con-
CONNECTIONS.AVAILABLE-CONNECTIONS[3]:UUID-ethernet-REPLACED-REPLACED-REPL | ethernet
<<<
size: 1399
size: 1405
location: src/tests/client/test-client.py:test_003()/76
cmd: $NMCLI -f all d
lang: C
returncode: 0
stdout: 1272 bytes
stdout: 1278 bytes
>>>
DEVICE TYPE STATE IP4-CONNECTIVITY IP6-CONNECTIVITY DBUS-PATH CONNECTION CON-UUID CON-PATH
eth1 ethernet connected unknown unknown /org/freedesktop/NetworkManager/Devices/2 ethernet UUID-ethernet-REPLACED-REPLACED-REPL /org/freedesktop/NetworkManager/ActiveConnection/2
eth0 ethernet connected unknown unknown /org/freedesktop/NetworkManager/Devices/1 ethernet UUID-ethernet-REPLACED-REPLACED-REPL /org/freedesktop/NetworkManager/ActiveConnection/1
wlan0 wifi unavailable unknown unknown /org/freedesktop/NetworkManager/Devices/3 -- -- --
wlan1 wifi unavailable unknown unknown /org/freedesktop/NetworkManager/Devices/4 -- -- --
wlan1 wifi unavailable unknown unknown /org/freedesktop/NetworkManager/Devices/5 -- -- --
wlan0 wifi disconnected unknown unknown /org/freedesktop/NetworkManager/Devices/3 -- -- --
wlan1 wifi disconnected unknown unknown /org/freedesktop/NetworkManager/Devices/4 -- -- --
wlan1 wifi disconnected unknown unknown /org/freedesktop/NetworkManager/Devices/5 -- -- --
<<<
size: 1416
size: 1413
location: src/tests/client/test-client.py:test_003()/77
cmd: $NMCLI -f all d
lang: pl_PL.UTF-8
returncode: 0
stdout: 1279 bytes
stdout: 1276 bytes
>>>
DEVICE TYPE STATE IP4-CONNECTIVITY IP6-CONNECTIVITY DBUS-PATH CONNECTION CON-UUID CON-PATH
eth1 ethernet połączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/2 ethernet UUID-ethernet-REPLACED-REPLACED-REPL /org/freedesktop/NetworkManager/ActiveConnection/2
eth0 ethernet połączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/1 ethernet UUID-ethernet-REPLACED-REPLACED-REPL /org/freedesktop/NetworkManager/ActiveConnection/1
wlan0 wifi niedostępne nieznane nieznane /org/freedesktop/NetworkManager/Devices/3 -- -- --
wlan1 wifi niedostępne nieznane nieznane /org/freedesktop/NetworkManager/Devices/4 -- -- --
wlan1 wifi niedostępne nieznane nieznane /org/freedesktop/NetworkManager/Devices/5 -- -- --
wlan0 wifi rozłączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/3 -- -- --
wlan1 wifi rozłączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/4 -- -- --
wlan1 wifi rozłączono nieznane nieznane /org/freedesktop/NetworkManager/Devices/5 -- -- --
<<<
size: 759
size: 762
location: src/tests/client/test-client.py:test_003()/78
cmd: $NMCLI
lang: C
returncode: 0
stdout: 641 bytes
stdout: 644 bytes
>>>
eth1: connected to ethernet
"eth1"
@ -3343,15 +3343,15 @@ eth0: connected to ethernet
"eth0"
ethernet (virtual), C0:61:AE:26:4D:D7, hw
wlan0: unavailable
wlan0: disconnected
"wlan0"
wifi (virtual), DC:39:87:BA:3E:5D, hw
wlan1: unavailable
wlan1: disconnected
"wlan1"
wifi (virtual), AC:4E:5B:7B:57:49, hw
wlan1: unavailable
wlan1: disconnected
"wlan1"
wifi (virtual), 24:C4:52:BC:3D:37, hw
@ -3379,15 +3379,15 @@ eth0: połączono do ethernet
"eth0"
ethernet (virtual), C0:61:AE:26:4D:D7, sprzęt
wlan0: niedostępne
wlan0: rozłączono
"wlan0"
wifi (virtual), DC:39:87:BA:3E:5D, sprzęt
wlan1: niedostępne
wlan1: rozłączono
"wlan1"
wifi (virtual), AC:4E:5B:7B:57:49, sprzęt
wlan1: niedostępne
wlan1: rozłączono
"wlan1"
wifi (virtual), 24:C4:52:BC:3D:37, sprzęt

File diff suppressed because it is too large Load diff

View file

@ -2445,8 +2445,11 @@ class TestNmCloudSetup(unittest.TestCase):
return f
def _mock_devices(self):
# Add a device with an active connection that has IPv4 configured
self.ctx.srv.op_AddObj("WiredDevice", iface="eth0", mac="cc:00:00:00:00:01")
self.ctx.srv.op_AddObj("WiredDevice", iface="eth0", mac=self._mac1)
self.ctx.srv.op_AddObj("WiredDevice", iface="eth1", mac=self._mac2)
def _mock_connection1(self):
# Active connection that has IPv4 configured for device1
self.ctx.srv.addAndActivateConnection(
{
"connection": {"type": "802-3-ethernet", "id": "con-eth0"},
@ -2456,8 +2459,8 @@ class TestNmCloudSetup(unittest.TestCase):
delay=0,
)
def _mock_connection2(self):
# The second connection has no IPv4
self.ctx.srv.op_AddObj("WiredDevice", iface="eth1", mac="cc:00:00:00:00:02")
self.ctx.srv.addAndActivateConnection(
{"connection": {"type": "802-3-ethernet", "id": "con-eth1"}},
"/org/freedesktop/NetworkManager/Devices/2",
@ -2465,13 +2468,18 @@ class TestNmCloudSetup(unittest.TestCase):
delay=0,
)
def _mock_connections(self):
self._mock_devices()
self._mock_connection1()
self._mock_connection2()
def _mock_path(self, path, body):
self.md_conn.request("PUT", path, body=body)
self.md_conn.getresponse().read()
@cloud_setup_test
def test_aliyun(self):
self._mock_devices()
self._mock_connections()
_aliyun_meta = "/2016-01-01/meta-data/"
_aliyun_macs = _aliyun_meta + "network/interfaces/macs/"
@ -2527,12 +2535,15 @@ class TestNmCloudSetup(unittest.TestCase):
)
pexp.expect("provider aliyun detected")
pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
pexp.expect(
"found interfaces: %s, %s"
% (TestNmCloudSetup._mac1.upper(), TestNmCloudSetup._mac2.upper())
)
pexp.expect("get-config: start fetching meta data")
pexp.expect("get-config: success")
pexp.expect("meta data received")
# One of the devices has no IPv4 configuration to be modified
pexp.expect("device has no suitable applied connection. Skip")
pexp.expect("skip applied connection due to missing IPv4 configuration")
# The other one was lacking an address set it up.
pexp.expect("some changes were applied for provider aliyun")
(exitstatus, signalstatus, valgrind_log) = self.ctx.cmd_close_pexpect(pexp)
@ -2555,7 +2566,10 @@ class TestNmCloudSetup(unittest.TestCase):
)
pexp.expect("provider aliyun detected")
pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
pexp.expect(
"found interfaces: %s, %s"
% (TestNmCloudSetup._mac1.upper(), TestNmCloudSetup._mac2.upper())
)
pexp.expect("get-config: starting")
pexp.expect("get-config: success")
pexp.expect("meta data received")
@ -2572,7 +2586,7 @@ class TestNmCloudSetup(unittest.TestCase):
@cloud_setup_test
def test_azure(self):
self._mock_devices()
self._mock_connections()
_azure_meta = "/metadata/instance"
_azure_iface = _azure_meta + "/network/interface/"
@ -2616,7 +2630,10 @@ class TestNmCloudSetup(unittest.TestCase):
)
pexp.expect("provider azure detected")
pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
pexp.expect(
"found interfaces: %s, %s"
% (TestNmCloudSetup._mac1.upper(), TestNmCloudSetup._mac2.upper())
)
pexp.expect("found azure interfaces: 2")
pexp.expect(r"interface\[0]: found a matching device with hwaddr")
pexp.expect(
@ -2628,7 +2645,7 @@ class TestNmCloudSetup(unittest.TestCase):
pexp.expect("get-config: success")
pexp.expect("meta data received")
# One of the devices has no IPv4 configuration to be modified
pexp.expect("device has no suitable applied connection. Skip")
pexp.expect("skip applied connection due to missing IPv4 configuration")
# The other one was lacking an address set it up.
pexp.expect("some changes were applied for provider azure")
(exitstatus, signalstatus, valgrind_log) = self.ctx.cmd_close_pexpect(pexp)
@ -2651,7 +2668,10 @@ class TestNmCloudSetup(unittest.TestCase):
)
pexp.expect("provider azure detected")
pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
pexp.expect(
"found interfaces: %s, %s"
% (TestNmCloudSetup._mac1.upper(), TestNmCloudSetup._mac2.upper())
)
pexp.expect("get-config: starting")
pexp.expect("get-config: success")
pexp.expect("meta data received")
@ -2668,7 +2688,7 @@ class TestNmCloudSetup(unittest.TestCase):
@cloud_setup_test
def test_ec2(self):
self._mock_devices()
self._mock_connections()
_ec2_macs = "/2018-09-24/meta-data/network/interfaces/macs/"
self._mock_path("/latest/meta-data/", "ami-id\n")
@ -2702,12 +2722,15 @@ class TestNmCloudSetup(unittest.TestCase):
)
pexp.expect("provider ec2 detected")
pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
pexp.expect(
"found interfaces: %s, %s"
% (TestNmCloudSetup._mac1.upper(), TestNmCloudSetup._mac2.upper())
)
pexp.expect("get-config: starting")
pexp.expect("get-config: success")
pexp.expect("meta data received")
# One of the devices has no IPv4 configuration to be modified
pexp.expect("device has no suitable applied connection. Skip")
pexp.expect("skip applied connection due to missing IPv4 configuration")
# The other one was lacking an address set it up.
pexp.expect("some changes were applied for provider ec2")
(exitstatus, signalstatus, valgrind_log) = self.ctx.cmd_close_pexpect(pexp)
@ -2730,7 +2753,10 @@ class TestNmCloudSetup(unittest.TestCase):
)
pexp.expect("provider ec2 detected")
pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
pexp.expect(
"found interfaces: %s, %s"
% (TestNmCloudSetup._mac1.upper(), TestNmCloudSetup._mac2.upper())
)
pexp.expect("get-config: starting")
pexp.expect("get-config: success")
pexp.expect("meta data received")
@ -2747,7 +2773,7 @@ class TestNmCloudSetup(unittest.TestCase):
@cloud_setup_test
def test_gcp(self):
self._mock_devices()
self._mock_connections()
gcp_meta = "/computeMetadata/v1/instance/"
gcp_iface = gcp_meta + "network-interfaces/"
@ -2772,13 +2798,16 @@ class TestNmCloudSetup(unittest.TestCase):
)
pexp.expect("provider GCP detected")
pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
pexp.expect(
"found interfaces: %s, %s"
% (TestNmCloudSetup._mac1.upper(), TestNmCloudSetup._mac2.upper())
)
pexp.expect("found GCP interfaces: 2")
pexp.expect(r"GCP interface\[0]: found a requested device with hwaddr")
pexp.expect("get-config: success")
pexp.expect("meta data received")
# One of the devices has no IPv4 configuration to be modified
pexp.expect("device has no suitable applied connection. Skip")
pexp.expect("skip applied connection due to missing IPv4 configuration")
# The other one was lacking an address set it up.
pexp.expect("some changes were applied for provider GCP")
(exitstatus, signalstatus, valgrind_log) = self.ctx.cmd_close_pexpect(pexp)
@ -2801,7 +2830,10 @@ class TestNmCloudSetup(unittest.TestCase):
)
pexp.expect("provider GCP detected")
pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
pexp.expect(
"found interfaces: %s, %s"
% (TestNmCloudSetup._mac1.upper(), TestNmCloudSetup._mac2.upper())
)
pexp.expect("get-config: starting")
pexp.expect("get-config: success")
pexp.expect("meta data received")
@ -2818,7 +2850,7 @@ class TestNmCloudSetup(unittest.TestCase):
@cloud_setup_test
def test_oci(self):
self._mock_devices()
self._mock_connections()
oci_meta = "/opc/v2/"
self._mock_path(oci_meta + "instance", "{}")
@ -2864,12 +2896,15 @@ class TestNmCloudSetup(unittest.TestCase):
)
pexp.expect("provider oci detected")
pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
pexp.expect(
"found interfaces: %s, %s"
% (TestNmCloudSetup._mac1.upper(), TestNmCloudSetup._mac2.upper())
)
pexp.expect("get-config: starting")
pexp.expect("get-config: success")
pexp.expect("meta data received")
# One of the devices has no IPv4 configuration to be modified
pexp.expect("device has no suitable applied connection. Skip")
pexp.expect("skip applied connection due to missing IPv4 configuration")
# The other one was lacking an address set it up.
pexp.expect("some changes were applied for provider oci")
(exitstatus, signalstatus, valgrind_log) = self.ctx.cmd_close_pexpect(pexp)
@ -2892,7 +2927,10 @@ class TestNmCloudSetup(unittest.TestCase):
)
pexp.expect("provider oci detected")
pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
pexp.expect(
"found interfaces: %s, %s"
% (TestNmCloudSetup._mac1.upper(), TestNmCloudSetup._mac2.upper())
)
pexp.expect("get-config: starting")
pexp.expect("get-config: success")
pexp.expect("meta data received")
@ -2909,7 +2947,7 @@ class TestNmCloudSetup(unittest.TestCase):
@cloud_setup_test
def test_oci_vlans(self):
self._mock_devices()
self._mock_connections()
oci_meta = "/opc/v2/"
self._mock_path(oci_meta + "instance", "{}")
@ -2966,17 +3004,23 @@ class TestNmCloudSetup(unittest.TestCase):
)
pexp.expect("provider oci detected")
pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
pexp.expect(
"found interfaces: %s, %s"
% (TestNmCloudSetup._mac1.upper(), TestNmCloudSetup._mac2.upper())
)
pexp.expect("get-config: starting")
pexp.expect("get-config: success")
pexp.expect("meta data received")
# No configuration for the ethernets
pexp.expect('configuring "eth0"')
pexp.expect("device has no suitable applied connection. Skip")
pexp.expect("skip applied connection due to missing IPv4 configuration")
# Setting up the VLAN
pexp.expect("creating macvlan2 connection for VLAN 700 on CC:00:00:00:00:01...")
pexp.expect(
"creating macvlan2 connection for VLAN 700 on %s..."
% (TestNmCloudSetup._mac1.upper())
)
pexp.expect("creating vlan connection for VLAN 700 on C0:00:00:00:00:10...")
pexp.expect("some changes were applied for provider oci")
@ -3008,12 +3052,15 @@ class TestNmCloudSetup(unittest.TestCase):
# Just the same ol' thing, just no changes this time
pexp.expect("provider oci detected")
pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
pexp.expect(
"found interfaces: %s, %s"
% (TestNmCloudSetup._mac1.upper(), TestNmCloudSetup._mac2.upper())
)
pexp.expect("get-config: starting")
pexp.expect("get-config: success")
pexp.expect("meta data received")
pexp.expect('configuring "eth0"')
pexp.expect("device has no suitable applied connection. Skip")
pexp.expect("skip applied connection due to missing IPv4 configuration")
pexp.expect("no changes were applied for provider oci")
(exitstatus, signalstatus, valgrind_log) = self.ctx.cmd_close_pexpect(pexp)
@ -3024,6 +3071,89 @@ class TestNmCloudSetup(unittest.TestCase):
)
self.assertEqual(exitstatus, 0, "Unexpectedly returned a non-zero status")
@cloud_setup_test
def test_oci_vm_vnic(self):
# One device unconnected, and one with a connection that needs changes
self._mock_devices()
self._mock_connection2()
oci_meta = "/opc/v2/"
self._mock_path(oci_meta + "instance", "{}")
self._mock_path(
oci_meta + "vnics",
"""
[
{
"macAddr": "%s",
"privateIp": "%s",
"subnetCidrBlock": "172.31.16.0/20",
"virtualRouterIp": "172.31.16.1",
"vlanTag": 1337,
"vnicId": "ocid1.vnic.oc1.cz-adamov1.foobarbaz"
},
{
"macAddr": "%s",
"privateIp": "%s",
"subnetCidrBlock": "172.31.166.0/20",
"virtualRouterIp": "172.31.166.1",
"vlanTag": 8086,
"vnicId": "ocid1.vnic.oc1.uk-hogwarts.expelliarmus"
}
]
"""
% (
TestNmCloudSetup._mac1,
TestNmCloudSetup._ip1,
TestNmCloudSetup._mac2,
TestNmCloudSetup._ip2,
),
)
pexp = self.ctx.cmd_call_pexpect(
ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH,
[],
{
"NM_CLOUD_SETUP_OCI_HOST": self.md_url,
"NM_CLOUD_SETUP_LOG": "trace",
"NM_CLOUD_SETUP_OCI": "yes",
},
)
pexp.expect("provider oci detected")
pexp.expect(
"found interfaces: %s, %s"
% (TestNmCloudSetup._mac1.upper(), TestNmCloudSetup._mac2.upper())
)
pexp.expect("get-config: starting")
pexp.expect("get-config: success")
pexp.expect("meta data received")
# First device lacks a connection: a new one will be created
pexp.expect('config device eth0: connection "connection-2"')
# Second device is skipped because it's activated without IPv4
pexp.expect(
"config device CC:00:00:00:00:02: skip applied connection due to missing IPv4 configuration"
)
# Finished!
pexp.expect("some changes were applied for provider oci")
(exitstatus, signalstatus, valgrind_log) = self.ctx.cmd_close_pexpect(pexp)
Util.valgrind_check_log(valgrind_log, "test_oci_vm_vnic")
self.assertIsNone(
signalstatus,
"Unexpectedly got " + Util.signal_no_to_str(signalstatus or 0),
)
self.assertEqual(exitstatus, 0, "Unexpectedly returned a non-zero status")
# TODO: Actually check the contents of the connection
# Probably needs changes to the mock service API
conn_macvlan = self.ctx.srv.findConnections(con_id="connection-3")
assert conn_macvlan is not None
conn_vlan = self.ctx.srv.findConnections(con_id="connection-4")
assert conn_vlan is not None
###############################################################################

View file

@ -6,26 +6,9 @@
from __future__ import print_function, unicode_literals
import xml.etree.ElementTree as ET
import argparse
import os
import gi
import re
gi.require_version("GIRepository", "2.0")
from gi.repository import GIRepository
try:
libs = os.environ["LD_LIBRARY_PATH"].split(":")
libs.reverse()
for lib in libs:
GIRepository.Repository.prepend_library_path(lib)
except AttributeError:
# An old GI version, that has no prepend_library_path
# It's alright, it probably interprets LD_LIBRARY_PATH
# correctly.
pass
except KeyError:
pass
gi.require_version("NM", "1.0")
from gi.repository import NM, GObject
@ -354,13 +337,6 @@ def main(gir_path_str, output_path_str, output_target):
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"-l",
"--lib-path",
metavar="PATH",
action="append",
help="path to scan for shared libraries",
)
parser.add_argument(
"-g",
"--gir",
@ -384,8 +360,4 @@ if __name__ == "__main__":
args = parser.parse_args()
if args.lib_path:
for lib in args.lib_path:
GIRepository.Repository.prepend_library_path(lib)
main(args.gir, args.output, args.target)

View file

@ -868,7 +868,7 @@ class Device(ExportedObj):
self.activation_state_change_delay_ms = 50
self.hwaddr = hwaddr is None if "" else hwaddr
self.prp_state = NM.DeviceState.UNAVAILABLE
self.prp_state = NM.DeviceState.DISCONNECTED
if devtype == NM.DeviceType.MODEM:
udi = "/org/freedesktop/ModemManager1/Modem/0"