diff -Nru netplan.io-0.102/debian/changelog netplan.io-0.101/debian/changelog --- netplan.io-0.102/debian/changelog 2021-03-26 12:35:37.000000000 +0000 +++ netplan.io-0.101/debian/changelog 2021-01-08 14:17:07.000000000 +0000 @@ -1,124 +1,3 @@ -netplan.io (0.102-0ubuntu1~20.04.1) focal; urgency=medium - - * Backport netplan.io 0.102-0ubuntu1 to 20.04 (LP: #1919453) - - Includes NetworkManager YAML backend API - - Includes 'congestion-window', 'advertised-receive-window' & 'ttl' keys - - Includes 'netplan set' improvements - * Keep riscv64 build-time tests disabled - * Add d/p/0002-tests-tunnels-improve-flaky-wireguard-test-with-wait.patch - - -- Lukas Märdian Fri, 26 Mar 2021 13:35:37 +0100 - -netplan.io (0.102-0ubuntu1) hirsute; urgency=medium - - * New upstream release: 0.102 (LP: #1919453) - - New API for NetworkManager YAML backend - - Added congestion-window & advertised-receive-window options for routes - - Added ttl option for tunnels (LP: #1846783) - - Improved netplan set CLI to override existing files - - Moved upstream repository to https://github.com/canonical/netplan/ - - Documentation improvements - - Improved Github Actions CI and CodeQL integration - - Minor cleanup/typos/test improvements - Bug fixes: - - systemd v247 compatibility (for changing MAC address) - - OVS 2.15 compatibility (wording changes) - - Allow networkmanager: backend options for modem devices - - Prevent duplicate ARPIPTargets in NetDev files (LP: #1915837) - * Drop all distro patches, which have been integrated upstream - * Update symbols file - * Enable pristine-tar in gbp - * Allow running more tests in a container - - -- Lukas Märdian Wed, 24 Mar 2021 08:54:23 +0100 - -netplan.io (0.101-4ubuntu3) hirsute; urgency=medium - - * d/changelog: Restore history, which was lost during previous merge - * d/watch, d/copyright: Update Github URL - * d/tests/control: - - Mark ovs & cloud-init tests non-flaky - - Mark tests with the "breaks-testbed" restriction - * Fix DNS issues during tests on ppc64el (LP: #1916888): - - d/p/0007-tests-keep-management-network-up-at-all-times-during.patch - - d/p/0008-tests-integration-cleanup-OVS-WPA-files.patch - - -- Lukas Märdian Fri, 26 Feb 2021 17:47:08 +0100 - -netplan.io (0.101-4ubuntu2) hirsute; urgency=medium - - * No change rebuild with fixed ownership. - - -- Dimitri John Ledkov Tue, 16 Feb 2021 15:18:25 +0000 - -netplan.io (0.101-4ubuntu1) hirsute; urgency=medium - - * Merge with Debian. Remaining changes: - - Keep running dh_auto_test - - Keep openvswitch dependency for all arches - - 0003-tests-adopt-to-wording-changes-as-of-OVS-2.15.patch - - 0004-tests-tunnels-improve-test-reliability.patch - - 0005-tests-dbus-improve-test-stability-of-timeouts.patch - - 0006-tests-integration-adopt-for-racy-systemd-MAC-assignm.patch - - -- Lukas Märdian Mon, 08 Feb 2021 11:54:07 +0100 - -netplan.io (0.101-4) unstable; urgency=medium - - * Build-depend on ovs on amd64 only due to a bug in its postinst. - See #979366 for details. - * Drop the custom build profile, nocheck is enough. - - -- Andrej Shadura Tue, 05 Jan 2021 22:01:50 +0100 - -netplan.io (0.101-3) unstable; urgency=medium - - * Mark the package linux-any. - * Skip openvswitch-switch dependency on m68k and ppc64. - - -- Andrej Shadura Tue, 05 Jan 2021 19:28:50 +0100 - -netplan.io (0.101-2) unstable; urgency=medium - - * Reindent debian/control. - * Add build profiles. - * Add cloud tests but mark them as flaky and skip-not-installable - for now. - - -- Andrej Shadura Tue, 05 Jan 2021 17:40:42 +0100 - -netplan.io (0.101-1) unstable; urgency=medium - - [ Andrej Shadura ] - * New upstream release. - * Merge changes from Ubuntu. - * Let tests fail. - * Remove the hack to fix build with GCC 10 (actually closes: #957603). - - [ Lukas Märdian ] - * d/control: fix lintian warning about trailing whitespace - * d/p/0001-Fix-changing-of-macaddress-with-systemd-v247-178.patch: - Fix MAC address changes with systemd v247 by using a new approach inside - systemd's .network file. It also works with older version of systemd. - * Add d/p/0002-parse-fix-networkmanager-backend-options-for-modem-c.patch: - Allows parsing of networkmanager: backend handlers for modem devices - * Update symbols file - - [ Michael Biebl ] - * Stop using deprecated systemd-resolve tool (Closes: #979266). - - -- Andrej Shadura Mon, 04 Jan 2021 20:34:58 +0100 - -netplan.io (0.101-0ubuntu5) hirsute; urgency=medium - - * Add d/p/0004-tests-tunnels-improve-test-reliability.patch - and d/p/0005-tests-dbus-improve-test-stability-of-timeouts.patch - for improved compile-time test stability - * Add d/p/0006-tests-integration-adopt-for-racy-systemd-MAC-assignm.patch - for compatibility with new systemd - - -- Lukas Märdian Thu, 04 Feb 2021 11:35:28 +0100 - netplan.io (0.101-0ubuntu3~20.04.2) focal; urgency=medium * Backport netplan.io 0.101-0ubuntu3 to 20.04 (LP: #1908509) @@ -132,15 +11,6 @@ -- Lukas Märdian Fri, 08 Jan 2021 15:17:07 +0100 -netplan.io (0.101-0ubuntu4) hirsute; urgency=medium - - * Add d/p/0003-tests-adopt-to-wording-changes-as-of-OVS-2.15.patch: - Adopt autopkgtests to wording changes of OVS 2.15 (slave -> member) - * d/control: fix lintian warning about trailing whitespace - * d/control: fix lintian warning about deprecated d/compat file - - -- Lukas Märdian Wed, 06 Jan 2021 09:56:57 +0100 - netplan.io (0.101-0ubuntu3) hirsute; urgency=medium * Add d/p/0002-parse-fix-networkmanager-backend-options-for-modem-c.patch: @@ -268,46 +138,6 @@ -- Lukas Märdian Thu, 03 Sep 2020 15:51:29 +0200 -netplan.io (0.99-2) experimental; urgency=medium - - * Split libnetplan off into separate packages. - * Force -fcommon to enable builds with GCC 10 to work around #957603. - - -- Andrej Shadura Mon, 27 Apr 2020 17:17:54 +0200 - -netplan.io (0.99-1) unstable; urgency=medium - - [ Andrej Shadura ] - * New upstream release. - * Drop old upstream patches. - * Update the co-maintainer list. - * Bump Standards-Version to 4.5.0. - * Update copyright years. - - [ Lukas Märdian ] - * debian:tests:control: add autopkgtest dependencies. - - -- Andrej Shadura Mon, 27 Apr 2020 11:01:26 +0200 - -netplan.io (0.99-0ubuntu6) groovy; urgency=medium - - * d/p/0005-Fix-GCC-10-fno-common-linker-errors-LP-1875412-155.patch - - Fix FTBFS if compiled using GCC-10 (LP: #1875412) - - Using upstream commit 50ac1a1 - * Refresh patches using 'gbp pq' - - -- Lukas Märdian Tue, 28 Jul 2020 14:12:28 +0200 - -netplan.io (0.99-0ubuntu5) groovy; urgency=medium - - * d/p/0001-Fix-autopkgtest-on-arm64-with-NM-1.24-146.patch - - Fix autopkgtest failure on arm64 in combination with new NM-1.24 - * d/p/0001-Call-daemon-reload-after-we-touched-systemd-unit-fil.patch - - Re-calculate systemd dependencies, after touching .service - units (LP: #1874494) - - -- Lukas Märdian Thu, 18 Jun 2020 11:36:59 +0200 - netplan.io (0.99-0ubuntu3~20.04.2) focal; urgency=medium * d/p/0002-Fix-process_link_changes-handling-up-interfaces.patch: @@ -315,13 +145,6 @@ -- Heitor Alves de Siqueira Thu, 28 May 2020 21:09:51 +0000 -netplan.io (0.99-0ubuntu4) groovy; urgency=medium - - * d/p/0002-Fix-process_link_changes-handling-up-interfaces.patch: - - Fix process_link_changes handling 'up' interfaces (LP: #1875411) - - -- Heitor Alves de Siqueira Thu, 28 May 2020 21:09:51 +0000 - netplan.io (0.99-0ubuntu3~20.04.1) focal; urgency=medium * Backport upstream bug fix for 0.99 to 20.04. (LP: #1871825) @@ -376,28 +199,6 @@ -- Łukasz 'sil2100' Zemczak Thu, 16 Apr 2020 09:13:50 +0200 -netplan.io (0.98-2) unstable; urgency=medium - - * Cherry-pick upstream commits. - * Use debhelper-compat instead of debian/compat. - * Bump debhelper from old 11 to 12. - * Bump Standards-Version to 4.4.1 (no changes). - - -- Andrej Shadura Fri, 01 Nov 2019 15:21:21 +0100 - -netplan.io (0.98-1) unstable; urgency=medium - - [ Andrej Shadura ] - * New upstream release: 0.98 (LP: #1840832). - * Run all autopkgtests with Restriction: isolation-machine (Closes: - #919426). - - [ Mathieu Trudel-Lapierre ] - * debian/control: Add Build-Depends on libsystemd-dev for the D-Bus feature, - and on dbus-x11 for dbus-launch used in tests. - - -- Andrej Shadura Thu, 26 Sep 2019 14:35:32 +0200 - netplan.io (0.98-0ubuntu4) focal; urgency=medium [ Lukas Märdian ] diff -Nru netplan.io-0.102/debian/compat netplan.io-0.101/debian/compat --- netplan.io-0.102/debian/compat 1970-01-01 00:00:00.000000000 +0000 +++ netplan.io-0.101/debian/compat 2021-01-08 09:01:54.000000000 +0000 @@ -0,0 +1 @@ +11 diff -Nru netplan.io-0.102/debian/control netplan.io-0.101/debian/control --- netplan.io-0.102/debian/control 2021-03-26 12:34:42.000000000 +0000 +++ netplan.io-0.101/debian/control 2021-01-08 09:52:18.000000000 +0000 @@ -3,13 +3,11 @@ XSBC-Original-Maintainer: Debian netplan Maintainers Uploaders: Andrej Shadura , - Mathieu Trudel-Lapierre , - Łukasz 'sil2100' Zemczak + Mathieu Trudel-Lapierre Section: net Priority: optional -Standards-Version: 4.5.0 -Build-Depends: - debhelper-compat (= 12), +Standards-Version: 4.1.3 +Build-Depends: debhelper (>= 11), pkg-config, bash-completion, libyaml-dev, @@ -22,20 +20,19 @@ libsystemd-dev, systemd, dbus-x11, - pyflakes3 , - pycodestyle | pep8 , - python3-nose , + pyflakes3, + pycodestyle | pep8, + python3-nose, pandoc, - openvswitch-switch [!riscv64] , + openvswitch-switch [!riscv64], Vcs-Git: https://salsa.debian.org/debian/netplan.io.git Vcs-Browser: https://salsa.debian.org/debian/netplan.io Homepage: https://netplan.io/ Package: netplan.io -Architecture: linux-any +Architecture: any Multi-Arch: foreign -Depends: - ${shlibs:Depends}, +Depends: ${shlibs:Depends}, ${misc:Depends}, iproute2, libnetplan0 (>= ${binary:Version}), @@ -43,8 +40,7 @@ python3-yaml, python3-netifaces, systemd (>= 239~), -Suggests: - network-manager | wpasupplicant, +Suggests: network-manager | wpasupplicant, openvswitch-switch [!riscv64], Conflicts: netplan Breaks: nplan (<< 0.34~), network-manager (<< 1.2.2-1) @@ -60,10 +56,9 @@ Currently supported backends are networkd and NetworkManager. Package: libnetplan0 -Architecture: linux-any +Architecture: any Multi-Arch: same -Depends: - ${shlibs:Depends}, +Depends: ${shlibs:Depends}, ${misc:Depends}, Description: YAML network configuration abstraction runtime library netplan reads YAML network configuration files which are written @@ -77,7 +72,7 @@ This package contains the necessary runtime library files. Package: libnetplan-dev -Architecture: linux-any +Architecture: any Multi-Arch: same Depends: ${misc:Depends}, libnetplan0 (= ${binary:Version}), @@ -92,3 +87,4 @@ . This package contains development files for developers wanting to use libnetplan in their applications. + diff -Nru netplan.io-0.102/debian/copyright netplan.io-0.101/debian/copyright --- netplan.io-0.102/debian/copyright 2021-03-25 10:09:36.000000000 +0000 +++ netplan.io-0.101/debian/copyright 2020-12-11 08:20:10.000000000 +0000 @@ -1,16 +1,16 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: netplan.io Upstream-Contact: Łukasz 'sil2100' Zemczak -Source: https://github.com/canonical/netplan +Source: https://github.com/CanonicalLtd/netplan Files: * -Copyright: 2016—2020 Canonical Ltd. +Copyright: 2016—2018 Canonical Ltd. License: GPL-3 Files: debian/* Copyright: - 2016—2020 Canonical Ltd. - 2018—2020 Andrej Shadura + 2016—2018 Canonical Ltd. + 2018 Andrej Shadura License: GPL-3 License: GPL-3 diff -Nru netplan.io-0.102/debian/gbp.conf netplan.io-0.101/debian/gbp.conf --- netplan.io-0.102/debian/gbp.conf 2021-03-25 10:09:36.000000000 +0000 +++ netplan.io-0.101/debian/gbp.conf 2021-01-08 14:05:36.000000000 +0000 @@ -1,7 +1,4 @@ [DEFAULT] -debian-branch=ubuntu/master +debian-branch=debian/master upstream-branch=upstream/latest upstream-vcs-tag=%(version)s - -[import-orig] -pristine-tar = True diff -Nru netplan.io-0.102/debian/libnetplan0.symbols netplan.io-0.101/debian/libnetplan0.symbols --- netplan.io-0.102/debian/libnetplan0.symbols 2021-03-25 10:09:36.000000000 +0000 +++ netplan.io-0.101/debian/libnetplan0.symbols 2020-12-17 11:33:20.000000000 +0000 @@ -1,9 +1,7 @@ libnetplan.so.0.0 libnetplan0 #MINVER# NETPLAN_OPTIONAL_ADDRESS_TYPES@Base 0.99 NETPLAN_WIFI_WOWLAN_TYPES@Base 0.99 - _write_netplan_conf@Base 0.102 address_option_handlers@Base 0.100 - cur_filename@Base 0.102 current_file@Base 0.99 find_yaml_glob@Base 0.101 g_string_free_to_file@Base 0.99 @@ -16,19 +14,11 @@ netdefs@Base 0.99 netdefs_ordered@Base 0.99 netplan_clear_netdefs@Base 0.101 - netplan_delete_connection@Base 0.102 netplan_finish_parse@Base 0.99 - netplan_generate@Base 0.102 - netplan_get_filename_by_id@Base 0.102 netplan_get_global_backend@Base 0.99 - netplan_get_id_from_nm_filename@Base 0.102 - netplan_netdef_new@Base 0.102 - netplan_parse_keyfile@Base 0.102 netplan_parse_yaml@Base 0.99 ovs_settings_global@Base 0.100 parser_error@Base 0.99 - process_input_file@Base 0.102 - process_yaml_hierarchy@Base 0.102 safe_mkdir_p_dir@Base 0.99 systemd_escape@Base 0.100 tunnel_mode_to_string@Base 0.99 @@ -41,5 +31,4 @@ wifi_get_freq24@Base 0.99 wifi_get_freq5@Base 0.99 wireguard_peer_handlers@Base 0.100 - write_netplan_conf@Base 0.102 yaml_error@Base 0.99 diff -Nru netplan.io-0.102/debian/patches/0000-Disable-some-tests-due-to-ovs-vsctl-missing-on-riscv.patch netplan.io-0.101/debian/patches/0000-Disable-some-tests-due-to-ovs-vsctl-missing-on-riscv.patch --- netplan.io-0.102/debian/patches/0000-Disable-some-tests-due-to-ovs-vsctl-missing-on-riscv.patch 2021-03-26 12:34:42.000000000 +0000 +++ netplan.io-0.101/debian/patches/0000-Disable-some-tests-due-to-ovs-vsctl-missing-on-riscv.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,202 +0,0 @@ -From 6bf7ed171aaf70e3b8f369ccbd12b4059d33bafa Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Lukas=20M=C3=A4rdian?= -Date: Fri, 19 Mar 2021 17:33:59 +0100 -Subject: Disable some tests, due to ovs-vsctl missing on riscv64 - ---- - tests/generator/test_ovs.py | 25 +++++++++++++++++++++++++ - 1 file changed, 25 insertions(+) - -diff --git a/tests/generator/test_ovs.py b/tests/generator/test_ovs.py -index e7084a9..15d4b59 100644 ---- a/tests/generator/test_ovs.py -+++ b/tests/generator/test_ovs.py -@@ -17,6 +17,9 @@ - # You should have received a copy of the GNU General Public License - # along with this program. If not, see . - -+import os -+import unittest -+ - from .base import TestBase, ND_EMPTY, ND_WITHIP, ND_DHCP4, ND_DHCP6, \ - OVS_PHYSICAL, OVS_VIRTUAL, \ - OVS_BR_EMPTY, OVS_BR_DEFAULT, \ -@@ -26,6 +29,7 @@ from .base import TestBase, ND_EMPTY, ND_WITHIP, ND_DHCP4, ND_DHCP6, \ - class TestOpenVSwitch(TestBase): - '''OVS output''' - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_interface_external_ids_other_config(self): - self.generate('''network: - version: 2 -@@ -118,6 +122,7 @@ ExecStart=/usr/bin/ovs-vsctl set open_vswitch . external-ids:netplan/other-confi - # Confirm that the networkd config is still sane - self.assert_networkd({'eth0.network': ND_DHCP4 % 'eth0'}) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_global_set_protocols(self): - self.generate('''network: - version: 2 -@@ -161,6 +166,7 @@ ExecStart=/usr/bin/ovs-vsctl set Bridge ovs0 external-ids:netplan/protocols=Open - self.assert_ovs({'cleanup.service': OVS_CLEANUP % {'iface': 'cleanup'}}) - self.assert_networkd({'eth0.network': ND_DHCP4 % 'eth0'}) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_bond_setup(self): - self.generate('''network: - version: 2 -@@ -200,6 +206,7 @@ ExecStart=/usr/bin/ovs-vsctl set Port bond0 external-ids:netplan/external-ids/if - 'br0.network': ND_WITHIP % ('br0', '192.170.1.1/24'), - 'bond0.network': ND_EMPTY % ('bond0', 'no')}) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_bond_no_bridge(self): - err = self.generate('''network: - version: 2 -@@ -213,6 +220,7 @@ ExecStart=/usr/bin/ovs-vsctl set Port bond0 external-ids:netplan/external-ids/if - ''', expect_fail=True) - self.assertIn("Bond bond0 needs to be a slave of an OpenVSwitch bridge", err) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_bond_not_enough_interfaces(self): - err = self.generate('''network: - version: 2 -@@ -230,6 +238,7 @@ ExecStart=/usr/bin/ovs-vsctl set Port bond0 external-ids:netplan/external-ids/if - ''', expect_fail=True) - self.assertIn("Bond bond0 needs to have at least 2 slave interfaces", err) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_bond_lacp(self): - self.generate('''network: - version: 2 -@@ -295,6 +304,7 @@ ExecStart=/usr/bin/ovs-vsctl set Port bond0 external-ids:netplan/lacp=active - ''', expect_fail=True) - self.assertIn("Key 'lacp' is only valid for interface type 'openvswitch bond'", err) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_bond_mode_implicit_params(self): - self.generate('''network: - version: 2 -@@ -333,6 +343,7 @@ ExecStart=/usr/bin/ovs-vsctl set Port bond0 external-ids:netplan/bond_mode=balan - 'br0.network': ND_WITHIP % ('br0', '192.170.1.1/24'), - 'bond0.network': ND_EMPTY % ('bond0', 'no')}) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_bond_mode_explicit_params(self): - self.generate('''network: - version: 2 -@@ -372,6 +383,7 @@ ExecStart=/usr/bin/ovs-vsctl set Port bond0 external-ids:netplan/bond_mode=activ - 'br0.network': ND_WITHIP % ('br0', '192.170.1.1/24'), - 'bond0.network': ND_EMPTY % ('bond0', 'no')}) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_bond_mode_ovs_invalid(self): - err = self.generate('''network: - version: 2 -@@ -392,6 +404,7 @@ ExecStart=/usr/bin/ovs-vsctl set Port bond0 external-ids:netplan/bond_mode=activ - ''', expect_fail=True) - self.assertIn("bond0: bond mode 'balance-rr' not supported by openvswitch", err) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_bridge_setup(self): - self.generate('''network: - version: 2 -@@ -418,6 +431,7 @@ ExecStart=/usr/bin/ovs-vsctl --may-exist add-port br0 eth2 - 'eth2.network': '[Match]\nName=eth2\n\n[Network]\nLinkLocalAddressing=no\nBridge=br0\n', - 'br0.network': ND_WITHIP % ('br0', '192.170.1.1/24')}) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_bridge_external_ids_other_config(self): - self.generate('''network: - version: 2 -@@ -443,6 +457,7 @@ ExecStart=/usr/bin/ovs-vsctl set Bridge br0 external-ids:netplan/other-config/di - # Confirm that the bridge has been only configured for OVS - self.assert_networkd({'br0.network': ND_EMPTY % ('br0', 'ipv6')}) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_bridge_non_default_parameters(self): - self.generate('''network: - version: 2 -@@ -509,6 +524,7 @@ ExecStart=/usr/bin/ovs-vsctl set Bridge br0 external-ids:netplan/rstp_enable=tru - ''', expect_fail=True) - self.assertIn("Key is only valid for interface type 'openvswitch bridge'", err) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_bridge_set_protocols(self): - self.generate('''network: - version: 2 -@@ -550,6 +566,7 @@ ExecStart=/usr/bin/ovs-vsctl set Bridge br0 external-ids:netplan/protocols=OpenF - ''', expect_fail=True) - self.assertIn("Key 'protocols' is only valid for interface type 'openvswitch bridge'", err) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_bridge_controller(self): - self.generate('''network: - version: 2 -@@ -687,6 +704,7 @@ ExecStart=/usr/bin/ovs-vsctl set open_vswitch . external-ids:netplan/global/set- - # Confirm that the networkd config is still sane - self.assert_networkd({}) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_missing_ssl(self): - err = self.generate('''network: - version: 2 -@@ -762,6 +780,7 @@ ExecStart=/usr/bin/ovs-vsctl set open_vswitch . external-ids:netplan/global/set- - self.assert_ovs({}) - self.assert_networkd({}) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_bridge_auto_ovs_backend(self): - self.generate('''network: - version: 2 -@@ -809,6 +828,7 @@ LinkLocalAddressing=no - Bond=bond0 - '''}) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_bond_auto_ovs_backend(self): - self.generate('''network: - version: 2 -@@ -870,6 +890,7 @@ ExecStart=/usr/bin/ovs-vsctl set Interface patchy external-ids:netplan=true - 'patchy.network': ND_EMPTY % ('patchy', 'no'), - 'eth0.network': '[Match]\nName=eth0\n\n[Network]\nLinkLocalAddressing=no\nBond=bond0\n'}) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_patch_ports(self): - self.generate('''network: - version: 2 -@@ -918,6 +939,7 @@ ExecStart=/usr/bin/ovs-vsctl set Port patch1-0 external-ids:netplan=true - 'patch0-1.network': ND_EMPTY % ('patch0-1', 'no'), - 'patch1-0.network': ND_EMPTY % ('patch1-0', 'no')}) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_fake_vlan_bridge_setup(self): - self.generate('''network: - version: 2 -@@ -950,6 +972,7 @@ ExecStart=/usr/bin/ovs-vsctl set Interface br0.100 external-ids:netplan=true - self.assert_networkd({'br0.network': ND_WITHIP % ('br0', '192.168.1.1/24'), - 'br0.100.network': ND_EMPTY % ('br0.100', 'ipv6')}) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_implicit_fake_vlan_bridge_setup(self): - # Test if, when a VLAN is added to an OVS bridge, netplan will - # implicitly assume the vlan should be done via OVS as well -@@ -979,6 +1002,7 @@ ExecStart=/usr/bin/ovs-vsctl set Interface br0.100 external-ids:netplan=true - self.assert_networkd({'br0.network': ND_WITHIP % ('br0', '192.168.1.1/24'), - 'br0.100.network': ND_EMPTY % ('br0.100', 'ipv6')}) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_invalid_device_type(self): - err = self.generate('''network: - version: 2 -@@ -990,6 +1014,7 @@ ExecStart=/usr/bin/ovs-vsctl set Interface br0.100 external-ids:netplan=true - self.assert_ovs({'cleanup.service': OVS_CLEANUP % {'iface': 'cleanup'}}) - self.assert_networkd({}) - -+ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') - def test_bridge_non_ovs_bond(self): - self.generate('''network: - version: 2 --- -2.27.0 - diff -Nru netplan.io-0.102/debian/patches/0001-Fix-changing-of-macaddress-with-systemd-v247-178.patch netplan.io-0.101/debian/patches/0001-Fix-changing-of-macaddress-with-systemd-v247-178.patch --- netplan.io-0.102/debian/patches/0001-Fix-changing-of-macaddress-with-systemd-v247-178.patch 1970-01-01 00:00:00.000000000 +0000 +++ netplan.io-0.101/debian/patches/0001-Fix-changing-of-macaddress-with-systemd-v247-178.patch 2021-01-08 14:05:14.000000000 +0000 @@ -0,0 +1,129 @@ +From: =?utf-8?q?Lukas_M=C3=A4rdian?= +Date: Wed, 16 Dec 2020 13:29:29 +0100 +Subject: Fix changing of macaddress with systemd v247 (#178) + +* networkd: Fix changing of macaddress with systemd v247 + +* networkd: avoid writing MACAddress= [Link] into .link file (we already have it in .network file) + +* network: some cleanup +--- + src/networkd.c | 13 +++++-------- + tests/generator/test_bridges.py | 3 +++ + tests/generator/test_ethernets.py | 12 +++--------- + tests/generator/test_vlans.py | 3 ++- + tests/integration/ethernets.py | 2 +- + 5 files changed, 14 insertions(+), 19 deletions(-) + +diff --git a/src/networkd.c b/src/networkd.c +index 7c86cd6..380095a 100644 +--- a/src/networkd.c ++++ b/src/networkd.c +@@ -227,7 +227,7 @@ write_link_file(const NetplanNetDefinition* def, const char* rootdir, const char + return; + + /* do we need to write a .link file? */ +- if (!def->set_name && !def->wake_on_lan && !def->mtubytes && !def->set_mac) ++ if (!def->set_name && !def->wake_on_lan && !def->mtubytes) + return; + + /* build file contents */ +@@ -241,9 +241,6 @@ write_link_file(const NetplanNetDefinition* def, const char* rootdir, const char + g_string_append_printf(s, "WakeOnLan=%s\n", def->wake_on_lan ? "magic" : "off"); + if (def->mtubytes) + g_string_append_printf(s, "MTUBytes=%u\n", def->mtubytes); +- if (def->set_mac) +- g_string_append_printf(s, "MACAddress=%s\n", def->set_mac); +- + + orig_umask = umask(022); + g_string_free_to_file(s, rootdir, path, ".link"); +@@ -569,13 +566,13 @@ write_network_file(const NetplanNetDefinition* def, const char* rootdir, const c + } + } + +- if (def->mtubytes) { ++ if (def->mtubytes) + g_string_append_printf(link, "MTUBytes=%u\n", def->mtubytes); +- } ++ if (def->set_mac) ++ g_string_append_printf(link, "MACAddress=%s\n", def->set_mac); + +- if (def->emit_lldp) { ++ if (def->emit_lldp) + g_string_append(network, "EmitLLDP=true\n"); +- } + + if (def->dhcp4 && def->dhcp6) + g_string_append(network, "DHCP=yes\n"); +diff --git a/tests/generator/test_bridges.py b/tests/generator/test_bridges.py +index b751074..ea048cf 100644 +--- a/tests/generator/test_bridges.py ++++ b/tests/generator/test_bridges.py +@@ -35,6 +35,9 @@ class TestNetworkd(TestBase): + self.assert_networkd({'br0.network': '''[Match] + Name=br0 + ++[Link] ++MACAddress=00:01:02:03:04:05 ++ + [Network] + DHCP=ipv4 + LinkLocalAddressing=ipv6 +diff --git a/tests/generator/test_ethernets.py b/tests/generator/test_ethernets.py +index 963aca1..a8be4ac 100644 +--- a/tests/generator/test_ethernets.py ++++ b/tests/generator/test_ethernets.py +@@ -225,8 +225,8 @@ unmanaged-devices+=interface-name:green,''') + macaddress: 00:01:02:03:04:05 + dhcp4: true''') + +- self.assert_networkd({'def1.network': ND_DHCP4 % 'green', +- 'def1.link': '[Match]\nOriginalName=green\n\n[Link]\nWakeOnLan=off\nMACAddress=00:01:02:03:04:05\n' ++ self.assert_networkd({'def1.network': (ND_DHCP4 % 'green') ++ .replace('[Network]', '[Link]\nMACAddress=00:01:02:03:04:05\n\n[Network]') + }) + self.assert_networkd_udev(None) + +@@ -442,13 +442,7 @@ method=ignore + macaddress: 00:01:02:03:04:05 + dhcp4: true''') + +- self.assert_networkd({'eth0.link': '''[Match] +-OriginalName=eth0 +- +-[Link] +-WakeOnLan=off +-MACAddress=00:01:02:03:04:05 +-'''}) ++ self.assert_networkd(None) + + self.assert_nm({'eth0': '''[connection] + id=netplan-eth0 +diff --git a/tests/generator/test_vlans.py b/tests/generator/test_vlans.py +index 606a0b1..85c5fca 100644 +--- a/tests/generator/test_vlans.py ++++ b/tests/generator/test_vlans.py +@@ -62,7 +62,8 @@ Kind=vlan + Id=3 + ''', + 'enblue.network': ND_WITHIP % ('enblue', '1.2.3.4/24'), +- 'enred.network': ND_EMPTY % ('enred', 'ipv6'), ++ 'enred.network': (ND_EMPTY % ('enred', 'ipv6')) ++ .replace('[Network]', '[Link]\nMACAddress=aa:bb:cc:dd:ee:11\n\n[Network]'), + 'engreen.network': (ND_DHCP6_WOCARRIER % 'engreen')}) + + self.assert_nm(None, '''[keyfile] +diff --git a/tests/integration/ethernets.py b/tests/integration/ethernets.py +index 74d4129..a362f2e 100644 +--- a/tests/integration/ethernets.py ++++ b/tests/integration/ethernets.py +@@ -72,7 +72,7 @@ class _CommonTests(): + ['master']) + out = subprocess.check_output(['ip', 'link', 'show', self.dev_e2_client], + universal_newlines=True) +- self.assertTrue('ether 00:01:02:03:04:05' in out) ++ self.assertIn('ether 00:01:02:03:04:05', out) + subprocess.check_call(['ip', 'link', 'set', self.dev_e2_client, + 'address', self.dev_e2_client_mac]) + diff -Nru netplan.io-0.102/debian/patches/0001-tests-bonds-fix-flaky-resend_igmp-test.patch netplan.io-0.101/debian/patches/0001-tests-bonds-fix-flaky-resend_igmp-test.patch --- netplan.io-0.102/debian/patches/0001-tests-bonds-fix-flaky-resend_igmp-test.patch 2021-03-25 10:09:36.000000000 +0000 +++ netplan.io-0.101/debian/patches/0001-tests-bonds-fix-flaky-resend_igmp-test.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,43 +0,0 @@ -From: =?utf-8?q?Lukas_M=C3=A4rdian?= -Date: Tue, 23 Mar 2021 10:59:02 +0100 -Subject: tests:bonds: fix flaky resend_igmp test - ---- - tests/integration/bonds.py | 8 ++++---- - 1 file changed, 4 insertions(+), 4 deletions(-) - -diff --git a/tests/integration/bonds.py b/tests/integration/bonds.py -index fe84f7e..072f6b0 100644 ---- a/tests/integration/bonds.py -+++ b/tests/integration/bonds.py -@@ -306,7 +306,7 @@ class _CommonTests(): - self.assertEqual(f.read().strip(), '15') - - def test_bond_resend_igmp(self): -- self.setup_eth(None) -+ self.setup_eth(None, False) - self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL) - with open(self.config, 'w') as f: - f.write('''network: -@@ -318,18 +318,18 @@ class _CommonTests(): - match: {name: %(e2c)s} - bonds: - mybond: -+ addresses: [192.168.9.9/24] - interfaces: [ethbn, ethb2] - parameters: - mode: balance-rr - mii-monitor-interval: 50s - resend-igmp: 100 -- dhcp4: yes''' % {'r': self.backend, 'ec': self.dev_e_client, 'e2c': self.dev_e2_client}) -+''' % {'r': self.backend, 'ec': self.dev_e_client, 'e2c': self.dev_e2_client}) - self.generate_and_settle() - self.assert_iface_up(self.dev_e_client, - ['master mybond'], - ['inet ']) -- self.assert_iface_up('mybond', -- ['inet 192.168.5.[0-9]+/24']) -+ self.assert_iface_up('mybond', ['inet 192.168.9.9/24']) - with open('/sys/class/net/mybond/bonding/slaves') as f: - result = f.read().strip() - self.assertIn(self.dev_e_client, result) diff -Nru netplan.io-0.102/debian/patches/0002-Disable-some-tests-due-to-ovs-vsctl-missing-on-riscv.patch netplan.io-0.101/debian/patches/0002-Disable-some-tests-due-to-ovs-vsctl-missing-on-riscv.patch --- netplan.io-0.102/debian/patches/0002-Disable-some-tests-due-to-ovs-vsctl-missing-on-riscv.patch 1970-01-01 00:00:00.000000000 +0000 +++ netplan.io-0.101/debian/patches/0002-Disable-some-tests-due-to-ovs-vsctl-missing-on-riscv.patch 2021-01-08 14:05:14.000000000 +0000 @@ -0,0 +1,198 @@ +From: =?utf-8?q?Lukas_M=C3=A4rdian?= +Date: Wed, 30 Sep 2020 15:00:20 +0200 +Subject: Disable some tests, due to ovs-vsctl missing on riscv64 + +--- + tests/generator/test_ovs.py | 25 +++++++++++++++++++++++++ + 1 file changed, 25 insertions(+) + +diff --git a/tests/generator/test_ovs.py b/tests/generator/test_ovs.py +index 601ed1c..2dacf58 100644 +--- a/tests/generator/test_ovs.py ++++ b/tests/generator/test_ovs.py +@@ -17,6 +17,9 @@ + # You should have received a copy of the GNU General Public License + # along with this program. If not, see . + ++import os ++import unittest ++ + from .base import TestBase, ND_EMPTY, ND_WITHIP, ND_DHCP4, ND_DHCP6, \ + OVS_PHYSICAL, OVS_VIRTUAL, \ + OVS_BR_EMPTY, OVS_BR_DEFAULT, \ +@@ -26,6 +29,7 @@ from .base import TestBase, ND_EMPTY, ND_WITHIP, ND_DHCP4, ND_DHCP6, \ + class TestOpenVSwitch(TestBase): + '''OVS output''' + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_interface_external_ids_other_config(self): + self.generate('''network: + version: 2 +@@ -118,6 +122,7 @@ ExecStart=/usr/bin/ovs-vsctl set open_vswitch . external-ids:netplan/other-confi + # Confirm that the networkd config is still sane + self.assert_networkd({'eth0.network': ND_DHCP4 % 'eth0'}) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_global_set_protocols(self): + self.generate('''network: + version: 2 +@@ -161,6 +166,7 @@ ExecStart=/usr/bin/ovs-vsctl set Bridge ovs0 external-ids:netplan/protocols=Open + self.assert_ovs({'cleanup.service': OVS_CLEANUP % {'iface': 'cleanup'}}) + self.assert_networkd({'eth0.network': ND_DHCP4 % 'eth0'}) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_bond_setup(self): + self.generate('''network: + version: 2 +@@ -200,6 +206,7 @@ ExecStart=/usr/bin/ovs-vsctl set Port bond0 external-ids:netplan/external-ids/if + 'br0.network': ND_WITHIP % ('br0', '192.170.1.1/24'), + 'bond0.network': ND_EMPTY % ('bond0', 'no')}) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_bond_no_bridge(self): + err = self.generate('''network: + version: 2 +@@ -213,6 +220,7 @@ ExecStart=/usr/bin/ovs-vsctl set Port bond0 external-ids:netplan/external-ids/if + ''', expect_fail=True) + self.assertIn("Bond bond0 needs to be a slave of an OpenVSwitch bridge", err) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_bond_not_enough_interfaces(self): + err = self.generate('''network: + version: 2 +@@ -230,6 +238,7 @@ ExecStart=/usr/bin/ovs-vsctl set Port bond0 external-ids:netplan/external-ids/if + ''', expect_fail=True) + self.assertIn("Bond bond0 needs to have at least 2 slave interfaces", err) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_bond_lacp(self): + self.generate('''network: + version: 2 +@@ -295,6 +304,7 @@ ExecStart=/usr/bin/ovs-vsctl set Port bond0 external-ids:netplan/lacp=active + ''', expect_fail=True) + self.assertIn("Key 'lacp' is only valid for iterface type 'openvswitch bond'", err) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_bond_mode_implicit_params(self): + self.generate('''network: + version: 2 +@@ -333,6 +343,7 @@ ExecStart=/usr/bin/ovs-vsctl set Port bond0 external-ids:netplan/bond_mode=balan + 'br0.network': ND_WITHIP % ('br0', '192.170.1.1/24'), + 'bond0.network': ND_EMPTY % ('bond0', 'no')}) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_bond_mode_explicit_params(self): + self.generate('''network: + version: 2 +@@ -372,6 +383,7 @@ ExecStart=/usr/bin/ovs-vsctl set Port bond0 external-ids:netplan/bond_mode=activ + 'br0.network': ND_WITHIP % ('br0', '192.170.1.1/24'), + 'bond0.network': ND_EMPTY % ('bond0', 'no')}) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_bond_mode_ovs_invalid(self): + err = self.generate('''network: + version: 2 +@@ -392,6 +404,7 @@ ExecStart=/usr/bin/ovs-vsctl set Port bond0 external-ids:netplan/bond_mode=activ + ''', expect_fail=True) + self.assertIn("bond0: bond mode 'balance-rr' not supported by openvswitch", err) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_bridge_setup(self): + self.generate('''network: + version: 2 +@@ -418,6 +431,7 @@ ExecStart=/usr/bin/ovs-vsctl --may-exist add-port br0 eth2 + 'eth2.network': '[Match]\nName=eth2\n\n[Network]\nLinkLocalAddressing=no\nBridge=br0\n', + 'br0.network': ND_WITHIP % ('br0', '192.170.1.1/24')}) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_bridge_external_ids_other_config(self): + self.generate('''network: + version: 2 +@@ -443,6 +457,7 @@ ExecStart=/usr/bin/ovs-vsctl set Bridge br0 external-ids:netplan/other-config/di + # Confirm that the bridge has been only configured for OVS + self.assert_networkd({'br0.network': ND_EMPTY % ('br0', 'ipv6')}) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_bridge_non_default_parameters(self): + self.generate('''network: + version: 2 +@@ -509,6 +524,7 @@ ExecStart=/usr/bin/ovs-vsctl set Bridge br0 external-ids:netplan/rstp_enable=tru + ''', expect_fail=True) + self.assertIn("Key is only valid for iterface type 'openvswitch bridge'", err) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_bridge_set_protocols(self): + self.generate('''network: + version: 2 +@@ -550,6 +566,7 @@ ExecStart=/usr/bin/ovs-vsctl set Bridge br0 external-ids:netplan/protocols=OpenF + ''', expect_fail=True) + self.assertIn("Key 'protocols' is only valid for iterface type 'openvswitch bridge'", err) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_bridge_controller(self): + self.generate('''network: + version: 2 +@@ -687,6 +704,7 @@ ExecStart=/usr/bin/ovs-vsctl set open_vswitch . external-ids:netplan/global/set- + # Confirm that the networkd config is still sane + self.assert_networkd({}) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_missing_ssl(self): + err = self.generate('''network: + version: 2 +@@ -762,6 +780,7 @@ ExecStart=/usr/bin/ovs-vsctl set open_vswitch . external-ids:netplan/global/set- + self.assert_ovs({}) + self.assert_networkd({}) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_bridge_auto_ovs_backend(self): + self.generate('''network: + version: 2 +@@ -809,6 +828,7 @@ LinkLocalAddressing=no + Bond=bond0 + '''}) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_bond_auto_ovs_backend(self): + self.generate('''network: + version: 2 +@@ -870,6 +890,7 @@ ExecStart=/usr/bin/ovs-vsctl set Interface patchy external-ids:netplan=true + 'patchy.network': ND_EMPTY % ('patchy', 'no'), + 'eth0.network': '[Match]\nName=eth0\n\n[Network]\nLinkLocalAddressing=no\nBond=bond0\n'}) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_patch_ports(self): + self.generate('''network: + version: 2 +@@ -918,6 +939,7 @@ ExecStart=/usr/bin/ovs-vsctl set Port patch1-0 external-ids:netplan=true + 'patch0-1.network': ND_EMPTY % ('patch0-1', 'no'), + 'patch1-0.network': ND_EMPTY % ('patch1-0', 'no')}) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_fake_vlan_bridge_setup(self): + self.generate('''network: + version: 2 +@@ -950,6 +972,7 @@ ExecStart=/usr/bin/ovs-vsctl set Interface br0.100 external-ids:netplan=true + self.assert_networkd({'br0.network': ND_WITHIP % ('br0', '192.168.1.1/24'), + 'br0.100.network': ND_EMPTY % ('br0.100', 'ipv6')}) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_implicit_fake_vlan_bridge_setup(self): + # Test if, when a VLAN is added to an OVS bridge, netplan will + # implicitly assume the vlan should be done via OVS as well +@@ -979,6 +1002,7 @@ ExecStart=/usr/bin/ovs-vsctl set Interface br0.100 external-ids:netplan=true + self.assert_networkd({'br0.network': ND_WITHIP % ('br0', '192.168.1.1/24'), + 'br0.100.network': ND_EMPTY % ('br0.100', 'ipv6')}) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_invalid_device_type(self): + err = self.generate('''network: + version: 2 +@@ -990,6 +1014,7 @@ ExecStart=/usr/bin/ovs-vsctl set Interface br0.100 external-ids:netplan=true + self.assert_ovs({}) + self.assert_networkd({}) + ++ @unittest.skipIf(os.uname().machine == 'riscv64', 'missing ovs-vsctl') + def test_bridge_non_ovs_bond(self): + self.generate('''network: + version: 2 diff -Nru netplan.io-0.102/debian/patches/0002-parse-fix-networkmanager-backend-options-for-modem-c.patch netplan.io-0.101/debian/patches/0002-parse-fix-networkmanager-backend-options-for-modem-c.patch --- netplan.io-0.102/debian/patches/0002-parse-fix-networkmanager-backend-options-for-modem-c.patch 1970-01-01 00:00:00.000000000 +0000 +++ netplan.io-0.101/debian/patches/0002-parse-fix-networkmanager-backend-options-for-modem-c.patch 2021-01-08 14:05:14.000000000 +0000 @@ -0,0 +1,63 @@ +From: =?utf-8?q?Lukas_M=C3=A4rdian?= +Date: Wed, 16 Dec 2020 18:19:46 +0100 +Subject: parse: fix 'networkmanager:' backend options for modem connections + (#179) + +The COMMON_BACKEND_HANDLERS have been forgotten for modem connections apparently. Add them to allow the definition of the special networkmanager: mapping, used for NetworkManager integration. We do not (yet) use that information (like uuid) in the current implementation. But reading YAML via NetworkManager will be broken if the networkmanager: mapping is not accepted. +--- + src/parse.c | 1 + + tests/generator/test_modems.py | 29 +++++++++++++++++++++++++++++ + 2 files changed, 30 insertions(+) + +diff --git a/src/parse.c b/src/parse.c +index 033c657..f1f6a6f 100644 +--- a/src/parse.c ++++ b/src/parse.c +@@ -2222,6 +2222,7 @@ static const mapping_entry_handler vlan_def_handlers[] = { + + static const mapping_entry_handler modem_def_handlers[] = { + COMMON_LINK_HANDLERS, ++ COMMON_BACKEND_HANDLERS, + {"apn", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(modem_params.apn)}, + {"auto-config", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(modem_params.auto_config)}, + {"device-id", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(modem_params.device_id)}, +diff --git a/tests/generator/test_modems.py b/tests/generator/test_modems.py +index 027aa65..ca68ddc 100644 +--- a/tests/generator/test_modems.py ++++ b/tests/generator/test_modems.py +@@ -367,6 +367,35 @@ wake-on-lan=0 + [ipv4] + method=link-local + ++[ipv6] ++method=ignore ++'''}) ++ self.assert_networkd({}) ++ self.assert_nm_udev(None) ++ ++ def test_modem_nm_integration(self): ++ self.generate('''network: ++ version: 2 ++ renderer: NetworkManager ++ modems: ++ mobilephone: ++ auto-config: true ++ networkmanager: ++ uuid: b22d8f0f-3f34-46bd-ac28-801fa87f1eb6''') ++ self.assert_nm({'mobilephone': '''[connection] ++id=netplan-mobilephone ++type=gsm ++interface-name=mobilephone ++ ++[gsm] ++auto-config=true ++ ++[ethernet] ++wake-on-lan=0 ++ ++[ipv4] ++method=link-local ++ + [ipv6] + method=ignore + '''}) diff -Nru netplan.io-0.102/debian/patches/0002-tests-tunnels-improve-flaky-wireguard-test-with-wait.patch netplan.io-0.101/debian/patches/0002-tests-tunnels-improve-flaky-wireguard-test-with-wait.patch --- netplan.io-0.102/debian/patches/0002-tests-tunnels-improve-flaky-wireguard-test-with-wait.patch 2021-03-26 12:35:10.000000000 +0000 +++ netplan.io-0.101/debian/patches/0002-tests-tunnels-improve-flaky-wireguard-test-with-wait.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -From: =?utf-8?q?Lukas_M=C3=A4rdian?= -Date: Thu, 25 Mar 2021 10:08:37 +0100 -Subject: tests:tunnels: improve flaky wireguard test with wait_output() - ---- - tests/integration/base.py | 16 ++++++++++------ - tests/integration/tunnels.py | 4 +++- - 2 files changed, 13 insertions(+), 7 deletions(-) - -diff --git a/tests/integration/base.py b/tests/integration/base.py -index c7df302..396a1b3 100644 ---- a/tests/integration/base.py -+++ b/tests/integration/base.py -@@ -333,17 +333,21 @@ class IntegrationTestsBase(unittest.TestCase): - else: - self.fail('timed out waiting for %s to get ready by NM' % iface) - -- def nm_wait_connected(self, iface, timeout): -- for t in range(timeout): -+ def wait_output(self, cmd, expected_output, timeout=10): -+ for _ in range(timeout): - try: -- out = subprocess.check_output(['nmcli', 'dev', 'show', iface]) -+ out = subprocess.check_output(cmd, universal_newlines=True) - except subprocess.CalledProcessError: -- out = b'' -- if b'(connected' in out: -+ out = '' -+ if expected_output in out: - break -+ sys.stdout.write('. ') # waiting indicator - time.sleep(1) - else: -- self.fail('timed out waiting for %s to get connected by NM:\n%s' % (iface, out.decode())) -+ self.fail('timed out waiting for "{}" to appear in {}'.format(expected_output, cmd)) -+ -+ def nm_wait_connected(self, iface, timeout): -+ self.wait_output(['nmcli', 'dev', 'show', iface], '(connected', timeout) - - @classmethod - def is_active(klass, unit): -diff --git a/tests/integration/tunnels.py b/tests/integration/tunnels.py -index 071479d..ab5d55e 100644 ---- a/tests/integration/tunnels.py -+++ b/tests/integration/tunnels.py -@@ -112,7 +112,9 @@ class _CommonTests(): - keepalive: 21 - ''' % {'r': self.backend, 'ec': self.dev_e_client, 'e2c': self.dev_e2_client}) - self.generate_and_settle() -- time.sleep(2) # Give some time for handshake/connection between client & server -+ # Wait for handshake/connection between client & server -+ self.wait_output(['wg', 'show', 'wg0'], 'latest handshake') -+ self.wait_output(['wg', 'show', 'wg1'], 'latest handshake') - # Verify server - out = subprocess.check_output(['wg', 'show', 'wg0', 'private-key'], universal_newlines=True) - self.assertIn("4GgaQCy68nzNsUE5aJ9fuLzHhB65tAlwbmA72MWnOm8=", out) diff -Nru netplan.io-0.102/debian/patches/0004-tests-tunnels-improve-test-reliability.patch netplan.io-0.101/debian/patches/0004-tests-tunnels-improve-test-reliability.patch --- netplan.io-0.102/debian/patches/0004-tests-tunnels-improve-test-reliability.patch 1970-01-01 00:00:00.000000000 +0000 +++ netplan.io-0.101/debian/patches/0004-tests-tunnels-improve-test-reliability.patch 2021-01-08 14:05:14.000000000 +0000 @@ -0,0 +1,21 @@ +From: =?utf-8?q?Lukas_M=C3=A4rdian?= +Date: Fri, 8 Jan 2021 09:28:23 +0100 +Subject: tests:tunnels: improve test reliability + +--- + tests/integration/tunnels.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/integration/tunnels.py b/tests/integration/tunnels.py +index 9299aa3..0bc6e58 100644 +--- a/tests/integration/tunnels.py ++++ b/tests/integration/tunnels.py +@@ -123,7 +123,7 @@ class _CommonTests(): + self.assertIn("fwmark: 0x2a", out) + self.assertIn("peer: M9nt4YujIOmNrRmpIRTmYSfMdrpvE7u6WkG8FY8WjG4=", out) + self.assertIn("allowed ips: 20.20.20.0/24", out) +- self.assertRegex(out, r'latest handshake: \d+ seconds? ago') ++ self.assertRegex(out, r'latest handshake: (\d+ seconds? ago|Now)') + self.assertRegex(out, r'transfer: \d+ B received, \d+ B sent') + self.assert_iface('wg0', ['inet 10.10.10.20/24']) + # Verify client diff -Nru netplan.io-0.102/debian/patches/0005-tests-dbus-improve-test-stability-of-timeouts.patch netplan.io-0.101/debian/patches/0005-tests-dbus-improve-test-stability-of-timeouts.patch --- netplan.io-0.102/debian/patches/0005-tests-dbus-improve-test-stability-of-timeouts.patch 1970-01-01 00:00:00.000000000 +0000 +++ netplan.io-0.101/debian/patches/0005-tests-dbus-improve-test-stability-of-timeouts.patch 2021-01-08 14:05:14.000000000 +0000 @@ -0,0 +1,192 @@ +From: =?utf-8?q?Lukas_M=C3=A4rdian?= +Date: Fri, 8 Jan 2021 10:51:24 +0100 +Subject: tests:dbus: improve test stability of timeouts + +--- + tests/dbus/test_dbus.py | 44 ++++++++++++++++++++++++-------------------- + 1 file changed, 24 insertions(+), 20 deletions(-) + +diff --git a/tests/dbus/test_dbus.py b/tests/dbus/test_dbus.py +index b3e1d3b..2a17e0a 100644 +--- a/tests/dbus/test_dbus.py ++++ b/tests/dbus/test_dbus.py +@@ -70,7 +70,7 @@ printf '\\0' >> %(log)s + with open(self.path, "a") as fp: + fp.write("cat << EOF\n%s\nEOF" % output) + +- def set_timeout(self, timeout=1): ++ def set_timeout(self, timeout_dsec=10): + with open(self.path, "a") as fp: + fp.write(""" + if [[ "$*" == *try* ]] +@@ -78,14 +78,13 @@ then + ACTIVE=1 + trap 'ACTIVE=0' SIGUSR1 + trap 'ACTIVE=0' SIGINT +- # timeout * 10 is the specified timeout in seconds (0.1 sec sleep increments) +- while (( $ACTIVE > 0 )) && (( $ACTIVE <= $(({}*10)) )) ++ while (( $ACTIVE > 0 )) && (( $ACTIVE <= {} )) + do + ACTIVE=$(($ACTIVE+1)) + sleep 0.1 + done + fi +-""".format(timeout)) ++""".format(timeout_dsec)) + + + class TestNetplanDBus(unittest.TestCase): +@@ -131,6 +130,7 @@ class TestNetplanDBus(unittest.TestCase): + os.environ["DBUS_TEST_NETPLAN_ROOT"] = self.tmp + p = subprocess.Popen(NETPLAN_DBUS_CMD, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) ++ time.sleep(1) # Give some time for our dbus daemon to be ready + self.addCleanup(self._cleanup_netplan_dbus, p) + + def _cleanup_netplan_dbus(self, p): +@@ -198,8 +198,8 @@ class TestNetplanDBus(unittest.TestCase): + ]) + + def test_netplan_dbus_noroot(self): +- # Process should fail instantly, if not: kill it after 1 sec +- r = subprocess.run(NETPLAN_DBUS_CMD, timeout=1, capture_output=True) ++ # Process should fail instantly, if not: kill it after 5 sec ++ r = subprocess.run(NETPLAN_DBUS_CMD, timeout=5, capture_output=True) + self.assertEquals(r.returncode, 1) + self.assertIn(b'Failed to acquire service name', r.stderr) + +@@ -339,6 +339,8 @@ class TestNetplanDBus(unittest.TestCase): + ] + out = subprocess.check_output(BUSCTL_NETPLAN_CMD) + self.assertEqual(b'b true\n', out) ++ ++ time.sleep(1) # Give some time for 'Cancel' to clean up + self.assertFalse(os.path.isdir(tmpdir)) + + # Verify the object is gone from the bus +@@ -366,6 +368,7 @@ class TestNetplanDBus(unittest.TestCase): + out = subprocess.check_output(BUSCTL_NETPLAN_CMD) + self.assertEqual(b'b true\n', out) + self.assertEquals(self.mock_netplan_cmd.calls(), [["netplan", "apply"]]) ++ time.sleep(1) # Give some time for 'Apply' to clean up + self.assertFalse(os.path.isdir(tmpdir)) + + # Verify the new YAML files were copied over +@@ -378,7 +381,8 @@ class TestNetplanDBus(unittest.TestCase): + self.assertIn('Unknown object \'/io/netplan/Netplan/config/{}\''.format(cid), err) + + def test_netplan_dbus_config_try_cancel(self): +- self.mock_netplan_cmd.set_timeout(2) ++ # self-terminate after 30 dsec = 3 sec, if not cancelled before ++ self.mock_netplan_cmd.set_timeout(30) + cid = self._new_config_object() + tmpdir = '/tmp/netplan-config-{}'.format(cid) + backup = '/tmp/netplan-config-BACKUP' +@@ -395,7 +399,7 @@ class TestNetplanDBus(unittest.TestCase): + "io.netplan.Netplan", + "/io/netplan/Netplan/config/{}".format(cid), + "io.netplan.Netplan.Config", +- "Try", "u", "2", ++ "Try", "u", "3", + ] + out = subprocess.check_output(BUSCTL_NETPLAN_CMD) + self.assertEqual(b'b true\n', out) +@@ -424,7 +428,7 @@ class TestNetplanDBus(unittest.TestCase): + ] + out = subprocess.check_output(BUSCTL_NETPLAN_CMD2) + self.assertEqual(b'b true\n', out) +- time.sleep(1) # Give some time for the 'netplan try' process ++ time.sleep(1) # Give some time for 'Cancel' to clean up + + # Verify the backup andconfig state dir are gone + self.assertFalse(os.path.isdir(backup)) +@@ -441,10 +445,10 @@ class TestNetplanDBus(unittest.TestCase): + self.assertIn('Unknown object \'/io/netplan/Netplan/config/{}\''.format(cid), err) + + # Verify 'netplan try' has been called +- self.assertEquals(self.mock_netplan_cmd.calls(), [["netplan", "try", "--timeout=2"]]) ++ self.assertEquals(self.mock_netplan_cmd.calls(), [["netplan", "try", "--timeout=3"]]) + + def test_netplan_dbus_config_try_cb(self): +- self.mock_netplan_cmd.set_timeout(1) # self-quit after 1 sec ++ self.mock_netplan_cmd.set_timeout(1) # actually self-terminate after 0.1 sec + cid = self._new_config_object() + tmpdir = '/tmp/netplan-config-{}'.format(cid) + backup = '/tmp/netplan-config-BACKUP' +@@ -484,14 +488,14 @@ class TestNetplanDBus(unittest.TestCase): + self.assertEquals(self.mock_netplan_cmd.calls(), [["netplan", "try", "--timeout=1"]]) + + def test_netplan_dbus_config_try_apply(self): +- self.mock_netplan_cmd.set_timeout(2) ++ self.mock_netplan_cmd.set_timeout(30) # 30 dsec = 3 sec + cid = self._new_config_object() + BUSCTL_NETPLAN_CMD = [ + "busctl", "call", "--system", + "io.netplan.Netplan", + "/io/netplan/Netplan/config/{}".format(cid), + "io.netplan.Netplan.Config", +- "Try", "u", "2", ++ "Try", "u", "3", + ] + out = subprocess.check_output(BUSCTL_NETPLAN_CMD) + self.assertEqual(b'b true\n', out) +@@ -507,14 +511,14 @@ class TestNetplanDBus(unittest.TestCase): + self.assertIn('Another \'netplan try\' process is already running', err) + + def test_netplan_dbus_config_try_config_try(self): +- self.mock_netplan_cmd.set_timeout(2) ++ self.mock_netplan_cmd.set_timeout(50) # 50 dsec = 5 sec + cid = self._new_config_object() + BUSCTL_NETPLAN_CMD = [ + "busctl", "call", "--system", + "io.netplan.Netplan", + "/io/netplan/Netplan/config/{}".format(cid), + "io.netplan.Netplan.Config", +- "Try", "u", "2", ++ "Try", "u", "3", + ] + out = subprocess.check_output(BUSCTL_NETPLAN_CMD) + self.assertEqual(b'b true\n', out) +@@ -525,13 +529,13 @@ class TestNetplanDBus(unittest.TestCase): + "io.netplan.Netplan", + "/io/netplan/Netplan/config/{}".format(cid2), + "io.netplan.Netplan.Config", +- "Try", "u", "2", ++ "Try", "u", "5", + ] + err = self._check_dbus_error(BUSCTL_NETPLAN_CMD2) + self.assertIn('Another Try() is currently in progress: PID ', err) + + def test_netplan_dbus_config_set_invalidate(self): +- self.mock_netplan_cmd.set_timeout(2) ++ self.mock_netplan_cmd.set_timeout(30) # 30 dsec = 3 sec + cid = self._new_config_object() + BUSCTL_NETPLAN_CMD = [ + "busctl", "call", "--system", +@@ -570,7 +574,7 @@ class TestNetplanDBus(unittest.TestCase): + "io.netplan.Netplan", + "/io/netplan/Netplan/config/{}".format(cid2), + "io.netplan.Netplan.Config", +- "Try", "u", "2", ++ "Try", "u", "3", + ] + err = self._check_dbus_error(BUSCTL_NETPLAN_CMD3) + self.assertIn('This config was invalidated by another config object', err) +@@ -675,7 +679,7 @@ class TestNetplanDBus(unittest.TestCase): + ]) + + def test_netplan_dbus_config_set_uninvalidate_timeout(self): +- self.mock_netplan_cmd.set_timeout(1) ++ self.mock_netplan_cmd.set_timeout(1) # actually self-terminate process after 0.1 sec + cid = self._new_config_object() + cid2 = self._new_config_object() + BUSCTL_NETPLAN_CMD = [ +@@ -709,7 +713,7 @@ class TestNetplanDBus(unittest.TestCase): + err = self._check_dbus_error(BUSCTL_NETPLAN_CMD2) + self.assertIn('This config was invalidated by another config object', err) + +- time.sleep(1.5) # Wait for the child process to cancel itself ++ time.sleep(1.5) # Wait for the child process to self-terminate + + # Calling Set() on the other config object works now + out = subprocess.check_output(BUSCTL_NETPLAN_CMD2) diff -Nru netplan.io-0.102/debian/patches/series netplan.io-0.101/debian/patches/series --- netplan.io-0.102/debian/patches/series 2021-03-26 12:35:10.000000000 +0000 +++ netplan.io-0.101/debian/patches/series 2021-01-08 14:05:14.000000000 +0000 @@ -1,3 +1,5 @@ -0000-Disable-some-tests-due-to-ovs-vsctl-missing-on-riscv.patch -0001-tests-bonds-fix-flaky-resend_igmp-test.patch -0002-tests-tunnels-improve-flaky-wireguard-test-with-wait.patch +0002-Disable-some-tests-due-to-ovs-vsctl-missing-on-riscv.patch +0001-Fix-changing-of-macaddress-with-systemd-v247-178.patch +0002-parse-fix-networkmanager-backend-options-for-modem-c.patch +0004-tests-tunnels-improve-test-reliability.patch +0005-tests-dbus-improve-test-stability-of-timeouts.patch diff -Nru netplan.io-0.102/debian/rules netplan.io-0.101/debian/rules --- netplan.io-0.102/debian/rules 2021-03-25 10:09:36.000000000 +0000 +++ netplan.io-0.101/debian/rules 2020-12-11 08:20:10.000000000 +0000 @@ -1,11 +1,7 @@ #!/usr/bin/make -f -include /usr/share/dpkg/architecture.mk - %: - dh $@ + dh $@ --fail-missing override_dh_auto_install: - dh_auto_install -- LIBDIR=/usr/lib/${DEB_HOST_MULTIARCH} - -.PHOHY: override_dh_auto_install + dh_auto_install -- LIBDIR=/usr/lib/${DEB_HOST_MULTIARCH}/ diff -Nru netplan.io-0.102/debian/tests/control netplan.io-0.101/debian/tests/control --- netplan.io-0.102/debian/tests/control 2021-03-25 10:09:36.000000000 +0000 +++ netplan.io-0.101/debian/tests/control 2020-12-11 08:20:10.000000000 +0000 @@ -9,7 +9,7 @@ python3-gi, gir1.2-nm-1.0, openvswitch-switch, -Restrictions: allow-stderr, needs-root, isolation-machine, skip-not-installable, breaks-testbed +Restrictions: allow-stderr, needs-root, isolation-machine Features: test-name=ovs Test-Command: python3 tests/integration/run.py --test=ethernets @@ -22,7 +22,7 @@ libnm0, python3-gi, gir1.2-nm-1.0, -Restrictions: allow-stderr, needs-root, isolation-container, breaks-testbed +Restrictions: allow-stderr, needs-root, isolation-machine Features: test-name=ethernets Test-Command: python3 tests/integration/run.py --test=bridges @@ -35,7 +35,7 @@ libnm0, python3-gi, gir1.2-nm-1.0, -Restrictions: allow-stderr, needs-root, isolation-machine, breaks-testbed +Restrictions: allow-stderr, needs-root, isolation-machine Features: test-name=bridges Test-Command: python3 tests/integration/run.py --test=bonds @@ -48,7 +48,7 @@ libnm0, python3-gi, gir1.2-nm-1.0, -Restrictions: allow-stderr, needs-root, isolation-machine, breaks-testbed +Restrictions: allow-stderr, needs-root, isolation-machine Features: test-name=bonds Test-Command: python3 tests/integration/run.py --test=routing @@ -61,7 +61,7 @@ libnm0, python3-gi, gir1.2-nm-1.0, -Restrictions: allow-stderr, needs-root, isolation-machine, breaks-testbed +Restrictions: allow-stderr, needs-root, isolation-machine Features: test-name=routing Test-Command: python3 tests/integration/run.py --test=vlans @@ -74,7 +74,7 @@ libnm0, python3-gi, gir1.2-nm-1.0, -Restrictions: allow-stderr, needs-root, isolation-container, breaks-testbed +Restrictions: allow-stderr, needs-root, isolation-machine Features: test-name=vlans Test-Command: python3 tests/integration/run.py --test=wifi @@ -87,7 +87,7 @@ libnm0, python3-gi, gir1.2-nm-1.0, -Restrictions: allow-stderr, needs-root, isolation-machine, flaky, breaks-testbed +Restrictions: allow-stderr, needs-root, isolation-machine, flaky Features: test-name=wifi Test-Command: python3 tests/integration/run.py --test=tunnels @@ -101,7 +101,7 @@ python3-gi, gir1.2-nm-1.0, wireguard-tools, -Restrictions: allow-stderr, needs-root, isolation-machine, breaks-testbed +Restrictions: allow-stderr, needs-root, isolation-machine Features: test-name=tunnels Test-Command: python3 tests/integration/run.py --test=scenarios @@ -114,7 +114,7 @@ libnm0, python3-gi, gir1.2-nm-1.0, -Restrictions: allow-stderr, needs-root, isolation-machine, breaks-testbed +Restrictions: allow-stderr, needs-root, isolation-machine Features: test-name=scenarios Test-Command: python3 tests/integration/run.py --test=regressions @@ -127,11 +127,11 @@ libnm0, python3-gi, gir1.2-nm-1.0, -Restrictions: allow-stderr, needs-root, isolation-container, breaks-testbed +Restrictions: allow-stderr, needs-root, isolation-machine Features: test-name=regressions Tests: autostart -Restrictions: allow-stderr, needs-root, isolation-container, breaks-testbed +Restrictions: allow-stderr, needs-root, isolation-container Tests: cloud-init -Restrictions: allow-stderr, needs-root, isolation-container, breaks-testbed +Restrictions: allow-stderr, needs-root, isolation-container diff -Nru netplan.io-0.102/debian/watch netplan.io-0.101/debian/watch --- netplan.io-0.102/debian/watch 2021-03-25 10:09:36.000000000 +0000 +++ netplan.io-0.101/debian/watch 2020-12-11 08:20:10.000000000 +0000 @@ -1,4 +1,4 @@ version=4 opts=filenamemangle=s/.+\/v?@ANY_VERSION@@ARCHIVE_EXT@/@PACKAGE@-$1.tar.gz/,uversionmangle=s/-?rc/~rc/ \ - https://github.com/canonical/netplan/releases .*/archive/v?@ANY_VERSION@@ARCHIVE_EXT@ + https://github.com/CanonicalLtd/netplan/releases .*/archive/v?@ANY_VERSION@@ARCHIVE_EXT@ diff -Nru netplan.io-0.102/doc/netplan.md netplan.io-0.101/doc/netplan.md --- netplan.io-0.102/doc/netplan.md 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/doc/netplan.md 2020-12-09 11:32:25.000000000 +0000 @@ -133,10 +133,6 @@ : Enable wake on LAN. Off by default. - **Note:** This will not work reliably for devices matched by name - only and rendered by networkd, due to interactions with device - renaming in udev. Match devices by MAC when setting wake on LAN. - ``emit-lldp`` (bool) – since **0.99** : (networkd backend only) Whether to emit LLDP packets. Off by default. @@ -285,10 +281,9 @@ ``dhcp-identifier`` (scalar) -: (networkd backend only) Sets the source of DHCPv4 client identifier. If ``mac`` - is specified, the MAC address of the link is used. If this option is omitted, - or if ``duid`` is specified, networkd will generate an RFC4361-compliant client - identifier for the interface by combining the link's IAID and DUID. +: When set to 'mac'; pass that setting over to systemd-networkd to use the + device's MAC address as a unique identifier rather than a RFC4361-compliant + Client ID. This has no effect when NetworkManager is used as a renderer. ``dhcp4-overrides`` (mapping) @@ -583,14 +578,6 @@ : The MTU to be used for the route, in bytes. Must be a positive integer value. - ``congestion-window`` (scalar) – since **0.102** - : The congestion window to be used for the route, represented by number - of segments. Must be a positive integer value. - - ``advertised-receive-window`` (scalar) – since **0.102** - : The receive window to be advertised for the route, represented by - number of segments. Must be a positive integer value. - ``routing-policy`` (mapping) : The ``routing-policy`` block defines extra routing policy for a network, @@ -1087,10 +1074,6 @@ : Defines the address of the remote endpoint of the tunnel. -``ttl`` (scalar) – since **0.102** - -: Defines the TTL of the tunnel. - ``key`` (scalar or mapping) : Define keys to use for the tunnel. The key can be a number or a dotted @@ -1224,7 +1207,7 @@ ``public`` and ``shared`` keys. ``public`` (scalar) – since **0.100** - : A base64-encoded public key, required for Wireguard peers. + : A base64-encoded public key, requried for Wireguard peers. ``shared`` (scalar) – since **0.100** : A base64-encoded preshared key. Optional for Wireguard peers. @@ -1256,10 +1239,6 @@ link: eno1 addresses: ... -## Properties for device type ``nm-devices:`` - -The ``nm-devices`` device type is for internal use only and should not be used in normal configuration files. It enables a fallback mode for unsupported settings, using the ``passthrough`` mapping. - ## Backend-specific configuration parameters @@ -1288,8 +1267,6 @@ ``device`` (scalar) – since **0.99** : Defines the interface name for which this connection applies. - ``passthrough`` (mapping) – since **0.102** - : Can be used as a fallback mechanism to missing keyfile settings. ## Examples Configure an ethernet device with networkd, identified by its name, and enable diff -Nru netplan.io-0.102/doc/netplan-set.md netplan.io-0.101/doc/netplan-set.md --- netplan.io-0.102/doc/netplan-set.md 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/doc/netplan-set.md 2020-12-09 11:32:25.000000000 +0000 @@ -35,7 +35,7 @@ : Write YAML files into this root instead of / --origin-hint -: Specify a name for the config file, e.g.: ``70-netplan-set`` => ``/etc/netplan/70-netplan-set.yaml`` +: Specify the name of the overwrite YAML file, e.g.: ``70-netplan-set`` => ``/etc/netplan/70-netplan-set.yaml`` # SEE ALSO diff -Nru netplan.io-0.102/.github/workflows/check-coverage.yml netplan.io-0.101/.github/workflows/check-coverage.yml --- netplan.io-0.102/.github/workflows/check-coverage.yml 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/.github/workflows/check-coverage.yml 2020-12-09 11:32:25.000000000 +0000 @@ -6,7 +6,7 @@ push: branches: [ master ] pull_request: - branches: [ '**' ] + branches: [ master ] # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: diff -Nru netplan.io-0.102/.github/workflows/codeql-analysis.yml netplan.io-0.101/.github/workflows/codeql-analysis.yml --- netplan.io-0.102/.github/workflows/codeql-analysis.yml 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/.github/workflows/codeql-analysis.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,74 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ master ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] - schedule: - - cron: '17 21 * * 2' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - language: [ 'cpp', 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Installs the build dependencies - - name: Install build depends - run: | - sudo sed -i '/deb-src/s/^# //' /etc/apt/sources.list - sudo apt update - sudo apt build-dep netplan.io - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff -Nru netplan.io-0.102/Makefile netplan.io-0.101/Makefile --- netplan.io-0.102/Makefile 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/Makefile 2020-12-09 11:32:25.000000000 +0000 @@ -38,15 +38,15 @@ %.o: src/%.c $(CC) $(BUILDFLAGS) $(CFLAGS) $(LDFLAGS) -c $^ `pkg-config --cflags --libs glib-2.0 gio-2.0 yaml-0.1 uuid` -libnetplan.so.$(NETPLAN_SOVER): parse.o netplan.o util.o validation.o error.o parse-nm.o +libnetplan.so.$(NETPLAN_SOVER): parse.o util.o validation.o error.o $(CC) -shared -Wl,-soname,libnetplan.so.$(NETPLAN_SOVER) $(BUILDFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $^ `pkg-config --libs glib-2.0 gio-2.0 yaml-0.1` ln -snf libnetplan.so.$(NETPLAN_SOVER) libnetplan.so generate: libnetplan.so.$(NETPLAN_SOVER) nm.o networkd.o openvswitch.o generate.o sriov.o $(CC) $(BUILDFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $^ -L. -lnetplan `pkg-config --cflags --libs glib-2.0 gio-2.0 yaml-0.1 uuid` -netplan-dbus: src/dbus.c src/_features.h parse.o util.o validation.o error.o - $(CC) $(BUILDFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $(patsubst %.h,,$^) `pkg-config --cflags --libs libsystemd glib-2.0 gio-2.0 yaml-0.1` +netplan-dbus: src/dbus.c src/_features.h util.o + $(CC) $(BUILDFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $^ `pkg-config --cflags --libs libsystemd glib-2.0 gio-2.0` src/_features.h: src/[^_]*.[hc] printf "#include \nstatic const char *feature_flags[] __attribute__((__unused__)) = {\n" > $@ diff -Nru netplan.io-0.102/netplan/cli/commands/apply.py netplan.io-0.101/netplan/cli/commands/apply.py --- netplan.io-0.102/netplan/cli/commands/apply.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/netplan/cli/commands/apply.py 2020-12-09 11:32:25.000000000 +0000 @@ -74,10 +74,6 @@ busctl = shutil.which("busctl") if busctl is None: raise RuntimeError("missing busctl utility") - # XXX: DO NOT TOUCH or change this API call, it is used by snapd to communicate - # using core20 netplan binary/client/CLI on core18 base systems. Any change - # must be agreed upon with the snapd team, so we don't break support for - # base systems running older netplan versions. res = subprocess.call([busctl, "call", "--quiet", "--system", "io.netplan.Netplan", # the service "/io/netplan/Netplan", # the object diff -Nru netplan.io-0.102/netplan/cli/commands/get.py netplan.io-0.101/netplan/cli/commands/get.py --- netplan.io-0.102/netplan/cli/commands/get.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/netplan/cli/commands/get.py 2020-12-09 11:32:25.000000000 +0000 @@ -48,7 +48,7 @@ if self.key != 'all': # The 'network.' prefix is optional for netsted keys, its always assumed to be there - if not self.key.startswith('network.') and not self.key == 'network': + if not self.key.startswith('network.'): self.key = 'network.' + self.key # Split at '.' but not at '\.' via negative lookbehind expression for k in re.split(r'(? (str, dict): - network = set_tree.get('network', {}) - # A mapping of 'origin-hint' -> YAML tree (one subtree per netdef) - subtrees = dict() - for devtype in network: - if devtype in GLOBAL_KEYS: - continue # special handling of global keys down below - for netdef in network.get(devtype, []): - hint = FALLBACK_HINT - filename = utils.netplan_get_filename_by_id(netdef, self.root_dir) - if filename: - hint = os.path.basename(filename)[:-5] # strip prefix and .yaml - netdef_tree = {'network': {devtype: {netdef: network.get(devtype).get(netdef)}}} - # Merge all netdef trees which are going to be written to the same file/hint - subtrees[hint] = self.merge(subtrees.get(hint, {}), netdef_tree) - - # Merge GLOBAL_KEYS into one of the available subtrees - # Write to same file (if only one hint/subtree is available) - # Write to FALLBACK_HINT if multiple hints/subtrees are available, as we do not know where it is supposed to go - if any(network.get(key) for key in GLOBAL_KEYS): - # Write to the same file, if we have only one file-hint or to FALLBACK_HINT otherwise - hint = list(subtrees)[0] if len(subtrees) == 1 else FALLBACK_HINT - for key in GLOBAL_KEYS: - tree = {'network': {key: network.get(key)}} - subtrees[hint] = self.merge(subtrees.get(hint, {}), tree) - - # return a list of (str:hint, dict:subtree) tuples - return subtrees.items() - def command_set(self): - if self.origin_hint is not None and len(self.origin_hint) == 0: + if len(self.origin_hint) == 0: raise Exception('Invalid/empty origin-hint') split = self.key_value.split('=', 1) if len(split) != 2: raise Exception('Invalid value specified') key, value = split set_tree = self.parse_key(key, yaml.safe_load(value)) - - hints = [(self.origin_hint, set_tree)] - # Override YAML config in each individual netdef file if origin-hint is not set - if self.origin_hint is None: - hints = self.split_tree_by_hint(set_tree) - - for hint, subtree in hints: - self.write_file(subtree, hint + '.yaml', self.root_dir) + self.write_file(set_tree, self.origin_hint + '.yaml', self.root_dir) def parse_key(self, key, value): # The 'network.' prefix is optional for netsted keys, its always assumed to be there - if not key.startswith('network.') and not key == 'network': + if not key.startswith('network.'): key = 'network.' + key # Split at '.' but not at '\.' via negative lookbehind expression split = re.split(r'(?config_id) { + if (d->config_id) root_dir = g_strdup_printf("--root-dir=%s/netplan-config-%s", g_get_tmp_dir(), d->config_id); - args[cur_arg] = root_dir; - cur_arg++; - } - gchar *argv[] = {SBINDIR "/" "netplan", "set", config_delta, args[0], args[1], NULL}; + gchar *argv[] = {SBINDIR "/" "netplan", "set", config_delta, origin, root_dir, NULL}; // for tests only: allow changing what netplan to run if (getenv("DBUS_TEST_NETPLAN_CMD") != 0) diff -Nru netplan.io-0.102/src/generate.c netplan.io-0.101/src/generate.c --- netplan.io-0.102/src/generate.c 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/src/generate.c 2020-12-09 11:32:25.000000000 +0000 @@ -171,6 +171,18 @@ return ret; } +static void +process_input_file(const char* f) +{ + GError* error = NULL; + + g_debug("Processing input file %s..", f); + if (!netplan_parse_yaml(f, &error)) { + g_fprintf(stderr, "%s\n", error->message); + exit(1); + } +} + int main(int argc, char** argv) { GError* error = NULL; @@ -213,8 +225,27 @@ if (files && !called_as_generator) { for (gchar** f = files; f && *f; ++f) process_input_file(*f); - } else if (!process_yaml_hierarchy(rootdir)) - return 1; // LCOV_EXCL_LINE + } else { + /* Files with asciibetically higher names override/append settings from + * earlier ones (in all config dirs); files in /run/netplan/ + * shadow files in /etc/netplan/ which shadow files in /lib/netplan/. + * To do that, we put all found files in a hash table, then sort it by + * file name, and add the entries from /run after the ones from /etc + * and those after the ones from /lib. */ + if (find_yaml_glob(rootdir, &gl) != 0) + return 1; // LCOV_EXCL_LINE + /* keys are strdup()ed, free them; values point into the glob_t, don't free them */ + g_autoptr(GHashTable) configs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + g_autoptr(GList) config_keys = NULL; + + for (size_t i = 0; i < gl.gl_pathc; ++i) + g_hash_table_insert(configs, g_path_get_basename(gl.gl_pathv[i]), gl.gl_pathv[i]); + + config_keys = g_list_sort(g_hash_table_get_keys(configs), (GCompareFunc) strcmp); + + for (GList* i = config_keys; i != NULL; i = i->next) + process_input_file(g_hash_table_lookup(configs, i->data)); + } netdefs = netplan_finish_parse(&error); if (error) { @@ -228,15 +259,16 @@ cleanup_ovs_conf(rootdir); cleanup_sriov_conf(rootdir); - if (mapping_iface && netdefs) + if (mapping_iface && netdefs) { return find_interface(mapping_iface); + } /* Generate backend specific configuration files from merged data. */ - write_ovs_conf_finish(rootdir); // OVS cleanup unit is always written if (netdefs) { g_debug("Generating output files.."); g_list_foreach (netdefs_ordered, nd_iterator_list, rootdir); write_nm_conf_finish(rootdir); + write_ovs_conf_finish(rootdir); if (any_sriov) write_sriov_conf_finish(rootdir); /* We may have written .rules & .link files, thus we must * invalidate udevd cache of its config as by default it only diff -Nru netplan.io-0.102/src/netplan.c netplan.io-0.101/src/netplan.c --- netplan.io-0.102/src/netplan.c 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/src/netplan.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,209 +0,0 @@ -/* - * Copyright (C) 2021 Canonical, Ltd. - * Author: Lukas Märdian - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include - -#include "netplan.h" -#include "parse.h" - -static gboolean -write_match(yaml_event_t* event, yaml_emitter_t* emitter, const NetplanNetDefinition* def) -{ - YAML_SCALAR_PLAIN(event, emitter, "match"); - YAML_MAPPING_OPEN(event, emitter); - YAML_STRING(event, emitter, "name", def->match.original_name); - YAML_MAPPING_CLOSE(event, emitter); - return TRUE; -error: return FALSE; // LCOV_EXCL_LINE -} - -typedef struct { - yaml_event_t* event; - yaml_emitter_t* emitter; -} _passthrough_handler_data; - -static void -_passthrough_handler(GQuark key_id, gpointer value, gpointer user_data) -{ - _passthrough_handler_data *d = user_data; - const gchar* key = g_quark_to_string(key_id); - YAML_SCALAR_PLAIN(d->event, d->emitter, key); - YAML_SCALAR_QUOTED(d->event, d->emitter, value); -error: return; // LCOV_EXCL_LINE -} - -static gboolean -write_backend_settings(yaml_event_t* event, yaml_emitter_t* emitter, NetplanBackendSettings s) { - if (s.nm.uuid || s.nm.name || s.nm.passthrough) { - YAML_SCALAR_PLAIN(event, emitter, "networkmanager"); - YAML_MAPPING_OPEN(event, emitter); - if (s.nm.uuid) { - YAML_SCALAR_PLAIN(event, emitter, "uuid"); - YAML_SCALAR_PLAIN(event, emitter, s.nm.uuid); - } - if (s.nm.name) { - YAML_SCALAR_PLAIN(event, emitter, "name"); - YAML_SCALAR_QUOTED(event, emitter, s.nm.name); - } - if (s.nm.passthrough) { - YAML_SCALAR_PLAIN(event, emitter, "passthrough"); - YAML_MAPPING_OPEN(event, emitter); - _passthrough_handler_data d; - d.event = event; - d.emitter = emitter; - g_datalist_foreach(&s.nm.passthrough, _passthrough_handler, &d); - YAML_MAPPING_CLOSE(event, emitter); - } - YAML_MAPPING_CLOSE(event, emitter); - } - return TRUE; -error: return FALSE; // LCOV_EXCL_LINE -} - -static gboolean -write_access_points(yaml_event_t* event, yaml_emitter_t* emitter, const NetplanNetDefinition* def) -{ - NetplanWifiAccessPoint* ap = NULL; - GHashTableIter iter; - gpointer key, value; - YAML_SCALAR_PLAIN(event, emitter, "access-points"); - YAML_MAPPING_OPEN(event, emitter); - g_hash_table_iter_init(&iter, def->access_points); - while (g_hash_table_iter_next(&iter, &key, &value)) { - ap = value; - YAML_SCALAR_QUOTED(event, emitter, ap->ssid); - YAML_MAPPING_OPEN(event, emitter); - if (ap->hidden) { - YAML_SCALAR_PLAIN(event, emitter, "hidden"); - YAML_SCALAR_PLAIN(event, emitter, "true"); - } - YAML_SCALAR_PLAIN(event, emitter, "mode"); - if (ap->mode != NETPLAN_WIFI_MODE_OTHER) { - YAML_SCALAR_PLAIN(event, emitter, netplan_wifi_mode_to_str[ap->mode]); - } else { - // LCOV_EXCL_START - g_warning("netplan: serialize: %s (SSID %s), unsupported AP mode, falling back to 'infrastructure'", def->id, ap->ssid); - YAML_SCALAR_PLAIN(event, emitter, "infrastructure"); //TODO: add YAML comment about unsupported mode - // LCOV_EXCL_STOP - } - if (!write_backend_settings(event, emitter, ap->backend_settings)) goto error; - YAML_MAPPING_CLOSE(event, emitter); - } - YAML_MAPPING_CLOSE(event, emitter); - return TRUE; -error: return FALSE; // LCOV_EXCL_LINE -} - -/** - * Generate the Netplan YAML configuration for the selected netdef - * @def: NetplanNetDefinition (as pointer), the data to be serialized - * @rootdir: If not %NULL, generate configuration in this root directory - * (useful for testing). - */ -void -write_netplan_conf(const NetplanNetDefinition* def, const char* rootdir) -{ - g_autofree gchar *filename = NULL; - g_autofree gchar *path = NULL; - - /* NetworkManager produces one file per connection profile - * It's 90-* to be higher priority than the default 70-netplan-set.yaml */ - if (def->backend_settings.nm.uuid) - filename = g_strconcat("90-NM-", def->backend_settings.nm.uuid, ".yaml", NULL); - else - filename = g_strconcat("10-netplan-", def->id, ".yaml", NULL); - path = g_build_path(G_DIR_SEPARATOR_S, rootdir ?: G_DIR_SEPARATOR_S, "etc", "netplan", filename, NULL); - - /* Start rendering YAML output */ - yaml_emitter_t emitter_data; - yaml_event_t event_data; - yaml_emitter_t* emitter = &emitter_data; - yaml_event_t* event = &event_data; - FILE *output = fopen(path, "wb"); - - YAML_OUT_START(event, emitter, output); - /* build the netplan boilerplate YAML structure */ - YAML_SCALAR_PLAIN(event, emitter, "network"); - YAML_MAPPING_OPEN(event, emitter); - // TODO: global backend/renderer - YAML_STRING_PLAIN(event, emitter, "version", "2"); - YAML_SCALAR_PLAIN(event, emitter, netplan_def_type_to_str[def->type]); - YAML_MAPPING_OPEN(event, emitter); - YAML_SCALAR_PLAIN(event, emitter, def->id); - YAML_MAPPING_OPEN(event, emitter); - YAML_STRING_PLAIN(event, emitter, "renderer", netplan_backend_to_name[def->backend]) - - if (def->type == NETPLAN_DEF_TYPE_NM) - goto only_passthrough; //do not try to handle "unknown" connection types - - if (def->has_match) - write_match(event, emitter, def); - - /* wake-on-lan */ - if (def->wake_on_lan) - YAML_STRING_PLAIN(event, emitter, "wakeonlan", "true"); - - /* some modem settings to auto-detect GSM vs CDMA connections */ - if (def->modem_params.auto_config) - YAML_STRING_PLAIN(event, emitter, "auto-config", "true"); - YAML_STRING(event, emitter, "apn", def->modem_params.apn); - YAML_STRING(event, emitter, "device-id", def->modem_params.device_id); - YAML_STRING(event, emitter, "network-id", def->modem_params.network_id); - YAML_STRING(event, emitter, "pin", def->modem_params.pin); - YAML_STRING(event, emitter, "sim-id", def->modem_params.sim_id); - YAML_STRING(event, emitter, "sim-operator-id", def->modem_params.sim_operator_id); - - if (def->type == NETPLAN_DEF_TYPE_WIFI) - if (!write_access_points(event, emitter, def)) goto error; -only_passthrough: - if (!write_backend_settings(event, emitter, def->backend_settings)) goto error; - - /* Close remaining mappings */ - YAML_MAPPING_CLOSE(event, emitter); - YAML_MAPPING_CLOSE(event, emitter); - YAML_MAPPING_CLOSE(event, emitter); - - /* Tear down the YAML emitter */ - YAML_OUT_STOP(event, emitter); - fclose(output); - return; - - // LCOV_EXCL_START -error: - yaml_emitter_delete(emitter); - fclose(output); - // LCOV_EXCL_STOP -} - -/* XXX: implement the following functions, once needed: -void write_netplan_conf_finish(const char* rootdir) -void cleanup_netplan_conf(const char* rootdir) -*/ - -/** - * Helper function for testing only - */ -void -_write_netplan_conf(const char* netdef_id, const char* rootdir) -{ - GHashTable* ht = NULL; - const NetplanNetDefinition* def = NULL; - ht = netplan_finish_parse(NULL); - def = g_hash_table_lookup(ht, netdef_id); - write_netplan_conf(def, rootdir); -} diff -Nru netplan.io-0.102/src/netplan.h netplan.io-0.101/src/netplan.h --- netplan.io-0.102/src/netplan.h 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/src/netplan.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2021 Canonical, Ltd. - * Author: Lukas Märdian - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - -#include "parse.h" - -#define YAML_MAPPING_OPEN(event_ptr, emitter_ptr) \ -{ \ - yaml_mapping_start_event_initialize(event_ptr, NULL, (yaml_char_t *)YAML_MAP_TAG, 1, YAML_ANY_MAPPING_STYLE); \ - if (!yaml_emitter_emit(emitter_ptr, event_ptr)) goto error; \ -} -#define YAML_MAPPING_CLOSE(event_ptr, emitter_ptr) \ -{ \ - yaml_mapping_end_event_initialize(event_ptr); \ - if (!yaml_emitter_emit(emitter_ptr, event_ptr)) goto error; \ -} -#define YAML_SCALAR_PLAIN(event_ptr, emitter_ptr, scalar) \ -{ \ - yaml_scalar_event_initialize(event_ptr, NULL, (yaml_char_t *)YAML_STR_TAG, (yaml_char_t *)scalar, strlen(scalar), 1, 0, YAML_PLAIN_SCALAR_STYLE); \ - if (!yaml_emitter_emit(emitter_ptr, event_ptr)) goto error; \ -} -/* Implicit plain and quoted tags, double quoted style */ -#define YAML_SCALAR_QUOTED(event_ptr, emitter_ptr, scalar) \ -{ \ - yaml_scalar_event_initialize(event_ptr, NULL, (yaml_char_t *)YAML_STR_TAG, (yaml_char_t *)scalar, strlen(scalar), 1, 1, YAML_DOUBLE_QUOTED_SCALAR_STYLE); \ - if (!yaml_emitter_emit(emitter_ptr, event_ptr)) goto error; \ -} -#define YAML_STRING(event_ptr, emitter_ptr, key, value_ptr) \ -{ \ - if (value_ptr) { \ - YAML_SCALAR_PLAIN(event, emitter, key); \ - YAML_SCALAR_QUOTED(event, emitter, value_ptr); \ - } \ -} -#define YAML_STRING_PLAIN(event_ptr, emitter_ptr, key, value_ptr) \ -{ \ - if (value_ptr) { \ - YAML_SCALAR_PLAIN(event, emitter, key); \ - YAML_SCALAR_PLAIN(event, emitter, value_ptr); \ - } \ -} -/* open YAML emitter, document, stream and initial mapping */ -#define YAML_OUT_START(event_ptr, emitter_ptr, file) \ -{ \ - yaml_emitter_initialize(emitter_ptr); \ - yaml_emitter_set_output_file(emitter_ptr, file); \ - yaml_stream_start_event_initialize(event_ptr, YAML_UTF8_ENCODING); \ - if (!yaml_emitter_emit(emitter_ptr, event_ptr)) goto error; \ - yaml_document_start_event_initialize(event_ptr, NULL, NULL, NULL, 1); \ - if (!yaml_emitter_emit(emitter_ptr, event_ptr)) goto error; \ - YAML_MAPPING_OPEN(event_ptr, emitter_ptr); \ -} -/* close initial YAML mapping, document, stream and emitter */ -#define YAML_OUT_STOP(event_ptr, emitter_ptr) \ -{ \ - YAML_MAPPING_CLOSE(event_ptr, emitter_ptr); \ - yaml_document_end_event_initialize(event_ptr, 1); \ - if (!yaml_emitter_emit(emitter_ptr, event_ptr)) goto error; \ - yaml_stream_end_event_initialize(event_ptr); \ - if (!yaml_emitter_emit(emitter_ptr, event_ptr)) goto error; \ - yaml_emitter_delete(emitter_ptr); \ -} - -static const char* const netplan_def_type_to_str[NETPLAN_DEF_TYPE_MAX_] = { - [NETPLAN_DEF_TYPE_NONE] = NULL, - [NETPLAN_DEF_TYPE_ETHERNET] = "ethernets", - [NETPLAN_DEF_TYPE_WIFI] = "wifis", - [NETPLAN_DEF_TYPE_MODEM] = "modems", - [NETPLAN_DEF_TYPE_VIRTUAL] = NULL, - [NETPLAN_DEF_TYPE_BRIDGE] = "bridges", - [NETPLAN_DEF_TYPE_BOND] = "bonds", - [NETPLAN_DEF_TYPE_VLAN] = "vlans", - [NETPLAN_DEF_TYPE_TUNNEL] = "tunnels", - [NETPLAN_DEF_TYPE_PORT] = NULL, - [NETPLAN_DEF_TYPE_NM] = "nm-devices", -}; - -void write_netplan_conf(const NetplanNetDefinition* def, const char* rootdir); diff -Nru netplan.io-0.102/src/networkd.c netplan.io-0.101/src/networkd.c --- netplan.io-0.102/src/networkd.c 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/src/networkd.c 2020-12-09 11:32:25.000000000 +0000 @@ -142,8 +142,6 @@ g_string_append_printf(params, "Mode=%s\n", tunnel_mode_to_string(def->tunnel.mode)); g_string_append_printf(params, "Local=%s\n", def->tunnel.local_ip); g_string_append_printf(params, "Remote=%s\n", def->tunnel.remote_ip); - if (def->tunnel.ttl) - g_string_append_printf(params, "TTL=%u\n", def->tunnel.ttl); if (def->tunnel.input_key) g_string_append_printf(params, "InputKey=%s\n", def->tunnel.input_key); if (def->tunnel.output_key) @@ -229,7 +227,7 @@ return; /* do we need to write a .link file? */ - if (!def->set_name && !def->wake_on_lan && !def->mtubytes) + if (!def->set_name && !def->wake_on_lan && !def->mtubytes && !def->set_mac) return; /* build file contents */ @@ -243,6 +241,9 @@ g_string_append_printf(s, "WakeOnLan=%s\n", def->wake_on_lan ? "magic" : "off"); if (def->mtubytes) g_string_append_printf(s, "MTUBytes=%u\n", def->mtubytes); + if (def->set_mac) + g_string_append_printf(s, "MACAddress=%s\n", def->set_mac); + orig_umask = umask(022); g_string_free_to_file(s, rootdir, path, ".link"); @@ -443,10 +444,6 @@ g_string_append_printf(s, "Table=%d\n", r->table); if (r->mtubytes != NETPLAN_MTU_UNSPEC) g_string_append_printf(s, "MTUBytes=%u\n", r->mtubytes); - if (r->congestion_window != NETPLAN_CONGESTION_WINDOW_UNSPEC) - g_string_append_printf(s, "InitialCongestionWindow=%u\n", r->congestion_window); - if (r->advertised_receive_window != NETPLAN_ADVERTISED_RECEIVE_WINDOW_UNSPEC) - g_string_append_printf(s, "InitialAdvertisedReceiveWindow=%u\n", r->advertised_receive_window); } static void @@ -572,13 +569,13 @@ } } - if (def->mtubytes) + if (def->mtubytes) { g_string_append_printf(link, "MTUBytes=%u\n", def->mtubytes); - if (def->set_mac) - g_string_append_printf(link, "MACAddress=%s\n", def->set_mac); + } - if (def->emit_lldp) + if (def->emit_lldp) { g_string_append(network, "EmitLLDP=true\n"); + } if (def->dhcp4 && def->dhcp6) g_string_append(network, "DHCP=yes\n"); @@ -900,6 +897,7 @@ static void write_wpa_unit(const NetplanNetDefinition* def, const char* rootdir) { + g_autoptr(GError) err = NULL; g_autofree gchar *stdouth = NULL; stdouth = systemd_escape(def->id); @@ -976,8 +974,8 @@ case NETPLAN_WIFI_MODE_ADHOC: g_string_append(s, " mode=1\n"); break; - default: - g_fprintf(stderr, "ERROR: %s: %s: networkd does not support this wifi mode\n", def->id, ap->ssid); + case NETPLAN_WIFI_MODE_AP: + g_fprintf(stderr, "ERROR: %s: networkd does not support wifi in access point mode\n", def->id); exit(1); } diff -Nru netplan.io-0.102/src/nm.c netplan.io-0.101/src/nm.c --- netplan.io-0.102/src/nm.c 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/src/nm.c 2020-12-09 11:32:25.000000000 +0000 @@ -1,7 +1,6 @@ /* - * Copyright (C) 2016-2021 Canonical, Ltd. + * Copyright (C) 2016 Canonical, Ltd. * Author: Martin Pitt - * Author: Lukas Märdian * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,10 +29,10 @@ #include "parse.h" #include "util.h" #include "validation.h" -#include "parse-nm.h" GString* udev_rules; + /** * Append NM device specifier of @def to @s. */ @@ -80,13 +79,10 @@ static const gboolean modem_is_gsm(const NetplanNetDefinition* def) { - if ( def->modem_params.apn - || def->modem_params.auto_config - || def->modem_params.device_id - || def->modem_params.network_id - || def->modem_params.pin - || def->modem_params.sim_id - || def->modem_params.sim_operator_id) + if (def->type == NETPLAN_DEF_TYPE_MODEM && (def->modem_params.apn || + def->modem_params.auto_config || def->modem_params.device_id || + def->modem_params.network_id || def->modem_params.pin || + def->modem_params.sim_id || def->modem_params.sim_operator_id)) return TRUE; return FALSE; @@ -119,9 +115,6 @@ if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_WIREGUARD) return "wireguard"; return "ip-tunnel"; - case NETPLAN_DEF_TYPE_NM: - /* needs to be overriden by passthrough "connection.type" setting */ - return NULL; // LCOV_EXCL_START default: g_assert_not_reached(); @@ -186,29 +179,19 @@ } static void -write_search_domains(const NetplanNetDefinition* def, const char* group, GKeyFile *kf) +write_search_domains(const NetplanNetDefinition* def, GString *s) { if (def->search_domains) { - const gchar* list[def->search_domains->len]; + g_string_append(s, "dns-search="); for (unsigned i = 0; i < def->search_domains->len; ++i) - list[i] = g_array_index(def->search_domains, char*, i); - g_key_file_set_string_list(kf, group, "dns-search", list, def->search_domains->len); + g_string_append_printf(s, "%s;", g_array_index(def->search_domains, char*, i)); + g_string_append(s, "\n"); } } static void -write_routes(const NetplanNetDefinition* def, GKeyFile *kf, int family) +write_routes(const NetplanNetDefinition* def, GString *s, int family) { - const gchar* group = NULL; - gchar* tmp_key = NULL; - GString* tmp_val = NULL; - - if (family == AF_INET) - group = "ipv4"; - else if (family == AF_INET6) - group = "ipv6"; - g_assert(group != NULL); - if (def->routes != NULL) { for (unsigned i = 0, j = 1; i < def->routes->len; ++i) { const NetplanIPRoute *cur_route = g_array_index(def->routes, NetplanIPRoute*, i); @@ -232,41 +215,28 @@ exit(1); } - tmp_key = g_strdup_printf("route%d", j); - tmp_val = g_string_new(NULL); - g_string_printf(tmp_val, "%s,%s", cur_route->to, cur_route->via); + g_string_append_printf(s, "route%d=%s,%s", + j, cur_route->to, cur_route->via); if (cur_route->metric != NETPLAN_METRIC_UNSPEC) - g_string_append_printf(tmp_val, ",%d", cur_route->metric); - g_key_file_set_string(kf, group, tmp_key, tmp_val->str); - g_free(tmp_key); - g_string_free(tmp_val, TRUE); + g_string_append_printf(s, ",%d", cur_route->metric); + g_string_append(s, "\n"); if ( cur_route->onlink - || cur_route->advertised_receive_window - || cur_route->congestion_window || cur_route->mtubytes || cur_route->table != NETPLAN_ROUTE_TABLE_UNSPEC || cur_route->from) { - tmp_key = g_strdup_printf("route%d_options", j); - tmp_val = g_string_new(NULL); + g_string_append_printf(s, "route%d_options=", j); if (cur_route->onlink) { /* onlink for IPv6 addresses is only supported since nm-1.18.0. */ - g_string_append_printf(tmp_val, "onlink=true,"); + g_string_append_printf(s, "onlink=true,"); } - if (cur_route->advertised_receive_window != NETPLAN_ADVERTISED_RECEIVE_WINDOW_UNSPEC) - g_string_append_printf(tmp_val, "initrwnd=%u,", cur_route->advertised_receive_window); - if (cur_route->congestion_window != NETPLAN_CONGESTION_WINDOW_UNSPEC) - g_string_append_printf(tmp_val, "initcwnd=%u,", cur_route->congestion_window); if (cur_route->mtubytes != NETPLAN_MTU_UNSPEC) - g_string_append_printf(tmp_val, "mtu=%u,", cur_route->mtubytes); + g_string_append_printf(s, "mtu=%u,", cur_route->mtubytes); if (cur_route->table != NETPLAN_ROUTE_TABLE_UNSPEC) - g_string_append_printf(tmp_val, "table=%u,", cur_route->table); + g_string_append_printf(s, "table=%u,", cur_route->table); if (cur_route->from) - g_string_append_printf(tmp_val, "src=%s,", cur_route->from); - tmp_val->str[tmp_val->len - 1] = '\0'; //remove trailing comma - g_key_file_set_string(kf, group, tmp_key, tmp_val->str); - g_free(tmp_key); - g_string_free(tmp_val, TRUE); + g_string_append_printf(s, "src=%s,", cur_route->from); + s->str[s->len - 1] = '\n'; } j++; } @@ -274,86 +244,100 @@ } static void -write_bond_parameters(const NetplanNetDefinition* def, GKeyFile *kf) +write_bond_parameters(const NetplanNetDefinition* def, GString *s) { - GString* tmp_val = NULL; + GString* params = NULL; + + params = g_string_sized_new(200); + if (def->bond_params.mode) - g_key_file_set_string(kf, "bond", "mode", def->bond_params.mode); + g_string_append_printf(params, "\nmode=%s", def->bond_params.mode); if (def->bond_params.lacp_rate) - g_key_file_set_string(kf, "bond", "lacp_rate", def->bond_params.lacp_rate); + g_string_append_printf(params, "\nlacp_rate=%s", def->bond_params.lacp_rate); if (def->bond_params.monitor_interval) - g_key_file_set_string(kf, "bond", "miimon", def->bond_params.monitor_interval); + g_string_append_printf(params, "\nmiimon=%s", def->bond_params.monitor_interval); if (def->bond_params.min_links) - g_key_file_set_integer(kf, "bond", "min_links", def->bond_params.min_links); + g_string_append_printf(params, "\nmin_links=%d", def->bond_params.min_links); if (def->bond_params.transmit_hash_policy) - g_key_file_set_string(kf, "bond", "xmit_hash_policy", def->bond_params.transmit_hash_policy); + g_string_append_printf(params, "\nxmit_hash_policy=%s", def->bond_params.transmit_hash_policy); if (def->bond_params.selection_logic) - g_key_file_set_string(kf, "bond", "ad_select", def->bond_params.selection_logic); + g_string_append_printf(params, "\nad_select=%s", def->bond_params.selection_logic); if (def->bond_params.all_slaves_active) - g_key_file_set_integer(kf, "bond", "all_slaves_active", def->bond_params.all_slaves_active); + g_string_append_printf(params, "\nall_slaves_active=%d", def->bond_params.all_slaves_active); if (def->bond_params.arp_interval) - g_key_file_set_string(kf, "bond", "arp_interval", def->bond_params.arp_interval); + g_string_append_printf(params, "\narp_interval=%s", def->bond_params.arp_interval); if (def->bond_params.arp_ip_targets) { - tmp_val = g_string_new(NULL); + g_string_append_printf(params, "\narp_ip_target="); for (unsigned i = 0; i < def->bond_params.arp_ip_targets->len; ++i) { if (i > 0) - g_string_append_printf(tmp_val, ","); - g_string_append_printf(tmp_val, "%s", g_array_index(def->bond_params.arp_ip_targets, char*, i)); + g_string_append_printf(params, ","); + g_string_append_printf(params, "%s", g_array_index(def->bond_params.arp_ip_targets, char*, i)); } - g_key_file_set_string(kf, "bond", "arp_ip_target", tmp_val->str); - g_string_free(tmp_val, TRUE); } if (def->bond_params.arp_validate) - g_key_file_set_string(kf, "bond", "arp_validate", def->bond_params.arp_validate); + g_string_append_printf(params, "\narp_validate=%s", def->bond_params.arp_validate); if (def->bond_params.arp_all_targets) - g_key_file_set_string(kf, "bond", "arp_all_targets", def->bond_params.arp_all_targets); + g_string_append_printf(params, "\narp_all_targets=%s", def->bond_params.arp_all_targets); if (def->bond_params.up_delay) - g_key_file_set_string(kf, "bond", "updelay", def->bond_params.up_delay); + g_string_append_printf(params, "\nupdelay=%s", def->bond_params.up_delay); if (def->bond_params.down_delay) - g_key_file_set_string(kf, "bond", "downdelay", def->bond_params.down_delay); + g_string_append_printf(params, "\ndowndelay=%s", def->bond_params.down_delay); if (def->bond_params.fail_over_mac_policy) - g_key_file_set_string(kf, "bond", "fail_over_mac", def->bond_params.fail_over_mac_policy); + g_string_append_printf(params, "\nfail_over_mac=%s", def->bond_params.fail_over_mac_policy); if (def->bond_params.gratuitous_arp) { - g_key_file_set_integer(kf, "bond", "num_grat_arp", def->bond_params.gratuitous_arp); + g_string_append_printf(params, "\nnum_grat_arp=%d", def->bond_params.gratuitous_arp); /* Work around issue in NM where unset unsolicited_na will overwrite num_grat_arp: * https://github.com/NetworkManager/NetworkManager/commit/42b0bef33c77a0921590b2697f077e8ea7805166 */ - g_key_file_set_integer(kf, "bond", "num_unsol_na", def->bond_params.gratuitous_arp); + g_string_append_printf(params, "\nnum_unsol_na=%d", def->bond_params.gratuitous_arp); } if (def->bond_params.packets_per_slave) - g_key_file_set_integer(kf, "bond", "packets_per_slave", def->bond_params.packets_per_slave); + g_string_append_printf(params, "\npackets_per_slave=%d", def->bond_params.packets_per_slave); if (def->bond_params.primary_reselect_policy) - g_key_file_set_string(kf, "bond", "primary_reselect", def->bond_params.primary_reselect_policy); + g_string_append_printf(params, "\nprimary_reselect=%s", def->bond_params.primary_reselect_policy); if (def->bond_params.resend_igmp) - g_key_file_set_integer(kf, "bond", "resend_igmp", def->bond_params.resend_igmp); + g_string_append_printf(params, "\nresend_igmp=%d", def->bond_params.resend_igmp); if (def->bond_params.learn_interval) - g_key_file_set_string(kf, "bond", "lp_interval", def->bond_params.learn_interval); + g_string_append_printf(params, "\nlp_interval=%s", def->bond_params.learn_interval); if (def->bond_params.primary_slave) - g_key_file_set_string(kf, "bond", "primary", def->bond_params.primary_slave); + g_string_append_printf(params, "\nprimary=%s", def->bond_params.primary_slave); + + if (params->len > 0) + g_string_append_printf(s, "\n[bond]%s\n", params->str); + + g_string_free(params, TRUE); } static void -write_bridge_params(const NetplanNetDefinition* def, GKeyFile *kf) +write_bridge_params(const NetplanNetDefinition* def, GString *s) { + GString* params = NULL; + if (def->custom_bridging) { + params = g_string_sized_new(200); + if (def->bridge_params.ageing_time) - g_key_file_set_string(kf, "bridge", "ageing-time", def->bridge_params.ageing_time); + g_string_append_printf(params, "ageing-time=%s\n", def->bridge_params.ageing_time); if (def->bridge_params.priority) - g_key_file_set_uint64(kf, "bridge", "priority", def->bridge_params.priority); + g_string_append_printf(params, "priority=%u\n", def->bridge_params.priority); if (def->bridge_params.forward_delay) - g_key_file_set_string(kf, "bridge", "forward-delay", def->bridge_params.forward_delay); + g_string_append_printf(params, "forward-delay=%s\n", def->bridge_params.forward_delay); if (def->bridge_params.hello_time) - g_key_file_set_string(kf, "bridge", "hello-time", def->bridge_params.hello_time); + g_string_append_printf(params, "hello-time=%s\n", def->bridge_params.hello_time); if (def->bridge_params.max_age) - g_key_file_set_string(kf, "bridge", "max-age", def->bridge_params.max_age); - g_key_file_set_boolean(kf, "bridge", "stp", def->bridge_params.stp); + g_string_append_printf(params, "max-age=%s\n", def->bridge_params.max_age); + g_string_append_printf(params, "stp=%s\n", def->bridge_params.stp ? "true" : "false"); + + g_string_append_printf(s, "\n[bridge]\n%s", params->str); + + g_string_free(params, TRUE); } } static void -write_wireguard_params(const NetplanNetDefinition* def, GKeyFile *kf) +write_wireguard_params(const NetplanNetDefinition* def, GString *s) { - gchar* tmp_group = NULL; g_assert(def->tunnel.private_key); + g_string_append(s, "\n[wireguard]\n"); /* The key was already validated via validate_tunnel_grammar(), but we need * to differentiate between base64 key VS absolute path key-file. And a base64 @@ -363,23 +347,22 @@ g_fprintf(stderr, "%s: private key needs to be base64 encoded when using the NM backend\n", def->id); exit(1); } else - g_key_file_set_string(kf, "wireguard", "private-key", def->tunnel.private_key); + g_string_append_printf(s, "private-key=%s\n", def->tunnel.private_key); if (def->tunnel.port) - g_key_file_set_uint64(kf, "wireguard", "listen-port", def->tunnel.port); + g_string_append_printf(s, "listen-port=%u\n", def->tunnel.port); if (def->tunnel.fwmark) - g_key_file_set_uint64(kf, "wireguard", "fwmark", def->tunnel.fwmark); + g_string_append_printf(s, "fwmark=%u\n", def->tunnel.fwmark); for (guint i = 0; i < def->wireguard_peers->len; i++) { NetplanWireguardPeer *peer = g_array_index (def->wireguard_peers, NetplanWireguardPeer*, i); g_assert(peer->public_key); - tmp_group = g_strdup_printf("wireguard-peer.%s", peer->public_key); + g_string_append_printf(s, "\n[wireguard-peer.%s]\n", peer->public_key); if (peer->keepalive) - g_key_file_set_integer(kf, tmp_group, "persistent-keepalive", peer->keepalive); + g_string_append_printf(s, "persistent-keepalive=%d\n", peer->keepalive); if (peer->endpoint) - g_key_file_set_string(kf, tmp_group, "endpoint", peer->endpoint); - + g_string_append_printf(s, "endpoint=%s\n", peer->endpoint); /* The key was already validated via validate_tunnel_grammar(), but we need * to differentiate between base64 key VS absolute path key-file. And a base64 * string could (theoretically) start with '/', so we use is_wireguard_key() @@ -389,93 +372,111 @@ g_fprintf(stderr, "%s: shared key needs to be base64 encoded when using the NM backend\n", def->id); exit(1); } else { - g_key_file_set_value(kf, tmp_group, "preshared-key", peer->preshared_key); - g_key_file_set_uint64(kf, tmp_group, "preshared-key-flags", 0); + g_string_append_printf(s, "preshared-key=%s\n", peer->preshared_key); + g_string_append(s, "preshared-key-flags=0\n"); } } if (peer->allowed_ips && peer->allowed_ips->len > 0) { - const gchar* list[peer->allowed_ips->len]; - for (guint j = 0; j < peer->allowed_ips->len; ++j) - list[j] = g_array_index(peer->allowed_ips, char*, j); - g_key_file_set_string_list(kf, tmp_group, "allowed-ips", list, peer->allowed_ips->len); + g_string_append(s, "allowed-ips="); + for (guint i = 0; i < peer->allowed_ips->len; ++i) { + if (i > 0 ) g_string_append_c(s, ';'); + g_string_append_printf(s, "%s", g_array_index(peer->allowed_ips, char*, i)); + } + g_string_append_c(s, '\n'); } - g_free(tmp_group); } } static void -write_tunnel_params(const NetplanNetDefinition* def, GKeyFile *kf) +write_tunnel_params(const NetplanNetDefinition* def, GString *s) { - g_key_file_set_integer(kf, "ip-tunnel", "mode", def->tunnel.mode); - g_key_file_set_string(kf, "ip-tunnel", "local", def->tunnel.local_ip); - g_key_file_set_string(kf, "ip-tunnel", "remote", def->tunnel.remote_ip); - if (def->tunnel.ttl) - g_key_file_set_uint64(kf, "ip-tunnel", "ttl", def->tunnel.ttl); + g_string_append(s, "\n[ip-tunnel]\n"); + + g_string_append_printf(s, "mode=%d\n", def->tunnel.mode); + g_string_append_printf(s, "local=%s\n", def->tunnel.local_ip); + g_string_append_printf(s, "remote=%s\n", def->tunnel.remote_ip); + if (def->tunnel.input_key) - g_key_file_set_string(kf, "ip-tunnel", "input-key", def->tunnel.input_key); + g_string_append_printf(s, "input-key=%s\n", def->tunnel.input_key); if (def->tunnel.output_key) - g_key_file_set_string(kf, "ip-tunnel", "output-key", def->tunnel.output_key); + g_string_append_printf(s, "output-key=%s\n", def->tunnel.output_key); } static void -write_dot1x_auth_parameters(const NetplanAuthenticationSettings* auth, GKeyFile *kf) +write_dot1x_auth_parameters(const NetplanAuthenticationSettings* auth, GString *s) { - if (auth->eap_method == NETPLAN_AUTH_EAP_NONE) + if (auth->eap_method == NETPLAN_AUTH_EAP_NONE) { return; + } + + g_string_append_printf(s, "\n[802-1x]\n"); switch (auth->eap_method) { case NETPLAN_AUTH_EAP_NONE: break; // LCOV_EXCL_LINE case NETPLAN_AUTH_EAP_TLS: - g_key_file_set_string(kf, "802-1x", "eap", "tls"); + g_string_append(s, "eap=tls\n"); break; case NETPLAN_AUTH_EAP_PEAP: - g_key_file_set_string(kf, "802-1x", "eap", "peap"); + g_string_append(s, "eap=peap\n"); break; case NETPLAN_AUTH_EAP_TTLS: - g_key_file_set_string(kf, "802-1x", "eap", "ttls"); + g_string_append(s, "eap=ttls\n"); break; } - if (auth->identity) - g_key_file_set_string(kf, "802-1x", "identity", auth->identity); - if (auth->anonymous_identity) - g_key_file_set_string(kf, "802-1x", "anonymous-identity", auth->anonymous_identity); - if (auth->password && auth->key_management != NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK) - g_key_file_set_string(kf, "802-1x", "password", auth->password); - if (auth->ca_certificate) - g_key_file_set_string(kf, "802-1x", "ca-cert", auth->ca_certificate); - if (auth->client_certificate) - g_key_file_set_string(kf, "802-1x", "client-cert", auth->client_certificate); - if (auth->client_key) - g_key_file_set_string(kf, "802-1x", "private-key", auth->client_key); - if (auth->client_key_password) - g_key_file_set_string(kf, "802-1x", "private-key-password", auth->client_key_password); - if (auth->phase2_auth) - g_key_file_set_string(kf, "802-1x", "phase2-auth", auth->phase2_auth); + if (auth->identity) { + g_string_append_printf(s, "identity=%s\n", auth->identity); + } + if (auth->anonymous_identity) { + g_string_append_printf(s, "anonymous-identity=%s\n", auth->anonymous_identity); + } + if (auth->password && auth->key_management != NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK) { + g_string_append_printf(s, "password=%s\n", auth->password); + } + if (auth->ca_certificate) { + g_string_append_printf(s, "ca-cert=%s\n", auth->ca_certificate); + } + if (auth->client_certificate) { + g_string_append_printf(s, "client-cert=%s\n", auth->client_certificate); + } + if (auth->client_key) { + g_string_append_printf(s, "private-key=%s\n", auth->client_key); + } + if (auth->client_key_password) { + g_string_append_printf(s, "private-key-password=%s\n", auth->client_key_password); + } + if (auth->phase2_auth) { + g_string_append_printf(s, "phase2-auth=%s\n", auth->phase2_auth); + } + } static void -write_wifi_auth_parameters(const NetplanAuthenticationSettings* auth, GKeyFile *kf) +write_wifi_auth_parameters(const NetplanAuthenticationSettings* auth, GString *s) { - if (auth->key_management == NETPLAN_AUTH_KEY_MANAGEMENT_NONE) + if (auth->key_management == NETPLAN_AUTH_KEY_MANAGEMENT_NONE) { return; + } + + g_string_append(s, "\n[wifi-security]\n"); switch (auth->key_management) { case NETPLAN_AUTH_KEY_MANAGEMENT_NONE: break; // LCOV_EXCL_LINE case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK: - g_key_file_set_string(kf, "wifi-security", "key-mgmt", "wpa-psk"); - if (auth->password) - g_key_file_set_string(kf, "wifi-security", "psk", auth->password); + g_string_append(s, "key-mgmt=wpa-psk\n"); + if (auth->password) { + g_string_append_printf(s, "psk=%s\n", auth->password); + } break; case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAP: - g_key_file_set_string(kf, "wifi-security", "key-mgmt", "wpa-eap"); + g_string_append(s, "key-mgmt=wpa-eap\n"); break; case NETPLAN_AUTH_KEY_MANAGEMENT_8021X: - g_key_file_set_string(kf, "wifi-security", "key-mgmt", "ieee8021x"); + g_string_append(s, "key-mgmt=ieee8021x\n"); break; } - write_dot1x_auth_parameters(auth, kf); + write_dot1x_auth_parameters(auth, s); } static void @@ -486,52 +487,6 @@ } /** - * Special handling for passthrough mode: read key-value pairs from - * "backend_settings.nm.passthrough" and inject them into the keyfile as-is. - */ -static void -write_fallback_key_value(GQuark key_id, gpointer value, gpointer user_data) -{ - GKeyFile *kf = user_data; - gchar* val = value; - /* Group name may contain dots, but key name may not. - * The "tc" group is a special case, where it is the other way around, e.g.: - * tc->qdisc.root - * tc->tfilter.ffff: */ - const gchar* key = g_quark_to_string(key_id); - gchar **group_key = g_strsplit(key, ".", -1); - guint len = g_strv_length(group_key); - g_autofree gchar* old_key = NULL; - gboolean has_key = FALSE; - g_autofree gchar* k = NULL; - g_autofree gchar* group = NULL; - if (!g_strcmp0(group_key[0], "tc") && len > 2) { - k = g_strconcat(group_key[1], ".", group_key[2], NULL); - group = g_strdup(group_key[0]); - } else { - k = group_key[len-1]; - group_key[len-1] = NULL; //remove key from array - group = g_strjoinv(".", group_key); //re-combine group parts - } - - has_key = g_key_file_has_key(kf, group, k, NULL); - old_key = g_key_file_get_string(kf, group, k, NULL); - g_key_file_set_string(kf, group, k, val); - /* delete the dummy key, if this was just an empty group */ - if (!g_strcmp0(k, NETPLAN_NM_EMPTY_GROUP)) - g_key_file_remove_key(kf, group, k, NULL); - else if (!has_key) { - g_debug("NetworkManager: passing through fallback key: %s.%s=%s", group, k, val); - g_key_file_set_comment(kf, group, k, "Netplan: passthrough setting", NULL); - } else if (!!g_strcmp0(val, old_key)) { - g_debug("NetworkManager: fallback override: %s.%s=%s", group, k, val); - g_key_file_set_comment(kf, group, k, "Netplan: passthrough override", NULL); - } - - g_strfreev(group_key); -} - -/** * Generate NetworkManager configuration in @rootdir/run/NetworkManager/ for a * particular NetplanNetDefinition and NetplanWifiAccessPoint, as NM requires a separate * connection file for each SSID. @@ -544,13 +499,8 @@ static void write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const NetplanWifiAccessPoint* ap) { - g_autoptr(GKeyFile) kf = NULL; - g_autoptr(GError) error = NULL; - g_autofree gchar* conf_path = NULL; - g_autofree gchar* full_path = NULL; - g_autofree gchar* nd_nm_id = NULL; - const gchar* nm_type = NULL; - gchar* tmp_key = NULL; + GString *s = NULL; + g_autofree char* conf_path = NULL; mode_t orig_umask; char uuidstr[37]; const char *match_interface_name = NULL; @@ -565,35 +515,19 @@ return; } - kf = g_key_file_new(); - if (ap && ap->backend_settings.nm.name) - g_key_file_set_string(kf, "connection", "id", ap->backend_settings.nm.name); - else if (def->backend_settings.nm.name) - g_key_file_set_string(kf, "connection", "id", def->backend_settings.nm.name); - else { - /* Auto-generate a name for the connection profile, if not specified */ - if (ap) - nd_nm_id = g_strdup_printf("netplan-%s-%s", def->id, ap->ssid); - else - nd_nm_id = g_strdup_printf("netplan-%s", def->id); - g_key_file_set_string(kf, "connection", "id", nd_nm_id); - } + s = g_string_new(NULL); + g_string_append_printf(s, "[connection]\nid=netplan-%s", def->id); + if (ap) + g_string_append_printf(s, "-%s", ap->ssid); + g_string_append_printf(s, "\ntype=%s\n", type_str(def)); - nm_type = type_str(def); - if (nm_type) - g_key_file_set_string(kf, "connection", "type", nm_type); - - if (ap && ap->backend_settings.nm.uuid) - g_key_file_set_string(kf, "connection", "uuid", ap->backend_settings.nm.uuid); - else if (def->backend_settings.nm.uuid) - g_key_file_set_string(kf, "connection", "uuid", def->backend_settings.nm.uuid); /* VLAN devices refer to us as their parent; if our ID is not a name but we * have matches, parent= must be the connection UUID, so put it into the * connection */ if (def->has_vlans && def->has_match) { maybe_generate_uuid(def); uuid_unparse(def->uuid, uuidstr); - g_key_file_set_string(kf, "connection", "uuid", uuidstr); + g_string_append_printf(s, "uuid=%s\n", uuidstr); } if (def->type < NETPLAN_DEF_TYPE_VIRTUAL) { @@ -601,70 +535,69 @@ * supported, MAC matching is done below (different keyfile section), * so only match names here */ if (def->set_name) - g_key_file_set_string(kf, "connection", "interface-name", def->set_name); + g_string_append_printf(s, "interface-name=%s\n", def->set_name); else if (!def->has_match) - g_key_file_set_string(kf, "connection", "interface-name", def->id); + g_string_append_printf(s, "interface-name=%s\n", def->id); else if (def->match.original_name) { if (strpbrk(def->match.original_name, "*[]?")) match_interface_name = def->match.original_name; else - g_key_file_set_string(kf, "connection", "interface-name", def->match.original_name); + g_string_append_printf(s, "interface-name=%s\n", def->match.original_name); } /* else matches on something other than the name, do not restrict interface-name */ } else { /* virtual (created) devices set a name */ - if (strlen(def->id) > 15) - g_debug("interface-name longer than 15 characters is not supported"); - else - g_key_file_set_string(kf, "connection", "interface-name", def->id); + g_string_append_printf(s, "interface-name=%s\n", def->id); if (def->type == NETPLAN_DEF_TYPE_BRIDGE) - write_bridge_params(def, kf); + write_bridge_params(def, s); } if (def->type == NETPLAN_DEF_TYPE_MODEM) { - const char* modem_type = modem_is_gsm(def) ? "gsm" : "cdma"; + if (modem_is_gsm(def)) + g_string_append_printf(s, "\n[gsm]\n"); + else + g_string_append_printf(s, "\n[cdma]\n"); /* Use NetworkManager's auto configuration feature if no APN, username, or password is specified */ if (def->modem_params.auto_config || (!def->modem_params.apn && !def->modem_params.username && !def->modem_params.password)) { - g_key_file_set_boolean(kf, modem_type, "auto-config", TRUE); + g_string_append_printf(s, "auto-config=true\n"); } else { if (def->modem_params.apn) - g_key_file_set_string(kf, modem_type, "apn", def->modem_params.apn); + g_string_append_printf(s, "apn=%s\n", def->modem_params.apn); if (def->modem_params.password) - g_key_file_set_string(kf, modem_type, "password", def->modem_params.password); + g_string_append_printf(s, "password=%s\n", def->modem_params.password); if (def->modem_params.username) - g_key_file_set_string(kf, modem_type, "username", def->modem_params.username); + g_string_append_printf(s, "username=%s\n", def->modem_params.username); } if (def->modem_params.device_id) - g_key_file_set_string(kf, modem_type, "device-id", def->modem_params.device_id); + g_string_append_printf(s, "device-id=%s\n", def->modem_params.device_id); if (def->mtubytes) - g_key_file_set_uint64(kf, modem_type, "mtu", def->mtubytes); + g_string_append_printf(s, "mtu=%u\n", def->mtubytes); if (def->modem_params.network_id) - g_key_file_set_string(kf, modem_type, "network-id", def->modem_params.network_id); + g_string_append_printf(s, "network-id=%s\n", def->modem_params.network_id); if (def->modem_params.number) - g_key_file_set_string(kf, modem_type, "number", def->modem_params.number); + g_string_append_printf(s, "number=%s\n", def->modem_params.number); if (def->modem_params.pin) - g_key_file_set_string(kf, modem_type, "pin", def->modem_params.pin); + g_string_append_printf(s, "pin=%s\n", def->modem_params.pin); if (def->modem_params.sim_id) - g_key_file_set_string(kf, modem_type, "sim-id", def->modem_params.sim_id); + g_string_append_printf(s, "sim-id=%s\n", def->modem_params.sim_id); if (def->modem_params.sim_operator_id) - g_key_file_set_string(kf, modem_type, "sim-operator-id", def->modem_params.sim_operator_id); + g_string_append_printf(s, "sim-operator-id=%s\n", def->modem_params.sim_operator_id); } if (def->bridge) { - g_key_file_set_string(kf, "connection", "slave-type", "bridge"); - g_key_file_set_string(kf, "connection", "master", def->bridge); + g_string_append_printf(s, "slave-type=bridge\nmaster=%s\n", def->bridge); + if (def->bridge_params.path_cost || def->bridge_params.port_priority) + g_string_append_printf(s, "\n[bridge-port]\n"); if (def->bridge_params.path_cost) - g_key_file_set_uint64(kf, "bridge-port", "path-cost", def->bridge_params.path_cost); + g_string_append_printf(s, "path-cost=%u\n", def->bridge_params.path_cost); if (def->bridge_params.port_priority) - g_key_file_set_uint64(kf, "bridge-port", "priority", def->bridge_params.port_priority); - } - if (def->bond) { - g_key_file_set_string(kf, "connection", "slave-type", "bond"); - g_key_file_set_string(kf, "connection", "master", def->bond); + g_string_append_printf(s, "priority=%u\n", def->bridge_params.port_priority); } + if (def->bond) + g_string_append_printf(s, "slave-type=bond\nmaster=%s\n", def->bond); if (def->ipv6_mtubytes) { g_fprintf(stderr, "ERROR: %s: NetworkManager definitions do not support ipv6-mtu\n", def->id); @@ -672,176 +605,184 @@ } if (def->type < NETPLAN_DEF_TYPE_VIRTUAL) { - if (def->type == NETPLAN_DEF_TYPE_ETHERNET) - g_key_file_set_integer(kf, "ethernet", "wake-on-lan", def->wake_on_lan ? 1 : 0); + GString *link_str = NULL; - const char* con_type = NULL; - switch (def->type) { - case NETPLAN_DEF_TYPE_WIFI: - con_type = "wifi"; - case NETPLAN_DEF_TYPE_MODEM: - /* Avoid adding an [ethernet] section into the [gsm/cdma] description. */ - break; - default: - con_type = "ethernet"; + link_str = g_string_new(NULL); + + g_string_append_printf(s, "\n[ethernet]\nwake-on-lan=%i\n", def->wake_on_lan ? 1 : 0); + + if (!def->set_name && def->match.mac) { + g_string_append_printf(link_str, "mac-address=%s\n", def->match.mac); + } + if (def->set_mac) { + g_string_append_printf(link_str, "cloned-mac-address=%s\n", def->set_mac); + } + if (def->mtubytes) { + g_string_append_printf(link_str, "mtu=%u\n", def->mtubytes); } + if (def->wowlan && def->wowlan > NETPLAN_WIFI_WOWLAN_DEFAULT) + g_string_append_printf(link_str, "wake-on-wlan=%u\n", def->wowlan); - if (con_type) { - if (!def->set_name && def->match.mac) - g_key_file_set_string(kf, con_type, "mac-address", def->match.mac); - if (def->set_mac) - g_key_file_set_string(kf, con_type, "cloned-mac-address", def->set_mac); - if (def->mtubytes) - g_key_file_set_uint64(kf, con_type, "mtu", def->mtubytes); - if (def->wowlan && def->wowlan > NETPLAN_WIFI_WOWLAN_DEFAULT) - g_key_file_set_uint64(kf, con_type, "wake-on-wlan", def->wowlan); + if (link_str->len > 0) { + switch (def->type) { + case NETPLAN_DEF_TYPE_WIFI: + g_string_append_printf(s, "\n[802-11-wireless]\n%s", link_str->str); break; + case NETPLAN_DEF_TYPE_MODEM: + /* Avoid adding an [ethernet] section into the [gsm/cdma] description. */ + break; + default: + g_string_append_printf(s, "\n[802-3-ethernet]\n%s", link_str->str); break; + } } + + g_string_free(link_str, TRUE); } else { - if (def->set_mac) - g_key_file_set_string(kf, "ethernet", "cloned-mac-address", def->set_mac); - if (def->mtubytes) - g_key_file_set_uint64(kf, "ethernet", "mtu", def->mtubytes); + GString *link_str = NULL; + + link_str = g_string_new(NULL); + + if (def->set_mac) { + g_string_append_printf(link_str, "cloned-mac-address=%s\n", def->set_mac); + } + if (def->mtubytes) { + g_string_append_printf(link_str, "mtu=%u\n", def->mtubytes); + } + + if (link_str->len > 0) { + g_string_append_printf(s, "\n[802-3-ethernet]\n%s", link_str->str); + } + + g_string_free(link_str, TRUE); } if (def->type == NETPLAN_DEF_TYPE_VLAN) { g_assert(def->vlan_id < G_MAXUINT); g_assert(def->vlan_link != NULL); - g_key_file_set_uint64(kf, "vlan", "id", def->vlan_id); + g_string_append_printf(s, "\n[vlan]\nid=%u\nparent=", def->vlan_id); if (def->vlan_link->has_match) { /* we need to refer to the parent's UUID as we don't have an * interface name with match: */ maybe_generate_uuid(def->vlan_link); uuid_unparse(def->vlan_link->uuid, uuidstr); - g_key_file_set_string(kf, "vlan", "parent", uuidstr); + g_string_append_printf(s, "%s\n", uuidstr); } else { /* if we have an interface name, use that as parent */ - g_key_file_set_string(kf, "vlan", "parent", def->vlan_link->id); + g_string_append_printf(s, "%s\n", def->vlan_link->id); } } if (def->type == NETPLAN_DEF_TYPE_BOND) - write_bond_parameters(def, kf); + write_bond_parameters(def, s); if (def->type == NETPLAN_DEF_TYPE_TUNNEL) { if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_WIREGUARD) - write_wireguard_params(def, kf); + write_wireguard_params(def, s); else - write_tunnel_params(def, kf); + write_tunnel_params(def, s); } if (match_interface_name) { - const gchar* list[1] = {match_interface_name}; - g_key_file_set_string_list(kf, "match", "interface-name", list, 1); + g_string_append(s, "\n[match]\n"); + g_string_append_printf(s, "interface-name=%s;\n", match_interface_name); } + g_string_append(s, "\n[ipv4]\n"); + if (ap && ap->mode == NETPLAN_WIFI_MODE_AP) - g_key_file_set_string(kf, "ipv4", "method", "shared"); + g_string_append(s, "method=shared\n"); else if (def->dhcp4) - g_key_file_set_string(kf, "ipv4", "method", "auto"); + g_string_append(s, "method=auto\n"); else if (def->ip4_addresses) /* This requires adding at least one address (done below) */ - g_key_file_set_string(kf, "ipv4", "method", "manual"); + g_string_append(s, "method=manual\n"); else if (def->type == NETPLAN_DEF_TYPE_TUNNEL) /* sit tunnels will not start in link-local apparently */ - g_key_file_set_string(kf, "ipv4", "method", "disabled"); + g_string_append(s, "method=disabled\n"); else /* Without any address, this is the only available mode */ - g_key_file_set_string(kf, "ipv4", "method", "link-local"); + g_string_append(s, "method=link-local\n"); - if (def->ip4_addresses) { - for (unsigned i = 0; i < def->ip4_addresses->len; ++i) { - tmp_key = g_strdup_printf("address%i", i+1); - g_key_file_set_string(kf, "ipv4", tmp_key, g_array_index(def->ip4_addresses, char*, i)); - g_free(tmp_key); - } - } + if (def->ip4_addresses) + for (unsigned i = 0; i < def->ip4_addresses->len; ++i) + g_string_append_printf(s, "address%i=%s\n", i+1, g_array_index(def->ip4_addresses, char*, i)); if (def->gateway4) - g_key_file_set_string(kf, "ipv4", "gateway", def->gateway4); + g_string_append_printf(s, "gateway=%s\n", def->gateway4); if (def->ip4_nameservers) { - const gchar* list[def->ip4_nameservers->len]; + g_string_append(s, "dns="); for (unsigned i = 0; i < def->ip4_nameservers->len; ++i) - list[i] = g_array_index(def->ip4_nameservers, char*, i); - g_key_file_set_string_list(kf, "ipv4", "dns", list, def->ip4_nameservers->len); + g_string_append_printf(s, "%s;", g_array_index(def->ip4_nameservers, char*, i)); + g_string_append(s, "\n"); } /* We can only write search domains and routes if we have an address */ if (def->ip4_addresses || def->dhcp4) { - write_search_domains(def, "ipv4", kf); - write_routes(def, kf, AF_INET); + write_search_domains(def, s); + write_routes(def, s, AF_INET); } if (!def->dhcp4_overrides.use_routes) { - g_key_file_set_boolean(kf, "ipv4", "ignore-auto-routes", TRUE); - g_key_file_set_boolean(kf, "ipv4", "never-default", TRUE); + g_string_append(s, "ignore-auto-routes=true\n"); + g_string_append(s, "never-default=true\n"); } if (def->dhcp4 && def->dhcp4_overrides.metric != NETPLAN_METRIC_UNSPEC) - g_key_file_set_uint64(kf, "ipv4", "route-metric", def->dhcp4_overrides.metric); + g_string_append_printf(s, "route-metric=%u\n", def->dhcp4_overrides.metric); if (def->dhcp6 || def->ip6_addresses || def->gateway6 || def->ip6_nameservers || def->ip6_addr_gen_mode) { - g_key_file_set_string(kf, "ipv6", "method", def->dhcp6 ? "auto" : "manual"); - - if (def->ip6_addresses) { - for (unsigned i = 0; i < def->ip6_addresses->len; ++i) { - tmp_key = g_strdup_printf("address%i", i+1); - g_key_file_set_string(kf, "ipv6", tmp_key, g_array_index(def->ip6_addresses, char*, i)); - g_free(tmp_key); - } - } + g_string_append(s, "\n[ipv6]\n"); + g_string_append(s, def->dhcp6 ? "method=auto\n" : "method=manual\n"); + if (def->ip6_addresses) + for (unsigned i = 0; i < def->ip6_addresses->len; ++i) + g_string_append_printf(s, "address%i=%s\n", i+1, g_array_index(def->ip6_addresses, char*, i)); if (def->ip6_addr_gen_token) { /* Token implies EUI-64, i.e mode=0 */ - g_key_file_set_integer(kf, "ipv6", "addr-gen-mode", 0); - g_key_file_set_string(kf, "ipv6", "token", def->ip6_addr_gen_token); - } else if (def->ip6_addr_gen_mode) - g_key_file_set_string(kf, "ipv6", "addr-gen-mode", addr_gen_mode_str(def->ip6_addr_gen_mode)); + g_string_append(s, "addr-gen-mode=0\n"); + g_string_append_printf(s, "token=%s\n", def->ip6_addr_gen_token); + } else if (def->ip6_addr_gen_mode) { + g_string_append_printf(s, "addr-gen-mode=%s\n", addr_gen_mode_str(def->ip6_addr_gen_mode)); + } if (def->ip6_privacy) - g_key_file_set_integer(kf, "ipv6", "ip6-privacy", 2); + g_string_append(s, "ip6-privacy=2\n"); if (def->gateway6) - g_key_file_set_string(kf, "ipv6", "gateway", def->gateway6); + g_string_append_printf(s, "gateway=%s\n", def->gateway6); if (def->ip6_nameservers) { - const gchar* list[def->ip6_nameservers->len]; + g_string_append(s, "dns="); for (unsigned i = 0; i < def->ip6_nameservers->len; ++i) - list[i] = g_array_index(def->ip6_nameservers, char*, i); - g_key_file_set_string_list(kf, "ipv6", "dns", list, def->ip6_nameservers->len); + g_string_append_printf(s, "%s;", g_array_index(def->ip6_nameservers, char*, i)); + g_string_append(s, "\n"); } /* nm-settings(5) specifies search-domain for both [ipv4] and [ipv6] -- * We need to specify it here for the IPv6-only case - see LP: #1786726 */ - write_search_domains(def, "ipv6", kf); + write_search_domains(def, s); /* We can only write valid routes if there is a DHCPv6 or static IPv6 address */ - write_routes(def, kf, AF_INET6); + write_routes(def, s, AF_INET6); if (!def->dhcp6_overrides.use_routes) { - g_key_file_set_boolean(kf, "ipv6", "ignore-auto-routes", TRUE); - g_key_file_set_boolean(kf, "ipv6", "never-default", TRUE); + g_string_append(s, "ignore-auto-routes=true\n"); + g_string_append(s, "never-default=true\n"); } if (def->dhcp6_overrides.metric != NETPLAN_METRIC_UNSPEC) - g_key_file_set_uint64(kf, "ipv6", "route-metric", def->dhcp6_overrides.metric); + g_string_append_printf(s, "route-metric=%u\n", def->dhcp6_overrides.metric); } - else - g_key_file_set_string(kf, "ipv6", "method", "ignore"); - - if (def->backend_settings.nm.passthrough) { - g_debug("NetworkManager: using keyfile passthrough mode"); - /* Write all key-value pairs from the hashtable into the keyfile, - * potentially overriding existing values, if not fully supported. */ - g_datalist_foreach(&def->backend_settings.nm.passthrough, write_fallback_key_value, kf); + else { + g_string_append(s, "\n[ipv6]\nmethod=ignore\n"); } if (ap) { g_autofree char* escaped_ssid = g_uri_escape_string(ap->ssid, NULL, TRUE); conf_path = g_strjoin(NULL, "run/NetworkManager/system-connections/netplan-", def->id, "-", escaped_ssid, ".nmconnection", NULL); - g_key_file_set_string(kf, "wifi", "ssid", ap->ssid); - if (ap->mode < NETPLAN_WIFI_MODE_OTHER) - g_key_file_set_string(kf, "wifi", "mode", wifi_mode_str(ap->mode)); - if (ap->bssid) - g_key_file_set_string(kf, "wifi", "bssid", ap->bssid); - if (ap->hidden) - g_key_file_set_boolean(kf, "wifi", "hidden", TRUE); + g_string_append_printf(s, "\n[wifi]\nssid=%s\nmode=%s\n", ap->ssid, wifi_mode_str(ap->mode)); + if (ap->bssid) { + g_string_append_printf(s, "bssid=%s\n", ap->bssid); + } + if (ap->hidden) { + g_string_append(s, "hidden=true\n"); + } if (ap->band == NETPLAN_WIFI_BAND_5 || ap->band == NETPLAN_WIFI_BAND_24) { - g_key_file_set_string(kf, "wifi", "band", wifi_band_str(ap->band)); + g_string_append_printf(s, "band=%s\n", wifi_band_str(ap->band)); /* Channel is only unambiguous, if band is set. */ if (ap->channel) { /* Validate WiFi channel */ @@ -849,38 +790,22 @@ wifi_get_freq5(ap->channel); else wifi_get_freq24(ap->channel); - g_key_file_set_uint64(kf, "wifi", "channel", ap->channel); + g_string_append_printf(s, "channel=%u\n", ap->channel); } } if (ap->has_auth) { - write_wifi_auth_parameters(&ap->auth, kf); - } - if (ap->backend_settings.nm.passthrough) { - g_debug("NetworkManager: using AP keyfile passthrough mode"); - /* Write all key-value pairs from the hashtable into the keyfile, - * potentially overriding existing values, if not fully supported. - * AP passthrough values have higher priority than ND passthrough, - * because they are more specific and bound to the current SSID's - * NM connection profile. */ - g_datalist_foreach((GData**)&ap->backend_settings.nm.passthrough, write_fallback_key_value, kf); + write_wifi_auth_parameters(&ap->auth, s); } } else { conf_path = g_strjoin(NULL, "run/NetworkManager/system-connections/netplan-", def->id, ".nmconnection", NULL); if (def->has_auth) { - write_dot1x_auth_parameters(&def->auth, kf); + write_dot1x_auth_parameters(&def->auth, s); } } /* NM connection files might contain secrets, and NM insists on tight permissions */ - full_path = g_strjoin(G_DIR_SEPARATOR_S, rootdir ?: "", conf_path, NULL); orig_umask = umask(077); - safe_mkdir_p_dir(full_path); - if (!g_key_file_save_to_file(kf, full_path, &error)) { - // LCOV_EXCL_START - g_fprintf(stderr, "ERROR: cannot create file %s: %s\n", full_path, error->message); - exit(1); - // LCOV_EXCL_STO - } + g_string_free_to_file(s, rootdir, conf_path, NULL); umask(orig_umask); } @@ -908,6 +833,7 @@ exit(1); } + /* for wifi we need to create a separate connection file for every SSID */ if (def->type == NETPLAN_DEF_TYPE_WIFI) { GHashTableIter iter; gpointer key; @@ -946,7 +872,7 @@ GString *s = NULL; gsize len; - if (!netdefs || g_hash_table_size(netdefs) == 0) + if (g_hash_table_size(netdefs) == 0) return; /* Set all devices not managed by us to unmanaged, so that NM does not diff -Nru netplan.io-0.102/src/parse.c netplan.io-0.101/src/parse.c --- netplan.io-0.102/src/parse.c 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/src/parse.c 2020-12-09 11:32:25.000000000 +0000 @@ -28,7 +28,6 @@ #include #include "parse.h" -#include "util.h" #include "error.h" #include "validation.h" @@ -59,9 +58,6 @@ static NetplanIPRoute* cur_route; static NetplanIPRule* cur_ip_rule; -/* Filename of the currently parsed YAML file */ -const char* cur_filename; - static NetplanBackend backend_global, backend_cur_type; /* global OpenVSwitch settings */ @@ -215,7 +211,7 @@ ovs_settings->rstp = FALSE; } -NetplanNetDefinition* +static NetplanNetDefinition* netplan_netdef_new(const char* id, NetplanDefType type, NetplanBackend backend) { /* create new network definition */ @@ -240,8 +236,6 @@ /* OpenVSwitch defaults */ initialize_ovs_settings(&cur_netdef->ovs_settings); - if (!netdefs) - netdefs = g_hash_table_new(g_str_hash, g_str_equal); g_hash_table_insert(netdefs, cur_netdef->id, cur_netdef); netdefs_ordered = g_list_append(netdefs_ordered, cur_netdef); return cur_netdef; @@ -452,34 +446,6 @@ return TRUE; } -/* - * Handler for setting a DataList field from a mapping node, inside a given struct - * @entryptr: pointer to the beginning of the to-be-modified data structure - * @data: offset into entryptr struct where the boolean field to write is located -*/ -static gboolean -handle_generic_datalist(yaml_document_t* doc, yaml_node_t* node, void* entryptr, const void* data, GError** error) -{ - guint offset = GPOINTER_TO_UINT(data); - GData** list = (GData**) ((void*) entryptr + offset); - if (!*list) - g_datalist_init(list); - - for (yaml_node_pair_t* entry = node->data.mapping.pairs.start; entry < node->data.mapping.pairs.top; entry++) { - yaml_node_t* key, *value; - - key = yaml_document_get_node(doc, entry->key); - value = yaml_document_get_node(doc, entry->value); - - assert_type(key, YAML_SCALAR_NODE); - assert_type(value, YAML_SCALAR_NODE); - - g_datalist_set_data_full(list, g_strdup(scalar(key)), g_strdup(scalar(value)), g_free); - } - - return TRUE; -} - /** * Generic handler for setting a cur_netdef string field from a scalar node * @data: offset into NetplanNetDefinition where the const char* field to write is @@ -654,13 +620,6 @@ return handle_generic_map(doc, node, cur_netdef, data, error); } -static gboolean -handle_netdef_datalist(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) -{ - g_assert(cur_netdef); - return handle_generic_datalist(doc, node, cur_netdef, data, error); -} - /**************************************************** * Grammar and handlers for network config "match" entry ****************************************************/ @@ -749,19 +708,6 @@ } static gboolean -handle_access_point_str(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) -{ - return handle_generic_str(doc, node, cur_access_point, data, error); -} - -static gboolean -handle_access_point_datalist(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) -{ - g_assert(cur_access_point); - return handle_generic_datalist(doc, node, cur_access_point, data, error); -} - -static gboolean handle_access_point_guint(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) { return handle_generic_guint(doc, node, cur_access_point, data, error); @@ -834,29 +780,6 @@ return TRUE; } -/* Keep in sync with ap_nm_backend_settings_handlers */ -static const mapping_entry_handler nm_backend_settings_handlers[] = { - {"name", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.name)}, - {"uuid", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.uuid)}, - {"stable-id", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.stable_id)}, - {"device", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.device)}, - /* Fallback mode, to support all NM settings of the NetworkManager netplan backend */ - {"passthrough", YAML_MAPPING_NODE, handle_netdef_datalist, NULL, netdef_offset(backend_settings.nm.passthrough)}, - {NULL} -}; - -/* Keep in sync with nm_backend_settings_handlers */ -static const mapping_entry_handler ap_nm_backend_settings_handlers[] = { - {"name", YAML_SCALAR_NODE, handle_access_point_str, NULL, access_point_offset(backend_settings.nm.name)}, - {"uuid", YAML_SCALAR_NODE, handle_access_point_str, NULL, access_point_offset(backend_settings.nm.uuid)}, - {"stable-id", YAML_SCALAR_NODE, handle_access_point_str, NULL, access_point_offset(backend_settings.nm.stable_id)}, - {"device", YAML_SCALAR_NODE, handle_access_point_str, NULL, access_point_offset(backend_settings.nm.device)}, - /* Fallback mode, to support all NM settings of the NetworkManager netplan backend */ - {"passthrough", YAML_MAPPING_NODE, handle_access_point_datalist, NULL, access_point_offset(backend_settings.nm.passthrough)}, - {NULL} -}; - - static const mapping_entry_handler wifi_access_point_handlers[] = { {"band", YAML_SCALAR_NODE, handle_access_point_band}, {"bssid", YAML_SCALAR_NODE, handle_access_point_mac, NULL, access_point_offset(bssid)}, @@ -865,7 +788,6 @@ {"mode", YAML_SCALAR_NODE, handle_access_point_mode}, {"password", YAML_SCALAR_NODE, handle_access_point_password}, {"auth", YAML_MAPPING_NODE, handle_access_point_auth}, - {"networkmanager", YAML_MAPPING_NODE, NULL, ap_nm_backend_settings_handlers}, {NULL} }; @@ -1596,8 +1518,6 @@ {"via", YAML_SCALAR_NODE, handle_routes_ip, NULL, route_offset(via)}, {"metric", YAML_SCALAR_NODE, handle_routes_guint, NULL, route_offset(metric)}, {"mtu", YAML_SCALAR_NODE, handle_routes_guint, NULL, route_offset(mtubytes)}, - {"congestion-window", YAML_SCALAR_NODE, handle_routes_guint, NULL, route_offset(congestion_window)}, - {"advertised-receive-window", YAML_SCALAR_NODE, handle_routes_guint, NULL, route_offset(advertised_receive_window)}, {NULL} }; @@ -1702,18 +1622,6 @@ static gboolean handle_arp_ip_targets(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) { - if (!cur_netdef->bond_params.arp_ip_targets) { - cur_netdef->bond_params.arp_ip_targets = g_array_new(FALSE, FALSE, sizeof(char *)); - } - - /* Avoid adding the same arp_ip_targets in a 2nd parsing pass by comparing - * the array size to the YAML sequence size. Skip if they are equal. */ - guint item_count = node->data.sequence.items.top - node->data.sequence.items.start; - if (cur_netdef->bond_params.arp_ip_targets->len == item_count) { - g_debug("%s: all arp ip targets have already been added", cur_netdef->id); - return TRUE; - } - for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) { g_autofree char* addr = NULL; yaml_node_t *entry = yaml_document_get_node(doc, *i); @@ -1723,6 +1631,8 @@ /* is it an IPv4 address? */ if (is_ip4_address(addr)) { + if (!cur_netdef->bond_params.arp_ip_targets) + cur_netdef->bond_params.arp_ip_targets = g_array_new(FALSE, FALSE, sizeof(char*)); char* s = g_strdup(scalar(entry)); g_array_append_val(cur_netdef->bond_params.arp_ip_targets, s); continue; @@ -1989,6 +1899,7 @@ } for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) { + g_autofree char* addr = NULL; yaml_node_t *entry = yaml_document_get_node(doc, *i); assert_type(entry, YAML_MAPPING_NODE); @@ -2011,11 +1922,19 @@ * Grammar and handlers for network devices ****************************************************/ +static const mapping_entry_handler nm_backend_settings_handlers[] = { + {"name", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.name)}, + {"uuid", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.uuid)}, + {"stable-id", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.stable_id)}, + {"device", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.device)}, + {NULL} +}; + static gboolean handle_ovs_bond_lacp(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) { if (cur_netdef->type != NETPLAN_DEF_TYPE_BOND) - return yaml_error(node, error, "Key 'lacp' is only valid for interface type 'openvswitch bond'"); + return yaml_error(node, error, "Key 'lacp' is only valid for iterface type 'openvswitch bond'"); if (g_strcmp0(scalar(node), "active") && g_strcmp0(scalar(node), "passive") && g_strcmp0(scalar(node), "off")) return yaml_error(node, error, "Value of 'lacp' needs to be 'active', 'passive' or 'off"); @@ -2027,7 +1946,7 @@ handle_ovs_bridge_bool(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) { if (cur_netdef->type != NETPLAN_DEF_TYPE_BRIDGE) - return yaml_error(node, error, "Key is only valid for interface type 'openvswitch bridge'"); + return yaml_error(node, error, "Key is only valid for iterface type 'openvswitch bridge'"); return handle_netdef_bool(doc, node, data, error); } @@ -2036,7 +1955,7 @@ handle_ovs_bridge_fail_mode(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) { if (cur_netdef->type != NETPLAN_DEF_TYPE_BRIDGE) - return yaml_error(node, error, "Key 'fail-mode' is only valid for interface type 'openvswitch bridge'"); + return yaml_error(node, error, "Key 'fail-mode' is only valid for iterface type 'openvswitch bridge'"); if (g_strcmp0(scalar(node), "standalone") && g_strcmp0(scalar(node), "secure")) return yaml_error(node, error, "Value of 'fail-mode' needs to be 'standalone' or 'secure'"); @@ -2078,7 +1997,7 @@ handle_ovs_bridge_protocol(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) { if (cur_netdef->type != NETPLAN_DEF_TYPE_BRIDGE) - return yaml_error(node, error, "Key 'protocols' is only valid for interface type 'openvswitch bridge'"); + return yaml_error(node, error, "Key 'protocols' is only valid for iterface type 'openvswitch bridge'"); return handle_ovs_protocol(doc, node, cur_netdef, data, error); } @@ -2087,7 +2006,7 @@ handle_ovs_bridge_controller_connection_mode(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) { if (cur_netdef->type != NETPLAN_DEF_TYPE_BRIDGE) - return yaml_error(node, error, "Key 'controller.connection-mode' is only valid for interface type 'openvswitch bridge'"); + return yaml_error(node, error, "Key 'controller.connection-mode' is only valid for iterface type 'openvswitch bridge'"); if (g_strcmp0(scalar(node), "in-band") && g_strcmp0(scalar(node), "out-of-band")) return yaml_error(node, error, "Value of 'connection-mode' needs to be 'in-band' or 'out-of-band'"); @@ -2099,7 +2018,7 @@ handle_ovs_bridge_controller_addresses(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) { if (cur_netdef->type != NETPLAN_DEF_TYPE_BRIDGE) - return yaml_error(node, error, "Key 'controller.addresses' is only valid for interface type 'openvswitch bridge'"); + return yaml_error(node, error, "Key 'controller.addresses' is only valid for iterface type 'openvswitch bridge'"); for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) { gchar** vec = NULL; @@ -2303,8 +2222,6 @@ static const mapping_entry_handler modem_def_handlers[] = { COMMON_LINK_HANDLERS, - COMMON_BACKEND_HANDLERS, - PHYSICAL_LINK_HANDLERS, {"apn", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(modem_params.apn)}, {"auto-config", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(modem_params.auto_config)}, {"device-id", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(modem_params.device_id)}, @@ -2323,7 +2240,6 @@ {"mode", YAML_SCALAR_NODE, handle_tunnel_mode}, {"local", YAML_SCALAR_NODE, handle_tunnel_addr, NULL, netdef_offset(tunnel.local_ip)}, {"remote", YAML_SCALAR_NODE, handle_tunnel_addr, NULL, netdef_offset(tunnel.remote_ip)}, - {"ttl", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(tunnel.ttl)}, /* Handle key/keys for clarity in config: this can be either a scalar or * mapping of multiple keys (input and output) @@ -2397,7 +2313,7 @@ assert_type(peer, YAML_SCALAR_NODE); /* Create port 1 netdef */ - component = netdefs ? g_hash_table_lookup(netdefs, scalar(port)) : NULL; + component = g_hash_table_lookup(netdefs, scalar(port)); if (!component) { component = netplan_netdef_new(scalar(port), NETPLAN_DEF_TYPE_PORT, NETPLAN_BACKEND_OVS); if (g_hash_table_remove(missing_id, scalar(port))) @@ -2411,7 +2327,7 @@ /* Create port 2 (peer) netdef */ component = NULL; - component = netdefs ? g_hash_table_lookup(netdefs, scalar(peer)) : NULL; + component = g_hash_table_lookup(netdefs, scalar(peer)); if (!component) { component = netplan_netdef_new(scalar(peer), NETPLAN_DEF_TYPE_PORT, NETPLAN_BACKEND_OVS); if (g_hash_table_remove(missing_id, scalar(peer))) @@ -2461,16 +2377,14 @@ if(g_hash_table_remove(missing_id, scalar(key))) missing_ids_found++; - cur_netdef = netdefs ? g_hash_table_lookup(netdefs, scalar(key)) : NULL; + cur_netdef = g_hash_table_lookup(netdefs, scalar(key)); if (cur_netdef) { /* already exists, overriding/amending previous definition */ if (cur_netdef->type != GPOINTER_TO_UINT(data)) return yaml_error(key, error, "Updated definition '%s' changes device type", scalar(key)); } else { - cur_netdef = netplan_netdef_new(scalar(key), GPOINTER_TO_UINT(data), backend_cur_type); + netplan_netdef_new(scalar(key), GPOINTER_TO_UINT(data), backend_cur_type); } - g_assert(cur_filename); - cur_netdef->filename = g_strdup(cur_filename); // XXX: breaks multi-pass parsing. //if (!g_hash_table_add(ids_in_file, cur_netdef->id)) @@ -2485,10 +2399,6 @@ case NETPLAN_DEF_TYPE_TUNNEL: handlers = tunnel_def_handlers; break; case NETPLAN_DEF_TYPE_VLAN: handlers = vlan_def_handlers; break; case NETPLAN_DEF_TYPE_WIFI: handlers = wifi_def_handlers; break; - case NETPLAN_DEF_TYPE_NM: - g_warning("netplan: %s: handling NetworkManager passthrough device, settings are not fully supported.", cur_netdef->id); - handlers = ethernet_def_handlers; - break; default: g_assert_not_reached(); // LCOV_EXCL_LINE } if (!process_mapping(doc, value, handlers, NULL, error)) @@ -2501,7 +2411,7 @@ /* convenience shortcut: physical device without match: means match * name on ID */ if (cur_netdef->type < NETPLAN_DEF_TYPE_VIRTUAL && !cur_netdef->has_match) - cur_netdef->match.original_name = g_strdup(cur_netdef->id); + cur_netdef->match.original_name = cur_netdef->id; } backend_cur_type = NETPLAN_BACKEND_NONE; return TRUE; @@ -2545,7 +2455,6 @@ {"vlans", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_VLAN)}, {"wifis", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_WIFI)}, {"modems", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_MODEM)}, - {"nm-devices", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_NM)}, {"openvswitch", YAML_MAPPING_NODE, NULL, ovs_network_settings_handlers}, {NULL} }; @@ -2623,6 +2532,9 @@ if (!load_yaml(filename, &doc, error)) return FALSE; + if (!netdefs) + netdefs = g_hash_table_new(g_str_hash, g_str_equal); + /* empty file? */ if (yaml_document_get_root_node(&doc) == NULL) return TRUE; @@ -2630,10 +2542,8 @@ g_assert(ids_in_file == NULL); ids_in_file = g_hash_table_new(g_str_hash, NULL); - cur_filename = filename; ret = process_document(&doc, error); - cur_filename = NULL; cur_netdef = NULL; yaml_document_delete(&doc); g_hash_table_destroy(ids_in_file); @@ -2696,49 +2606,6 @@ /* FIXME: make sure that any dynamically allocated netdef data is freed */ if (n > 0) g_hash_table_remove_all(netdefs); - netdefs = NULL; } - if(netdefs_ordered) { - g_clear_list(&netdefs_ordered, g_free); - netdefs_ordered = NULL; - } return n; } - -void -process_input_file(const char* f) -{ - GError* error = NULL; - - g_debug("Processing input file %s..", f); - if (!netplan_parse_yaml(f, &error)) { - g_fprintf(stderr, "%s\n", error->message); - exit(1); - } -} - -gboolean -process_yaml_hierarchy(const char* rootdir) -{ - glob_t gl; - /* Files with asciibetically higher names override/append settings from - * earlier ones (in all config dirs); files in /run/netplan/ - * shadow files in /etc/netplan/ which shadow files in /lib/netplan/. - * To do that, we put all found files in a hash table, then sort it by - * file name, and add the entries from /run after the ones from /etc - * and those after the ones from /lib. */ - if (find_yaml_glob(rootdir, &gl) != 0) - return FALSE; // LCOV_EXCL_LINE - /* keys are strdup()ed, free them; values point into the glob_t, don't free them */ - g_autoptr(GHashTable) configs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); - g_autoptr(GList) config_keys = NULL; - - for (size_t i = 0; i < gl.gl_pathc; ++i) - g_hash_table_insert(configs, g_path_get_basename(gl.gl_pathv[i]), gl.gl_pathv[i]); - - config_keys = g_list_sort(g_hash_table_get_keys(configs), (GCompareFunc) strcmp); - - for (GList* i = config_keys; i != NULL; i = i->next) - process_input_file(g_hash_table_lookup(configs, i->data)); - return TRUE; -} diff -Nru netplan.io-0.102/src/parse.h netplan.io-0.101/src/parse.h --- netplan.io-0.102/src/parse.h 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/src/parse.h 2020-12-09 11:32:25.000000000 +0000 @@ -51,9 +51,6 @@ NETPLAN_DEF_TYPE_VLAN, NETPLAN_DEF_TYPE_TUNNEL, NETPLAN_DEF_TYPE_PORT, - /* Type fallback/passthrough */ - NETPLAN_DEF_TYPE_NM, - NETPLAN_DEF_TYPE_MAX_ } NetplanDefType; typedef enum { @@ -221,19 +218,6 @@ NetplanAuthenticationSettings ssl; } NetplanOVSSettings; -typedef union { - struct NetplanNMSettings { - char *name; - char *uuid; - char *stable_id; - char *device; - GData* passthrough; - } nm; - struct NetplanNetworkdSettings { - char *unit; - } networkd; -} NetplanBackendSettings; - /** * Represent a configuration stanza */ @@ -246,7 +230,6 @@ NetplanDefType type; NetplanBackend backend; char* id; - char* filename; /* only necessary for NetworkManager connection UUIDs in some cases */ uuid_t uuid; @@ -376,7 +359,6 @@ char *private_key; /* used for wireguard */ guint fwmark; guint port; - guint ttl; } tunnel; NetplanAuthenticationSettings auth; @@ -392,24 +374,25 @@ /* netplan-feature: openvswitch */ NetplanOVSSettings ovs_settings; - NetplanBackendSettings backend_settings; + union { + struct NetplanNMSettings { + char *name; + char *uuid; + char *stable_id; + char *device; + } nm; + struct NetplanNetworkdSettings { + char *unit; + } networkd; + } backend_settings; }; typedef enum { NETPLAN_WIFI_MODE_INFRASTRUCTURE, NETPLAN_WIFI_MODE_ADHOC, - NETPLAN_WIFI_MODE_AP, - NETPLAN_WIFI_MODE_OTHER, - NETPLAN_WIFI_MODE_MAX_ + NETPLAN_WIFI_MODE_AP } NetplanWifiMode; -static const char* const netplan_wifi_mode_to_str[NETPLAN_WIFI_MODE_MAX_] = { - [NETPLAN_WIFI_MODE_INFRASTRUCTURE] = "infrastructure", - [NETPLAN_WIFI_MODE_ADHOC] = "adhoc", - [NETPLAN_WIFI_MODE_AP] = "ap", - [NETPLAN_WIFI_MODE_OTHER] = NULL, -}; - typedef struct { char *endpoint; char *public_key; @@ -440,12 +423,8 @@ NetplanAuthenticationSettings auth; gboolean has_auth; - - NetplanBackendSettings backend_settings; } NetplanWifiAccessPoint; -#define NETPLAN_ADVERTISED_RECEIVE_WINDOW_UNSPEC 0 -#define NETPLAN_CONGESTION_WINDOW_UNSPEC 0 #define NETPLAN_MTU_UNSPEC 0 #define NETPLAN_METRIC_UNSPEC G_MAXUINT #define NETPLAN_ROUTE_TABLE_UNSPEC 0 @@ -470,8 +449,6 @@ guint metric; guint mtubytes; - guint congestion_window; - guint advertised_receive_window; } NetplanIPRoute; typedef struct { @@ -500,10 +477,5 @@ gboolean netplan_parse_yaml(const char* filename, GError** error); GHashTable* netplan_finish_parse(GError** error); -guint netplan_clear_netdefs(); NetplanBackend netplan_get_global_backend(); const char* tunnel_mode_to_string(NetplanTunnelMode mode); -NetplanNetDefinition* netplan_netdef_new(const char* id, NetplanDefType type, NetplanBackend renderer); - -void process_input_file(const char* f); -gboolean process_yaml_hierarchy(const char* rootdir); diff -Nru netplan.io-0.102/src/parse-nm.c netplan.io-0.101/src/parse-nm.c --- netplan.io-0.102/src/parse-nm.c 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/src/parse-nm.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,275 +0,0 @@ -/* - * Copyright (C) 2021 Canonical, Ltd. - * Author: Lukas Märdian - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include - -#include "netplan.h" -#include "parse-nm.h" -#include "parse.h" -#include "util.h" - -/** - * NetworkManager writes the alias for '802-3-ethernet' (ethernet), - * '802-11-wireless' (wifi) and '802-11-wireless-security' (wifi-security) - * by default, so we only need to check for those. See: - * https://bugzilla.gnome.org/show_bug.cgi?id=696940 - * https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/commit/c36200a225aefb2a3919618e75682646899b82c0 - */ -static const NetplanDefType -type_from_str(const char* type_str) -{ - if (!g_strcmp0(type_str, "ethernet") || !g_strcmp0(type_str, "802-3-ethernet")) - return NETPLAN_DEF_TYPE_ETHERNET; - else if (!g_strcmp0(type_str, "wifi") || !g_strcmp0(type_str, "802-11-wireless")) - return NETPLAN_DEF_TYPE_WIFI; - else if (!g_strcmp0(type_str, "gsm") || !g_strcmp0(type_str, "cdma")) - return NETPLAN_DEF_TYPE_MODEM; - else if (!g_strcmp0(type_str, "bridge")) - return NETPLAN_DEF_TYPE_BRIDGE; - else if (!g_strcmp0(type_str, "bond")) - return NETPLAN_DEF_TYPE_BOND; - else if (!g_strcmp0(type_str, "vlan")) - return NETPLAN_DEF_TYPE_VLAN; - else if (!g_strcmp0(type_str, "ip-tunnel") || !g_strcmp0(type_str, "wireguard")) - return NETPLAN_DEF_TYPE_TUNNEL; - /* Unsupported type, needs to be specified via passthrough */ - return NETPLAN_DEF_TYPE_NM; -} - -static const NetplanWifiMode -ap_type_from_str(const char* type_str) -{ - if (!g_strcmp0(type_str, "infrastructure")) - return NETPLAN_WIFI_MODE_INFRASTRUCTURE; - else if (!g_strcmp0(type_str, "ap")) - return NETPLAN_WIFI_MODE_AP; - else if (!g_strcmp0(type_str, "adhoc")) - return NETPLAN_WIFI_MODE_ADHOC; - /* Unsupported mode, like "mesh" */ - return NETPLAN_WIFI_MODE_OTHER; -} - -static gboolean -_kf_clear_key(GKeyFile* kf, const gchar* group, const gchar* key) -{ - gsize len = 1; - gboolean ret = FALSE; - ret = g_key_file_remove_key(kf, group, key, NULL); - g_strfreev(g_key_file_get_keys(kf, group, &len, NULL)); - /* clear group if this was the last key */ - if (len == 0) - ret &= g_key_file_remove_group(kf, group, NULL); - return ret; -} - -/* Read the key-value pairs from the keyfile and pass them through to a map */ -static void -read_passthrough(GKeyFile* kf, GData** list) -{ - gchar **groups = NULL; - gchar **keys = NULL; - gchar *group_key = NULL; - gchar *value = NULL; - gsize klen = 0; - gsize glen = 0; - - if (!*list) - g_datalist_init(list); - groups = g_key_file_get_groups(kf, &glen); - if (groups) { - for (unsigned i = 0; i < glen; ++i) { - klen = 0; - keys = g_key_file_get_keys(kf, groups[i], &klen, NULL); - if (klen == 0) { - /* empty group */ - g_datalist_set_data_full(list, g_strconcat(groups[i], ".", NETPLAN_NM_EMPTY_GROUP, NULL), g_strdup(""), g_free); - continue; - } - for (unsigned j = 0; j < klen; ++j) { - value = g_key_file_get_string(kf, groups[i], keys[j], NULL); - if (!value) { - // LCOV_EXCL_START - g_warning("netplan: Keyfile: cannot read value of %s.%s", groups[i], keys[j]); - continue; - // LCOV_EXCL_STOP - } - group_key = g_strconcat(groups[i], ".", keys[j], NULL); - g_datalist_set_data_full(list, group_key, value, g_free); - /* no need to free group_key and value: they stay in the list */ - } - g_strfreev(keys); - } - g_strfreev(groups); - } -} - -/** - * Parse keyfile into a NetplanNetDefinition struct - * @filename: full path to the NetworkManager keyfile - */ -gboolean -netplan_parse_keyfile(const char* filename, GError** error) -{ - g_autofree gchar *nd_id = NULL; - g_autofree gchar *uuid = NULL; - g_autofree gchar *type = NULL; - g_autofree gchar* wifi_mode = NULL; - g_autofree gchar* ssid = NULL; - g_autofree gchar* netdef_id = NULL; - NetplanNetDefinition* nd = NULL; - NetplanWifiAccessPoint* ap = NULL; - g_autoptr(GKeyFile) kf = g_key_file_new(); - NetplanDefType nd_type = NETPLAN_DEF_TYPE_NONE; - if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_NONE, error)) { - g_warning("netplan: cannot load keyfile"); - return FALSE; - } - - ssid = g_key_file_get_string(kf, "wifi", "ssid", NULL); - if (!ssid) - ssid = g_key_file_get_string(kf, "802-11-wireless", "ssid", NULL); - - netdef_id = netplan_get_id_from_nm_filename(filename, ssid); - uuid = g_key_file_get_string(kf, "connection", "uuid", NULL); - if (!uuid) { - g_warning("netplan: Keyfile: cannot find connection.uuid"); - return FALSE; - } - - type = g_key_file_get_string(kf, "connection", "type", NULL); - if (!type) { - g_warning("netplan: Keyfile: cannot find connection.type"); - return FALSE; - } - nd_type = type_from_str(type); - - /* Use previously existing netdef IDs, if available, to override connections - * Else: generate a "NM-" ID */ - if (netdef_id) - nd_id = g_strdup(netdef_id); - else - nd_id = g_strconcat("NM-", uuid, NULL); - nd = netplan_netdef_new(nd_id, nd_type, NETPLAN_BACKEND_NM); - - /* Handle uuid & NM name/id */ - nd->backend_settings.nm.uuid = g_strdup(uuid); - _kf_clear_key(kf, "connection", "uuid"); - nd->backend_settings.nm.name = g_key_file_get_string(kf, "connection", "id", NULL); - if (nd->backend_settings.nm.name) - _kf_clear_key(kf, "connection", "id"); - - if (nd_type == NETPLAN_DEF_TYPE_NM) - goto only_passthrough; //do not try to handle any keys for connections types unknown to netplan - - /* remove supported values from passthrough, which have been handled */ - if ( nd_type == NETPLAN_DEF_TYPE_ETHERNET - || nd_type == NETPLAN_DEF_TYPE_WIFI - || nd_type == NETPLAN_DEF_TYPE_MODEM - || nd_type == NETPLAN_DEF_TYPE_BRIDGE - || nd_type == NETPLAN_DEF_TYPE_BOND - || nd_type == NETPLAN_DEF_TYPE_VLAN) - _kf_clear_key(kf, "connection", "type"); - - /* Handle match: Netplan usually defines a connection per interface, while - * NM connection profiles are usually applied to any interface of matching - * type (like wifi/ethernet/...). */ - if (nd->type < NETPLAN_DEF_TYPE_VIRTUAL) { - nd->match.original_name = g_key_file_get_string(kf, "connection", "interface-name", NULL); - if (nd->match.original_name) - _kf_clear_key(kf, "connection", "interface-name"); - /* Set match, even if it is empty, so the NM renderer will not force - * the netdef ID as interface-name */ - nd->has_match = TRUE; - } - - /* Modem parameters - * NM differentiates between GSM and CDMA connections, while netplan - * combines them as "modems". We need to parse a basic set of parameters - * to enable the generator (in nm.c) to detect GSM vs CDMA connections, - * using its modem_is_gsm() util. */ - nd->modem_params.auto_config = g_key_file_get_boolean(kf, "gsm", "auto-config", NULL); - _kf_clear_key(kf, "gsm", "auto-config"); - nd->modem_params.apn = g_key_file_get_string(kf, "gsm", "apn", NULL); - if (nd->modem_params.apn) - _kf_clear_key(kf, "gsm", "apn"); - nd->modem_params.device_id = g_key_file_get_string(kf, "gsm", "device-id", NULL); - if (nd->modem_params.device_id) - _kf_clear_key(kf, "gsm", "device-id"); - nd->modem_params.network_id = g_key_file_get_string(kf, "gsm", "network-id", NULL); - if (nd->modem_params.network_id) - _kf_clear_key(kf, "gsm", "network-id"); - nd->modem_params.pin = g_key_file_get_string(kf, "gsm", "pin", NULL); - if (nd->modem_params.pin) - _kf_clear_key(kf, "gsm", "pin"); - nd->modem_params.sim_id = g_key_file_get_string(kf, "gsm", "sim-id", NULL); - if (nd->modem_params.sim_id) - _kf_clear_key(kf, "gsm", "sim-id"); - nd->modem_params.sim_operator_id = g_key_file_get_string(kf, "gsm", "sim-operator-id", NULL); - if (nd->modem_params.sim_operator_id) - _kf_clear_key(kf, "gsm", "sim-operator-id"); - - /* wake-on-lan, do not clear passthrough as we do not fully support this setting */ - if (g_key_file_has_group(kf, "ethernet")) { - if (!g_key_file_has_key(kf, "ethernet", "wake-on-lan", NULL)) { - nd->wake_on_lan = TRUE; //NM's default is "1" - } else { - //XXX: fix delta between options in NM (0x1, 0x2, 0x4, ...) and netplan (bool) - nd->wake_on_lan = g_key_file_get_uint64(kf, "ethernet", "wake-on-lan", NULL) > 0; - } - } - - /* Special handling for WiFi "access-points:" mapping */ - if (nd->type == NETPLAN_DEF_TYPE_WIFI) { - ap = g_new0(NetplanWifiAccessPoint, 1); - ap->ssid = g_key_file_get_string(kf, "wifi", "ssid", NULL); - if (!ap->ssid) { - g_warning("netplan: Keyfile: cannot find SSID for WiFi connection"); - return FALSE; - } else - _kf_clear_key(kf, "wifi", "ssid"); - - wifi_mode = g_key_file_get_string(kf, "wifi", "mode", NULL); - if (wifi_mode) { - ap->mode = ap_type_from_str(wifi_mode); - if (ap->mode != NETPLAN_WIFI_MODE_OTHER) - _kf_clear_key(kf, "wifi", "mode"); - } - - ap->hidden = g_key_file_get_boolean(kf, "wifi", "hidden", NULL); - _kf_clear_key(kf, "wifi", "hidden"); - - if (!nd->access_points) - nd->access_points = g_hash_table_new(g_str_hash, g_str_equal); - g_hash_table_insert(nd->access_points, ap->ssid, ap); - - /* Last: handle passthrough for everything left in the keyfile - * Also, transfer backend_settings from netdef to AP */ - ap->backend_settings.nm.uuid = nd->backend_settings.nm.uuid; - ap->backend_settings.nm.name = nd->backend_settings.nm.name; - /* No need to clear nm.uuid & nm.name from def->backend_settings, - * as we have only one AP. */ - read_passthrough(kf, &ap->backend_settings.nm.passthrough); - } else { -only_passthrough: - /* Last: handle passthrough for everything left in the keyfile */ - read_passthrough(kf, &nd->backend_settings.nm.passthrough); - } - - g_key_file_free(kf); - return TRUE; -} diff -Nru netplan.io-0.102/src/parse-nm.h netplan.io-0.101/src/parse-nm.h --- netplan.io-0.102/src/parse-nm.h 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/src/parse-nm.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2021 Canonical, Ltd. - * Author: Lukas Märdian - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - -#define NETPLAN_NM_EMPTY_GROUP "_" - -gboolean netplan_parse_keyfile(const char* filename, GError** error); diff -Nru netplan.io-0.102/src/util.c netplan.io-0.101/src/util.c --- netplan.io-0.102/src/util.c 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/src/util.c 2020-12-09 11:32:25.000000000 +0000 @@ -22,7 +22,6 @@ #include #include "util.h" -#include "netplan.h" GHashTable* wifi_frequency_24; GHashTable* wifi_frequency_5; @@ -52,12 +51,10 @@ void g_string_free_to_file(GString* s, const char* rootdir, const char* path, const char* suffix) { g_autofree char* full_path = NULL; - g_autofree char* path_suffix = NULL; g_autofree char* contents = g_string_free(s, FALSE); GError* error = NULL; - path_suffix = g_strjoin(NULL, path, suffix, NULL); - full_path = g_build_path(G_DIR_SEPARATOR_S, rootdir ?: G_DIR_SEPARATOR_S, path_suffix, NULL); + full_path = g_strjoin(NULL, rootdir ?: "", G_DIR_SEPARATOR_S, path, suffix, NULL); safe_mkdir_p_dir(full_path); if (!g_file_set_contents(full_path, contents, -1, &error)) { /* the mkdir() just succeeded, there is no sensible @@ -198,117 +195,3 @@ return escaped; } - -gboolean -netplan_delete_connection(const char* id, const char* rootdir) -{ - g_autofree gchar* filename = NULL; - g_autofree gchar* del = NULL; - g_autoptr(GError) error = NULL; - NetplanNetDefinition* nd = NULL; - - /* parse all YAML files */ - if (!process_yaml_hierarchy(rootdir)) - return FALSE; // LCOV_EXCL_LINE - - netdefs = netplan_finish_parse(&error); - if (!netdefs) { - // LCOV_EXCL_START - g_fprintf(stderr, "netplan_delete_connection: %s\n", error->message); - return FALSE; - // LCOV_EXCL_STOP - } - - /* find filename for specified netdef ID */ - nd = g_hash_table_lookup(netdefs, id); - if (!nd) { - g_warning("netplan_delete_connection: Cannot delete %s, does not exist.", id); - return FALSE; - } - - filename = g_path_get_basename(nd->filename); - filename[strlen(filename) - 5] = '\0'; //stip ".yaml" suffix - del = g_strdup_printf("network.%s.%s=NULL", netplan_def_type_to_str[nd->type], id); - netplan_clear_netdefs(); - - /* TODO: refactor logic to actually be inside the library instead of spawning another process */ - const gchar *argv[] = { "/sbin/netplan", "set", del, "--origin-hint" , filename, NULL, NULL, NULL }; - if (rootdir) { - argv[5] = "--root-dir"; - argv[6] = rootdir; - } - if (getenv("TEST_NETPLAN_CMD") != 0) - argv[0] = getenv("TEST_NETPLAN_CMD"); - return g_spawn_sync(NULL, (gchar**)argv, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL); -} - -gboolean -netplan_generate(const char* rootdir) -{ - /* TODO: refactor logic to actually be inside the library instead of spawning another process */ - const gchar *argv[] = { "/sbin/netplan", "generate", NULL , NULL, NULL }; - if (rootdir) { - argv[2] = "--root-dir"; - argv[3] = rootdir; - } - if (getenv("TEST_NETPLAN_CMD") != 0) - argv[0] = getenv("TEST_NETPLAN_CMD"); - return g_spawn_sync(NULL, (gchar**)argv, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL); -} - -/** - * Extract the netplan netdef ID from a NetworkManager connection profile (keyfile), - * generated by netplan. Used by the NetworkManager YAML backend. - */ -gchar* -netplan_get_id_from_nm_filename(const char* filename, const char* ssid) -{ - g_autofree gchar* escaped_ssid = NULL; - g_autofree gchar* suffix = NULL; - const char* nm_prefix = "/run/NetworkManager/system-connections/netplan-"; - const char* pos = g_strrstr(filename, nm_prefix); - const char* start = NULL; - const char* end = NULL; - gsize id_len = 0; - - if (!pos) - return NULL; - - if (ssid) { - escaped_ssid = g_uri_escape_string(ssid, NULL, TRUE); - suffix = g_strdup_printf("-%s.nmconnection", escaped_ssid); - end = g_strrstr(filename, suffix); - } else - end = g_strrstr(filename, ".nmconnection"); - - if (!end) - return NULL; - - /* Move pointer to start of netplan ID inside filename string */ - start = pos + strlen(nm_prefix); - id_len = end - start; - return g_strndup(start, id_len); -} - -/** - * Get the filename from which the given netdef has been parsed. - * @rootdir: ID of the netdef to be looked up - * @rootdir: parse files from this root directory - */ -gchar* -netplan_get_filename_by_id(const char* netdef_id, const char* rootdir) -{ - gchar* filename = NULL; - netplan_clear_netdefs(); - if (!process_yaml_hierarchy(rootdir)) - return NULL; // LCOV_EXCL_LINE - GHashTable* netdefs = netplan_finish_parse(NULL); - if (!netdefs) - return NULL; - NetplanNetDefinition* nd = g_hash_table_lookup(netdefs, netdef_id); - if (!nd) - return NULL; - filename = g_strdup(nd->filename); - netplan_clear_netdefs(); - return filename; -} diff -Nru netplan.io-0.102/src/util.h netplan.io-0.101/src/util.h --- netplan.io-0.102/src/util.h 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/src/util.h 2020-12-09 11:32:25.000000000 +0000 @@ -31,9 +31,5 @@ int wifi_get_freq5(int channel); gchar* systemd_escape(char* string); -gboolean netplan_delete_connection(const char* id, const char* rootdir); -gboolean netplan_generate(const char* rootdir); -gchar* netplan_get_id_from_nm_filename(const char* filename, const char* ssid); -gchar* netplan_get_filename_by_id(const char* netdef_id, const char* rootdir); #define OPENVSWITCH_OVS_VSCTL "/usr/bin/ovs-vsctl" diff -Nru netplan.io-0.102/src/validation.c netplan.io-0.101/src/validation.c --- netplan.io-0.102/src/validation.c 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/src/validation.c 2020-12-09 11:32:25.000000000 +0000 @@ -205,8 +205,6 @@ return yaml_error(node, error, "%s: missing 'local' property for tunnel", nd->id); if (!nd->tunnel.remote_ip) return yaml_error(node, error, "%s: missing 'remote' property for tunnel", nd->id); - if (nd->tunnel.ttl && nd->tunnel.ttl > 255) - return yaml_error(node, error, "%s: 'ttl' property for tunnel must be in range [1...255]", nd->id); switch(nd->tunnel.mode) { case NETPLAN_TUNNEL_MODE_IPIP6: @@ -342,9 +340,6 @@ // LCOV_EXCL_STOP } - if (nd->type == NETPLAN_DEF_TYPE_NM && (!nd->backend_settings.nm.passthrough || !g_datalist_get_data(&nd->backend_settings.nm.passthrough, "connection.type"))) - return yaml_error(node, error, "%s: network type 'nm-devices:' needs to provide a 'connection.type' via passthrough", nd->id); - valid = TRUE; netdef_grammar_error: diff -Nru netplan.io-0.102/tests/cli.py netplan.io-0.101/tests/cli.py --- netplan.io-0.102/tests/cli.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/cli.py 2020-12-09 11:32:25.000000000 +0000 @@ -72,7 +72,7 @@ stderr=subprocess.PIPE) (out, err) = p.communicate() self.assertEqual(out, b'') - self.assertEqual(os.listdir(self.workdir.name), ['run']) + self.assertEqual(os.listdir(self.workdir.name), []) def test_with_empty_config(self): c = os.path.join(self.workdir.name, 'etc', 'netplan') diff -Nru netplan.io-0.102/tests/dbus/test_dbus.py netplan.io-0.101/tests/dbus/test_dbus.py --- netplan.io-0.102/tests/dbus/test_dbus.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/dbus/test_dbus.py 2020-12-09 11:32:25.000000000 +0000 @@ -20,8 +20,6 @@ import unittest import time -from tests.test_utils import MockCmd - rootdir = os.path.dirname(os.path.dirname( os.path.dirname(os.path.abspath(__file__)))) exe_cli = [os.path.join(rootdir, 'src', 'netplan.script')] @@ -33,6 +31,63 @@ NETPLAN_DBUS_CMD = os.path.join(os.path.dirname(__file__), "..", "..", "netplan-dbus") +class MockCmd: + """MockCmd will mock a given command name and capture all calls to it""" + + def __init__(self, name): + self._tmp = tempfile.TemporaryDirectory() + self.name = name + self.path = os.path.join(self._tmp.name, name) + self.call_log = os.path.join(self._tmp.name, "call.log") + with open(self.path, "w") as fp: + fp.write("""#!/bin/bash +printf "%%s" "$(basename "$0")" >> %(log)s +printf '\\0' >> %(log)s + +for arg in "$@"; do + printf "%%s" "$arg" >> %(log)s + printf '\\0' >> %(log)s +done + +printf '\\0' >> %(log)s +""" % {'log': self.call_log}) + os.chmod(self.path, 0o755) + + def calls(self): + """ + calls() returns the calls to the given mock command in the form of + [ ["cmd", "call1-arg1"], ["cmd", "call2-arg1"], ... ] + """ + with open(self.call_log) as fp: + b = fp.read() + calls = [] + for raw_call in b.rstrip("\0\0").split("\0\0"): + call = raw_call.rstrip("\0") + calls.append(call.split("\0")) + return calls + + def set_output(self, output): + with open(self.path, "a") as fp: + fp.write("cat << EOF\n%s\nEOF" % output) + + def set_timeout(self, timeout=1): + with open(self.path, "a") as fp: + fp.write(""" +if [[ "$*" == *try* ]] +then + ACTIVE=1 + trap 'ACTIVE=0' SIGUSR1 + trap 'ACTIVE=0' SIGINT + # timeout * 10 is the specified timeout in seconds (0.1 sec sleep increments) + while (( $ACTIVE > 0 )) && (( $ACTIVE <= $(({}*10)) )) + do + ACTIVE=$(($ACTIVE+1)) + sleep 0.1 + done +fi +""".format(timeout)) + + class TestNetplanDBus(unittest.TestCase): def setUp(self): @@ -76,7 +131,6 @@ os.environ["DBUS_TEST_NETPLAN_ROOT"] = self.tmp p = subprocess.Popen(NETPLAN_DBUS_CMD, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - time.sleep(1) # Give some time for our dbus daemon to be ready self.addCleanup(self._cleanup_netplan_dbus, p) def _cleanup_netplan_dbus(self, p): @@ -144,8 +198,8 @@ ]) def test_netplan_dbus_noroot(self): - # Process should fail instantly, if not: kill it after 5 sec - r = subprocess.run(NETPLAN_DBUS_CMD, timeout=5, capture_output=True) + # Process should fail instantly, if not: kill it after 1 sec + r = subprocess.run(NETPLAN_DBUS_CMD, timeout=1, capture_output=True) self.assertEquals(r.returncode, 1) self.assertIn(b'Failed to acquire service name', r.stderr) @@ -242,14 +296,13 @@ "io.netplan.Netplan", "/io/netplan/Netplan/config/{}".format(cid), "io.netplan.Netplan.Config", - "Set", "ss", "ethernets.eth42.dhcp6=true", "", + "Set", "ss", "ethernets.eth42.dhcp6=true", "testfile", ] out = subprocess.check_output(BUSCTL_NETPLAN_CMD) self.assertEqual(b'b true\n', out) - print(self.mock_netplan_cmd.calls(), flush=True) self.assertEquals(self.mock_netplan_cmd.calls(), [[ "netplan", "set", "ethernets.eth42.dhcp6=true", - "--root-dir={}".format(tmpdir) + "--origin-hint=testfile", "--root-dir={}".format(tmpdir) ]]) def test_netplan_dbus_config_get(self): @@ -286,8 +339,6 @@ ] out = subprocess.check_output(BUSCTL_NETPLAN_CMD) self.assertEqual(b'b true\n', out) - - time.sleep(1) # Give some time for 'Cancel' to clean up self.assertFalse(os.path.isdir(tmpdir)) # Verify the object is gone from the bus @@ -315,7 +366,6 @@ out = subprocess.check_output(BUSCTL_NETPLAN_CMD) self.assertEqual(b'b true\n', out) self.assertEquals(self.mock_netplan_cmd.calls(), [["netplan", "apply"]]) - time.sleep(1) # Give some time for 'Apply' to clean up self.assertFalse(os.path.isdir(tmpdir)) # Verify the new YAML files were copied over @@ -328,8 +378,7 @@ self.assertIn('Unknown object \'/io/netplan/Netplan/config/{}\''.format(cid), err) def test_netplan_dbus_config_try_cancel(self): - # self-terminate after 30 dsec = 3 sec, if not cancelled before - self.mock_netplan_cmd.set_timeout(30) + self.mock_netplan_cmd.set_timeout(2) cid = self._new_config_object() tmpdir = '/tmp/netplan-config-{}'.format(cid) backup = '/tmp/netplan-config-BACKUP' @@ -346,7 +395,7 @@ "io.netplan.Netplan", "/io/netplan/Netplan/config/{}".format(cid), "io.netplan.Netplan.Config", - "Try", "u", "3", + "Try", "u", "2", ] out = subprocess.check_output(BUSCTL_NETPLAN_CMD) self.assertEqual(b'b true\n', out) @@ -375,7 +424,7 @@ ] out = subprocess.check_output(BUSCTL_NETPLAN_CMD2) self.assertEqual(b'b true\n', out) - time.sleep(1) # Give some time for 'Cancel' to clean up + time.sleep(1) # Give some time for the 'netplan try' process # Verify the backup andconfig state dir are gone self.assertFalse(os.path.isdir(backup)) @@ -392,10 +441,10 @@ self.assertIn('Unknown object \'/io/netplan/Netplan/config/{}\''.format(cid), err) # Verify 'netplan try' has been called - self.assertEquals(self.mock_netplan_cmd.calls(), [["netplan", "try", "--timeout=3"]]) + self.assertEquals(self.mock_netplan_cmd.calls(), [["netplan", "try", "--timeout=2"]]) def test_netplan_dbus_config_try_cb(self): - self.mock_netplan_cmd.set_timeout(1) # actually self-terminate after 0.1 sec + self.mock_netplan_cmd.set_timeout(1) # self-quit after 1 sec cid = self._new_config_object() tmpdir = '/tmp/netplan-config-{}'.format(cid) backup = '/tmp/netplan-config-BACKUP' @@ -435,14 +484,14 @@ self.assertEquals(self.mock_netplan_cmd.calls(), [["netplan", "try", "--timeout=1"]]) def test_netplan_dbus_config_try_apply(self): - self.mock_netplan_cmd.set_timeout(30) # 30 dsec = 3 sec + self.mock_netplan_cmd.set_timeout(2) cid = self._new_config_object() BUSCTL_NETPLAN_CMD = [ "busctl", "call", "--system", "io.netplan.Netplan", "/io/netplan/Netplan/config/{}".format(cid), "io.netplan.Netplan.Config", - "Try", "u", "3", + "Try", "u", "2", ] out = subprocess.check_output(BUSCTL_NETPLAN_CMD) self.assertEqual(b'b true\n', out) @@ -458,14 +507,14 @@ self.assertIn('Another \'netplan try\' process is already running', err) def test_netplan_dbus_config_try_config_try(self): - self.mock_netplan_cmd.set_timeout(50) # 50 dsec = 5 sec + self.mock_netplan_cmd.set_timeout(2) cid = self._new_config_object() BUSCTL_NETPLAN_CMD = [ "busctl", "call", "--system", "io.netplan.Netplan", "/io/netplan/Netplan/config/{}".format(cid), "io.netplan.Netplan.Config", - "Try", "u", "3", + "Try", "u", "2", ] out = subprocess.check_output(BUSCTL_NETPLAN_CMD) self.assertEqual(b'b true\n', out) @@ -476,13 +525,13 @@ "io.netplan.Netplan", "/io/netplan/Netplan/config/{}".format(cid2), "io.netplan.Netplan.Config", - "Try", "u", "5", + "Try", "u", "2", ] err = self._check_dbus_error(BUSCTL_NETPLAN_CMD2) self.assertIn('Another Try() is currently in progress: PID ', err) def test_netplan_dbus_config_set_invalidate(self): - self.mock_netplan_cmd.set_timeout(30) # 30 dsec = 3 sec + self.mock_netplan_cmd.set_timeout(2) cid = self._new_config_object() BUSCTL_NETPLAN_CMD = [ "busctl", "call", "--system", @@ -521,7 +570,7 @@ "io.netplan.Netplan", "/io/netplan/Netplan/config/{}".format(cid2), "io.netplan.Netplan.Config", - "Try", "u", "3", + "Try", "u", "2", ] err = self._check_dbus_error(BUSCTL_NETPLAN_CMD3) self.assertIn('This config was invalidated by another config object', err) @@ -626,7 +675,7 @@ ]) def test_netplan_dbus_config_set_uninvalidate_timeout(self): - self.mock_netplan_cmd.set_timeout(1) # actually self-terminate process after 0.1 sec + self.mock_netplan_cmd.set_timeout(1) cid = self._new_config_object() cid2 = self._new_config_object() BUSCTL_NETPLAN_CMD = [ @@ -660,7 +709,7 @@ err = self._check_dbus_error(BUSCTL_NETPLAN_CMD2) self.assertIn('This config was invalidated by another config object', err) - time.sleep(1.5) # Wait for the child process to self-terminate + time.sleep(1.5) # Wait for the child process to cancel itself # Calling Set() on the other config object works now out = subprocess.check_output(BUSCTL_NETPLAN_CMD2) diff -Nru netplan.io-0.102/tests/generator/test_args.py netplan.io-0.101/tests/generator/test_args.py --- netplan.io-0.102/tests/generator/test_args.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/generator/test_args.py 2020-12-09 11:32:25.000000000 +0000 @@ -27,7 +27,7 @@ def test_no_files(self): subprocess.check_call([exe_generate, '--root-dir', self.workdir.name]) - self.assertEqual(os.listdir(self.workdir.name), ['run']) + self.assertEqual(os.listdir(self.workdir.name), []) self.assert_nm_udev(None) def test_no_configs(self): @@ -113,8 +113,7 @@ eth0: dhcp4: true''') err = self.generate('', extra_args=['--root-dir', '/proc/foo', conf], expect_fail=True) - # can be /proc/foor/run/systemd/{network,system} - self.assertIn('cannot create directory /proc/foo/run/systemd/', err) + self.assertIn('cannot create directory /proc/foo/run/systemd/network', err) def test_systemd_generator(self): conf = os.path.join(self.confdir, 'a.yaml') diff -Nru netplan.io-0.102/tests/generator/test_auth.py netplan.io-0.101/tests/generator/test_auth.py --- netplan.io-0.102/tests/generator/test_auth.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/generator/test_auth.py 2020-12-09 11:32:25.000000000 +0000 @@ -291,6 +291,9 @@ type=wifi interface-name=wl0 +[ethernet] +wake-on-lan=0 + [ipv4] method=auto @@ -310,6 +313,9 @@ type=wifi interface-name=wl0 +[ethernet] +wake-on-lan=0 + [ipv4] method=auto @@ -329,6 +335,9 @@ type=wifi interface-name=wl0 +[ethernet] +wake-on-lan=0 + [ipv4] method=auto @@ -353,6 +362,9 @@ type=wifi interface-name=wl0 +[ethernet] +wake-on-lan=0 + [ipv4] method=auto @@ -377,6 +389,9 @@ type=wifi interface-name=wl0 +[ethernet] +wake-on-lan=0 + [ipv4] method=auto @@ -401,6 +416,9 @@ type=wifi interface-name=wl0 +[ethernet] +wake-on-lan=0 + [ipv4] method=auto @@ -429,6 +447,9 @@ type=wifi interface-name=wl0 +[ethernet] +wake-on-lan=0 + [ipv4] method=auto @@ -444,6 +465,9 @@ type=wifi interface-name=wl0 +[ethernet] +wake-on-lan=0 + [ipv4] method=auto diff -Nru netplan.io-0.102/tests/generator/test_bridges.py netplan.io-0.101/tests/generator/test_bridges.py --- netplan.io-0.102/tests/generator/test_bridges.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/generator/test_bridges.py 2020-12-09 11:32:25.000000000 +0000 @@ -35,9 +35,6 @@ self.assert_networkd({'br0.network': '''[Match] Name=br0 -[Link] -MACAddress=00:01:02:03:04:05 - [Network] DHCP=ipv4 LinkLocalAddressing=ipv6 @@ -361,7 +358,7 @@ type=bridge interface-name=br0 -[ethernet] +[802-3-ethernet] cloned-mac-address=00:01:02:03:04:05 [ipv4] diff -Nru netplan.io-0.102/tests/generator/test_common.py netplan.io-0.101/tests/generator/test_common.py --- netplan.io-0.102/tests/generator/test_common.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/generator/test_common.py 2020-12-09 11:32:25.000000000 +0000 @@ -390,41 +390,6 @@ 'br0.network': ND_EMPTY % ('br0', 'ipv6'), 'br0.netdev': '[NetDev]\nName=br0\nKind=bridge\n'}) - def test_bond_arp_ip_targets_multi_pass(self): - self.generate('''network: - bonds: - bond0: - interfaces: - - eno1 - parameters: - arp-ip-targets: - - 10.10.10.10 - - 20.20.20.20 - ethernets: - eno1: {} - version: 2''') - self.assert_networkd({'bond0.netdev': '''[NetDev] -Name=bond0 -Kind=bond - -[Bond] -ARPIPTargets=10.10.10.10 20.20.20.20 -''', - 'bond0.network': '''[Match] -Name=bond0 - -[Network] -LinkLocalAddressing=ipv6 -ConfigureWithoutCarrier=yes -''', - 'eno1.network': '''[Match] -Name=eno1 - -[Network] -LinkLocalAddressing=no -Bond=bond0 -'''}) - def test_dhcp_critical_true(self): self.generate('''network: version: 2 @@ -740,7 +705,7 @@ type=bond interface-name=bond0 -[ethernet] +[802-3-ethernet] mtu=9000 [ipv4] @@ -758,6 +723,8 @@ [ethernet] wake-on-lan=0 + +[802-3-ethernet] mtu=1280 [ipv4] diff -Nru netplan.io-0.102/tests/generator/test_errors.py netplan.io-0.101/tests/generator/test_errors.py --- netplan.io-0.102/tests/generator/test_errors.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/generator/test_errors.py 2020-12-09 11:32:25.000000000 +0000 @@ -367,17 +367,6 @@ ipv6-address-token: INVALID''', expect_fail=True) self.assertIn("invalid ipv6-address-token 'INVALID'", err) - def test_nm_devices_missing_passthrough(self): - err = self.generate('''network: - version: 2 - renderer: NetworkManager - nm-devices: - engreen: - networkmanager: - passthrough: - connection.uuid: "123456"''', expect_fail=True) - self.assertIn("engreen: network type 'nm-devices:' needs to provide a 'connection.type' via passthrough", err) - def test_invalid_address_node_type(self): err = self.generate('''network: version: 2 @@ -551,36 +540,6 @@ addresses: - 192.168.14.2/24 - 2001:FFfe::1/64''', expect_fail=True) - - self.assertIn("invalid unsigned int value '-1'", err) - - def test_device_bad_route_congestion_window(self): - err = self.generate('''network: - version: 2 - ethernets: - engreen: - routes: - - to: 10.10.0.0/16 - via: 10.1.1.1 - congestion-window: -1 - addresses: - - 192.168.14.2/24 - - 2001:FFfe::1/64''', expect_fail=True) - - self.assertIn("invalid unsigned int value '-1'", err) - - def test_device_bad_route_advertised_receive_window(self): - err = self.generate('''network: - version: 2 - ethernets: - engreen: - routes: - - to: 10.10.0.0/16 - via: 10.1.1.1 - advertised-receive-window: -1 - addresses: - - 192.168.14.2/24 - - 2001:FFfe::1/64''', expect_fail=True) self.assertIn("invalid unsigned int value '-1'", err) diff -Nru netplan.io-0.102/tests/generator/test_ethernets.py netplan.io-0.101/tests/generator/test_ethernets.py --- netplan.io-0.102/tests/generator/test_ethernets.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/generator/test_ethernets.py 2020-12-09 11:32:25.000000000 +0000 @@ -225,8 +225,8 @@ macaddress: 00:01:02:03:04:05 dhcp4: true''') - self.assert_networkd({'def1.network': (ND_DHCP4 % 'green') - .replace('[Network]', '[Link]\nMACAddress=00:01:02:03:04:05\n\n[Network]') + self.assert_networkd({'def1.network': ND_DHCP4 % 'green', + 'def1.link': '[Match]\nOriginalName=green\n\n[Link]\nWakeOnLan=off\nMACAddress=00:01:02:03:04:05\n' }) self.assert_networkd_udev(None) @@ -354,6 +354,8 @@ [ethernet] wake-on-lan=0 + +[802-3-ethernet] mtu=1280 [ipv4] @@ -440,7 +442,13 @@ macaddress: 00:01:02:03:04:05 dhcp4: true''') - self.assert_networkd(None) + self.assert_networkd({'eth0.link': '''[Match] +OriginalName=eth0 + +[Link] +WakeOnLan=off +MACAddress=00:01:02:03:04:05 +'''}) self.assert_nm({'eth0': '''[connection] id=netplan-eth0 @@ -449,6 +457,8 @@ [ethernet] wake-on-lan=0 + +[802-3-ethernet] cloned-mac-address=00:01:02:03:04:05 [ipv4] @@ -565,6 +575,8 @@ [ethernet] wake-on-lan=0 + +[802-3-ethernet] mac-address=11:22:33:44:55:66 [ipv4] @@ -703,6 +715,8 @@ [ethernet] wake-on-lan=0 + +[802-3-ethernet] mac-address=00:11:22:33:44:55 [ipv4] diff -Nru netplan.io-0.102/tests/generator/test_modems.py netplan.io-0.101/tests/generator/test_modems.py --- netplan.io-0.102/tests/generator/test_modems.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/generator/test_modems.py 2020-12-09 11:32:25.000000000 +0000 @@ -59,6 +59,9 @@ username=test-user number=#666 +[ethernet] +wake-on-lan=0 + [ipv4] method=link-local @@ -83,6 +86,9 @@ [gsm] auto-config=true +[ethernet] +wake-on-lan=0 + [ipv4] method=link-local @@ -112,6 +118,9 @@ number=*99# pin=1234 +[ethernet] +wake-on-lan=0 + [ipv4] method=link-local @@ -136,6 +145,9 @@ [gsm] apn=internet +[ethernet] +wake-on-lan=0 + [ipv4] method=link-local @@ -164,6 +176,9 @@ password=some-pass username=some-user +[ethernet] +wake-on-lan=0 + [ipv4] method=link-local @@ -189,6 +204,9 @@ auto-config=true device-id=test +[ethernet] +wake-on-lan=0 + [ipv4] method=link-local @@ -214,6 +232,9 @@ auto-config=true network-id=test +[ethernet] +wake-on-lan=0 + [ipv4] method=link-local @@ -239,6 +260,9 @@ auto-config=true pin=1234 +[ethernet] +wake-on-lan=0 + [ipv4] method=link-local @@ -264,6 +288,9 @@ auto-config=true sim-id=test +[ethernet] +wake-on-lan=0 + [ipv4] method=link-local @@ -289,6 +316,9 @@ auto-config=true sim-operator-id=test +[ethernet] +wake-on-lan=0 + [ipv4] method=link-local @@ -331,32 +361,8 @@ sim-id=89148000000060671234 sim-operator-id=310260 -[ipv4] -method=link-local - -[ipv6] -method=ignore -'''}) - self.assert_networkd({}) - self.assert_nm_udev(None) - - def test_modem_nm_integration(self): - self.generate('''network: - version: 2 - renderer: NetworkManager - modems: - mobilephone: - auto-config: true - networkmanager: - uuid: b22d8f0f-3f34-46bd-ac28-801fa87f1eb6''') - self.assert_nm({'mobilephone': '''[connection] -id=netplan-mobilephone -type=gsm -uuid=b22d8f0f-3f34-46bd-ac28-801fa87f1eb6 -interface-name=mobilephone - -[gsm] -auto-config=true +[ethernet] +wake-on-lan=0 [ipv4] method=link-local @@ -366,60 +372,3 @@ '''}) self.assert_networkd({}) self.assert_nm_udev(None) - - def test_modem_nm_integration_gsm_cdma(self): - self.generate('''network: - version: 2 - modems: - NM-a08c5805-7cf5-43f7-afb9-12cb30f6eca3: - renderer: NetworkManager - match: {} - apn: internet2.voicestream.com - networkmanager: - uuid: a08c5805-7cf5-43f7-afb9-12cb30f6eca3 - name: "T-Mobile Funkadelic 2" - passthrough: - connection.type: "bluetooth" - gsm.apn: "internet2.voicestream.com" - gsm.device-id: "da812de91eec16620b06cd0ca5cbc7ea25245222" - gsm.username: "george.clinton.again" - gsm.sim-operator-id: "310260" - gsm.pin: "123456" - gsm.sim-id: "89148000000060671234" - gsm.password: "parliament2" - gsm.network-id: "254098" - ipv4.method: "auto" - ipv6.method: "auto"''') - self.assert_nm({'NM-a08c5805-7cf5-43f7-afb9-12cb30f6eca3': '''[connection] -id=T-Mobile Funkadelic 2 -#Netplan: passthrough override -type=bluetooth -uuid=a08c5805-7cf5-43f7-afb9-12cb30f6eca3 - -[gsm] -apn=internet2.voicestream.com -#Netplan: passthrough setting -device-id=da812de91eec16620b06cd0ca5cbc7ea25245222 -#Netplan: passthrough setting -username=george.clinton.again -#Netplan: passthrough setting -sim-operator-id=310260 -#Netplan: passthrough setting -pin=123456 -#Netplan: passthrough setting -sim-id=89148000000060671234 -#Netplan: passthrough setting -password=parliament2 -#Netplan: passthrough setting -network-id=254098 - -[ipv4] -#Netplan: passthrough override -method=auto - -[ipv6] -#Netplan: passthrough override -method=auto -'''}) - self.assert_networkd({}) - self.assert_nm_udev(None) diff -Nru netplan.io-0.102/tests/generator/test_ovs.py netplan.io-0.101/tests/generator/test_ovs.py --- netplan.io-0.102/tests/generator/test_ovs.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/generator/test_ovs.py 2020-12-09 11:32:25.000000000 +0000 @@ -293,7 +293,7 @@ openvswitch: lacp: passive ''', expect_fail=True) - self.assertIn("Key 'lacp' is only valid for interface type 'openvswitch bond'", err) + self.assertIn("Key 'lacp' is only valid for iterface type 'openvswitch bond'", err) def test_bond_mode_implicit_params(self): self.generate('''network: @@ -497,7 +497,7 @@ openvswitch: fail-mode: glorious ''', expect_fail=True) - self.assertIn("Key 'fail-mode' is only valid for interface type 'openvswitch bridge'", err) + self.assertIn("Key 'fail-mode' is only valid for iterface type 'openvswitch bridge'", err) def test_rstp_non_bridge(self): err = self.generate('''network: @@ -507,7 +507,7 @@ openvswitch: rstp: true ''', expect_fail=True) - self.assertIn("Key is only valid for interface type 'openvswitch bridge'", err) + self.assertIn("Key is only valid for iterface type 'openvswitch bridge'", err) def test_bridge_set_protocols(self): self.generate('''network: @@ -548,7 +548,7 @@ openvswitch: protocols: [OpenFlow10, OpenFlow15] ''', expect_fail=True) - self.assertIn("Key 'protocols' is only valid for interface type 'openvswitch bridge'", err) + self.assertIn("Key 'protocols' is only valid for iterface type 'openvswitch bridge'", err) def test_bridge_controller(self): self.generate('''network: @@ -651,7 +651,7 @@ controller: connection-mode: in-band ''', expect_fail=True) - self.assertIn("Key 'controller.connection-mode' is only valid for interface type 'openvswitch bridge'", err) + self.assertIn("Key 'controller.connection-mode' is only valid for iterface type 'openvswitch bridge'", err) self.assert_ovs({}) self.assert_networkd({}) @@ -664,7 +664,7 @@ controller: addresses: [unix:/some/socket] ''', expect_fail=True) - self.assertIn("Key 'controller.addresses' is only valid for interface type 'openvswitch bridge'", err) + self.assertIn("Key 'controller.addresses' is only valid for iterface type 'openvswitch bridge'", err) self.assert_ovs({}) self.assert_networkd({}) @@ -700,7 +700,7 @@ ''', expect_fail=True) self.assertIn("ERROR: openvswitch bridge controller target 'ssl:10.10.10.1' needs SSL configuration, but global \ 'openvswitch.ssl' settings are not set", err) - self.assert_ovs({'cleanup.service': OVS_CLEANUP % {'iface': 'cleanup'}}) + self.assert_ovs({}) self.assert_networkd({}) def test_global_ports(self): @@ -711,7 +711,7 @@ - [patch0-1, patch1-0] ''', expect_fail=True) self.assertIn('patch0-1: OpenVSwitch patch port needs to be assigned to a bridge/bond', err) - self.assert_ovs({'cleanup.service': OVS_CLEANUP % {'iface': 'cleanup'}}) + self.assert_ovs({}) self.assert_networkd({}) def test_few_ports(self): @@ -987,7 +987,7 @@ openvswitch: {} ''', expect_fail=True) self.assertIn('eth0: This device type is not supported with the OpenVSwitch backend', err) - self.assert_ovs({'cleanup.service': OVS_CLEANUP % {'iface': 'cleanup'}}) + self.assert_ovs({}) self.assert_networkd({}) def test_bridge_non_ovs_bond(self): diff -Nru netplan.io-0.102/tests/generator/test_passthrough.py netplan.io-0.101/tests/generator/test_passthrough.py --- netplan.io-0.102/tests/generator/test_passthrough.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/generator/test_passthrough.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,285 +0,0 @@ -# -# Tests for passthrough config generated via netplan -# -# Copyright (C) 2021 Canonical, Ltd. -# Author: Lukas Märdian -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from .base import TestBase - - -# No passthrough mode (yet) for systemd-networkd -class TestNetworkd(TestBase): - pass - - -class TestNetworkManager(TestBase): - - def test_passthrough_basic(self): - self.generate('''network: - version: 2 - ethernets: - NM-87749f1d-334f-40b2-98d4-55db58965f5f: - renderer: NetworkManager - match: {} - networkmanager: - uuid: 87749f1d-334f-40b2-98d4-55db58965f5f - name: some NM id - passthrough: - connection.uuid: 87749f1d-334f-40b2-98d4-55db58965f5f - connection.type: ethernet - connection.permissions:''') - - self.assert_nm({'NM-87749f1d-334f-40b2-98d4-55db58965f5f': '''[connection] -id=some NM id -type=ethernet -uuid=87749f1d-334f-40b2-98d4-55db58965f5f -#Netplan: passthrough setting -permissions= - -[ethernet] -wake-on-lan=0 - -[ipv4] -method=link-local - -[ipv6] -method=ignore -'''}) - - def test_passthrough_wifi(self): - self.generate('''network: - version: 2 - wifis: - NM-87749f1d-334f-40b2-98d4-55db58965f5f: - renderer: NetworkManager - match: {} - access-points: - "SOME-SSID": - networkmanager: - uuid: 87749f1d-334f-40b2-98d4-55db58965f5f - name: myid with spaces - passthrough: - connection.permissions: - wifi.ssid: SOME-SSID - "OTHER-SSID": - hidden: true''') - - self.assert_nm({'NM-87749f1d-334f-40b2-98d4-55db58965f5f-SOME-SSID': '''[connection] -id=myid with spaces -type=wifi -uuid=87749f1d-334f-40b2-98d4-55db58965f5f -#Netplan: passthrough setting -permissions= - -[ipv4] -method=link-local - -[ipv6] -method=ignore - -[wifi] -ssid=SOME-SSID -mode=infrastructure -''', - 'NM-87749f1d-334f-40b2-98d4-55db58965f5f-OTHER-SSID': '''[connection] -id=netplan-NM-87749f1d-334f-40b2-98d4-55db58965f5f-OTHER-SSID -type=wifi - -[ipv4] -method=link-local - -[ipv6] -method=ignore - -[wifi] -ssid=OTHER-SSID -mode=infrastructure -hidden=true -'''}) - - def test_passthrough_type_nm_devices(self): - self.generate('''network: - nm-devices: - NM-87749f1d-334f-40b2-98d4-55db58965f5f: - renderer: NetworkManager - match: {} - networkmanager: - passthrough: - connection.uuid: 87749f1d-334f-40b2-98d4-55db58965f5f - connection.type: dummy''') - - self.assert_nm({'NM-87749f1d-334f-40b2-98d4-55db58965f5f': '''[connection] -id=netplan-NM-87749f1d-334f-40b2-98d4-55db58965f5f -#Netplan: passthrough setting -uuid=87749f1d-334f-40b2-98d4-55db58965f5f -#Netplan: passthrough setting -type=dummy - -[ipv4] -method=link-local - -[ipv6] -method=ignore -'''}) - - def test_passthrough_dotted_group(self): - self.generate('''network: - nm-devices: - dotted-group-test: - renderer: NetworkManager - match: {} - networkmanager: - passthrough: - connection.type: "wireguard" - wireguard-peer.some-key.endpoint: 1.2.3.4''') - - self.assert_nm({'dotted-group-test': '''[connection] -id=netplan-dotted-group-test -#Netplan: passthrough setting -type=wireguard - -[ipv4] -method=link-local - -[ipv6] -method=ignore - -[wireguard-peer.some-key] -#Netplan: passthrough setting -endpoint=1.2.3.4 -'''}) - - def test_passthrough_dotted_key(self): - self.generate('''network: - ethernets: - dotted-key-test: - renderer: NetworkManager - match: {} - networkmanager: - passthrough: - tc.qdisc.root: something - tc.qdisc.fff1: ":abc" - tc.filters.test: "test"''') - - self.assert_nm({'dotted-key-test': '''[connection] -id=netplan-dotted-key-test -type=ethernet - -[ethernet] -wake-on-lan=0 - -[ipv4] -method=link-local - -[ipv6] -method=ignore - -[tc] -#Netplan: passthrough setting -qdisc.root=something -#Netplan: passthrough setting -qdisc.fff1=:abc -#Netplan: passthrough setting -filters.test=test -'''}) - - def test_passthrough_unsupported_setting(self): - self.generate('''network: - wifis: - test: - renderer: NetworkManager - match: {} - access-points: - "SOME-SSID": # implicit "mode: infrasturcutre" - networkmanager: - passthrough: - wifi.mode: "mesh"''') - - self.assert_nm({'test-SOME-SSID': '''[connection] -id=netplan-test-SOME-SSID -type=wifi - -[ipv4] -method=link-local - -[ipv6] -method=ignore - -[wifi] -ssid=SOME-SSID -#Netplan: passthrough override -mode=mesh -'''}) - - def test_passthrough_empty_group(self): - self.generate('''network: - ethernets: - test: - renderer: NetworkManager - match: {} - networkmanager: - passthrough: - proxy._: ""''') - - self.assert_nm({'test': '''[connection] -id=netplan-test -type=ethernet - -[ethernet] -wake-on-lan=0 - -[ipv4] -method=link-local - -[ipv6] -method=ignore - -[proxy] -'''}) - - def test_passthrough_interface_rename_existing_id(self): - self.generate('''network: - version: 2 - renderer: NetworkManager - ethernets: - # This is the original netdef, generating "netplan-eth0.nmconnection" - eth0: - dhcp4: true - # This is the override netdef, modifying match.original_name (i.e. interface-name) - # it should still generate a "netplan-eth0.nmconnection" file (not netplan-eth33.nmconnection). - eth0: - renderer: NetworkManager - match: - name: "eth33" - networkmanager: - uuid: 626dd384-8b3d-3690-9511-192b2c79b3fd - name: "netplan-eth0" -''') - - self.assert_nm({'eth0': '''[connection] -id=netplan-eth0 -type=ethernet -uuid=626dd384-8b3d-3690-9511-192b2c79b3fd -interface-name=eth33 - -[ethernet] -wake-on-lan=0 - -[ipv4] -method=auto - -[ipv6] -method=ignore -'''}) diff -Nru netplan.io-0.102/tests/generator/test_routing.py netplan.io-0.101/tests/generator/test_routing.py --- netplan.io-0.102/tests/generator/test_routing.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/generator/test_routing.py 2020-12-09 11:32:25.000000000 +0000 @@ -369,56 +369,6 @@ MTUBytes=1500 '''}) - def test_route_v4_congestion_window(self): - self.generate('''network: - version: 2 - ethernets: - engreen: - addresses: ["192.168.14.2/24"] - routes: - - to: 10.10.10.0/24 - via: 192.168.14.20 - congestion-window: 16 - ''') - - self.assert_networkd({'engreen.network': '''[Match] -Name=engreen - -[Network] -LinkLocalAddressing=ipv6 -Address=192.168.14.2/24 - -[Route] -Destination=10.10.10.0/24 -Gateway=192.168.14.20 -InitialCongestionWindow=16 -'''}) - - def test_route_v4_advertised_receive_window(self): - self.generate('''network: - version: 2 - ethernets: - engreen: - addresses: ["192.168.14.2/24"] - routes: - - to: 10.10.10.0/24 - via: 192.168.14.20 - advertised-receive-window: 16 - ''') - - self.assert_networkd({'engreen.network': '''[Match] -Name=engreen - -[Network] -LinkLocalAddressing=ipv6 -Address=192.168.14.2/24 - -[Route] -Destination=10.10.10.0/24 -Gateway=192.168.14.20 -InitialAdvertisedReceiveWindow=16 -'''}) - def test_route_v6_single(self): self.generate('''network: version: 2 @@ -979,72 +929,6 @@ [ipv6] method=ignore -'''}) - self.assert_networkd({}) - - def test_route_congestion_window(self): - out = self.generate('''network: - version: 2 - ethernets: - engreen: - renderer: NetworkManager - addresses: ["192.168.14.2/24"] - routes: - - to: 10.10.10.0/24 - via: 192.168.1.20 - congestion-window: 16 - ''') - self.assertEqual('', out) - - self.assert_nm({'engreen': '''[connection] -id=netplan-engreen -type=ethernet -interface-name=engreen - -[ethernet] -wake-on-lan=0 - -[ipv4] -method=manual -address1=192.168.14.2/24 -route1=10.10.10.0/24,192.168.1.20 -route1_options=initcwnd=16 - -[ipv6] -method=ignore -'''}) - self.assert_networkd({}) - - def test_route_advertised_receive_window(self): - out = self.generate('''network: - version: 2 - ethernets: - engreen: - renderer: NetworkManager - addresses: ["192.168.14.2/24"] - routes: - - to: 10.10.10.0/24 - via: 192.168.1.20 - advertised-receive-window: 16 - ''') - self.assertEqual('', out) - - self.assert_nm({'engreen': '''[connection] -id=netplan-engreen -type=ethernet -interface-name=engreen - -[ethernet] -wake-on-lan=0 - -[ipv4] -method=manual -address1=192.168.14.2/24 -route1=10.10.10.0/24,192.168.1.20 -route1_options=initrwnd=16 - -[ipv6] -method=ignore '''}) self.assert_networkd({}) diff -Nru netplan.io-0.102/tests/generator/test_tunnels.py netplan.io-0.101/tests/generator/test_tunnels.py --- netplan.io-0.102/tests/generator/test_tunnels.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/generator/test_tunnels.py 2020-12-09 11:32:25.000000000 +0000 @@ -19,7 +19,7 @@ from .base import TestBase, ND_WITHIPGW, ND_EMPTY, NM_WG, ND_WG -def prepare_config_for_mode(renderer, mode, key=None, ttl=None): +def prepare_config_for_mode(renderer, mode, key=None): config = """network: version: 2 renderer: {} @@ -36,16 +36,15 @@ local_ip = "10.10.10.10" remote_ip = "20.20.20.20" - append_ttl = '\n ttl: {}'.format(ttl) if ttl else '' config += """ tunnels: tun0: mode: {} local: {} - remote: {}{} + remote: {} addresses: [ 15.15.15.15/24 ] gateway4: 20.20.20.21 -""".format(mode, local_ip, remote_ip, append_ttl) +""".format(mode, local_ip, remote_ip) # Handle key/keys as str or dict as required by the test if type(key) is str: @@ -360,7 +359,7 @@ endpoint=1.2.3.4:5 preshared-key=7voRZ/ojfXgfPOlswo3Lpma1RJq7qijIEEUEMShQFV8= preshared-key-flags=0 -allowed-ips=0.0.0.0/0;2001:fe:ad:de:ad:be:ef:1/24;''')}) +allowed-ips=0.0.0.0/0;2001:fe:ad:de:ad:be:ef:1/24''')}) def test_simple_multi_pass(self): """[wireguard] Validate generation of a wireguard config, which is parsed multiple times""" @@ -398,7 +397,7 @@ [wireguard-peer.M9nt4YujIOmNrRmpIRTmYSfMdrpvE7u6WkG8FY8WjG4=] persistent-keepalive=23 endpoint=1.2.3.4:5 -allowed-ips=0.0.0.0/0;2001:fe:ad:de:ad:be:ef:1/24; +allowed-ips=0.0.0.0/0;2001:fe:ad:de:ad:be:ef:1/24 [ipv4] method=manual @@ -428,7 +427,7 @@ 'allowed-ips': '[0.0.0.0/0, "2001:fe:ad:de:ad:be:ef:1/24"]', 'keepalive': 23, 'endpoint': '1.2.3.4:5'}, { - 'public-key': 'M9nt4YujIOmNrRmpIRTmYSfMdrpvE7u6WkG8FY8WjG5=', + 'public-key': 'M9nt4YujIOmNrRmpIRTmYSfMdrpvE7u6WkG8FY8WjG4=', 'allowed-ips': '[0.0.0.0/0, "2001:fe:ad:de:ad:be:ef:1/24"]', 'keepalive': 23, 'endpoint': '1.2.3.4:5'}], renderer=self.backend) @@ -442,7 +441,7 @@ Endpoint=1.2.3.4:5 [WireGuardPeer] -PublicKey=M9nt4YujIOmNrRmpIRTmYSfMdrpvE7u6WkG8FY8WjG5= +PublicKey=M9nt4YujIOmNrRmpIRTmYSfMdrpvE7u6WkG8FY8WjG4= AllowedIPs=0.0.0.0/0,2001:fe:ad:de:ad:be:ef:1/24 PersistentKeepalive=23 Endpoint=1.2.3.4:5'''), @@ -453,12 +452,12 @@ [wireguard-peer.M9nt4YujIOmNrRmpIRTmYSfMdrpvE7u6WkG8FY8WjG4=] persistent-keepalive=23 endpoint=1.2.3.4:5 -allowed-ips=0.0.0.0/0;2001:fe:ad:de:ad:be:ef:1/24; +allowed-ips=0.0.0.0/0;2001:fe:ad:de:ad:be:ef:1/24 -[wireguard-peer.M9nt4YujIOmNrRmpIRTmYSfMdrpvE7u6WkG8FY8WjG5=] +[wireguard-peer.M9nt4YujIOmNrRmpIRTmYSfMdrpvE7u6WkG8FY8WjG4=] persistent-keepalive=23 endpoint=1.2.3.4:5 -allowed-ips=0.0.0.0/0;2001:fe:ad:de:ad:be:ef:1/24;''')}) +allowed-ips=0.0.0.0/0;2001:fe:ad:de:ad:be:ef:1/24''')}) def test_privatekeyfile(self): """[wireguard] Validate generation of another simple wireguard config""" @@ -505,7 +504,7 @@ [wireguard-peer.M9nt4YujIOmNrRmpIRTmYSfMdrpvE7u6WkG8FY8WjG4=] persistent-keepalive=23 endpoint=[2001:fe:ad:de:ad:be:ef:11]:5 -allowed-ips=0.0.0.0/0;2001:fe:ad:de:ad:be:ef:1/24;''')}) +allowed-ips=0.0.0.0/0;2001:fe:ad:de:ad:be:ef:1/24''')}) # Execute the _CommonParserErrors only for one backend, to spare some test cycles @@ -745,7 +744,7 @@ def test_ipip(self): """[networkd] Validate generation of IPIP tunnels""" - config = prepare_config_for_mode('networkd', 'ipip', ttl=64) + config = prepare_config_for_mode('networkd', 'ipip') self.generate(config) self.assert_networkd({'tun0.netdev': '''[NetDev] Name=tun0 @@ -755,7 +754,6 @@ Independent=true Local=10.10.10.10 Remote=20.20.20.20 -TTL=64 ''', 'tun0.network': '''[Match] Name=tun0 @@ -1068,7 +1066,7 @@ def test_ipip(self): """[NetworkManager] Validate generation of IPIP tunnels""" - config = prepare_config_for_mode('NetworkManager', 'ipip', ttl=64) + config = prepare_config_for_mode('NetworkManager', 'ipip') self.generate(config) self.assert_nm({'tun0': '''[connection] id=netplan-tun0 @@ -1079,7 +1077,6 @@ mode=1 local=10.10.10.10 remote=20.20.20.20 -ttl=64 [ipv4] method=manual @@ -1263,20 +1260,6 @@ out = self.generate(config, expect_fail=True) self.assertIn("Error in network definition: tun0: missing 'remote' property for tunnel", out) - def test_invalid_ttl(self): - """Fail if TTL not in range [1...255]""" - config = '''network: - version: 2 - tunnels: - tun0: - mode: ipip - local: 20.20.20.20 - remote: 10.10.10.10 - ttl: 300 -''' - out = self.generate(config, expect_fail=True) - self.assertIn("Error in network definition: tun0: 'ttl' property for tunnel must be in range [1...255]", out) - def test_wrong_local_ip_for_mode_v4(self): """Show an error when an IPv6 local addr is used for an IPv4 tunnel mode""" config = '''network: diff -Nru netplan.io-0.102/tests/generator/test_vlans.py netplan.io-0.101/tests/generator/test_vlans.py --- netplan.io-0.102/tests/generator/test_vlans.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/generator/test_vlans.py 2020-12-09 11:32:25.000000000 +0000 @@ -62,8 +62,7 @@ Id=3 ''', 'enblue.network': ND_WITHIP % ('enblue', '1.2.3.4/24'), - 'enred.network': (ND_EMPTY % ('enred', 'ipv6')) - .replace('[Network]', '[Link]\nMACAddress=aa:bb:cc:dd:ee:11\n\n[Network]'), + 'enred.network': ND_EMPTY % ('enred', 'ipv6'), 'engreen.network': (ND_DHCP6_WOCARRIER % 'engreen')}) self.assert_nm(None, '''[keyfile] @@ -232,6 +231,8 @@ [ethernet] wake-on-lan=0 + +[802-3-ethernet] mac-address=11:22:33:44:55:66 [ipv4] diff -Nru netplan.io-0.102/tests/generator/test_wifis.py netplan.io-0.101/tests/generator/test_wifis.py --- netplan.io-0.102/tests/generator/test_wifis.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/generator/test_wifis.py 2020-12-09 11:32:25.000000000 +0000 @@ -265,7 +265,7 @@ password: "c0mpany1" mode: ap dhcp4: yes''', expect_fail=True) - self.assertIn('wl0: workplace: networkd does not support this wifi mode', err) + self.assertIn('networkd does not support wifi in access point mode', err) def test_wifi_wowlan(self): self.generate('''network: @@ -382,6 +382,9 @@ type=wifi interface-name=wl0 +[ethernet] +wake-on-lan=0 + [ipv4] method=auto @@ -404,6 +407,9 @@ type=wifi interface-name=wl0 +[ethernet] +wake-on-lan=0 + [ipv4] method=auto @@ -426,6 +432,9 @@ type=wifi interface-name=wl0 +[ethernet] +wake-on-lan=0 + [ipv4] method=auto @@ -446,6 +455,9 @@ type=wifi interface-name=wl0 +[ethernet] +wake-on-lan=0 + [ipv4] method=auto @@ -465,6 +477,9 @@ type=wifi interface-name=wl0 +[ethernet] +wake-on-lan=0 + [ipv4] method=auto @@ -480,6 +495,9 @@ type=wifi interface-name=wl0 +[ethernet] +wake-on-lan=0 + [ipv4] method=auto @@ -509,16 +527,21 @@ id=netplan-all-workplace type=wifi -[wifi] +[ethernet] +wake-on-lan=0 + +[802-11-wireless] mac-address=11:22:33:44:55:66 -ssid=workplace -mode=infrastructure [ipv4] method=link-local [ipv6] method=ignore + +[wifi] +ssid=workplace +mode=infrastructure '''}) def test_wifi_match_all(self): @@ -535,6 +558,9 @@ id=netplan-all-workplace type=wifi +[ethernet] +wake-on-lan=0 + [ipv4] method=link-local @@ -562,6 +588,9 @@ type=wifi interface-name=wl0 +[ethernet] +wake-on-lan=0 + [ipv4] method=shared @@ -594,6 +623,9 @@ type=wifi interface-name=wl0 +[ethernet] +wake-on-lan=0 + [ipv4] method=link-local @@ -620,16 +652,21 @@ type=wifi interface-name=wl0 -[wifi] +[ethernet] +wake-on-lan=0 + +[802-11-wireless] wake-on-wlan=330 -ssid=homenet -mode=infrastructure [ipv4] method=link-local [ipv6] method=ignore + +[wifi] +ssid=homenet +mode=infrastructure '''}) def test_wifi_wowlan_default(self): @@ -647,6 +684,9 @@ type=wifi interface-name=wl0 +[ethernet] +wake-on-lan=0 + [ipv4] method=link-local diff -Nru netplan.io-0.102/tests/integration/base.py netplan.io-0.101/tests/integration/base.py --- netplan.io-0.102/tests/integration/base.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/integration/base.py 2020-12-09 11:32:25.000000000 +0000 @@ -30,7 +30,6 @@ import unittest import shutil import gi -import glob # make sure we point to libnetplan properly. os.environ.update({'LD_LIBRARY_PATH': '.:{}'.format(os.environ.get('LD_LIBRARY_PATH'))}) @@ -52,35 +51,39 @@ class IntegrationTestsBase(unittest.TestCase): '''Common functionality for network test cases - setUp() creates two test ethernet devices (self.dev_e_{ap,client} and - self.dev_e2_{ap,client}. + setUp() creates two test wlan devices, one for a simulated access point + (self.dev_w_ap), the other for a simulated client device + (self.dev_w_client), and two test ethernet devices (self.dev_e_{ap,client} + and self.dev_e2_{ap,client}. - Each test should call self.setup_eth() with the desired configuration. + Each test should call self.setup_ap() or self.setup_eth() with the desired + configuration. ''' @classmethod def setUpClass(klass): - shutil.rmtree('/etc/netplan', ignore_errors=True) - os.makedirs('/etc/netplan', exist_ok=True) - # Try to keep autopkgtest's management network (eth0/ens3) up and - # configured. It should be running all the time, independently of netplan - os.makedirs('/etc/systemd/network', exist_ok=True) - with open('/etc/systemd/network/20-wired.network', 'w') as f: - f.write('[Match]\nName=eth0 en*\n\n[Network]\nDHCP=ipv4') - # ensure NM can manage our fake eths os.makedirs('/run/udev/rules.d', exist_ok=True) + with open('/run/udev/rules.d/99-nm-veth-test.rules', 'w') as f: f.write('ENV{ID_NET_DRIVER}=="veth", ENV{INTERFACE}=="eth42|eth43", ENV{NM_UNMANAGED}="0"\n') subprocess.check_call(['udevadm', 'control', '--reload']) - os.makedirs('/etc/NetworkManager/conf.d', exist_ok=True) - with open('/etc/NetworkManager/conf.d/99-test-ignore.conf', 'w') as f: - f.write('[keyfile]\nunmanaged-devices+=interface-name:eth0,interface-name:en*,interface-name:veth42,interface-name:veth43') - subprocess.check_call(['netplan', 'apply']) - subprocess.call(['/lib/systemd/systemd-networkd-wait-online', '--quiet', '--timeout=30']) + # ensure we have this so that iw works + try: + subprocess.check_call(['modprobe', 'cfg80211']) + # set regulatory domain "EU", so that we can use 80211.a 5 GHz channels + out = subprocess.check_output(['iw', 'reg', 'get'], universal_newlines=True) + m = re.match(r'^(?:global\n)?country (\S+):', out) + assert m + klass.orig_country = m.group(1) + subprocess.check_call(['iw', 'reg', 'set', 'EU']) + except Exception: + raise unittest.SkipTest("cfg80211 (wireless) is unavailable, can't test") + @classmethod def tearDownClass(klass): + subprocess.check_call(['iw', 'reg', 'set', klass.orig_country]) try: os.remove('/run/NetworkManager/conf.d/test-blacklist.conf') except FileNotFoundError: @@ -100,22 +103,17 @@ shutil.rmtree('/etc/netplan', ignore_errors=True) shutil.rmtree('/run/NetworkManager', ignore_errors=True) shutil.rmtree('/run/systemd/network', ignore_errors=True) - for f in glob.glob('/run/systemd/system/netplan-*'): - os.remove(f) - for f in glob.glob('/run/systemd/system/**/netplan-*'): - os.remove(f) - subprocess.call(['systemctl', 'daemon-reload']) try: os.remove('/run/systemd/generator/netplan.stamp') except FileNotFoundError: pass - # Keep the management network (eth0/ens3 from 20-wired.network) up - subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) @classmethod def create_devices(klass): - '''Create Access Point and Client devices with veth''' + '''Create Access Point and Client devices with mac80211_hwsim and veth''' + if os.path.exists('/sys/module/mac80211_hwsim'): + raise SystemError('mac80211_hwsim module already loaded') if os.path.exists('/sys/class/net/eth42'): raise SystemError('eth42 interface already exists') @@ -126,20 +124,15 @@ klass.dev_e_client = 'eth42' klass.dev_e_ap_ip4 = '192.168.5.1/24' klass.dev_e_ap_ip6 = '2600::1/64' + out = subprocess.check_output(['ip', '-br', 'link', 'show', 'dev', 'eth42'], + universal_newlines=True) + klass.dev_e_client_mac = out.split()[2] subprocess.check_call(['ip', 'link', 'add', 'name', 'eth43', 'type', 'veth', 'peer', 'name', 'veth43']) klass.dev_e2_ap = 'veth43' klass.dev_e2_client = 'eth43' klass.dev_e2_ap_ip4 = '192.168.6.1/24' klass.dev_e2_ap_ip6 = '2601::1/64' - # Creation of the veths introduces a race with newer versions of - # systemd, as it will change the initial MAC address after the device - # was created and networkd took control. Give it some time, so we read - # the correct MAC address - time.sleep(0.1) - out = subprocess.check_output(['ip', '-br', 'link', 'show', 'dev', 'eth42'], - universal_newlines=True) - klass.dev_e_client_mac = out.split()[2] out = subprocess.check_output(['ip', '-br', 'link', 'show', 'dev', 'eth43'], universal_newlines=True) klass.dev_e2_client_mac = out.split()[2] @@ -150,6 +143,28 @@ with open('/run/NetworkManager/conf.d/11-globally-managed-devices.conf', 'w') as f: f.write('[keyfile]\nunmanaged-devices=') + # create virtual wlan devs + before_wlan = set([c for c in os.listdir('/sys/class/net') if c.startswith('wlan')]) + subprocess.check_call(['modprobe', 'mac80211_hwsim']) + # wait 5 seconds for fake devices to appear + timeout = 50 + while timeout > 0: + after_wlan = set([c for c in os.listdir('/sys/class/net') if c.startswith('wlan')]) + if len(after_wlan) - len(before_wlan) >= 2: + break + timeout -= 1 + time.sleep(0.1) + else: + raise SystemError('timed out waiting for fake devices to appear') + + devs = list(after_wlan - before_wlan) + klass.dev_w_ap = devs[0] + klass.dev_w_client = devs[1] + + # don't let NM trample over our fake AP + with open('/run/NetworkManager/conf.d/test-blacklist.conf', 'w') as f: + f.write('[main]\nplugins=keyfile\n[keyfile]\nunmanaged-devices+=nptestsrv,%s\n' % klass.dev_w_ap) + @classmethod def shutdown_devices(klass): '''Remove test devices''' @@ -160,10 +175,14 @@ klass.dev_e_client = None klass.dev_e2_ap = None klass.dev_e2_client = None + klass.dev_w_ap = None + klass.dev_w_client = None subprocess.call(['ip', 'link', 'del', 'dev', 'mybr'], stderr=subprocess.PIPE) + subprocess.check_call(['rmmod', 'mac80211_hwsim']) + def setUp(self): '''Create test devices and workdir''' @@ -179,6 +198,25 @@ with open(self.entropy_file, 'wb') as f: f.write(b'012345678901234567890') + def setup_ap(self, hostapd_conf, ipv6_mode): + '''Set up simulated access point + + On self.dev_w_ap, run hostapd with given configuration. Setup dnsmasq + according to ipv6_mode, see start_dnsmasq(). + + This is torn down automatically at the end of the test. + ''' + + # give our AP an IP + subprocess.check_call(['ip', 'a', 'flush', 'dev', self.dev_w_ap]) + if ipv6_mode is not None: + subprocess.check_call(['ip', 'a', 'add', self.dev_e_ap_ip6, 'dev', self.dev_w_ap]) + else: + subprocess.check_call(['ip', 'a', 'add', self.dev_e_ap_ip4, 'dev', self.dev_w_ap]) + + self.start_hostapd(hostapd_conf) + self.start_dnsmasq(ipv6_mode, self.dev_w_ap) + def setup_eth(self, ipv6_mode, start_dnsmasq=True): '''Set up simulated ethernet router @@ -199,7 +237,6 @@ subprocess.check_call(['ip', 'link', 'set', self.dev_e2_ap, 'up']) if start_dnsmasq: self.start_dnsmasq(ipv6_mode, self.dev_e_ap) - self.start_dnsmasq(ipv6_mode, self.dev_e2_ap) # # Internal implementation details @@ -232,6 +269,19 @@ assert timeout > 0, 'Timed out waiting for "%s":\n------------\n%s\n-------\n' % (string, log) + def start_hostapd(self, conf): + hostapd_conf = os.path.join(self.workdir, 'hostapd.conf') + with open(hostapd_conf, 'w') as f: + f.write('interface=%s\ndriver=nl80211\n' % self.dev_w_ap) + f.write(conf) + + log = os.path.join(self.workdir, 'hostapd.log') + p = subprocess.Popen(['hostapd', '-e', self.entropy_file, '-f', log, hostapd_conf], + stdout=subprocess.PIPE) + self.addCleanup(p.wait) + self.addCleanup(p.terminate) + self.poll_text(log, '' + self.dev_w_ap + ': AP-ENABLED', 500) + def start_dnsmasq(self, ipv6_mode, iface): '''Start dnsmasq. @@ -293,18 +343,24 @@ if 'bond' not in iface: self.assertIn('state UP', out) + if iface == self.dev_w_client: + out = subprocess.check_output(['iw', 'dev', iface, 'link'], + universal_newlines=True) + # self.assertIn('Connected to ' + self.mac_w_ap, out) + self.assertIn('SSID: fake net', out) + def generate_and_settle(self): '''Generate config, launch and settle NM and networkd''' # regenerate netplan config - out = subprocess.check_output(['netplan', 'apply'], stderr=subprocess.STDOUT, universal_newlines=True) + out = subprocess.check_output(['netplan', 'apply'], universal_newlines=True) if 'Run \'systemctl daemon-reload\' to reload units.' in out: self.fail('systemd units changed without reload') # start NM so that we can verify that it does not manage anything subprocess.check_call(['systemctl', 'start', '--no-block', 'NetworkManager.service']) # wait until networkd is done if self.is_active('systemd-networkd.service'): - if subprocess.call(['/lib/systemd/systemd-networkd-wait-online', '--quiet', '--timeout=30']) != 0: + if subprocess.call(['/lib/systemd/systemd-networkd-wait-online', '--quiet', '--timeout=20']) != 0: subprocess.call(['journalctl', '-b', '--no-pager', '-t', 'systemd-networkd']) st = subprocess.check_output(['networkctl'], stderr=subprocess.PIPE, universal_newlines=True) st_e = subprocess.check_output(['networkctl', 'status', self.dev_e_client], @@ -352,110 +408,3 @@ p = subprocess.Popen(['systemctl', 'is-active', unit], stdout=subprocess.PIPE) out = p.communicate()[0] return p.returncode == 0 or out.startswith(b'activating') - - -class IntegrationTestsWifi(IntegrationTestsBase): - '''Common functionality for network test cases - - setUp() creates two test wlan devices, one for a simulated access point - (self.dev_w_ap), the other for a simulated client device - (self.dev_w_client), and two test ethernet devices (self.dev_e_{ap,client} - and self.dev_e2_{ap,client}. - - Each test should call self.setup_ap() or self.setup_eth() with the desired - configuration. - ''' - @classmethod - def setUpClass(klass): - super().setUpClass() - # ensure we have this so that iw works - try: - subprocess.check_call(['modprobe', 'cfg80211']) - # set regulatory domain "EU", so that we can use 80211.a 5 GHz channels - out = subprocess.check_output(['iw', 'reg', 'get'], universal_newlines=True) - m = re.match(r'^(?:global\n)?country (\S+):', out) - assert m - klass.orig_country = m.group(1) - subprocess.check_call(['iw', 'reg', 'set', 'EU']) - except Exception: - raise unittest.SkipTest("cfg80211 (wireless) is unavailable, can't test") - - @classmethod - def tearDownClass(klass): - subprocess.check_call(['iw', 'reg', 'set', klass.orig_country]) - super().tearDownClass() - - @classmethod - def create_devices(klass): - '''Create Access Point and Client devices with mac80211_hwsim and veth''' - if os.path.exists('/sys/module/mac80211_hwsim'): - raise SystemError('mac80211_hwsim module already loaded') - super().create_devices() - # create virtual wlan devs - before_wlan = set([c for c in os.listdir('/sys/class/net') if c.startswith('wlan')]) - subprocess.check_call(['modprobe', 'mac80211_hwsim']) - # wait 5 seconds for fake devices to appear - timeout = 50 - while timeout > 0: - after_wlan = set([c for c in os.listdir('/sys/class/net') if c.startswith('wlan')]) - if len(after_wlan) - len(before_wlan) >= 2: - break - timeout -= 1 - time.sleep(0.1) - else: - raise SystemError('timed out waiting for fake devices to appear') - - devs = list(after_wlan - before_wlan) - klass.dev_w_ap = devs[0] - klass.dev_w_client = devs[1] - - # don't let NM trample over our fake AP - with open('/run/NetworkManager/conf.d/test-blacklist.conf', 'w') as f: - f.write('[main]\nplugins=keyfile\n[keyfile]\nunmanaged-devices+=nptestsrv,%s\n' % klass.dev_w_ap) - - @classmethod - def shutdown_devices(klass): - '''Remove test devices''' - super().shutdown_devices() - klass.dev_w_ap = None - klass.dev_w_client = None - subprocess.check_call(['rmmod', 'mac80211_hwsim']) - - def start_hostapd(self, conf): - hostapd_conf = os.path.join(self.workdir, 'hostapd.conf') - with open(hostapd_conf, 'w') as f: - f.write('interface=%s\ndriver=nl80211\n' % self.dev_w_ap) - f.write(conf) - - log = os.path.join(self.workdir, 'hostapd.log') - p = subprocess.Popen(['hostapd', '-e', self.entropy_file, '-f', log, hostapd_conf], - stdout=subprocess.PIPE) - self.addCleanup(p.wait) - self.addCleanup(p.terminate) - self.poll_text(log, '' + self.dev_w_ap + ': AP-ENABLED', 500) - - def setup_ap(self, hostapd_conf, ipv6_mode): - '''Set up simulated access point - - On self.dev_w_ap, run hostapd with given configuration. Setup dnsmasq - according to ipv6_mode, see start_dnsmasq(). - - This is torn down automatically at the end of the test. - ''' - # give our AP an IP - subprocess.check_call(['ip', 'a', 'flush', 'dev', self.dev_w_ap]) - if ipv6_mode is not None: - subprocess.check_call(['ip', 'a', 'add', self.dev_e_ap_ip6, 'dev', self.dev_w_ap]) - else: - subprocess.check_call(['ip', 'a', 'add', self.dev_e_ap_ip4, 'dev', self.dev_w_ap]) - self.start_hostapd(hostapd_conf) - self.start_dnsmasq(ipv6_mode, self.dev_w_ap) - - def assert_iface_up(self, iface, expected_ip_a=None, unexpected_ip_a=None): - '''Assert that client interface is up''' - super().assert_iface_up(iface, expected_ip_a, unexpected_ip_a) - if iface == self.dev_w_client: - out = subprocess.check_output(['iw', 'dev', iface, 'link'], - universal_newlines=True) - # self.assertIn('Connected to ' + self.mac_w_ap, out) - self.assertIn('SSID: fake net', out) diff -Nru netplan.io-0.102/tests/integration/bonds.py netplan.io-0.101/tests/integration/bonds.py --- netplan.io-0.102/tests/integration/bonds.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/integration/bonds.py 2020-12-09 11:32:25.000000000 +0000 @@ -354,7 +354,6 @@ match: name: %(ec)s macaddress: %(ec_mac)s - %(e2c)s: {} bonds: mybond: interfaces: [ethbn] @@ -429,6 +428,7 @@ def test_bond_arp_interval(self): self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL) with open(self.config, 'w') as f: f.write('''network: @@ -458,6 +458,7 @@ def test_bond_arp_targets(self): self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL) with open(self.config, 'w') as f: f.write('''network: @@ -487,6 +488,7 @@ def test_bond_arp_targets_many_lp1829264(self): self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL) with open(self.config, 'w') as f: f.write('''network: @@ -518,6 +520,7 @@ def test_bond_arp_all_targets(self): self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL) with open(self.config, 'w') as f: f.write('''network: @@ -549,6 +552,7 @@ def test_bond_arp_validate(self): self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL) with open(self.config, 'w') as f: f.write('''network: @@ -647,6 +651,7 @@ def test_bond_arp_interval(self): self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL) with open(self.config, 'w') as f: f.write('''network: @@ -676,6 +681,7 @@ def test_bond_arp_targets(self): self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL) with open(self.config, 'w') as f: f.write('''network: @@ -705,6 +711,7 @@ def test_bond_arp_all_targets(self): self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL) with open(self.config, 'w') as f: f.write('''network: diff -Nru netplan.io-0.102/tests/integration/bridges.py netplan.io-0.101/tests/integration/bridges.py --- netplan.io-0.102/tests/integration/bridges.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/integration/bridges.py 2020-12-09 11:32:25.000000000 +0000 @@ -32,6 +32,7 @@ def test_eth_and_bridge(self): self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybr'], stderr=subprocess.DEVNULL) with open(self.config, 'w') as f: f.write('''network: @@ -68,6 +69,7 @@ def test_bridge_path_cost(self): self.setup_eth(None) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybr'], stderr=subprocess.DEVNULL) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s @@ -101,6 +103,7 @@ def test_bridge_ageing_time(self): self.setup_eth(None) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybr'], stderr=subprocess.DEVNULL) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s @@ -133,6 +136,7 @@ def test_bridge_max_age(self): self.setup_eth(None) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybr'], stderr=subprocess.DEVNULL) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s @@ -165,6 +169,7 @@ def test_bridge_hello_time(self): self.setup_eth(None) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybr'], stderr=subprocess.DEVNULL) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s @@ -197,6 +202,7 @@ def test_bridge_forward_delay(self): self.setup_eth(None) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybr'], stderr=subprocess.DEVNULL) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s @@ -229,6 +235,7 @@ def test_bridge_stp_false(self): self.setup_eth(None) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybr'], stderr=subprocess.DEVNULL) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s @@ -268,6 +275,7 @@ def test_bridge_mac(self): self.setup_eth(None) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'br0'], stderr=subprocess.DEVNULL) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s @@ -276,7 +284,6 @@ match: name: %(ec)s macaddress: %(ec_mac)s - %(e2c)s: {} bridges: br0: interfaces: [ethbr] @@ -294,6 +301,7 @@ def test_bridge_anonymous(self): self.setup_eth(None) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybr'], stderr=subprocess.DEVNULL) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s @@ -321,6 +329,7 @@ def test_bridge_isolated(self): self.setup_eth(None) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybr'], stderr=subprocess.DEVNULL) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s @@ -340,6 +349,7 @@ def test_bridge_port_priority(self): self.setup_eth(None) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybr'], stderr=subprocess.DEVNULL) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s @@ -383,6 +393,7 @@ def test_bridge_priority(self): self.setup_eth(None) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybr'], stderr=subprocess.DEVNULL) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s @@ -415,6 +426,7 @@ def test_bridge_port_priority(self): self.setup_eth(None) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybr'], stderr=subprocess.DEVNULL) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s diff -Nru netplan.io-0.102/tests/integration/ethernets.py netplan.io-0.101/tests/integration/ethernets.py --- netplan.io-0.102/tests/integration/ethernets.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/integration/ethernets.py 2020-12-09 11:32:25.000000000 +0000 @@ -32,6 +32,7 @@ def test_eth_mtu(self): self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s @@ -54,6 +55,7 @@ def test_eth_mac(self): self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s @@ -70,7 +72,7 @@ ['master']) out = subprocess.check_output(['ip', 'link', 'show', self.dev_e2_client], universal_newlines=True) - self.assertIn('ether 00:01:02:03:04:05', out) + self.assertTrue('ether 00:01:02:03:04:05' in out) subprocess.check_call(['ip', 'link', 'set', self.dev_e2_client, 'address', self.dev_e2_client_mac]) @@ -79,6 +81,7 @@ Interface globbing was introduced as of NM 1.14+''' self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s @@ -149,7 +152,7 @@ elif resolved_in_use(): sys.stdout.write('[resolved] ') sys.stdout.flush() - out = subprocess.check_output(['resolvectl', 'status'], universal_newlines=True) + out = subprocess.check_output(['systemd-resolve', '--status'], universal_newlines=True) self.assertIn('DNS Servers: 172.1.2.3', out) self.assertIn('fakesuffix', out) else: @@ -172,6 +175,7 @@ addresses: ["172.16.7.2/30", "4321:AAAA::99/80"] dhcp4: yes ''' % {'r': self.backend, 'ec': self.dev_e_client, 'e2c': self.dev_e2_client}) + self.start_dnsmasq(None, self.dev_e2_ap) self.generate_and_settle() if self.backend == 'NetworkManager': self.nm_online_full(self.dev_e2_client) diff -Nru netplan.io-0.102/tests/integration/ovs.py netplan.io-0.101/tests/integration/ovs.py --- netplan.io-0.102/tests/integration/ovs.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/integration/ovs.py 2020-12-09 11:32:25.000000000 +0000 @@ -78,13 +78,12 @@ # If we have just OVS interfaces/ports networkd/networkctl will not be # aware that our network is ready. %(ec)s: {addresses: [10.10.10.20/24]} - %(e2c)s: {addresses: [10.10.10.30/24]} openvswitch: ports: - [patch0-1, patch1-0] bridges: ovs0: {interfaces: [patch0-1]} - ovs1: {interfaces: [patch1-0]}''' % {'ec': self.dev_e_client, 'e2c': self.dev_e2_client}) + ovs1: {interfaces: [patch1-0]}''' % {'ec': self.dev_e_client}) self.generate_and_settle() # Basic verification that the bridges/ports/interfaces are there in OVS out = subprocess.check_output(['ovs-vsctl', 'show']) @@ -120,13 +119,12 @@ f.write('''network: ethernets: %(ec)s: {addresses: [10.10.10.20/24]} - %(e2c)s: {addresses: [10.10.10.30/24]} openvswitch: ports: [[patch0-1, patch1-0]] bonds: bond0: {interfaces: [patch1-0, %(ec)s]} bridges: - ovs0: {interfaces: [patch0-1, bond0]}''' % {'ec': self.dev_e_client, 'e2c': self.dev_e2_client}) + ovs0: {interfaces: [patch0-1, bond0]}''' % {'ec': self.dev_e_client}) self.generate_and_settle() # Basic verification that the bridges/ports/interfaces are there in OVS out = subprocess.check_output(['ovs-vsctl', 'show']) @@ -139,7 +137,6 @@ f.write('''network: ethernets: %(ec)s: {addresses: [10.10.10.20/24]} - %(ec)s: {addresses: [10.10.10.30/24]} openvswitch: ports: [[patchx, patchy]] bonds: @@ -281,12 +278,12 @@ self.assertIn(b'---- mybond ----', out) self.assertIn(b'bond_mode: balance-slb', out) self.assertIn(b'lacp_status: off', out) - self.assertRegex(out, br'(slave|member) %b: enabled' % self.dev_e_client.encode()) - self.assertRegex(out, br'(slave|member) %b: enabled' % self.dev_e2_client.encode()) + self.assertIn(b'slave %b: enabled' % self.dev_e_client.encode(), out) + self.assertIn(b'slave %b: enabled' % self.dev_e2_client.encode(), out) self.assert_iface('ovsbr', ['inet 192.170.1.1/24']) def test_bridge_patch_ports(self): - self.setup_eth(None) + self.setup_eth(None, False) self.addCleanup(subprocess.call, ['ovs-vsctl', '--if-exists', 'del-br', 'br0']) self.addCleanup(subprocess.call, ['ovs-vsctl', '--if-exists', 'del-br', 'br1']) self.addCleanup(subprocess.call, ['ovs-vsctl', '--if-exists', 'del-port', 'patch0-1']) @@ -377,12 +374,11 @@ nameservers: addresses: [10.5.32.99] search: [maas] - %(e2c)s: {} vlans: %(ec)s.21: id: 21 link: %(ec)s - mtu: 1500''' % {'ec': self.dev_e_client, 'e2c': self.dev_e2_client}) + mtu: 1500''' % {'ec': self.dev_e_client}) self.generate_and_settle() # Basic verification that the interfaces/ports are set up in OVS out = subprocess.check_output(['ovs-vsctl', 'show'], universal_newlines=True) diff -Nru netplan.io-0.102/tests/integration/regressions.py netplan.io-0.101/tests/integration/regressions.py --- netplan.io-0.102/tests/integration/regressions.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/integration/regressions.py 2020-12-09 11:32:25.000000000 +0000 @@ -42,6 +42,7 @@ def test_lp1802322_bond_mac_rename(self): self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'mybond'], stderr=subprocess.DEVNULL) with open(self.config, 'w') as f: f.write('''network: diff -Nru netplan.io-0.102/tests/integration/routing.py netplan.io-0.101/tests/integration/routing.py --- netplan.io-0.102/tests/integration/routing.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/integration/routing.py 2020-12-09 11:32:25.000000000 +0000 @@ -35,6 +35,7 @@ The on-link option was introduced as of NM 1.12+ (for IPv4) The on-link option was introduced as of NM 1.18+ (for IPv6)''' self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s @@ -58,6 +59,7 @@ The from option was introduced as of NM 1.8+''' self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s @@ -80,6 +82,7 @@ The table option was introduced as of NM 1.10+''' self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) table_id = '255' # This is the 'local' FIB of /etc/iproute2/rt_tables with open(self.config, 'w') as f: f.write('''network: @@ -192,41 +195,6 @@ self.assertIn(b'mtu 777', # check mtu from static route subprocess.check_output(['ip', 'route', 'show', '10.10.10.0/24'])) - def test_per_route_congestion_window(self): - self.setup_eth(None) - with open(self.config, 'w') as f: - f.write('''network: - renderer: %(r)s - ethernets: - %(ec)s: - addresses: - - 192.168.5.99/24 - gateway4: 192.168.5.1 - routes: - - to: 10.10.10.0/24 - via: 192.168.5.254 - congestion-window: 16''' % {'r': self.backend, 'ec': self.dev_e_client}) - self.generate_and_settle() - self.assertIn(b'initcwnd 16', # check initcwnd from static route - subprocess.check_output(['ip', 'route', 'show', '10.10.10.0/24'])) - - def test_per_route_advertised_receive_window(self): - self.setup_eth(None) - with open(self.config, 'w') as f: - f.write('''network: - renderer: %(r)s - ethernets: - %(ec)s: - addresses: - - 192.168.5.99/24 - gateway4: 192.168.5.1 - routes: - - to: 10.10.10.0/24 - via: 192.168.5.254 - advertised-receive-window: 16''' % {'r': self.backend, 'ec': self.dev_e_client}) - self.generate_and_settle() - self.assertIn(b'initrwnd 16', # check initrwnd from static route - subprocess.check_output(['ip', 'route', 'show', '10.10.10.0/24'])) @unittest.skipIf("networkd" not in test_backends, "skipping as networkd backend tests are disabled") @@ -260,6 +228,7 @@ @unittest.skip("networkd does not handle non-unicast routes correctly yet (Invalid argument)") def test_route_type_blackhole(self): self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s @@ -279,6 +248,7 @@ def test_route_with_policy(self): self.setup_eth(None) + self.start_dnsmasq(None, self.dev_e2_ap) with open(self.config, 'w') as f: f.write('''network: renderer: %(r)s diff -Nru netplan.io-0.102/tests/integration/tunnels.py netplan.io-0.101/tests/integration/tunnels.py --- netplan.io-0.102/tests/integration/tunnels.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/integration/tunnels.py 2020-12-09 11:32:25.000000000 +0000 @@ -67,7 +67,6 @@ mode: ipip local: 192.168.5.1 remote: 99.99.99.99 - ttl: 64 ''' % {'r': self.backend, 'ec': self.dev_e_client, 'e2c': self.dev_e2_client}) self.generate_and_settle() self.assert_iface('tun0', ['tun0@NONE', 'link.* 192.168.5.1 peer 99.99.99.99']) @@ -124,8 +123,8 @@ self.assertIn("fwmark: 0x2a", out) self.assertIn("peer: M9nt4YujIOmNrRmpIRTmYSfMdrpvE7u6WkG8FY8WjG4=", out) self.assertIn("allowed ips: 20.20.20.0/24", out) - self.assertRegex(out, r'latest handshake: (\d+ seconds? ago|Now)') - self.assertRegex(out, r'transfer: \d+.*B received, \d+.*B sent') + self.assertRegex(out, r'latest handshake: \d+ seconds? ago') + self.assertRegex(out, r'transfer: \d+ B received, \d+ B sent') self.assert_iface('wg0', ['inet 10.10.10.20/24']) # Verify client out = subprocess.check_output(['wg', 'show', 'wg1', 'private-key'], universal_newlines=True) @@ -139,7 +138,7 @@ self.assertIn("allowed ips: 0.0.0.0/0", out) self.assertIn("persistent keepalive: every 21 seconds", out) self.assertRegex(out, r'latest handshake: (\d+ seconds? ago|Now)') - self.assertRegex(out, r'transfer: \d+.*B received, \d+.*B sent') + self.assertRegex(out, r'transfer: \d+ B received, \d+ B sent') self.assert_iface('wg1', ['inet 20.20.20.10/24']) diff -Nru netplan.io-0.102/tests/integration/wifi.py netplan.io-0.101/tests/integration/wifi.py --- netplan.io-0.102/tests/integration/wifi.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/integration/wifi.py 2020-12-09 11:32:25.000000000 +0000 @@ -24,7 +24,7 @@ import subprocess import unittest -from base import IntegrationTestsWifi, test_backends +from base import IntegrationTestsBase, test_backends class _CommonTests(): @@ -125,13 +125,13 @@ @unittest.skipIf("networkd" not in test_backends, "skipping as networkd backend tests are disabled") -class TestNetworkd(IntegrationTestsWifi, _CommonTests): +class TestNetworkd(IntegrationTestsBase, _CommonTests): backend = 'networkd' @unittest.skipIf("NetworkManager" not in test_backends, "skipping as NetworkManager backend tests are disabled") -class TestNetworkManager(IntegrationTestsWifi, _CommonTests): +class TestNetworkManager(IntegrationTestsBase, _CommonTests): backend = 'NetworkManager' def test_wifi_ap_open(self): diff -Nru netplan.io-0.102/tests/test_cli_get_set.py netplan.io-0.101/tests/test_cli_get_set.py --- netplan.io-0.102/tests/test_cli_get_set.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/test_cli_get_set.py 2020-12-09 11:32:25.000000000 +0000 @@ -69,12 +69,6 @@ with open(self.path, 'r') as f: self.assertIn('network:\n ethernets:\n eth0:\n dhcp4: \'yes\'', f.read()) - def test_set_global(self): - self._set([r'network={renderer: NetworkManager}']) - self.assertTrue(os.path.isfile(self.path)) - with open(self.path, 'r') as f: - self.assertIn('network:\n renderer: NetworkManager', f.read()) - def test_set_sequence(self): self._set(['ethernets.eth0.addresses=[1.2.3.4/24, \'5.6.7.8/24\']']) self.assertTrue(os.path.isfile(self.path)) @@ -138,6 +132,14 @@ self.assertIsInstance(err, Exception) self.assertIn('tun0: \'input-key\' is not required for this tunnel type', str(err)) + def test_set_invalid_yaml_read(self): + with open(self.path, 'w') as f: + f.write('''network: {}}''') + err = self._set(['ethernets.eth0.dhcp4=true']) + self.assertIsInstance(err, Exception) + self.assertTrue(os.path.isfile(self.path)) + self.assertIn('expected , but found \'}\'', str(err)) + def test_set_append(self): with open(self.path, 'w') as f: f.write('''network: @@ -182,7 +184,7 @@ f.write('''network:\n version: 2\n renderer: NetworkManager ethernets: ens3: {dhcp4: yes, dhcp6: yes} - eth0: {addresses: [1.2.3.4/24]}''') + eth0: {addresses: [1.2.3.4]}''') self._set(['ethernets.eth0.addresses=NULL']) self._set(['ethernets.ens3.dhcp6=null']) self.assertTrue(os.path.isfile(self.path)) @@ -204,17 +206,6 @@ # The file should be deleted if this was the last/only key left self.assertFalse(os.path.isfile(self.path)) - def test_set_delete_file_with_version(self): - with open(self.path, 'w') as f: - f.write('''network: - version: 2 - ethernets: - ens3: {dhcp4: yes}''') - out = self._set(['network.ethernets.ens3=NULL']) - print(out, flush=True) - # The file should be deleted if only "network: {version: 2}" is left - self.assertFalse(os.path.isfile(self.path)) - def test_set_invalid_delete(self): with open(self.path, 'w') as f: f.write('''network:\n version: 2\n renderer: NetworkManager @@ -235,55 +226,6 @@ self.assertIsInstance(err, Exception) self.assertEquals('Invalid input: {\'network\': {\'ethernets\': {\'eth0\': {\'dhcp4:false\': None}}}}', str(err)) - def test_set_override_existing_file(self): - override = os.path.join(self.workdir.name, 'etc', 'netplan', 'some-file.yaml') - with open(override, 'w') as f: - f.write(r'network: {ethernets: {eth0: {dhcp4: true}, eth1: {dhcp6: false}}}') - self._set([r'ethernets.eth0.dhcp4=false']) - self.assertFalse(os.path.isfile(self.path)) - self.assertTrue(os.path.isfile(override)) - with open(override, 'r') as f: - out = f.read() - self.assertIn('network:\n ethernets:\n eth0:\n dhcp4: false', out) # new - self.assertIn('eth1:\n dhcp6: false', out) # old - - def test_set_override_existing_file_escaped_dot(self): - override = os.path.join(self.workdir.name, 'etc', 'netplan', 'some-file.yaml') - with open(override, 'w') as f: - f.write(r'network: {ethernets: {eth0.123: {dhcp4: true}}}') - self._set([r'ethernets.eth0\.123.dhcp4=false']) - self.assertFalse(os.path.isfile(self.path)) - self.assertTrue(os.path.isfile(override)) - with open(override, 'r') as f: - self.assertIn('network:\n ethernets:\n eth0.123:\n dhcp4: false', f.read()) - - def test_set_override_multiple_existing_files(self): - file1 = os.path.join(self.workdir.name, 'etc', 'netplan', 'eth0.yaml') - with open(file1, 'w') as f: - f.write(r'network: {ethernets: {eth0.1: {dhcp4: true}, eth0.2: {dhcp4: true}}}') - file2 = os.path.join(self.workdir.name, 'etc', 'netplan', 'eth1.yaml') - with open(file2, 'w') as f: - f.write(r'network: {ethernets: {eth1: {dhcp4: true}}}') - self._set([(r'network={renderer: NetworkManager, version: 2,' - r'ethernets:{' - r'eth1:{dhcp4: false},' - r'eth0.1:{dhcp4: false},' - r'eth0.2:{dhcp4: false}},' - r'bridges:{' - r'br99:{dhcp4: false}}}')]) - self.assertTrue(os.path.isfile(file1)) - with open(file1, 'r') as f: - self.assertIn('network:\n ethernets:\n eth0.1:\n dhcp4: false', f.read()) - self.assertTrue(os.path.isfile(file2)) - with open(file2, 'r') as f: - self.assertIn('network:\n ethernets:\n eth1:\n dhcp4: false', f.read()) - self.assertTrue(os.path.isfile(self.path)) - with open(self.path, 'r') as f: - out = f.read() - self.assertIn('network:\n bridges:\n br99:\n dhcp4: false', out) - self.assertIn(' version: 2', out) - self.assertIn(' renderer: NetworkManager', out) - class TestGet(unittest.TestCase): '''Test netplan get''' @@ -378,9 +320,3 @@ eth0: dhcp4: true version: 2\n''', out) - - def test_get_network(self): - with open(self.path, 'w') as f: - f.write('network:\n version: 2\n renderer: NetworkManager') - out = self._get(['network']) - self.assertEquals('renderer: NetworkManager\nversion: 2\n', out) diff -Nru netplan.io-0.102/tests/test_configmanager.py netplan.io-0.101/tests/test_configmanager.py --- netplan.io-0.102/tests/test_configmanager.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/test_configmanager.py 2020-12-09 11:32:25.000000000 +0000 @@ -118,13 +118,6 @@ addresses: - "2001:dead:beef::2/64" gateway6: "2001:dead:beef::1" - nm-devices: - fallback: - renderer: NetworkManager - networkmanager: - passthrough: - connection.id: some-nm-id - connection.uuid: some-uuid ''', file=fd) with open(os.path.join(self.workdir.name, "run/systemd/network/01-pretend.network"), 'w') as fd: print("pretend .network", file=fd) @@ -150,7 +143,6 @@ self.assertIn('ports', self.configmanager.openvswitch) self.assertEquals(2, self.configmanager.version) self.assertEquals('networkd', self.configmanager.renderer) - self.assertIn('fallback', self.configmanager.nm_devices) def test_parse_merging(self): self.configmanager.parse(extra_config=[os.path.join(self.workdir.name, "newfile_merging.yaml")]) diff -Nru netplan.io-0.102/tests/test_nm_backend.py netplan.io-0.101/tests/test_nm_backend.py --- netplan.io-0.102/tests/test_nm_backend.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/test_nm_backend.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,660 +0,0 @@ -#!/usr/bin/python3 -# Blackbox tests of NetworkManager netplan backend. These are run during -# "make check" and don't touch the system configuration at all. -# -# Copyright (C) 2020 Canonical, Ltd. -# Author: Lukas Märdian -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import shutil -import ctypes -import ctypes.util - -from generator.base import TestBase -from tests.test_utils import MockCmd - -rootdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -exe_cli = os.path.join(rootdir, 'src', 'netplan.script') -# Make sure we can import our development netplan. -os.environ.update({'PYTHONPATH': '.'}) - -lib = ctypes.CDLL(ctypes.util.find_library('netplan')) -lib.netplan_get_id_from_nm_filename.restype = ctypes.c_char_p - - -class TestNetworkManagerBackend(TestBase): - '''Test libnetplan functionality as used by NetworkManager backend''' - - def setUp(self): - super().setUp() - os.makedirs(self.confdir) - - def tearDown(self): - shutil.rmtree(self.workdir.name) - super().tearDown() - - def test_get_id_from_filename(self): - out = lib.netplan_get_id_from_nm_filename( - '/run/NetworkManager/system-connections/netplan-some-id.nmconnection'.encode(), None) - self.assertEqual(out, b'some-id') - - def test_get_id_from_filename_rootdir(self): - out = lib.netplan_get_id_from_nm_filename( - '/some/rootdir/run/NetworkManager/system-connections/netplan-some-id.nmconnection'.encode(), None) - self.assertEqual(out, b'some-id') - - def test_get_id_from_filename_wifi(self): - out = lib.netplan_get_id_from_nm_filename( - '/run/NetworkManager/system-connections/netplan-some-id-SOME-SSID.nmconnection'.encode(), 'SOME-SSID'.encode()) - self.assertEqual(out, b'some-id') - - def test_get_id_from_filename_wifi_invalid_suffix(self): - out = lib.netplan_get_id_from_nm_filename( - '/run/NetworkManager/system-connections/netplan-some-id-SOME-SSID'.encode(), 'SOME-SSID'.encode()) - self.assertEqual(out, None) - - def test_get_id_from_filename_invalid_prefix(self): - out = lib.netplan_get_id_from_nm_filename('INVALID/netplan-some-id.nmconnection'.encode(), None) - self.assertEqual(out, None) - - def test_generate(self): - self.mock_netplan_cmd = MockCmd("netplan") - os.environ["TEST_NETPLAN_CMD"] = self.mock_netplan_cmd.path - self.assertTrue(lib.netplan_generate(self.workdir.name.encode())) - self.assertEquals(self.mock_netplan_cmd.calls(), [ - ["netplan", "generate", "--root-dir", self.workdir.name], - ]) - - def test_delete_connection(self): - os.environ["TEST_NETPLAN_CMD"] = exe_cli - FILENAME = os.path.join(self.confdir, 'some-filename.yaml') - with open(FILENAME, 'w') as f: - f.write('''network: - ethernets: - some-netplan-id: - dhcp4: true''') - self.assertTrue(os.path.isfile(FILENAME)) - # Parse all YAML and delete 'some-netplan-id' connection file - self.assertTrue(lib.netplan_delete_connection('some-netplan-id'.encode(), self.workdir.name.encode())) - self.assertFalse(os.path.isfile(FILENAME)) - - def test_delete_connection_id_not_found(self): - FILENAME = os.path.join(self.confdir, 'some-filename.yaml') - with open(FILENAME, 'w') as f: - f.write('''network: - ethernets: - some-netplan-id: - dhcp4: true''') - self.assertTrue(os.path.isfile(FILENAME)) - self.assertFalse(lib.netplan_delete_connection('unknown-id'.encode(), self.workdir.name.encode())) - self.assertTrue(os.path.isfile(FILENAME)) - - def test_delete_connection_two_in_file(self): - os.environ["TEST_NETPLAN_CMD"] = exe_cli - FILENAME = os.path.join(self.confdir, 'some-filename.yaml') - with open(FILENAME, 'w') as f: - f.write('''network: - ethernets: - some-netplan-id: - dhcp4: true - other-id: - dhcp6: true''') - self.assertTrue(os.path.isfile(FILENAME)) - self.assertTrue(lib.netplan_delete_connection('some-netplan-id'.encode(), self.workdir.name.encode())) - self.assertTrue(os.path.isfile(FILENAME)) - # Verify the file still exists and still contains the other connection - with open(FILENAME, 'r') as f: - self.assertEquals(f.read(), 'network:\n ethernets:\n other-id:\n dhcp6: true\n') - - def test_serialize_gsm(self): - self.maxDiff = None - UUID = 'a08c5805-7cf5-43f7-afb9-12cb30f6eca3' - FILE = os.path.join(self.workdir.name, 'tmp/some.keyfile') - os.makedirs(os.path.dirname(FILE)) - with open(FILE, 'w') as file: - file.write('''[connection] -id=T-Mobile Funkadelic 2 -uuid=a08c5805-7cf5-43f7-afb9-12cb30f6eca3 -type=gsm - -[gsm] -apn=internet2.voicestream.com -device-id=da812de91eec16620b06cd0ca5cbc7ea25245222 -home-only=true -network-id=254098 -password=parliament2 -pin=123456 -sim-id=89148000000060671234 -sim-operator-id=310260 -username=george.clinton.again - -[ipv4] -dns-search= -method=auto - -[ipv6] -addr-gen-mode=stable-privacy -dns-search= -method=auto -''') - lib.netplan_parse_keyfile(FILE.encode(), None) - lib._write_netplan_conf('NM-a08c5805-7cf5-43f7-afb9-12cb30f6eca3'.encode(), self.workdir.name.encode()) - lib.netplan_clear_netdefs() - self.assertTrue(os.path.isfile(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)))) - with open(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)), 'r') as f: - self.assertEqual(f.read(), '''network: - version: 2 - modems: - NM-{}: - renderer: NetworkManager - match: {{}} - apn: "internet2.voicestream.com" - device-id: "da812de91eec16620b06cd0ca5cbc7ea25245222" - network-id: "254098" - pin: "123456" - sim-id: "89148000000060671234" - sim-operator-id: "310260" - networkmanager: - uuid: {} - name: "T-Mobile Funkadelic 2" - passthrough: - gsm.home-only: "true" - gsm.password: "parliament2" - gsm.username: "george.clinton.again" - ipv4.dns-search: "" - ipv4.method: "auto" - ipv6.addr-gen-mode: "stable-privacy" - ipv6.dns-search: "" - ipv6.method: "auto" -'''.format(UUID, UUID)) - - def test_serialize_gsm_via_bluetooth(self): - self.maxDiff = None - UUID = 'a08c5805-7cf5-43f7-afb9-12cb30f6eca3' - FILE = os.path.join(self.workdir.name, 'tmp/some.keyfile') - os.makedirs(os.path.dirname(FILE)) - with open(FILE, 'w') as file: - file.write('''[connection] -id=T-Mobile Funkadelic 2 -uuid=a08c5805-7cf5-43f7-afb9-12cb30f6eca3 -type=bluetooth - -[gsm] -apn=internet2.voicestream.com -device-id=da812de91eec16620b06cd0ca5cbc7ea25245222 -home-only=true -network-id=254098 -password=parliament2 -pin=123456 -sim-id=89148000000060671234 -sim-operator-id=310260 -username=george.clinton.again - -[ipv4] -dns-search= -method=auto - -[ipv6] -addr-gen-mode=stable-privacy -dns-search= -method=auto - -[proxy] -''') - lib.netplan_parse_keyfile(FILE.encode(), None) - lib._write_netplan_conf('NM-a08c5805-7cf5-43f7-afb9-12cb30f6eca3'.encode(), self.workdir.name.encode()) - lib.netplan_clear_netdefs() - self.assertTrue(os.path.isfile(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)))) - with open(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)), 'r') as f: - self.assertEqual(f.read(), '''network: - version: 2 - nm-devices: - NM-{}: - renderer: NetworkManager - networkmanager: - uuid: {} - name: "T-Mobile Funkadelic 2" - passthrough: - connection.type: "bluetooth" - gsm.apn: "internet2.voicestream.com" - gsm.device-id: "da812de91eec16620b06cd0ca5cbc7ea25245222" - gsm.home-only: "true" - gsm.network-id: "254098" - gsm.password: "parliament2" - gsm.pin: "123456" - gsm.sim-id: "89148000000060671234" - gsm.sim-operator-id: "310260" - gsm.username: "george.clinton.again" - ipv4.dns-search: "" - ipv4.method: "auto" - ipv6.addr-gen-mode: "stable-privacy" - ipv6.dns-search: "" - ipv6.method: "auto" - proxy._: "" -'''.format(UUID, UUID)) - - def _template_serialize_keyfile(self, nd_type, nm_type, supported=True): - self.maxDiff = None - UUID = '87749f1d-334f-40b2-98d4-55db58965f5f' - FILE = os.path.join(self.workdir.name, 'tmp/some.keyfile') - os.makedirs(os.path.dirname(FILE)) - with open(FILE, 'w') as file: - file.write('[connection]\ntype={}\nuuid={}'.format(nm_type, UUID)) - self.assertEqual(lib.netplan_clear_netdefs(), 0) - lib.netplan_parse_keyfile(FILE.encode(), None) - lib._write_netplan_conf('NM-{}'.format(UUID).encode(), self.workdir.name.encode()) - lib.netplan_clear_netdefs() - self.assertTrue(os.path.isfile(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)))) - t = '\n passthrough:\n connection.type: "{}"'.format(nm_type) if not supported else '' - match = '\n match: {}' if nd_type in ['ethernets', 'modems', 'wifis'] else '' - with open(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)), 'r') as f: - self.assertEqual(f.read(), '''network: - version: 2 - {}: - NM-{}: - renderer: NetworkManager{} - networkmanager: - uuid: {}{} -'''.format(nd_type, UUID, match, UUID, t)) - - def test_serialize_keyfile_ethernet(self): - self._template_serialize_keyfile('ethernets', 'ethernet') - - def test_serialize_keyfile_type_modem_gsm(self): - self._template_serialize_keyfile('modems', 'gsm') - - def test_serialize_keyfile_type_modem_cdma(self): - self._template_serialize_keyfile('modems', 'cdma') - - def test_serialize_keyfile_type_bridge(self): - self._template_serialize_keyfile('bridges', 'bridge') - - def test_serialize_keyfile_type_bond(self): - self._template_serialize_keyfile('bonds', 'bond') - - def test_serialize_keyfile_type_vlan(self): - self._template_serialize_keyfile('vlans', 'vlan') - - def test_serialize_keyfile_type_tunnel(self): - self._template_serialize_keyfile('tunnels', 'ip-tunnel', False) - - def test_serialize_keyfile_type_wireguard(self): - self._template_serialize_keyfile('tunnels', 'wireguard', False) - - def test_serialize_keyfile_type_other(self): - self._template_serialize_keyfile('nm-devices', 'dummy', False) - - def test_serialize_keyfile_missing_uuid(self): - FILE = os.path.join(self.workdir.name, 'tmp/some.keyfile') - os.makedirs(os.path.dirname(FILE)) - with open(FILE, 'w') as file: - file.write('[connection]\ntype=ethernets') - self.assertFalse(lib.netplan_parse_keyfile(FILE.encode(), None)) - - def test_serialize_keyfile_missing_type(self): - UUID = '87749f1d-334f-40b2-98d4-55db58965f5f' - FILE = os.path.join(self.workdir.name, 'tmp/some.keyfile') - os.makedirs(os.path.dirname(FILE)) - with open(FILE, 'w') as file: - file.write('[connection]\nuuid={}'.format(UUID)) - self.assertFalse(lib.netplan_parse_keyfile(FILE.encode(), None)) - - def test_serialize_keyfile_missing_file(self): - FILE = os.path.join(self.workdir.name, 'tmp/some.keyfile') - os.makedirs(os.path.dirname(FILE)) - self.assertFalse(lib.netplan_parse_keyfile(FILE.encode(), None)) - - def test_serialize_keyfile_type_wifi(self): - self.maxDiff = None - UUID = '87749f1d-334f-40b2-98d4-55db58965f5f' - FILE = os.path.join(self.workdir.name, 'tmp/some.keyfile') - os.makedirs(os.path.dirname(FILE)) - with open(FILE, 'w') as file: - file.write('''[connection] -type=wifi -uuid={} -permissions= -id=myid with spaces -interface-name=eth0 - -[wifi] -ssid=SOME-SSID -mode=infrastructure -hidden=true - -[ipv4] -method=auto -dns-search='''.format(UUID)) - lib.netplan_parse_keyfile(FILE.encode(), None) - lib._write_netplan_conf('NM-{}'.format(UUID).encode(), self.workdir.name.encode()) - lib.netplan_clear_netdefs() - self.assertTrue(os.path.isfile(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)))) - with open(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)), 'r') as f: - self.assertEqual(f.read(), '''network: - version: 2 - wifis: - NM-{}: - renderer: NetworkManager - match: - name: "eth0" - access-points: - "SOME-SSID": - hidden: true - mode: infrastructure - networkmanager: - uuid: {} - name: "myid with spaces" - passthrough: - connection.permissions: "" - ipv4.method: "auto" - ipv4.dns-search: "" - networkmanager: - uuid: {} - name: "myid with spaces" -'''.format(UUID, UUID, UUID)) - - def _template_serialize_keyfile_type_wifi(self, nd_mode, nm_mode): - self.maxDiff = None - UUID = '87749f1d-334f-40b2-98d4-55db58965f5f' - FILE = os.path.join(self.workdir.name, 'tmp/some.keyfile') - os.makedirs(os.path.dirname(FILE)) - with open(FILE, 'w') as file: - file.write('''[connection] -type=wifi -uuid={} -id=myid with spaces - -[ipv4] -method=auto - -[wifi] -ssid=SOME-SSID -mode={}'''.format(UUID, nm_mode)) - lib.netplan_parse_keyfile(FILE.encode(), None) - lib._write_netplan_conf('NM-{}'.format(UUID).encode(), self.workdir.name.encode()) - lib.netplan_clear_netdefs() - self.assertTrue(os.path.isfile(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)))) - wifi_mode = '' - if nm_mode != nd_mode: - wifi_mode = '\n wifi.mode: "{}"'.format(nm_mode) - with open(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)), 'r') as f: - self.assertEqual(f.read(), '''network: - version: 2 - wifis: - NM-{}: - renderer: NetworkManager - match: {{}} - access-points: - "SOME-SSID": - mode: {} - networkmanager: - uuid: {} - name: "myid with spaces" - passthrough: - ipv4.method: "auto"{} - networkmanager: - uuid: {} - name: "myid with spaces" -'''.format(UUID, nd_mode, UUID, wifi_mode, UUID)) - - def test_serialize_keyfile_type_wifi_ap(self): - self._template_serialize_keyfile_type_wifi('ap', 'ap') - - def test_serialize_keyfile_type_wifi_adhoc(self): - self._template_serialize_keyfile_type_wifi('adhoc', 'adhoc') - - def test_serialize_keyfile_type_wifi_unknown(self): - self._template_serialize_keyfile_type_wifi('infrastructure', 'mesh') - - def test_serialize_keyfile_type_wifi_missing_ssid(self): - self.maxDiff = None - UUID = '87749f1d-334f-40b2-98d4-55db58965f5f' - FILE = os.path.join(self.workdir.name, 'tmp/some.keyfile') - os.makedirs(os.path.dirname(FILE)) - with open(FILE, 'w') as file: - file.write('''[connection]\ntype=wifi\nuuid={}\nid=myid with spaces'''.format(UUID)) - self.assertFalse(lib.netplan_parse_keyfile(FILE.encode(), None)) - self.assertFalse(os.path.isfile(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)))) - - def test_serialize_keyfile_wake_on_lan(self): - self.maxDiff = None - UUID = '87749f1d-334f-40b2-98d4-55db58965f5f' - FILE = os.path.join(self.workdir.name, 'tmp/some.keyfile') - os.makedirs(os.path.dirname(FILE)) - with open(FILE, 'w') as file: - file.write('''[connection] -type=ethernet -uuid={} -id=myid with spaces - -[ethernet] -wake-on-lan=2 - -[ipv4] -method=auto'''.format(UUID)) - lib.netplan_parse_keyfile(FILE.encode(), None) - lib._write_netplan_conf('NM-{}'.format(UUID).encode(), self.workdir.name.encode()) - lib.netplan_clear_netdefs() - self.assertTrue(os.path.isfile(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)))) - with open(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)), 'r') as f: - self.assertEqual(f.read(), '''network: - version: 2 - ethernets: - NM-{}: - renderer: NetworkManager - match: {{}} - wakeonlan: true - networkmanager: - uuid: {} - name: "myid with spaces" - passthrough: - ethernet.wake-on-lan: "2" - ipv4.method: "auto" -'''.format(UUID, UUID)) - - def test_serialize_keyfile_wake_on_lan_nm_default(self): - self.maxDiff = None - UUID = '87749f1d-334f-40b2-98d4-55db58965f5f' - FILE = os.path.join(self.workdir.name, 'tmp/some.keyfile') - os.makedirs(os.path.dirname(FILE)) - with open(FILE, 'w') as file: - file.write('''[connection] -type=ethernet -uuid={} -id=myid with spaces - -[ethernet] - -[ipv4] -method=auto'''.format(UUID)) - lib.netplan_parse_keyfile(FILE.encode(), None) - lib._write_netplan_conf('NM-{}'.format(UUID).encode(), self.workdir.name.encode()) - lib.netplan_clear_netdefs() - self.assertTrue(os.path.isfile(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)))) - with open(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)), 'r') as f: - self.assertEqual(f.read(), '''network: - version: 2 - ethernets: - NM-{}: - renderer: NetworkManager - match: {{}} - wakeonlan: true - networkmanager: - uuid: {} - name: "myid with spaces" - passthrough: - ethernet._: "" - ipv4.method: "auto" -'''.format(UUID, UUID)) - - def test_serialize_keyfile_modem_gsm(self): - self.maxDiff = None - UUID = '87749f1d-334f-40b2-98d4-55db58965f5f' - FILE = os.path.join(self.workdir.name, 'tmp/some.keyfile') - os.makedirs(os.path.dirname(FILE)) - with open(FILE, 'w') as file: - file.write('''[connection] -type=gsm -uuid={} -id=myid with spaces - -[ipv4] -method=auto - -[gsm] -auto-config=true'''.format(UUID)) - lib.netplan_parse_keyfile(FILE.encode(), None) - lib._write_netplan_conf('NM-{}'.format(UUID).encode(), self.workdir.name.encode()) - lib.netplan_clear_netdefs() - self.assertTrue(os.path.isfile(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)))) - with open(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)), 'r') as f: - self.assertEqual(f.read(), '''network: - version: 2 - modems: - NM-{}: - renderer: NetworkManager - match: {{}} - auto-config: true - networkmanager: - uuid: {} - name: "myid with spaces" - passthrough: - ipv4.method: "auto" -'''.format(UUID, UUID)) - - def test_serialize_keyfile_existing_id(self): - self.maxDiff = None - UUID = '87749f1d-334f-40b2-98d4-55db58965f5f' - FILE = os.path.join(self.workdir.name, 'run/NetworkManager/system-connections/netplan-mybr.nmconnection') - os.makedirs(os.path.dirname(FILE)) - with open(FILE, 'w') as file: - file.write('''[connection] -type=bridge -uuid={} -id=renamed netplan bridge - -[ipv4] -method=auto'''.format(UUID)) - lib.netplan_parse_keyfile(FILE.encode(), None) - lib._write_netplan_conf('mybr'.encode(), self.workdir.name.encode()) - lib.netplan_clear_netdefs() - self.assertTrue(os.path.isfile(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)))) - with open(os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)), 'r') as f: - self.assertEqual(f.read(), '''network: - version: 2 - bridges: - mybr: - renderer: NetworkManager - networkmanager: - uuid: {} - name: "renamed netplan bridge" - passthrough: - ipv4.method: "auto" -'''.format(UUID)) - - def test_keyfile_yaml_wifi_hotspot(self): - self.maxDiff = None - UUID = 'ff9d6ebc-226d-4f82-a485-b7ff83b9607f' - FILE_KF = os.path.join(self.workdir.name, 'tmp/Hotspot.nmconnection') - CONTENT_KF = '''[connection] -id=Hotspot-1 -type=wifi -uuid={} -interface-name=wlan0 -#Netplan: passthrough setting -autoconnect=false -#Netplan: passthrough setting -permissions= - -[ipv4] -method=shared -#Netplan: passthrough setting -dns-search= - -[ipv6] -method=ignore -#Netplan: passthrough setting -addr-gen-mode=stable-privacy -#Netplan: passthrough setting -dns-search= - -[wifi] -ssid=my-hotspot -mode=ap -#Netplan: passthrough setting -mac-address-blacklist= - -[wifi-security] -#Netplan: passthrough setting -group=ccmp; -#Netplan: passthrough setting -key-mgmt=wpa-psk -#Netplan: passthrough setting -pairwise=ccmp; -#Netplan: passthrough setting -proto=rsn; -#Netplan: passthrough setting -psk=test1234 - -[proxy] -'''.format(UUID) - os.makedirs(os.path.dirname(FILE_KF)) - with open(FILE_KF, 'w') as file: - file.write(CONTENT_KF) - # Convert Keyfile to YAML and compare - lib.netplan_parse_keyfile(FILE_KF.encode(), None) - lib._write_netplan_conf('NM-{}'.format(UUID).encode(), self.workdir.name.encode()) - lib.netplan_clear_netdefs() - FILE_YAML = os.path.join(self.confdir, '90-NM-{}.yaml'.format(UUID)) - CONTENT_YAML = '''network: - version: 2 - wifis: - NM-ff9d6ebc-226d-4f82-a485-b7ff83b9607f: - renderer: NetworkManager - match: - name: "wlan0" - access-points: - "my-hotspot": - mode: ap - networkmanager: - uuid: ff9d6ebc-226d-4f82-a485-b7ff83b9607f - name: "Hotspot-1" - passthrough: - connection.autoconnect: "false" - connection.permissions: "" - ipv4.method: "shared" - ipv4.dns-search: "" - ipv6.method: "ignore" - ipv6.addr-gen-mode: "stable-privacy" - ipv6.dns-search: "" - wifi.mac-address-blacklist: "" - wifi-security.group: "ccmp;" - wifi-security.key-mgmt: "wpa-psk" - wifi-security.pairwise: "ccmp;" - wifi-security.proto: "rsn;" - wifi-security.psk: "test1234" - proxy._: "" - networkmanager: - uuid: {} - name: "Hotspot-1" -'''.format(UUID) - self.assertTrue(os.path.isfile(FILE_YAML)) - with open(FILE_YAML, 'r') as f: - self.assertEqual(f.read(), CONTENT_YAML) - - # Convert YAML back to Keyfile and compare to original KF - os.remove(FILE_YAML) - self.generate(CONTENT_YAML) - self.assert_nm({'NM-ff9d6ebc-226d-4f82-a485-b7ff83b9607f-my-hotspot': CONTENT_KF}) diff -Nru netplan.io-0.102/tests/test_serialize.py netplan.io-0.101/tests/test_serialize.py --- netplan.io-0.102/tests/test_serialize.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/test_serialize.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,82 +0,0 @@ -#!/usr/bin/python3 -# Blackbox tests of the netplan YAML serializer. These are run during -# "make check" and don't touch the system configuration at all. -# -# Copyright (C) 2021 Canonical, Ltd. -# Author: Lukas Märdian -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import shutil -import ctypes -import ctypes.util - -from generator.base import TestBase - -lib = ctypes.CDLL(ctypes.util.find_library('netplan')) - - -class TestNetplanSerialize(TestBase): - '''Test netplan's YAML serializer''' - - def setUp(self): - super().setUp() - os.makedirs(self.confdir) - - def tearDown(self): - shutil.rmtree(self.workdir.name) - super().tearDown() - - def _template_serialize_yaml(self, yaml_content, netdef_id='myid'): - FILENAME = os.path.join(self.confdir, 'some-filename.yaml') - NEWFILE = os.path.join(self.confdir, '10-netplan-{}.yaml'.format(netdef_id)) - with open(FILENAME, 'w') as f: - f.write(yaml_content) - # Parse YAML and and re-write the specified netdef ID into a new file - lib.netplan_parse_yaml(FILENAME.encode(), None) - lib._write_netplan_conf(netdef_id.encode(), self.workdir.name.encode()) - lib.netplan_clear_netdefs() - self.assertTrue(os.path.isfile(NEWFILE)) - with open(FILENAME, 'r') as f: - with open(NEWFILE, 'r') as new: - self.assertEquals(f.read(), new.read()) - - def test_serialize_yaml_basic(self): - self._template_serialize_yaml('''network: - version: 2 - ethernets: - some-netplan-id: - renderer: networkd - match: - name: "eth42" -''', 'some-netplan-id') - - def test_serialize_yaml_wifi_ap(self): - self._template_serialize_yaml('''network: - version: 2 - wifis: - myid: - renderer: NetworkManager - match: - name: "eth42" - access-points: - "SOME-SSID": - hidden: true - mode: infrastructure - networkmanager: - uuid: some-uuid - name: "Some NM name with spaces" - passthrough: - wifi.mode: "mesh" -''', 'myid') diff -Nru netplan.io-0.102/tests/test_utils.py netplan.io-0.101/tests/test_utils.py --- netplan.io-0.102/tests/test_utils.py 2021-03-16 11:38:25.000000000 +0000 +++ netplan.io-0.101/tests/test_utils.py 2020-12-09 11:32:25.000000000 +0000 @@ -19,70 +19,13 @@ import unittest import tempfile import glob -import netifaces -import netplan.cli.utils as utils from unittest.mock import patch - -DEVICES = ['eth0', 'eth1', 'ens3', 'ens4', 'br0'] +import netplan.cli.utils as utils -# Consider switching to something more standard, like MockProc -class MockCmd: - """MockCmd will mock a given command name and capture all calls to it""" - - def __init__(self, name): - self._tmp = tempfile.TemporaryDirectory() - self.name = name - self.path = os.path.join(self._tmp.name, name) - self.call_log = os.path.join(self._tmp.name, "call.log") - with open(self.path, "w") as fp: - fp.write("""#!/bin/bash -printf "%%s" "$(basename "$0")" >> %(log)s -printf '\\0' >> %(log)s - -for arg in "$@"; do - printf "%%s" "$arg" >> %(log)s - printf '\\0' >> %(log)s -done - -printf '\\0' >> %(log)s -""" % {'log': self.call_log}) - os.chmod(self.path, 0o755) - - def calls(self): - """ - calls() returns the calls to the given mock command in the form of - [ ["cmd", "call1-arg1"], ["cmd", "call2-arg1"], ... ] - """ - with open(self.call_log) as fp: - b = fp.read() - calls = [] - for raw_call in b.rstrip("\0\0").split("\0\0"): - call = raw_call.rstrip("\0") - calls.append(call.split("\0")) - return calls - - def set_output(self, output): - with open(self.path, "a") as fp: - fp.write("cat << EOF\n%s\nEOF" % output) - - def set_timeout(self, timeout_dsec=10): - with open(self.path, "a") as fp: - fp.write(""" -if [[ "$*" == *try* ]] -then - ACTIVE=1 - trap 'ACTIVE=0' SIGUSR1 - trap 'ACTIVE=0' SIGINT - while (( $ACTIVE > 0 )) && (( $ACTIVE <= {} )) - do - ACTIVE=$(($ACTIVE+1)) - sleep 0.1 - done -fi -""".format(timeout_dsec)) +DEVICES = ['eth0', 'eth1', 'ens3', 'ens4', 'br0'] class TestUtils(unittest.TestCase): @@ -153,42 +96,3 @@ match = {'name': 'ens?', 'driver': 'f*'} iface = utils.find_matching_iface(DEVICES, match) self.assertEqual(iface, 'ens4') - - @patch('netifaces.ifaddresses') - def test_interface_macaddress(self, ifaddr): - ifaddr.side_effect = lambda _: {netifaces.AF_LINK: [{'addr': '00:01:02:03:04:05'}]} - self.assertEqual(utils.get_interface_macaddress('eth42'), '00:01:02:03:04:05') - - @patch('netifaces.ifaddresses') - def test_interface_macaddress_empty(self, ifaddr): - ifaddr.side_effect = lambda _: {} - self.assertEqual(utils.get_interface_macaddress('eth42'), '') - - def test_netplan_get_filename_by_id(self): - file_a = os.path.join(self.workdir.name, 'etc/netplan/a.yaml') - file_b = os.path.join(self.workdir.name, 'etc/netplan/b.yaml') - with open(file_a, 'w') as f: - f.write('network:\n ethernets:\n id_a:\n dhcp4: true') - with open(file_b, 'w') as f: - f.write('network:\n ethernets:\n id_b:\n dhcp4: true\n id_a:\n dhcp4: true') - # netdef:b can only be found in b.yaml - basename = os.path.basename(utils.netplan_get_filename_by_id('id_b', self.workdir.name)) - self.assertEqual(basename, 'b.yaml') - # netdef:a is defined in a.yaml, overriden by b.yaml - basename = os.path.basename(utils.netplan_get_filename_by_id('id_a', self.workdir.name)) - self.assertEqual(basename, 'b.yaml') - - def test_netplan_get_filename_by_id_no_files(self): - self.assertIsNone(utils.netplan_get_filename_by_id('some-id', self.workdir.name)) - - def test_netplan_get_filename_by_id_invalid(self): - file = os.path.join(self.workdir.name, 'etc/netplan/a.yaml') - with open(file, 'w') as f: - f.write('''network: - tunnels: - id_a: - mode: sit - local: 0.0.0.0 - remote: 0.0.0.0 - key: 0.0.0.0''') - self.assertIsNone(utils.netplan_get_filename_by_id('some-id', self.workdir.name))