Merge ~slyon/netplan/+git/ubuntu:slyon/ubuntu/focal-0100 into ~ubuntu-core-dev/netplan/+git/ubuntu:ubuntu/focal
- Git
- lp:~slyon/netplan/+git/ubuntu
- slyon/ubuntu/focal-0100
- Merge into ubuntu/focal
Proposed by
Lukas Märdian
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Łukasz Zemczak | ||||
Approved revision: | 7b74153bb50509435eaeea31a105edc2875d7adc | ||||
Merged at revision: | 7b74153bb50509435eaeea31a105edc2875d7adc | ||||
Proposed branch: | ~slyon/netplan/+git/ubuntu:slyon/ubuntu/focal-0100 | ||||
Merge into: | ~ubuntu-core-dev/netplan/+git/ubuntu:ubuntu/focal | ||||
Diff against target: |
9043 lines (+6250/-549) 55 files modified
Makefile (+3/-2) debian/changelog (+69/-0) debian/control (+1/-0) debian/libnetplan0.symbols (+7/-0) debian/patches/0001-Implement-just-in-time-behaviour-for-generate-162.patch (+296/-0) debian/patches/series (+1/-2) debian/rules (+4/-0) debian/tests/autostart (+9/-5) debian/tests/cloud-init (+115/-0) debian/tests/control (+18/-0) dev/null (+0/-34) doc/netplan.md (+298/-74) examples/sriov.yaml (+14/-0) examples/sriov_vlan.yaml (+18/-0) examples/static.yaml (+2/-2) netplan/cli/commands/apply.py (+91/-17) netplan/cli/commands/try_command.py (+2/-2) netplan/cli/ovs.py (+168/-0) netplan/cli/sriov.py (+32/-18) netplan/cli/utils.py (+32/-0) netplan/configmanager.py (+32/-0) src/generate.c (+14/-2) src/networkd.c (+114/-32) src/networkd.h (+2/-0) src/nm.c (+109/-26) src/openvswitch.c (+484/-0) src/openvswitch.h (+24/-0) src/parse.c (+763/-241) src/parse.h (+56/-4) src/sriov.c (+40/-0) src/sriov.h (+21/-0) src/util.c (+29/-0) src/util.h (+6/-2) src/validation.c (+155/-10) src/validation.h (+3/-0) tests/generator/base.py (+54/-0) tests/generator/test_args.py (+18/-4) tests/generator/test_bonds.py (+42/-0) tests/generator/test_common.py (+194/-1) tests/generator/test_errors.py (+69/-1) tests/generator/test_ethernets.py (+23/-7) tests/generator/test_ovs.py (+1021/-0) tests/generator/test_routing.py (+145/-17) tests/generator/test_tunnels.py (+477/-5) tests/generator/test_wifis.py (+143/-0) tests/integration/base.py (+6/-4) tests/integration/ethernets.py (+38/-0) tests/integration/ovs.py (+559/-0) tests/integration/routing.py (+76/-21) tests/integration/tunnels.py (+71/-0) tests/test_configmanager.py (+21/-0) tests/test_ovs.py (+129/-0) tests/test_sriov.py (+55/-12) tests/test_utils.py (+73/-0) tests/validate_docs.sh (+4/-4) |
||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Łukasz Zemczak | Approve | ||
Review via email: mp+391145@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Lukas Märdian (slyon) wrote : | # |
Revision history for this message
Łukasz Zemczak (sil2100) wrote : | # |
Ok, glimpsing through the diff the changes seem fine. It's a bit sad that we had to disable the whole test suit on riscv64, but then again - we don't really care about the test situation for that arch!
So I'm all +1 on this. Let me merge it and sponsor.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/Makefile b/Makefile |
2 | index 3ce1347..4b80a18 100644 |
3 | --- a/Makefile |
4 | +++ b/Makefile |
5 | @@ -43,7 +43,7 @@ libnetplan.so.$(NETPLAN_SOVER): parse.o util.o validation.o error.o |
6 | ln -snf libnetplan.so.$(NETPLAN_SOVER) libnetplan.so |
7 | |
8 | #generate: src/generate.[hc] src/parse.[hc] src/util.[hc] src/networkd.[hc] src/nm.[hc] src/validation.[hc] src/error.[hc] |
9 | -generate: libnetplan.so.$(NETPLAN_SOVER) nm.o networkd.o generate.o |
10 | +generate: libnetplan.so.$(NETPLAN_SOVER) nm.o networkd.o openvswitch.o generate.o sriov.o |
11 | $(CC) $(BUILDFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $^ -L. -lnetplan `pkg-config --cflags --libs glib-2.0 gio-2.0 yaml-0.1 uuid` |
12 | |
13 | netplan-dbus: src/dbus.c src/_features.h |
14 | @@ -66,7 +66,8 @@ clean: |
15 | rm -f *.o *.so* |
16 | rm -f netplan-dbus dbus/*.service |
17 | rm -f *.gcda *.gcno generate.info |
18 | - rm -rf test-coverage .coverage |
19 | + rm -rf test-coverage .coverage coverage.xml |
20 | + find . | grep -E "(__pycache__|\.pyc)" | xargs rm -rf |
21 | |
22 | check: default linting |
23 | tests/cli.py |
24 | diff --git a/debian/changelog b/debian/changelog |
25 | index 06d97bb..a4534e7 100644 |
26 | --- a/debian/changelog |
27 | +++ b/debian/changelog |
28 | @@ -1,3 +1,72 @@ |
29 | +netplan.io (0.100-0ubuntu4~20.04.1) UNRELEASED; urgency=medium |
30 | + |
31 | + * Backport netplan.io 0.100-0ubuntu4 to 20.04 (LP: #1894197) |
32 | + - Includes fix for OVS/WPA first-time boot issues (LP: #1892851) |
33 | + * Drop distro patches, which are included in upstream release |
34 | + * Ignore openvswitch-switch Build-Depends on riscv64, due to missing package |
35 | + - Failing unit-/integration tests will be ignored on riscv64 as well |
36 | + |
37 | + -- Lukas Märdian <lukas.maerdian@canonical.com> Tue, 22 Sep 2020 14:22:01 +0200 |
38 | + |
39 | +netplan.io (0.100-0ubuntu4) groovy; urgency=medium |
40 | + |
41 | + * debian/tests/cloud-init |
42 | + - Improve reboot test to avoid failure on arm64 |
43 | + |
44 | + -- Lukas Märdian <lukas.maerdian@canonical.com> Mon, 21 Sep 2020 12:23:02 +0200 |
45 | + |
46 | +netplan.io (0.100-0ubuntu3) groovy; urgency=medium |
47 | + |
48 | + * debian/tests: |
49 | + - Avoid SKIP of 'autostart' test |
50 | + - Add 'cloud-init' test script (instead of python 'cloud-init' test) to |
51 | + avoid network related issues on the autopkgtest infrastructure |
52 | + - Try to keep up the management network interface at all times |
53 | + |
54 | + -- Lukas Märdian <lukas.maerdian@canonical.com> Wed, 16 Sep 2020 09:47:07 +0200 |
55 | + |
56 | +netplan.io (0.100-0ubuntu2) groovy; urgency=medium |
57 | + |
58 | + * Add d/p/0001-Implement-just-in-time-behaviour-for-generate-162.patch: |
59 | + - As of upstream 6b3ac28d1522e07a88a72a42b31a8c9ff9727ac3 |
60 | + - Fixes first-boot issues with OVS/WPA service units when used on |
61 | + cloud-init enabled systems (LP: #1892851) |
62 | + * debian/tests/control: Enable new 'cloud-init' test |
63 | + - To verify first-boot just-in-time generation |
64 | + |
65 | + -- Lukas Märdian <lukas.maerdian@canonical.com> Wed, 09 Sep 2020 13:29:21 +0200 |
66 | + |
67 | +netplan.io (0.100-0ubuntu1) groovy; urgency=medium |
68 | + |
69 | + * New upstream release: 0.100 |
70 | + - Documentation improvements |
71 | + - Improved integration tests |
72 | + - Overall cleanup and refactoring |
73 | + - Improved SR-IOV first boot experience |
74 | + - Initial Open vSwitch support (LP: #1728134) |
75 | + - Add support for Wireguard tunnels |
76 | + - Add support for IP address labels (LP: #1743200) |
77 | + - Improved routing & globbing in NetworkManager backend |
78 | + - route attributes "from", "onlink" and "table" |
79 | + - matching interfaces by glob pattern |
80 | + - Add support for hidden wireless SSIDs (LP: #1866100) |
81 | + - Introduce support for networkd address options (LP: #1803203) |
82 | + - Implement ipv6-address-token key (LP: #1737976) |
83 | + Bug fixes: |
84 | + - Not connect to WiFi after 'netplan apply' (LP: #1874377) |
85 | + - Call daemon-reload after we touched systemd unit files (LP: #1874494) |
86 | + - Don't fail if same primary slave was set before (LP: #1817651) |
87 | + - Fix process_link_changes handling 'up' interfaces (LP: #1875411) |
88 | + - Fix GCC-10 -fno-common linker errors (LP: #1875412) |
89 | + - Flush IPs of NM managed interfaces (LP: #1870561) |
90 | + * Enable autopkgtests for OVS and wireguard |
91 | + * Drop all distro patches, which have been integrated upstream |
92 | + * Update symbols file |
93 | + * Added openvswitch-switch to Build-Depends |
94 | + - ovs-vsctl is required to pass the unit-tests |
95 | + |
96 | + -- Lukas Märdian <lukas.maerdian@canonical.com> Thu, 03 Sep 2020 15:51:29 +0200 |
97 | + |
98 | netplan.io (0.99-0ubuntu3~20.04.2) focal; urgency=medium |
99 | |
100 | * d/p/0002-Fix-process_link_changes-handling-up-interfaces.patch: |
101 | diff --git a/debian/control b/debian/control |
102 | index d6a69b1..1abfcf7 100644 |
103 | --- a/debian/control |
104 | +++ b/debian/control |
105 | @@ -24,6 +24,7 @@ Build-Depends: debhelper (>= 11), |
106 | pycodestyle | pep8, |
107 | python3-nose, |
108 | pandoc, |
109 | + openvswitch-switch [!riscv64], |
110 | Vcs-Git: https://salsa.debian.org/debian/netplan.io.git |
111 | Vcs-Browser: https://salsa.debian.org/debian/netplan.io |
112 | Homepage: https://netplan.io/ |
113 | diff --git a/debian/libnetplan0.symbols b/debian/libnetplan0.symbols |
114 | index 62adadd..36b49bb 100644 |
115 | --- a/debian/libnetplan0.symbols |
116 | +++ b/debian/libnetplan0.symbols |
117 | @@ -1,10 +1,13 @@ |
118 | libnetplan.so.0.0 libnetplan0 #MINVER# |
119 | NETPLAN_OPTIONAL_ADDRESS_TYPES@Base 0.99 |
120 | NETPLAN_WIFI_WOWLAN_TYPES@Base 0.99 |
121 | + address_option_handlers@Base 0.100 |
122 | current_file@Base 0.99 |
123 | g_string_free_to_file@Base 0.99 |
124 | + is_hostname@Base 0.100 |
125 | is_ip4_address@Base 0.99 |
126 | is_ip6_address@Base 0.99 |
127 | + is_wireguard_key@Base 0.100 |
128 | missing_id@Base 0.99 |
129 | missing_ids_found@Base 0.99 |
130 | netdefs@Base 0.99 |
131 | @@ -12,14 +15,18 @@ libnetplan.so.0.0 libnetplan0 #MINVER# |
132 | netplan_finish_parse@Base 0.99 |
133 | netplan_get_global_backend@Base 0.99 |
134 | netplan_parse_yaml@Base 0.99 |
135 | + ovs_settings_global@Base 0.100 |
136 | parser_error@Base 0.99 |
137 | safe_mkdir_p_dir@Base 0.99 |
138 | + systemd_escape@Base 0.100 |
139 | tunnel_mode_to_string@Base 0.99 |
140 | unlink_glob@Base 0.99 |
141 | validate_backend_rules@Base 0.99 |
142 | validate_netdef_grammar@Base 0.99 |
143 | + validate_ovs_target@Base 0.100 |
144 | wifi_frequency_24@Base 0.99 |
145 | wifi_frequency_5@Base 0.99 |
146 | wifi_get_freq24@Base 0.99 |
147 | wifi_get_freq5@Base 0.99 |
148 | + wireguard_peer_handlers@Base 0.100 |
149 | yaml_error@Base 0.99 |
150 | diff --git a/debian/patches/0001-Fix-LP-1874377-Not-connect-to-WiFi-after-netplan-app.patch b/debian/patches/0001-Fix-LP-1874377-Not-connect-to-WiFi-after-netplan-app.patch |
151 | deleted file mode 100644 |
152 | index d401e97..0000000 |
153 | --- a/debian/patches/0001-Fix-LP-1874377-Not-connect-to-WiFi-after-netplan-app.patch |
154 | +++ /dev/null |
155 | @@ -1,175 +0,0 @@ |
156 | -From: Lukas Maerdian <lukas.maerdian@canonical.com> |
157 | -Date: Tue, 28 Apr 2020 14:35:36 +0200 |
158 | -Subject: Fix LP#1874377: Not connect to WiFi after 'netplan apply' (#133) |
159 | - |
160 | -* Fix LP#1874377: Not connect to WiFi after 'netplan apply' |
161 | - |
162 | -Seems like the 'netplan apply' command was not properly adopted in #109 when wired wpa_supplicant support was introduced. |
163 | - |
164 | -Fixes: https://bugs.launchpad.net/ubuntu/+source/netplan.io/+bug/1874377 |
165 | ---- |
166 | - netplan/cli/commands/apply.py | 10 ++++-- |
167 | - netplan/cli/utils.py | 7 +++++ |
168 | - src/networkd.c | 4 +++ |
169 | - tests/generator/test_wifis.py | 71 +++++++++++++++++++++++++++++++++++++++++++ |
170 | - tests/integration/base.py | 2 +- |
171 | - 5 files changed, 91 insertions(+), 3 deletions(-) |
172 | - |
173 | -diff --git a/netplan/cli/commands/apply.py b/netplan/cli/commands/apply.py |
174 | -index 0ec95f5..cf9f122 100644 |
175 | ---- a/netplan/cli/commands/apply.py |
176 | -+++ b/netplan/cli/commands/apply.py |
177 | -@@ -108,7 +108,13 @@ class NetplanApply(utils.NetplanCommand): |
178 | - # stop backends |
179 | - if restart_networkd: |
180 | - logging.debug('netplan generated networkd configuration changed, restarting networkd') |
181 | -- utils.systemctl_networkd('stop', sync=sync, extra_services=['netplan-wpa@*.service']) |
182 | -+ wpa_services = ['netplan-wpa-*.service'] |
183 | -+ # Historically (up to v0.98) we had netplan-wpa@*.service files, in case of an |
184 | -+ # upgraded system, we need to make sure to stop those. |
185 | -+ if utils.systemctl_is_active('netplan-wpa@*.service'): |
186 | -+ wpa_services.insert(0, 'netplan-wpa@*.service') |
187 | -+ utils.systemctl_networkd('stop', sync=sync, extra_services=wpa_services) |
188 | -+ |
189 | - else: |
190 | - logging.debug('no netplan generated networkd configuration exists') |
191 | - |
192 | -@@ -169,7 +175,7 @@ class NetplanApply(utils.NetplanCommand): |
193 | - |
194 | - # (re)start backends |
195 | - if restart_networkd: |
196 | -- netplan_wpa = [os.path.basename(f) for f in glob.glob('/run/systemd/system/*.wants/netplan-wpa@*.service')] |
197 | -+ netplan_wpa = [os.path.basename(f) for f in glob.glob('/run/systemd/system/*.wants/netplan-wpa-*.service')] |
198 | - utils.systemctl_networkd('start', sync=sync, extra_services=netplan_wpa) |
199 | - if restart_nm: |
200 | - utils.systemctl_network_manager('start', sync=sync) |
201 | -diff --git a/netplan/cli/utils.py b/netplan/cli/utils.py |
202 | -index 5f54b1a..c0eee03 100644 |
203 | ---- a/netplan/cli/utils.py |
204 | -+++ b/netplan/cli/utils.py |
205 | -@@ -86,6 +86,13 @@ def systemctl_networkd(action, sync=False, extra_services=[]): # pragma: nocove |
206 | - subprocess.check_call(command) |
207 | - |
208 | - |
209 | -+def systemctl_is_active(unit_pattern): # pragma: nocover (covered in autopkgtest) |
210 | -+ '''Return True if at least one matching unit is running''' |
211 | -+ if subprocess.call(['systemctl', '--quiet', 'is-active', unit_pattern]) == 0: |
212 | -+ return True |
213 | -+ return False |
214 | -+ |
215 | -+ |
216 | - def get_interface_driver_name(interface, only_down=False): # pragma: nocover (covered in autopkgtest) |
217 | - devdir = os.path.join('/sys/class/net', interface) |
218 | - if only_down: |
219 | -diff --git a/src/networkd.c b/src/networkd.c |
220 | -index e2bb111..6f6173a 100644 |
221 | ---- a/src/networkd.c |
222 | -+++ b/src/networkd.c |
223 | -@@ -990,8 +990,12 @@ cleanup_networkd_conf(const char* rootdir) |
224 | - { |
225 | - unlink_glob(rootdir, "/run/systemd/network/10-netplan-*"); |
226 | - unlink_glob(rootdir, "/run/netplan/wpa-*.conf"); |
227 | -+ unlink_glob(rootdir, "/run/systemd/system/systemd-networkd.service.wants/netplan-wpa-*.service"); |
228 | - unlink_glob(rootdir, "/run/systemd/system/netplan-wpa-*.service"); |
229 | - unlink_glob(rootdir, "/run/udev/rules.d/99-netplan-*"); |
230 | -+ /* Historically (up to v0.98) we had netplan-wpa@*.service files, in case of an |
231 | -+ * upgraded system, we need to make sure to clean those up. */ |
232 | -+ unlink_glob(rootdir, "/run/systemd/system/systemd-networkd.service.wants/netplan-wpa@*.service"); |
233 | - } |
234 | - |
235 | - /** |
236 | -diff --git a/tests/generator/test_wifis.py b/tests/generator/test_wifis.py |
237 | -index 8eb804e..d5b79cf 100644 |
238 | ---- a/tests/generator/test_wifis.py |
239 | -+++ b/tests/generator/test_wifis.py |
240 | -@@ -116,6 +116,77 @@ network={ |
241 | - self.assertTrue(os.path.islink(os.path.join( |
242 | - self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa-wl0.service'))) |
243 | - |
244 | -+ def test_wifi_upgrade(self): |
245 | -+ # pretend an old 'netplan-wpa@*.service' link still exists on an upgraded system |
246 | -+ os.makedirs(os.path.join(self.workdir.name, 'lib/systemd/system')) |
247 | -+ os.makedirs(os.path.join(self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants')) |
248 | -+ with open(os.path.join(self.workdir.name, 'lib/systemd/system/netplan-wpa@.service'), 'w') as out: |
249 | -+ out.write('''[Unit] |
250 | -+Description=WPA supplicant for netplan %I |
251 | -+DefaultDependencies=no |
252 | -+Requires=sys-subsystem-net-devices-%i.device |
253 | -+After=sys-subsystem-net-devices-%i.device |
254 | -+Before=network.target |
255 | -+Wants=network.target |
256 | -+ |
257 | -+[Service] |
258 | -+Type=simple |
259 | -+ExecStart=/sbin/wpa_supplicant -c /run/netplan/wpa-%I.conf -i%I''') |
260 | -+ os.symlink(os.path.join(self.workdir.name, 'lib/systemd/system/netplan-wpa@.service'), |
261 | -+ os.path.join(self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa@wl0.service')) |
262 | -+ |
263 | -+ # run generate, which should cleanup the old files/symlinks |
264 | -+ self.generate('''network: |
265 | -+ version: 2 |
266 | -+ wifis: |
267 | -+ wl0: |
268 | -+ access-points: |
269 | -+ "Joe's Home": |
270 | -+ password: "s0s3kr1t" |
271 | -+ dhcp4: yes''') |
272 | -+ |
273 | -+ # verify new files/links exist, while old have been removed |
274 | -+ self.assertTrue(os.path.isfile(os.path.join( |
275 | -+ self.workdir.name, 'run/systemd/system/netplan-wpa-wl0.service'))) |
276 | -+ self.assertTrue(os.path.islink(os.path.join( |
277 | -+ self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa-wl0.service'))) |
278 | -+ # old files/links |
279 | -+ self.assertTrue(os.path.isfile(os.path.join( |
280 | -+ self.workdir.name, 'lib/systemd/system/netplan-wpa@.service'))) |
281 | -+ self.assertFalse(os.path.islink(os.path.join( |
282 | -+ self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa@wl0.service'))) |
283 | -+ |
284 | -+ # pretend another old systemd service file exists for wl1 |
285 | -+ os.symlink(os.path.join(self.workdir.name, 'lib/systemd/system/netplan-wpa@.service'), |
286 | -+ os.path.join(self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa@wl1.service')) |
287 | -+ |
288 | -+ # run generate again, to verify the historical netplan-wpa@.service links and wl0 links are gone |
289 | -+ self.generate('''network: |
290 | -+ version: 2 |
291 | -+ wifis: |
292 | -+ wl1: |
293 | -+ access-points: |
294 | -+ "Other Home": |
295 | -+ password: "s0s3kr1t" |
296 | -+ dhcp4: yes''') |
297 | -+ |
298 | -+ # verify new files/links exist, while old have been removed |
299 | -+ self.assertTrue(os.path.isfile(os.path.join( |
300 | -+ self.workdir.name, 'run/systemd/system/netplan-wpa-wl1.service'))) |
301 | -+ self.assertTrue(os.path.islink(os.path.join( |
302 | -+ self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa-wl1.service'))) |
303 | -+ # old files/links |
304 | -+ self.assertTrue(os.path.isfile(os.path.join( |
305 | -+ self.workdir.name, 'lib/systemd/system/netplan-wpa@.service'))) |
306 | -+ self.assertFalse(os.path.islink(os.path.join( |
307 | -+ self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa@wl1.service'))) |
308 | -+ self.assertFalse(os.path.islink(os.path.join( |
309 | -+ self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa@wl0.service'))) |
310 | -+ self.assertFalse(os.path.isfile(os.path.join( |
311 | -+ self.workdir.name, 'run/systemd/system/netplan-wpa-wl0.service'))) |
312 | -+ self.assertFalse(os.path.islink(os.path.join( |
313 | -+ self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa-wl0.service'))) |
314 | -+ |
315 | - def test_wifi_route(self): |
316 | - self.generate('''network: |
317 | - version: 2 |
318 | -diff --git a/tests/integration/base.py b/tests/integration/base.py |
319 | -index ed7100e..16fd2ee 100644 |
320 | ---- a/tests/integration/base.py |
321 | -+++ b/tests/integration/base.py |
322 | -@@ -93,7 +93,7 @@ class IntegrationTestsBase(unittest.TestCase): |
323 | - pass |
324 | - |
325 | - def tearDown(self): |
326 | -- subprocess.call(['systemctl', 'stop', 'NetworkManager', 'systemd-networkd', 'netplan-wpa@*', |
327 | -+ subprocess.call(['systemctl', 'stop', 'NetworkManager', 'systemd-networkd', 'netplan-wpa-*', |
328 | - 'systemd-networkd.socket']) |
329 | - # NM has KillMode=process and leaks dhclient processes |
330 | - subprocess.call(['systemctl', 'kill', 'NetworkManager']) |
331 | diff --git a/debian/patches/0001-Implement-just-in-time-behaviour-for-generate-162.patch b/debian/patches/0001-Implement-just-in-time-behaviour-for-generate-162.patch |
332 | new file mode 100644 |
333 | index 0000000..a5388c0 |
334 | --- /dev/null |
335 | +++ b/debian/patches/0001-Implement-just-in-time-behaviour-for-generate-162.patch |
336 | @@ -0,0 +1,296 @@ |
337 | +From: Dimitri John Ledkov <19779+xnox@users.noreply.github.com> |
338 | +Date: Wed, 9 Sep 2020 12:21:06 +0100 |
339 | +Subject: Implement just-in-time behaviour for generate (#162) |
340 | +MIME-Version: 1.0 |
341 | +Content-Type: text/plain; charset="utf-8" |
342 | +Content-Transfer-Encoding: 8bit |
343 | + |
344 | +If system is initializing (basic.target not reached yet), and netplan generate is called ensure that any netplan generated service units are added to the initial boot transaction. |
345 | + |
346 | +This should resolve cloud-init first-time booting with systemd-networkd disabled, or with requirement to add wlan/wifi units. |
347 | + |
348 | +Commits: |
349 | +* generate: implement just-in-time behaviour of generate |
350 | + |
351 | +If system is initializing (basic.target not reached yet), and `netplan |
352 | +generate` is called ensure that any netplan generated service units |
353 | +are added to the initial boot transaction. |
354 | + |
355 | +This should resolve cloud-init first-time booting with |
356 | +systemd-networkd disabled, or with requirement to add wlan/wifi units. |
357 | + |
358 | +* Add integration tests for cloud-init OVS/WPA first-boot |
359 | + |
360 | +* generate: coverage 100%, by excluding special parts, covered by the integration test |
361 | + |
362 | +* generate: jit starting of units only as root |
363 | + |
364 | +Units shall only be started JIT during early boot, by the system user |
365 | +(root). If a normal user calls 'netplan generate' it shall not start the |
366 | +units. Avoid asking for a password if a user (or test) executes this |
367 | +command and rather fail with missing authentication. |
368 | + |
369 | +* Add documentation and feature flag |
370 | + |
371 | +* generate: no jit if network.target already started |
372 | + |
373 | +Do not try to enqueue new network related netplan-*.service units, if |
374 | +network.target was already started. |
375 | + |
376 | +Co-authored-by: Lukas Märdian <lukas.maerdian@canonical.com> |
377 | +--- |
378 | + doc/netplan-generate.md | 5 ++ |
379 | + src/generate.c | 59 +++++++++++++++++++++- |
380 | + tests/integration/base.py | 12 ++++- |
381 | + tests/integration/cloud-init.py | 106 ++++++++++++++++++++++++++++++++++++++++ |
382 | + 4 files changed, 179 insertions(+), 3 deletions(-) |
383 | + create mode 100644 tests/integration/cloud-init.py |
384 | + |
385 | +diff --git a/doc/netplan-generate.md b/doc/netplan-generate.md |
386 | +index b325169..5ba5ee1 100644 |
387 | +--- a/doc/netplan-generate.md |
388 | ++++ b/doc/netplan-generate.md |
389 | +@@ -25,6 +25,11 @@ configuration. |
390 | + You will not normally need to run this directly as it is run by |
391 | + **netplan apply**, **netplan try**, or at boot. |
392 | + |
393 | ++Only if executed during the systemd ``initializing`` phase |
394 | ++(i.e. "Early bootup, before ``basic.target`` is reached"), will |
395 | ++it attempt to start/apply the newly created service units. |
396 | ++**Requires feature: generate-just-in-time** |
397 | ++ |
398 | + For details of the configuration file format, see **netplan**(5). |
399 | + |
400 | + # OPTIONS |
401 | +diff --git a/src/generate.c b/src/generate.c |
402 | +index e88dd7f..50de4dc 100644 |
403 | +--- a/src/generate.c |
404 | ++++ b/src/generate.c |
405 | +@@ -53,6 +53,34 @@ reload_udevd(void) |
406 | + g_spawn_sync(NULL, (gchar**)argv, NULL, G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, NULL, NULL, NULL, NULL); |
407 | + }; |
408 | + |
409 | ++// LCOV_EXCL_START |
410 | ++/* covered via 'cloud-init' integration test */ |
411 | ++static gboolean |
412 | ++check_called_just_in_time() |
413 | ++{ |
414 | ++ const gchar *argv[] = { "/bin/systemctl", "is-system-running", NULL }; |
415 | ++ gchar *output = NULL; |
416 | ++ g_spawn_sync(NULL, (gchar**)argv, NULL, G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, &output, NULL, NULL, NULL); |
417 | ++ if (output != NULL && strstr(output, "initializing") != NULL) { |
418 | ++ g_free(output); |
419 | ++ const gchar *argv2[] = { "/bin/systemctl", "is-active", "network.target", NULL }; |
420 | ++ gint exit_code = 0; |
421 | ++ g_spawn_sync(NULL, (gchar**)argv2, NULL, G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, NULL, NULL, &exit_code, NULL); |
422 | ++ /* return TRUE, if network.target is not yet active */ |
423 | ++ return !g_spawn_check_exit_status(exit_code, NULL); |
424 | ++ } |
425 | ++ g_free(output); |
426 | ++ return FALSE; |
427 | ++}; |
428 | ++ |
429 | ++static void |
430 | ++start_unit_jit(gchar *unit) |
431 | ++{ |
432 | ++ const gchar *argv[] = { "/bin/systemctl", "start", "--no-block", "--no-ask-password", unit, NULL }; |
433 | ++ g_spawn_sync(NULL, (gchar**)argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, NULL, NULL, NULL); |
434 | ++}; |
435 | ++// LCOV_EXCL_END |
436 | ++ |
437 | + static void |
438 | + nd_iterator_list(gpointer value, gpointer user_data) |
439 | + { |
440 | +@@ -162,6 +190,8 @@ int main(int argc, char** argv) |
441 | + /* are we being called as systemd generator? */ |
442 | + gboolean called_as_generator = (strstr(argv[0], "systemd/system-generators/") != NULL); |
443 | + g_autofree char* generator_run_stamp = NULL; |
444 | ++ glob_t gl; |
445 | ++ int rc; |
446 | + |
447 | + /* Parse CLI options */ |
448 | + opt_context = g_option_context_new(NULL); |
449 | +@@ -206,8 +236,6 @@ int main(int argc, char** argv) |
450 | + g_autofree char* glob_etc = g_strjoin(NULL, rootdir ?: "", G_DIR_SEPARATOR_S, "etc/netplan/*.yaml", NULL); |
451 | + g_autofree char* glob_run = g_strjoin(NULL, rootdir ?: "", G_DIR_SEPARATOR_S, "run/netplan/*.yaml", NULL); |
452 | + g_autofree char* glob_lib = g_strjoin(NULL, rootdir ?: "", G_DIR_SEPARATOR_S, "lib/netplan/*.yaml", NULL); |
453 | +- glob_t gl; |
454 | +- int rc; |
455 | + /* keys are strdup()ed, free them; values point into the glob_t, don't free them */ |
456 | + g_autoptr(GHashTable) configs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); |
457 | + g_autoptr(GList) config_keys = NULL; |
458 | +@@ -292,6 +320,33 @@ int main(int argc, char** argv) |
459 | + FILE* f = fopen(generator_run_stamp, "w"); |
460 | + g_assert(f != NULL); |
461 | + fclose(f); |
462 | ++ } else if (check_called_just_in_time()) { |
463 | ++ /* netplan-feature: generate-just-in-time */ |
464 | ++ /* When booting with cloud-init, network configuration |
465 | ++ * might be provided just-in-time. Specifically after |
466 | ++ * system-generators were executed, but before |
467 | ++ * network.target is started. In such case, auxiliary |
468 | ++ * units that netplan enables have not been included in |
469 | ++ * the initial boot transaction. Detect such scenario and |
470 | ++ * add all netplan units to the initial boot transaction. |
471 | ++ */ |
472 | ++ // LCOV_EXCL_START |
473 | ++ /* covered via 'cloud-init' integration test */ |
474 | ++ if (any_networkd) { |
475 | ++ start_unit_jit("systemd-networkd.socket"); |
476 | ++ start_unit_jit("systemd-networkd-wait-online.service"); |
477 | ++ start_unit_jit("systemd-networkd.service"); |
478 | ++ } |
479 | ++ g_autofree char* glob_run = g_strjoin(NULL, rootdir ?: "", G_DIR_SEPARATOR_S, |
480 | ++ "run/systemd/system/netplan-*.service", NULL); |
481 | ++ if (!glob(glob_run, 0, NULL, &gl)) { |
482 | ++ for (size_t i = 0; i < gl.gl_pathc; ++i) { |
483 | ++ gchar *unit_name = g_path_get_basename(gl.gl_pathv[i]); |
484 | ++ start_unit_jit(unit_name); |
485 | ++ g_free(unit_name); |
486 | ++ } |
487 | ++ } |
488 | ++ // LCOV_EXCL_END |
489 | + } |
490 | + |
491 | + return 0; |
492 | +diff --git a/tests/integration/base.py b/tests/integration/base.py |
493 | +index 4bba1e0..b4cee8e 100644 |
494 | +--- a/tests/integration/base.py |
495 | ++++ b/tests/integration/base.py |
496 | +@@ -4,9 +4,10 @@ |
497 | + # Wifi (mac80211-hwsim). These need to be run in a VM and do change the system |
498 | + # configuration. |
499 | + # |
500 | +-# Copyright (C) 2018 Canonical, Ltd. |
501 | ++# Copyright (C) 2018-2020 Canonical, Ltd. |
502 | + # Author: Martin Pitt <martin.pitt@ubuntu.com> |
503 | + # Author: Mathieu Trudel-Lapierre <mathieu.trudel-lapierre@canonical.com> |
504 | ++# Author: Lukas Märdian <lukas.maerdian@canonical.com> |
505 | + # |
506 | + # This program is free software; you can redistribute it and/or modify |
507 | + # it under the terms of the GNU General Public License as published by |
508 | +@@ -407,3 +408,12 @@ class IntegrationTestsBase(unittest.TestCase): |
509 | + p = subprocess.Popen(['systemctl', 'is-active', unit], stdout=subprocess.PIPE) |
510 | + out = p.communicate()[0] |
511 | + return p.returncode == 0 or out.startswith(b'activating') |
512 | ++ |
513 | ++ |
514 | ++class IntegrationTestReboot(IntegrationTestsBase): |
515 | ++ |
516 | ++ def tearDown(self): |
517 | ++ # Do not tearDown in the middle of a reboot test |
518 | ++ # Only after the 2nd part of the test ran (after reboot) |
519 | ++ if os.getenv('AUTOPKGTEST_REBOOT_MARK'): |
520 | ++ super().tearDown() |
521 | +diff --git a/tests/integration/cloud-init.py b/tests/integration/cloud-init.py |
522 | +new file mode 100644 |
523 | +index 0000000..4a9910c |
524 | +--- /dev/null |
525 | ++++ b/tests/integration/cloud-init.py |
526 | +@@ -0,0 +1,106 @@ |
527 | ++#!/usr/bin/python3 |
528 | ++# |
529 | ++# Integration tests for complex networking scenarios |
530 | ++# (ie. mixes of various features, may test real live cases) |
531 | ++# |
532 | ++# These need to be run in a VM and do change the system |
533 | ++# configuration. |
534 | ++# |
535 | ++# Copyright (C) 2020 Canonical, Ltd. |
536 | ++# Author: Lukas Märdian <lukas.maerdian@canonical.com> |
537 | ++# |
538 | ++# This program is free software; you can redistribute it and/or modify |
539 | ++# it under the terms of the GNU General Public License as published by |
540 | ++# the Free Software Foundation; version 3. |
541 | ++# |
542 | ++# This program is distributed in the hope that it will be useful, |
543 | ++# but WITHOUT ANY WARRANTY; without even the implied warranty of |
544 | ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
545 | ++# GNU General Public License for more details. |
546 | ++# |
547 | ++# You should have received a copy of the GNU General Public License |
548 | ++# along with this program. If not, see <http://www.gnu.org/licenses/>. |
549 | ++ |
550 | ++import sys |
551 | ++import os |
552 | ++import time |
553 | ++import subprocess |
554 | ++import unittest |
555 | ++ |
556 | ++from base import IntegrationTestReboot, test_backends |
557 | ++ |
558 | ++ |
559 | ++@unittest.skipIf("networkd" not in test_backends, |
560 | ++ "skipping as networkd backend tests are disabled") |
561 | ++class TestSpecialReboot(IntegrationTestReboot): |
562 | ++ backend = 'networkd' |
563 | ++ |
564 | ++ def test_generate_start_services_just_in_time(self): |
565 | ++ self.setup_eth(None) |
566 | ++ MARKER = 'cloud_init_generate' |
567 | ++ # PART 1: set up the requried files before rebooting |
568 | ++ if os.getenv('AUTOPKGTEST_REBOOT_MARK') != MARKER: |
569 | ++ # any netplan YAML config |
570 | ++ with open(self.config, 'w') as f: |
571 | ++ f.write('''network: |
572 | ++ ethernets: |
573 | ++ ethbn: |
574 | ++ match: {name: %(ec)s} |
575 | ++ dhcp4: true''' % {'ec': self.dev_e_client}) |
576 | ++ # Prepare a dummy netplan service unit, which will be moved to /run/systemd/system/ |
577 | ++ # during early boot, as if it would have been created by 'netplan generate' |
578 | ++ with open ('/netplan-dummy.service', 'w') as f: |
579 | ++ f.write('''[Unit] |
580 | ++Description=Check if this dummy is properly started by systemd |
581 | ++ |
582 | ++[Service] |
583 | ++Type=oneshot |
584 | ++# Keep it running, so we can verify it was properly started |
585 | ++RemainAfterExit=yes |
586 | ++ExecStart=echo "Doing nothing ..." |
587 | ++''') |
588 | ++ # A service simulating cloud-init, calling 'netplan generate' during early boot |
589 | ++ # at the 'initialization' phase of systemd (before basic.target is reached). |
590 | ++ with open ('/etc/systemd/system/cloud-init-dummy.service', 'w') as f: |
591 | ++ f.write('''[Unit] |
592 | ++Description=Simulating cloud-init's 'netplan generate' call during early boot |
593 | ++DefaultDependencies=no |
594 | ++Before=basic.target |
595 | ++After=sysinit.target |
596 | ++ |
597 | ++[Install] |
598 | ++WantedBy=multi-user.target |
599 | ++ |
600 | ++[Service] |
601 | ++Type=oneshot |
602 | ++# Keep it running, so we can verify it was properly started |
603 | ++RemainAfterExit=yes |
604 | ++# Simulate creating a new service unit (i.e. netplan-wpa-*.service / netplan-ovs-*.service) |
605 | ++ExecStart=/bin/mv /netplan-dummy.service /run/systemd/system/ |
606 | ++ExecStart=/usr/sbin/netplan generate |
607 | ++''') |
608 | ++ subprocess.check_call(['systemctl', '--quiet', 'enable', 'cloud-init-dummy.service']) |
609 | ++ subprocess.check_call(['systemctl', '--quiet', 'disable', 'systemd-networkd.service']) |
610 | ++ subprocess.check_call(['/tmp/autopkgtest-reboot', MARKER]) |
611 | ++ # PART 2: after reboot verify all (newly created) services have been started |
612 | ++ else: |
613 | ++ self.addCleanup(subprocess.call, ['rm', '/run/systemd/system/netplan-dummy.service']) |
614 | ++ self.addCleanup(subprocess.call, ['rm', '/etc/systemd/system/cloud-init-dummy.service']) |
615 | ++ self.addCleanup(subprocess.call, ['systemctl', '--quiet', 'disable', 'cloud-init-dummy.service']) |
616 | ++ |
617 | ++ time.sleep(5) # Give some time for systemd to finish the boot transaction |
618 | ++ # Verify our cloud-init simulation worked |
619 | ++ out = subprocess.check_output(['systemctl', 'status', 'cloud-init-dummy.service'], universal_newlines=True) |
620 | ++ self.assertIn('Active: active (exited)', out) |
621 | ++ self.assertIn('mv /netplan-dummy.service /run/systemd/system/ (code=exited, status=0/SUCCESS)', out) |
622 | ++ self.assertIn('netplan generate (code=exited, status=0/SUCCESS)', out) |
623 | ++ # Verify the previously disabled networkd is running again |
624 | ++ out = subprocess.check_output(['systemctl', 'status', 'systemd-networkd.service'], universal_newlines=True) |
625 | ++ self.assertIn('Active: active (running)', out) |
626 | ++ # Verify the newly created services were started just-in-time |
627 | ++ out = subprocess.check_output(['systemctl', 'status', 'netplan-dummy.service'], universal_newlines=True) |
628 | ++ self.assertIn('Active: active (exited)', out) |
629 | ++ self.assertIn('echo Doing nothing ... (code=exited, status=0/SUCCESS)', out) |
630 | ++ |
631 | ++ |
632 | ++unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2)) |
633 | diff --git a/debian/patches/0002-Fix-process_link_changes-handling-up-interfaces.patch b/debian/patches/0002-Fix-process_link_changes-handling-up-interfaces.patch |
634 | deleted file mode 100644 |
635 | index 17b9d8d..0000000 |
636 | --- a/debian/patches/0002-Fix-process_link_changes-handling-up-interfaces.patch |
637 | +++ /dev/null |
638 | @@ -1,34 +0,0 @@ |
639 | -From 028f26f1fbef974284732480a92396550d118525 Mon Sep 17 00:00:00 2001 |
640 | -From: Heitor Alves de Siqueira <halves@canonical.com> |
641 | -Date: Thu, 28 May 2020 09:19:07 -0300 |
642 | -Subject: Fix process_link_changes handling 'up' interfaces |
643 | - |
644 | -b7f1d9b04212 refactored process_link_changes with helper methods to get |
645 | -the interface driver name and MAC address. This new code introduced a |
646 | -regression where it's possible for an interface in the up state to be |
647 | -included in the changelist by its MAC address. |
648 | - |
649 | -This patch restores the previous behaviour, skipping interfaces that |
650 | -don't have driver_name set. |
651 | - |
652 | -Fixes: https://bugs.launchpad.net/bugs/1875411 |
653 | -Origin: upstream, https://github.com/CanonicalLtd/netplan/commit/8f77deec17ce |
654 | -Bug-Ubuntu: https://bugs.launchpad.net/bugs/1875411 |
655 | ---- |
656 | - netplan/cli/commands/apply.py | 3 +++ |
657 | - 1 file changed, 3 insertions(+) |
658 | - |
659 | -diff --git a/netplan/cli/commands/apply.py b/netplan/cli/commands/apply.py |
660 | -index cf9f122..4b91ae9 100644 |
661 | ---- a/netplan/cli/commands/apply.py |
662 | -+++ b/netplan/cli/commands/apply.py |
663 | -@@ -244,6 +244,9 @@ class NetplanApply(utils.NetplanCommand): |
664 | - continue |
665 | - |
666 | - driver_name = utils.get_interface_driver_name(interface, only_down=True) |
667 | -+ if not driver_name: |
668 | -+ # don't allow up interfaces to match by mac |
669 | -+ continue |
670 | - macaddress = utils.get_interface_macaddress(interface) |
671 | - if driver_name in matches['by-driver']: |
672 | - new_name = matches['by-driver'][driver_name] |
673 | diff --git a/debian/patches/series b/debian/patches/series |
674 | index 4a0d63c..16adbf4 100644 |
675 | --- a/debian/patches/series |
676 | +++ b/debian/patches/series |
677 | @@ -1,2 +1 @@ |
678 | -0001-Fix-LP-1874377-Not-connect-to-WiFi-after-netplan-app.patch |
679 | -0002-Fix-process_link_changes-handling-up-interfaces.patch |
680 | +0001-Implement-just-in-time-behaviour-for-generate-162.patch |
681 | diff --git a/debian/rules b/debian/rules |
682 | index f64d5f7..7bb0bd4 100755 |
683 | --- a/debian/rules |
684 | +++ b/debian/rules |
685 | @@ -3,5 +3,9 @@ |
686 | %: |
687 | dh $@ --fail-missing |
688 | |
689 | +override_dh_auto_test: |
690 | + # The test suite for this package is busted on riscv64, due to missing openvswitch. |
691 | + if [ ${DEB_BUILD_ARCH} != riscv64 ]; then dh_auto_test; fi |
692 | + |
693 | override_dh_auto_install: |
694 | dh_auto_install -- LIBDIR=/usr/lib/${DEB_HOST_MULTIARCH}/ |
695 | diff --git a/debian/tests/autostart b/debian/tests/autostart |
696 | index 066a39a..85fd64b 100755 |
697 | --- a/debian/tests/autostart |
698 | +++ b/debian/tests/autostart |
699 | @@ -27,15 +27,19 @@ assert_is_running() { |
700 | fi |
701 | } |
702 | |
703 | +# Always try to keep the management interface up and running |
704 | +mkdir -p /etc/systemd/network |
705 | +cat <<EOF > /etc/systemd/network/20-mgmt.network |
706 | +[Match] |
707 | +Name=en* |
708 | + |
709 | +[Network] |
710 | +DHCP=yes |
711 | +EOF |
712 | |
713 | case "${AUTOPKGTEST_REBOOT_MARK:-}" in |
714 | '') |
715 | echo "INFO: Doing initial check that there is no existing netplan config." |
716 | - if [ -n "$(ls /etc/netplan 2>/dev/null || true)" ]; then |
717 | - echo "SKIP: Testbed already has netplan config" |
718 | - exit 0 |
719 | - fi |
720 | - |
721 | # right after installation systemd-networkd may or may not be started |
722 | assert_is_running systemd-networkd.service status |
723 | echo "INFO: systemd-networkd is fine, rebooting..." |
724 | diff --git a/debian/tests/cloud-init b/debian/tests/cloud-init |
725 | new file mode 100755 |
726 | index 0000000..577bb05 |
727 | --- /dev/null |
728 | +++ b/debian/tests/cloud-init |
729 | @@ -0,0 +1,115 @@ |
730 | +#!/bin/sh |
731 | +# |
732 | +# Check that netplan, systemd and cloud-init will properly cooperate |
733 | +# and run newly generated service units just-in-time. |
734 | +# |
735 | +set -eu |
736 | + |
737 | +if [ ! -x /tmp/autopkgtest-reboot ]; then |
738 | + echo "SKIP: Testbed does not support reboot" |
739 | + exit 0 |
740 | +fi |
741 | + |
742 | +# parameters: service expect_running |
743 | +assert_is_running() { |
744 | + if [ "$2" = 1 ] && ! systemctl --quiet is-active "$1"; then |
745 | + echo "ERROR: expected $1 to have started, but it was not" >&2 |
746 | + systemctl --no-pager status "$1" |
747 | + exit 1 |
748 | + elif [ "$2" = 0 ] && systemctl --quiet is-active "$1"; then |
749 | + echo "ERROR: expected $1 to not have started, but it was" >&2 |
750 | + systemctl --no-pager status "$1" |
751 | + exit 1 |
752 | + else |
753 | + systemctl --no-pager status "$1" || true |
754 | + fi |
755 | +} |
756 | + |
757 | +# Always try to keep the management interface up and running |
758 | +mkdir -p /etc/systemd/network |
759 | +cat <<EOF > /etc/systemd/network/20-mgmt.network |
760 | +[Match] |
761 | +Name=en* |
762 | + |
763 | +[Network] |
764 | +DHCP=yes |
765 | +EOF |
766 | + |
767 | +case "${AUTOPKGTEST_REBOOT_MARK:-}" in |
768 | + '') |
769 | + echo "INFO: Preparing configuration" |
770 | + mkdir -p /etc/netplan |
771 | + # Any netplan YAML config |
772 | + cat <<EOF > /etc/netplan/00test.yaml |
773 | +network: |
774 | + version: 2 |
775 | + bridges: |
776 | + brtest00: |
777 | + addresses: [10.42.1.1/24] |
778 | +EOF |
779 | + # Prepare a dummy netplan service unit, which will be moved to /run/systemd/system/ |
780 | + # during early boot, as if it would have been created by 'netplan generate' |
781 | + cat <<EOF > /netplan-dummy.service |
782 | +[Unit] |
783 | +Description=Check if this dummy is properly started by systemd |
784 | + |
785 | +[Service] |
786 | +Type=oneshot |
787 | +# Keep it running, so we can verify it was properly started |
788 | +RemainAfterExit=yes |
789 | +ExecStart=echo "Doing nothing ..." |
790 | +EOF |
791 | + # A service simulating cloud-init, calling 'netplan generate' during early boot |
792 | + # at the 'initialization' phase of systemd (before basic.target is reached). |
793 | + cat <<EOF > /etc/systemd/system/cloud-init-dummy.service |
794 | +[Unit] |
795 | +Description=Simulating cloud-init's 'netplan generate' call during early boot |
796 | +DefaultDependencies=no |
797 | +Before=basic.target |
798 | +Before=network.target |
799 | +After=sysinit.target |
800 | + |
801 | +[Install] |
802 | +RequiredBy=basic.target |
803 | + |
804 | +[Service] |
805 | +Type=oneshot |
806 | +# Keep it running, so we can verify it was properly started |
807 | +RemainAfterExit=yes |
808 | +# Simulate creating a new service unit (i.e. netplan-wpa-*.service / netplan-ovs-*.service) |
809 | +ExecStart=/bin/cp /netplan-dummy.service /run/systemd/system/ |
810 | +ExecStart=/usr/sbin/netplan generate |
811 | +EOF |
812 | + |
813 | + # right after installation systemd-networkd may or may not be started |
814 | + assert_is_running systemd-networkd.service status |
815 | + |
816 | + systemctl disable systemd-networkd.service |
817 | + systemctl enable cloud-init-dummy.service |
818 | + echo "INFO: Configuration written, rebooting..." |
819 | + /tmp/autopkgtest-reboot config |
820 | + ;; |
821 | + |
822 | + config) |
823 | + sleep 5 # Give some time for systemd to finish the boot transaction |
824 | + echo "INFO: Validate that systemd-networkd, cloud-init-dummy.service and netplan-dummy.service are running and our test bridge exists..." |
825 | + assert_is_running systemd-networkd.service 1 |
826 | + assert_is_running cloud-init-dummy.service 1 |
827 | + assert_is_running netplan-dummy.service 1 |
828 | + ip a show dev brtest00 |
829 | + echo "OK: Test bridge is configured and just-in-time services running." |
830 | + |
831 | + # Cleanup |
832 | + systemctl enable systemd-networkd.service |
833 | + systemctl disable cloud-init-dummy.service |
834 | + rm /netplan-dummy.service |
835 | + rm /run/systemd/system/netplan-dummy.service |
836 | + rm /etc/systemd/system/cloud-init-dummy.service |
837 | + rm /etc/netplan/00test.yaml |
838 | + ;; |
839 | + |
840 | + *) |
841 | + echo "INTERNAL ERROR: autopkgtest marker $AUTOPKGTEST_REBOOT_MARK unexpected" >&2 |
842 | + exit 1 |
843 | + ;; |
844 | +esac |
845 | diff --git a/debian/tests/control b/debian/tests/control |
846 | index 727e3da..02d364b 100644 |
847 | --- a/debian/tests/control |
848 | +++ b/debian/tests/control |
849 | @@ -1,3 +1,17 @@ |
850 | +Test-Command: python3 tests/integration/run.py --test=ovs |
851 | +Tests-Directory: tests/integration |
852 | +Depends: @, |
853 | + systemd, |
854 | + network-manager, |
855 | + hostapd, |
856 | + dnsmasq-base, |
857 | + libnm0, |
858 | + python3-gi, |
859 | + gir1.2-nm-1.0, |
860 | + openvswitch-switch, |
861 | +Restrictions: allow-stderr, needs-root, isolation-machine |
862 | +Features: test-name=ovs |
863 | + |
864 | Test-Command: python3 tests/integration/run.py --test=ethernets |
865 | Tests-Directory: tests/integration |
866 | Depends: @, |
867 | @@ -86,6 +100,7 @@ Depends: @, |
868 | libnm0, |
869 | python3-gi, |
870 | gir1.2-nm-1.0, |
871 | + wireguard-tools, |
872 | Restrictions: allow-stderr, needs-root, isolation-machine |
873 | Features: test-name=tunnels |
874 | |
875 | @@ -117,3 +132,6 @@ Features: test-name=regressions |
876 | |
877 | Tests: autostart |
878 | Restrictions: allow-stderr, needs-root, isolation-container |
879 | + |
880 | +Tests: cloud-init |
881 | +Restrictions: allow-stderr, needs-root, isolation-container |
882 | diff --git a/doc/netplan.md b/doc/netplan.md |
883 | index c4b0ba3..97dc945 100644 |
884 | --- a/doc/netplan.md |
885 | +++ b/doc/netplan.md |
886 | @@ -43,8 +43,8 @@ configuration files. Their primary purpose is to serve as anchor names for |
887 | composite devices, for example to enumerate the members of a bridge that is |
888 | currently being defined. |
889 | |
890 | -If an interface is defined with an ID in a configuration file; it will be |
891 | -brought up by the applicable renderer. To not have netplan touch an interface |
892 | +(Since 0.97) If an interface is defined with an ID in a configuration file; it will |
893 | +be brought up by the applicable renderer. To not have netplan touch an interface |
894 | at all, it should be completely omitted from the netplan configuration files. |
895 | |
896 | There are two physically/structurally different classes of device definitions, |
897 | @@ -89,8 +89,8 @@ Virtual devices |
898 | : Current interface name. Globs are supported, and the primary use case |
899 | for matching on names, as selecting one fixed name can be more easily |
900 | achieved with having no ``match:`` at all and just using the ID (see |
901 | - above). Note that currently only networkd supports globbing, |
902 | - NetworkManager does not. |
903 | + above). |
904 | + (``NetworkManager``: as of v1.14.0) |
905 | |
906 | ``macaddress`` (scalar) |
907 | : Device's MAC address in the form "XX:XX:XX:XX:XX:XX". Globs are not |
908 | @@ -133,10 +133,80 @@ Virtual devices |
909 | |
910 | : Enable wake on LAN. Off by default. |
911 | |
912 | -``emit-lldp`` (bool) |
913 | +``emit-lldp`` (bool) – since **0.99** |
914 | |
915 | : (networkd backend only) Whether to emit LLDP packets. Off by default. |
916 | |
917 | +``openvswitch`` (mapping) – since **0.100** |
918 | + |
919 | +: This provides additional configuration for the network device for openvswitch. |
920 | + If openvswitch is not available on the system, netplan treats the presence of |
921 | + openvswitch configuration as an error. |
922 | + |
923 | + Any supported network device that is declared with the ``openvswitch`` mapping |
924 | + (or any bond/bridge that includes an interface with an openvswitch configuration) |
925 | + will be created in openvswitch instead of the defined renderer. |
926 | + In the case of a ``vlan`` definition declared the same way, netplan will create |
927 | + a fake VLAN bridge in openvswitch with the requested vlan properties. |
928 | + |
929 | + ``external-ids`` (mapping) – since **0.100** |
930 | + : Passed-through directly to OpenVSwitch |
931 | + |
932 | + ``other-config`` (mapping) – since **0.100** |
933 | + : Passed-through directly to OpenVSwitch |
934 | + |
935 | + ``lacp`` (scalar) – since **0.100** |
936 | + : Valid for bond interfaces. Accepts ``active``, ``passive`` or ``off`` (the default). |
937 | + |
938 | + ``fail-mode`` (scalar) – since **0.100** |
939 | + : Valid for bridge interfaces. Accepts ``secure`` or ``standalone`` (the default). |
940 | + |
941 | + ``mcast-snooping`` (bool) – since **0.100** |
942 | + : Valid for bridge interfaces. False by default. |
943 | + |
944 | + ``protocols`` (sequence of scalars) – since **0.100** |
945 | + : Valid for bridge interfaces or the network section. List of protocols to be used when |
946 | + negotiating a connection with the controller. Accepts ``OpenFlow10``, ``OpenFlow11``, |
947 | + ``OpenFlow12``, ``OpenFlow13``, ``OpenFlow14``, ``OpenFlow15`` and ``OpenFlow16``. |
948 | + |
949 | + ``rstp`` (bool) – since **0.100** |
950 | + : Valid for bridge interfaces. False by default. |
951 | + |
952 | + ``controller`` (mapping) – since **0.100** |
953 | + : Valid for bridge interfaces. Specify an external OpenFlow controller. |
954 | + |
955 | + ``addresses`` (sequence of scalars) |
956 | + : Set the list of addresses to use for the controller targets. The |
957 | + syntax of these addresses is as defined in ovs-vsctl(8). Example: |
958 | + addresses: ``[tcp:127.0.0.1:6653, "ssl:[fe80::1234%eth0]:6653"]`` |
959 | + |
960 | + ``connection-mode`` (scalar) |
961 | + : Set the connection mode for the controller. Supported options are |
962 | + ``in-band`` and ``out-of-band``. The default is ``in-band``. |
963 | + |
964 | + ``ports`` (sequence of sequence of scalars) – since **0.100** |
965 | + : OpenvSwitch patch ports. Each port is declared as a pair of names |
966 | + which can be referenced as interfaces in dependent virtual devices |
967 | + (bonds, bridges). |
968 | + |
969 | + Example: |
970 | + |
971 | + openvswitch: |
972 | + ports: |
973 | + - [patch0-1, patch1-0] |
974 | + |
975 | + ``ssl`` (mapping) – since **0.100** |
976 | + : Valid for global ``openvswitch`` settings. Options for configuring SSL |
977 | + server endpoint for the switch. |
978 | + |
979 | + ``ca-cert`` (scalar) |
980 | + : Path to a file containing the CA certificate to be used. |
981 | + |
982 | + ``certificate`` (scalar) |
983 | + : Path to a file containing the server certificate. |
984 | + |
985 | + ``private-key`` (scalar) |
986 | + : Path to a file containing the private key for the server. |
987 | |
988 | ## Common properties for all device types |
989 | |
990 | @@ -144,13 +214,13 @@ Virtual devices |
991 | |
992 | : Use the given networking backend for this definition. Currently supported are |
993 | ``networkd`` and ``NetworkManager``. This property can be specified globally |
994 | - in ``networks:``, for a device type (in e. g. ``ethernets:``) or |
995 | + in ``network:``, for a device type (in e. g. ``ethernets:``) or |
996 | for a particular device definition. Default is ``networkd``. |
997 | |
998 | - The ``renderer`` property has one additional acceptable value for vlan objects |
999 | - (i. e. defined in ``vlans:``): ``sriov``. If a vlan is defined with the ``sriov`` |
1000 | - renderer for an SR-IOV Virtual Function interface, this causes netplan to set |
1001 | - up a hardware VLAN filter for it. There can be only one defined per VF. |
1002 | + (Since 0.99) The ``renderer`` property has one additional acceptable value for vlan |
1003 | + objects (i. e. defined in ``vlans:``): ``sriov``. If a vlan is defined with the |
1004 | + ``sriov`` renderer for an SR-IOV Virtual Function interface, this causes netplan to |
1005 | + set up a hardware VLAN filter for it. There can be only one defined per VF. |
1006 | |
1007 | ``dhcp4`` (bool) |
1008 | |
1009 | @@ -172,7 +242,7 @@ Virtual devices |
1010 | Note that **``rdnssd``**(8) is required to use RDNSS with networkd. No extra |
1011 | software is required for NetworkManager. |
1012 | |
1013 | -``ipv6-mtu`` (scalar) |
1014 | +``ipv6-mtu`` (scalar) – since **0.98** |
1015 | : Set the IPv6 MTU (only supported with `networkd` backend). Note |
1016 | that needing to set this is an unusual requirement. |
1017 | |
1018 | @@ -231,7 +301,7 @@ Virtual devices |
1019 | When enabled, accept Router Advertisements. When disabled, do not respond to |
1020 | Router Advertisements. If unset use the host kernel default setting. |
1021 | |
1022 | -``addresses`` (sequence of scalars) |
1023 | +``addresses`` (sequence of scalars and mappings) |
1024 | |
1025 | : Add static addresses to the interface in addition to the ones received |
1026 | through DHCP or RA. Each sequence entry is in CIDR notation, i. e. of the |
1027 | @@ -242,13 +312,41 @@ Virtual devices |
1028 | configured and DHCP is disabled, the interface may still be brought online, |
1029 | but will not be addressable from the network. |
1030 | |
1031 | + In addition to the addresses themselves one can specify configuration |
1032 | + parameters as mappings. Current supported options are: |
1033 | + |
1034 | + ``lifetime`` (scalar) – since **0.100** |
1035 | + : Default: ``forever``. This can be ``forever`` or ``0`` and corresponds |
1036 | + to the ``PreferredLifetime`` option in ``systemd-networkd``'s Address |
1037 | + section. Currently supported on the ``networkd`` backend only. |
1038 | + |
1039 | + ``label`` (scalar) – since **0.100** |
1040 | + : An IP address label, equivalent to the ``ip address label`` |
1041 | + command. Currently supported on the ``networkd`` backend only. |
1042 | + |
1043 | Example: ``addresses: [192.168.14.2/24, "2001:1::1/64"]`` |
1044 | |
1045 | -``ipv6-address-generation`` (scalar) |
1046 | + Example: |
1047 | + |
1048 | + ethernets: |
1049 | + eth0: |
1050 | + addresses: |
1051 | + - 10.0.0.15/24: |
1052 | + lifetime: 0 |
1053 | + label: "maas" |
1054 | + - "2001:1::1/64" |
1055 | + |
1056 | +``ipv6-address-generation`` (scalar) – since **0.99** |
1057 | |
1058 | : Configure method for creating the address for use with RFC4862 IPv6 |
1059 | - Stateless Address Autoconfiguration. Possible values are ``eui64`` |
1060 | - or ``stable-privacy``. |
1061 | + Stateless Address Autoconfiguration (only supported with `NetworkManager` |
1062 | + backend). Possible values are ``eui64`` or ``stable-privacy``. |
1063 | + |
1064 | +``ipv6-address-token`` (scalar) – since **0.100** |
1065 | + |
1066 | +: Define an IPv6 address token for creating a static interface identifier for |
1067 | + IPv6 Stateless Address Autoconfiguration. This is mutually exclusive with |
1068 | + ``ipv6-address-generation``. |
1069 | |
1070 | ``gateway4``, ``gateway6`` (scalar) |
1071 | |
1072 | @@ -333,11 +431,11 @@ similar to ``gateway*``, and ``search:`` is a list of search domains. |
1073 | dhcp6: true |
1074 | optional-addresses: [ ipv4-ll, dhcp6 ] |
1075 | |
1076 | -``routes`` (mapping) |
1077 | +``routes`` (sequence of mappings) |
1078 | |
1079 | : Configure static routing for the device; see the ``Routing`` section below. |
1080 | |
1081 | -``routing-policy`` (mapping) |
1082 | +``routing-policy`` (sequence of mappings) |
1083 | |
1084 | : Configure policy routing for the device; see the ``Routing`` section below. |
1085 | |
1086 | @@ -358,6 +456,8 @@ When using the NetworkManager backend, different values may be specified for |
1087 | ``dhcp4-overrides`` and ``dhcp6-overrides``, and will be applied to the DHCP |
1088 | client processes as specified in the netplan YAML. |
1089 | |
1090 | +``dhcp4-overrides``, ``dhcp6-overrides`` (mapping) |
1091 | + |
1092 | : The ``dhcp4-overrides`` and ``dhcp6-overrides`` mappings override the |
1093 | default DHCP behavior. |
1094 | |
1095 | @@ -405,15 +505,15 @@ client processes as specified in the netplan YAML. |
1096 | |
1097 | ``route-metric`` (scalar) |
1098 | : Use this value for default metric for automatically-added routes. |
1099 | - Use this to prioritize routes for devices by setting a higher metric |
1100 | + Use this to prioritize routes for devices by setting a lower metric |
1101 | on a preferred interface. Available for both the ``networkd`` and |
1102 | ``NetworkManager`` backends. |
1103 | |
1104 | - ``use-domains`` (scalar) |
1105 | - : Takes a boolean, or the special value "route". When true, the domain |
1106 | + ``use-domains`` (scalar) – since **0.98** |
1107 | + : Takes a boolean, or the special value "route". When true, the domain |
1108 | name received from the DHCP server will be used as DNS search domain |
1109 | over this link, similar to the effect of the Domains= setting. If set |
1110 | - to "route", the domain name received from the DHCP server will be |
1111 | + to "route", the domain name received from the DHCP server will be |
1112 | used for routing DNS queries only, but not for searching, similar to |
1113 | the effect of the Domains= setting when the argument is prefixed with |
1114 | "~". |
1115 | @@ -438,6 +538,7 @@ These options are available for all types of interfaces. |
1116 | |
1117 | ``from`` (scalar) |
1118 | : Set a source IP address for traffic going through the route. |
1119 | + (``NetworkManager``: as of v1.8.0) |
1120 | |
1121 | ``to`` (scalar) |
1122 | : Destination address for the route. |
1123 | @@ -448,6 +549,7 @@ These options are available for all types of interfaces. |
1124 | ``on-link`` (bool) |
1125 | : When set to "true", specifies that the route is directly connected |
1126 | to the interface. |
1127 | + (``NetworkManager``: as of v1.12.0 for IPv4 and v1.18.0 for IPv6) |
1128 | |
1129 | ``metric`` (scalar) |
1130 | : The relative priority of the route. Must be a positive integer value. |
1131 | @@ -458,7 +560,8 @@ These options are available for all types of interfaces. |
1132 | |
1133 | ``scope`` (scalar) |
1134 | : The route scope, how wide-ranging it is to the network. Possible |
1135 | - values are "global", "link", or "host". |
1136 | + values are "global", "link", or "host". ``NetworkManager`` does |
1137 | + not support setting a scope. |
1138 | |
1139 | ``table`` (scalar) |
1140 | : The table number to use for the route. In some scenarios, it may be |
1141 | @@ -467,6 +570,7 @@ These options are available for all types of interfaces. |
1142 | parameter. Allowed values are positive integers starting from 1. |
1143 | Some values are already in use to refer to specific routing tables: |
1144 | see ``/etc/iproute2/rt_tables``. |
1145 | + (``NetworkManager``: as of v1.10.0) |
1146 | |
1147 | ``routing-policy`` (mapping) |
1148 | |
1149 | @@ -555,7 +659,7 @@ interfaces, as well as individual wifi networks, by means of the ``auth`` block. |
1150 | : Password to use to decrypt the private key specified in |
1151 | ``client-key`` if it is encrypted. |
1152 | |
1153 | - ``phase2-auth`` (scalar) |
1154 | + ``phase2-auth`` (scalar) – since **0.99** |
1155 | : Phase 2 authentication mechanism. |
1156 | |
1157 | |
1158 | @@ -563,7 +667,7 @@ interfaces, as well as individual wifi networks, by means of the ``auth`` block. |
1159 | Ethernet device definitions, beyond common ones described above, also support |
1160 | some additional properties that can be used for SR-IOV devices. |
1161 | |
1162 | -``link`` (scalar) |
1163 | +``link`` (scalar) – since **0.99** |
1164 | |
1165 | : (SR-IOV devices only) The ``link`` property declares the device as a |
1166 | Virtual Function of the selected Physical Function device, as identified |
1167 | @@ -576,7 +680,7 @@ Example: |
1168 | enp1s16f1: |
1169 | link: enp1 |
1170 | |
1171 | -``virtual-function-count`` (scalar) |
1172 | +``virtual-function-count`` (scalar) – since **0.99** |
1173 | |
1174 | : (SR-IOV devices only) In certain special cases VFs might need to be |
1175 | configured outside of netplan. For such configurations ``virtual-function-count`` |
1176 | @@ -585,47 +689,67 @@ Example: |
1177 | VFs as are defined in the netplan configuration. This should be used for special |
1178 | cases only. |
1179 | |
1180 | + **Requires feature: sriov** |
1181 | + |
1182 | ## Properties for device type ``modems:`` |
1183 | -GSM/CDMA modem configuration is only supported for the ``NetworkManager`` backend. ``systemd-networkd`` does |
1184 | -not support modems. |
1185 | +GSM/CDMA modem configuration is only supported for the ``NetworkManager`` |
1186 | +backend. ``systemd-networkd`` does not support modems. |
1187 | |
1188 | -``apn`` (scalar) |
1189 | -: Set the carrier APN (Access Point Name). This can be omitted if ``auto-config`` is enabled. |
1190 | +**Requires feature: modems** |
1191 | |
1192 | -``auto-config`` (bool) |
1193 | -: Specify whether to try and autoconfigure the modem by doing a lookup of the carrier |
1194 | - against the Mobile Broadband Provider database. This may not work for all carriers. |
1195 | +``apn`` (scalar) – since **0.99** |
1196 | |
1197 | -``device-id`` (scalar) |
1198 | -: Specify the device ID (as given by the WWAN management service) of the modem to match. |
1199 | - This can be found using ``mmcli``. |
1200 | +: Set the carrier APN (Access Point Name). This can be omitted if |
1201 | + ``auto-config`` is enabled. |
1202 | + |
1203 | +``auto-config`` (bool) – since **0.99** |
1204 | + |
1205 | +: Specify whether to try and autoconfigure the modem by doing a lookup of |
1206 | + the carrier against the Mobile Broadband Provider database. This may not |
1207 | + work for all carriers. |
1208 | + |
1209 | +``device-id`` (scalar) – since **0.99** |
1210 | + |
1211 | +: Specify the device ID (as given by the WWAN management service) of the |
1212 | + modem to match. This can be found using ``mmcli``. |
1213 | + |
1214 | +``network-id`` (scalar) – since **0.99** |
1215 | + |
1216 | +: Specify the Network ID (GSM LAI format). If this is specified, the device |
1217 | + will not roam networks. |
1218 | + |
1219 | +``number`` (scalar) – since **0.99** |
1220 | + |
1221 | +: The number to dial to establish the connection to the mobile broadband |
1222 | + network. (Deprecated for GSM) |
1223 | |
1224 | -``network-id`` (scalar) |
1225 | -: Specify the Network ID (GSM LAI format). If this is specified, the device will not roam networks. |
1226 | +``password`` (scalar) – since **0.99** |
1227 | |
1228 | -``number`` (scalar) |
1229 | -: The number to dial to establish the connection to the mobile broadband network. (Deprecated for GSM) |
1230 | +: Specify the password used to authenticate with the carrier network. This |
1231 | + can be omitted if ``auto-config`` is enabled. |
1232 | |
1233 | -``password`` (scalar) |
1234 | -: Specify the password used to authenticate with the carrier network. This can be omitted |
1235 | - if ``auto-config`` is enabled. |
1236 | +``pin`` (scalar) – since **0.99** |
1237 | |
1238 | -``pin`` (scalar) |
1239 | : Specify the SIM PIN to allow it to operate if a PIN is set. |
1240 | |
1241 | -``sim-id`` (scalar) |
1242 | -: Specify the SIM unique identifier (as given by the WWAN management service) which this |
1243 | - connection applies to. If given, the connection will apply to any device also allowed by |
1244 | - ``device-id`` which contains a SIM card matching the given identifier. |
1245 | +``sim-id`` (scalar) – since **0.99** |
1246 | |
1247 | -``sim-operator-id`` (scalar) |
1248 | -: Specify the MCC/MNC string (such as "310260" or "21601") which identifies the carrier that |
1249 | - this connection should apply to. If given, the connection will apply to any device also |
1250 | - allowed by ``device-id`` and ``sim-id`` which contains a SIM card provisioned by the given operator. |
1251 | +: Specify the SIM unique identifier (as given by the WWAN management service) |
1252 | + which this connection applies to. If given, the connection will apply to |
1253 | + any device also allowed by ``device-id`` which contains a SIM card matching |
1254 | + the given identifier. |
1255 | |
1256 | -``username`` (scalar) |
1257 | -: Specify the username used to authentiate with the carrier network. This can be omitted if |
1258 | - ``auto-config`` is enabled. |
1259 | +``sim-operator-id`` (scalar) – since **0.99** |
1260 | + |
1261 | +: Specify the MCC/MNC string (such as "310260" or "21601") which identifies |
1262 | + the carrier that this connection should apply to. If given, the connection |
1263 | + will apply to any device also allowed by ``device-id`` and ``sim-id`` |
1264 | + which contains a SIM card provisioned by the given operator. |
1265 | + |
1266 | +``username`` (scalar) – since **0.99** |
1267 | + |
1268 | +: Specify the username used to authentiate with the carrier network. This |
1269 | + can be omitted if ``auto-config`` is enabled. |
1270 | |
1271 | ## Properties for device type ``wifis:`` |
1272 | Note that ``systemd-networkd`` does not natively support wifi, so you need |
1273 | @@ -657,21 +781,27 @@ wpasupplicant installed if you let the ``networkd`` renderer handle wifi. |
1274 | and ``adhoc`` (peer to peer networks without a central access point). |
1275 | ``ap`` is only supported with NetworkManager. |
1276 | |
1277 | - ``bssid`` (scalar) |
1278 | + ``bssid`` (scalar) – since **0.99** |
1279 | : If specified, directs the device to only associate with the given |
1280 | access point. |
1281 | |
1282 | - ``band`` (scalar) |
1283 | + ``band`` (scalar) – since **0.99** |
1284 | : Possible bands are ``5GHz`` (for 5GHz 802.11a) and ``2.4GHz`` |
1285 | (for 2.4GHz 802.11), do not restrict the 802.11 frequency band of the |
1286 | network if unset (the default). |
1287 | |
1288 | - ``channel`` (scalar) |
1289 | + ``channel`` (scalar) – since **0.99** |
1290 | : Wireless channel to use for the Wi-Fi connection. Because channel |
1291 | numbers overlap between bands, this property takes effect only if |
1292 | the ``band`` property is also set. |
1293 | |
1294 | -``wakeonwlan`` (sequence of scalars) |
1295 | + ``hidden`` (bool) – since **0.100** |
1296 | + : Set to ``true`` to change the SSID scan technique for connecting to |
1297 | + hidden WiFi networks. Note this may have slower performance compared |
1298 | + to ``false`` (the default) when connecting to publicly broadcast |
1299 | + SSIDs. |
1300 | + |
1301 | +``wakeonwlan`` (sequence of scalars) – since **0.99** |
1302 | |
1303 | : This enables WakeOnWLan on supported devices. Not all drivers support all |
1304 | options. May be any combination of ``any``, ``disconnect``, ``magic_pkt``, |
1305 | @@ -783,6 +913,8 @@ wpasupplicant installed if you let the ``networkd`` renderer handle wifi. |
1306 | ``balance-rr`` (round robin). Possible values are ``balance-rr``, |
1307 | ``active-backup``, ``balance-xor``, ``broadcast``, ``802.3ad``, |
1308 | ``balance-tlb``, and ``balance-alb``. |
1309 | + For OpenVSwitch ``active-backup`` and the additional modes |
1310 | + ``balance-tcp`` and ``balance-slb`` are supported. |
1311 | |
1312 | ``lacp-rate`` (scalar) |
1313 | : Set the rate at which LACPDUs are transmitted. This is only useful |
1314 | @@ -923,8 +1055,9 @@ more general information about tunnels. |
1315 | ``mode`` (scalar) |
1316 | |
1317 | : Defines the tunnel mode. Valid options are ``sit``, ``gre``, ``ip6gre``, |
1318 | - ``ipip``, ``ipip6``, ``ip6ip6``, ``vti``, and ``vti6``. Additionally, |
1319 | - the ``networkd`` backend also supports ``gretap`` and ``ip6gretap`` modes. |
1320 | + ``ipip``, ``ipip6``, ``ip6ip6``, ``vti``, ``vti6`` and ``wireguard``. |
1321 | + Additionally, the ``networkd`` backend also supports ``gretap`` and |
1322 | + ``ip6gretap`` modes. |
1323 | In addition, the ``NetworkManager`` backend supports ``isatap`` tunnels. |
1324 | |
1325 | ``local`` (scalar) |
1326 | @@ -938,14 +1071,16 @@ more general information about tunnels. |
1327 | ``key`` (scalar or mapping) |
1328 | |
1329 | : Define keys to use for the tunnel. The key can be a number or a dotted |
1330 | - quad (an IPv4 address). It is used for identification of IP transforms. |
1331 | - This is only required for ``vti`` and ``vti6`` when using the networkd |
1332 | - backend, and for ``gre`` or ``ip6gre`` tunnels when using the |
1333 | - NetworkManager backend. |
1334 | + quad (an IPv4 address). For ``wireguard`` it can be a base64-encoded |
1335 | + private key or (as of ``networkd`` v242+) an absolute path to a file, |
1336 | + containing the private key (since 0.100). |
1337 | + It is used for identification of IP transforms. This is only required |
1338 | + for ``vti`` and ``vti6`` when using the networkd backend, and for |
1339 | + ``gre`` or ``ip6gre`` tunnels when using the NetworkManager backend. |
1340 | |
1341 | This field may be used as a scalar (meaning that a single key is |
1342 | - specified and to be used for both input and output key), or as a mapping, |
1343 | - where you can then further specify ``input`` and ``output``. |
1344 | + specified and to be used for input, output and private key), or as a |
1345 | + mapping, where you can further specify ``input``/``output``/``private``. |
1346 | |
1347 | ``input`` (scalar) |
1348 | : The input key for the tunnel |
1349 | @@ -953,6 +1088,15 @@ more general information about tunnels. |
1350 | ``output`` (scalar) |
1351 | : The output key for the tunnel |
1352 | |
1353 | + ``private`` (scalar) – since **0.100** |
1354 | + : A base64-encoded private key required for Wireguard tunnels. When the |
1355 | + ``systemd-networkd`` backend (v242+) is used, this can also be an |
1356 | + absolute path to a file containing the private key. |
1357 | + |
1358 | +``keys`` (scalar or mapping) |
1359 | + |
1360 | +: Alternate name for the ``key`` field. See above. |
1361 | + |
1362 | Examples: |
1363 | |
1364 | tunnels: |
1365 | @@ -971,10 +1115,91 @@ Examples: |
1366 | remote: ... |
1367 | key: 59568549 |
1368 | |
1369 | -``keys`` (scalar or mapping) |
1370 | + tunnels: |
1371 | + wg0: |
1372 | + mode: wireguard |
1373 | + addresses: [...] |
1374 | + peers: |
1375 | + - keys: |
1376 | + public: rlbInAj0qV69CysWPQY7KEBnKxpYCpaWqOs/dLevdWc= |
1377 | + shared: /path/to/shared.key |
1378 | + ... |
1379 | + key: mNb7OIIXTdgW4khM7OFlzJ+UPs7lmcWHV7xjPgakMkQ= |
1380 | + |
1381 | + tunnels: |
1382 | + wg0: |
1383 | + mode: wireguard |
1384 | + addresses: [...] |
1385 | + peers: |
1386 | + - keys: |
1387 | + public: rlbInAj0qV69CysWPQY7KEBnKxpYCpaWqOs/dLevdWc= |
1388 | + ... |
1389 | + keys: |
1390 | + private: /path/to/priv.key |
1391 | |
1392 | -: Alternate name for the ``key`` field. See above. |
1393 | |
1394 | +Wireguard specific keys: |
1395 | + |
1396 | + ``mark`` (scalar) – since **0.100** |
1397 | + : Firewall mark for outgoing WireGuard packets from this interface, |
1398 | + optional. |
1399 | + |
1400 | + ``port`` (scalar) – since **0.100** |
1401 | + : UDP port to listen at or ``auto``. Optional, defaults to ``auto``. |
1402 | + |
1403 | + ``peers`` (sequence of mappings) – since **0.100** |
1404 | + : A list of peers, each having keys documented below. |
1405 | + |
1406 | + Example: |
1407 | + |
1408 | + tunnels: |
1409 | + wg0: |
1410 | + mode: wireguard |
1411 | + key: /path/to/private.key |
1412 | + mark: 42 |
1413 | + port: 5182 |
1414 | + peers: |
1415 | + - keys: |
1416 | + public: rlbInAj0qV69CysWPQY7KEBnKxpYCpaWqOs/dLevdWc= |
1417 | + allowed-ips: [0.0.0.0/0, "2001:fe:ad:de:ad:be:ef:1/24"] |
1418 | + keepalive: 23 |
1419 | + endpoint: 1.2.3.4:5 |
1420 | + - keys: |
1421 | + public: M9nt4YujIOmNrRmpIRTmYSfMdrpvE7u6WkG8FY8WjG4= |
1422 | + shared: /some/shared.key |
1423 | + allowed-ips: [10.10.10.20/24] |
1424 | + keepalive: 22 |
1425 | + endpoint: 5.4.3.2:1 |
1426 | + |
1427 | + ``endpoint`` (scalar) – since **0.100** |
1428 | + : Remote endpoint IPv4/IPv6 address or a hostname, followed by a colon |
1429 | + and a port number. |
1430 | + |
1431 | + ``allowed-ips`` (sequence of scalars) – since **0.100** |
1432 | + : A list of IP (v4 or v6) addresses with CIDR masks from which this peer |
1433 | + is allowed to send incoming traffic and to which outgoing traffic for |
1434 | + this peer is directed. The catch-all 0.0.0.0/0 may be specified for |
1435 | + matching all IPv4 addresses, and ::/0 may be specified for matching |
1436 | + all IPv6 addresses. |
1437 | + |
1438 | + ``keepalive`` (scalar) – since **0.100** |
1439 | + : An interval in seconds, between 1 and 65535 inclusive, of how often to |
1440 | + send an authenticated empty packet to the peer for the purpose of |
1441 | + keeping a stateful firewall or NAT mapping valid persistently. Optional. |
1442 | + |
1443 | + ``keys`` (mapping) – since **0.100** |
1444 | + : Define keys to use for the Wireguard peers. |
1445 | + |
1446 | + This field can be used as a mapping, where you can further specify the |
1447 | + ``public`` and ``shared`` keys. |
1448 | + |
1449 | + ``public`` (scalar) – since **0.100** |
1450 | + : A base64-encoded public key, requried for Wireguard peers. |
1451 | + |
1452 | + ``shared`` (scalar) – since **0.100** |
1453 | + : A base64-encoded preshared key. Optional for Wireguard peers. |
1454 | + When the ``systemd-networkd`` backend (v242+) is used, this can |
1455 | + also be an absolute path to a file containing the preshared key. |
1456 | |
1457 | ## Properties for device type ``vlans:`` |
1458 | |
1459 | @@ -1009,24 +1234,24 @@ backends may require to record some of their own parameters in netplan, |
1460 | especially if the netplan definitions are generated automatically by the |
1461 | consumer of that backend. Currently, this is only used with ``NetworkManager``. |
1462 | |
1463 | -``networkmanager`` (mapping) |
1464 | +``networkmanager`` (mapping) – since **0.99** |
1465 | |
1466 | : Keeps the NetworkManager-specific configuration parameters used by the |
1467 | daemon to recognize connections. |
1468 | |
1469 | - ``name`` (scalar) |
1470 | + ``name`` (scalar) – since **0.99** |
1471 | : Set the display name for the connection. |
1472 | |
1473 | - ``uuid`` (scalar) |
1474 | + ``uuid`` (scalar) – since **0.99** |
1475 | : Defines the UUID (unique identifier) for this connection, as |
1476 | generated by NetworkManager itself. |
1477 | |
1478 | - ``stable-id`` (scalar) |
1479 | + ``stable-id`` (scalar) – since **0.99** |
1480 | : Defines the stable ID (a different form of a connection name) used |
1481 | by NetworkManager in case the name of the connection might otherwise |
1482 | change, such as when sharing connections between users. |
1483 | |
1484 | - ``device`` (scalar) |
1485 | + ``device`` (scalar) – since **0.99** |
1486 | : Defines the interface name for which this connection applies. |
1487 | |
1488 | |
1489 | @@ -1114,7 +1339,6 @@ This is a complex example which shows most available features: |
1490 | switchports: |
1491 | # all cards on second PCI bus unconfigured by |
1492 | # themselves, will be added to br0 below |
1493 | - # note: globbing is not supported by NetworkManager |
1494 | match: |
1495 | name: enp2* |
1496 | mtu: 1280 |
1497 | diff --git a/examples/sriov.yaml b/examples/sriov.yaml |
1498 | new file mode 100644 |
1499 | index 0000000..67de132 |
1500 | --- /dev/null |
1501 | +++ b/examples/sriov.yaml |
1502 | @@ -0,0 +1,14 @@ |
1503 | +network: |
1504 | + version: 2 |
1505 | + renderer: networkd |
1506 | + ethernets: |
1507 | + eno1: |
1508 | + mtu: 9000 |
1509 | + enp1s16f1: |
1510 | + link: eno1 |
1511 | + addresses : [ "10.15.98.25/24" ] |
1512 | + vf1: |
1513 | + match: |
1514 | + name: enp1s16f[2-3] |
1515 | + link: eno1 |
1516 | + addresses : [ "10.15.99.25/24" ] |
1517 | diff --git a/examples/sriov_vlan.yaml b/examples/sriov_vlan.yaml |
1518 | new file mode 100644 |
1519 | index 0000000..2c664d7 |
1520 | --- /dev/null |
1521 | +++ b/examples/sriov_vlan.yaml |
1522 | @@ -0,0 +1,18 @@ |
1523 | +network: |
1524 | + version: 2 |
1525 | + renderer: networkd |
1526 | + ethernets: |
1527 | + eno1: |
1528 | + mtu: 9000 |
1529 | + enp1s16f1: |
1530 | + link: eno1 |
1531 | + addresses : [ "10.15.98.25/24" ] |
1532 | + vlans: |
1533 | + vlan1: |
1534 | + id: 15 |
1535 | + link: enp1s16f1 |
1536 | + addresses: [ "10.3.99.5/24" ] |
1537 | + vlan2_hw: |
1538 | + id: 10 |
1539 | + link: enp1s16f1 |
1540 | + renderer: sriov |
1541 | diff --git a/examples/static.yaml b/examples/static.yaml |
1542 | index e618909..977710b 100644 |
1543 | --- a/examples/static.yaml |
1544 | +++ b/examples/static.yaml |
1545 | @@ -7,5 +7,5 @@ network: |
1546 | - 10.10.10.2/24 |
1547 | gateway4: 10.10.10.1 |
1548 | nameservers: |
1549 | - search: [mydomain,otherdomain] |
1550 | - addresses: [10.10.10.1, 1.1.1.1] |
1551 | + search: [mydomain, otherdomain] |
1552 | + addresses: [10.10.10.1, 1.1.1.1] |
1553 | diff --git a/netplan/cli/commands/apply.py b/netplan/cli/commands/apply.py |
1554 | index 0ec95f5..fb5a6ad 100644 |
1555 | --- a/netplan/cli/commands/apply.py |
1556 | +++ b/netplan/cli/commands/apply.py |
1557 | @@ -3,6 +3,7 @@ |
1558 | # Copyright (C) 2018-2020 Canonical, Ltd. |
1559 | # Author: Mathieu Trudel-Lapierre <mathieu.trudel-lapierre@canonical.com> |
1560 | # Author: Łukasz 'sil2100' Zemczak <lukasz.zemczak@canonical.com> |
1561 | +# Author: Lukas 'slyon' Märdian <lukas.maerdian@canonical.com> |
1562 | # |
1563 | # This program is free software; you can redistribute it and/or modify |
1564 | # it under the terms of the GNU General Public License as published by |
1565 | @@ -28,6 +29,7 @@ import shutil |
1566 | import netplan.cli.utils as utils |
1567 | from netplan.configmanager import ConfigManager, ConfigurationError |
1568 | from netplan.cli.sriov import apply_sriov_config |
1569 | +from netplan.cli.ovs import apply_ovs_cleanup |
1570 | |
1571 | import netifaces |
1572 | |
1573 | @@ -38,15 +40,33 @@ class NetplanApply(utils.NetplanCommand): |
1574 | super().__init__(command_id='apply', |
1575 | description='Apply current netplan config to running system', |
1576 | leaf=True) |
1577 | + self.sriov_only = False |
1578 | + self.only_ovs_cleanup = False |
1579 | |
1580 | def run(self): # pragma: nocover (covered in autopkgtest) |
1581 | - self.func = NetplanApply.command_apply |
1582 | + self.parser.add_argument('--sriov-only', action='store_true', |
1583 | + help='Only apply SR-IOV related configuration and exit') |
1584 | + self.parser.add_argument('--only-ovs-cleanup', action='store_true', |
1585 | + help='Only clean up old OpenVSwitch interfaces and exit') |
1586 | + |
1587 | + self.func = self.command_apply |
1588 | |
1589 | self.parse_args() |
1590 | self.run_command() |
1591 | |
1592 | - @staticmethod |
1593 | - def command_apply(run_generate=True, sync=False, exit_on_error=True): # pragma: nocover (covered in autopkgtest) |
1594 | + def command_apply(self, run_generate=True, sync=False, exit_on_error=True): # pragma: nocover (covered in autopkgtest) |
1595 | + config_manager = ConfigManager() |
1596 | + |
1597 | + # For certain use-cases, we might want to only apply specific configuration. |
1598 | + # If we only need SR-IOV configuration, do that and exit early. |
1599 | + if self.sriov_only: |
1600 | + NetplanApply.process_sriov_config(config_manager, exit_on_error) |
1601 | + return |
1602 | + # If we only need OpenVSwitch cleanup, do that and exit early. |
1603 | + elif self.only_ovs_cleanup: |
1604 | + NetplanApply.process_ovs_cleanup(config_manager, False, False, exit_on_error) |
1605 | + return |
1606 | + |
1607 | # if we are inside a snap, then call dbus to run netplan apply instead |
1608 | if "SNAP" in os.environ: |
1609 | # TODO: maybe check if we are inside a classic snap and don't do |
1610 | @@ -73,8 +93,16 @@ class NetplanApply(utils.NetplanCommand): |
1611 | else: |
1612 | return |
1613 | |
1614 | + ovs_cleanup_service = '/run/systemd/system/netplan-ovs-cleanup.service' |
1615 | old_files_networkd = bool(glob.glob('/run/systemd/network/*netplan-*')) |
1616 | - old_files_nm = bool(glob.glob('/run/NetworkManager/system-connections/netplan-*')) |
1617 | + old_ovs_glob = glob.glob('/run/systemd/system/netplan-ovs-*') |
1618 | + # Ignore netplan-ovs-cleanup.service, as it can always be there |
1619 | + if ovs_cleanup_service in old_ovs_glob: |
1620 | + old_ovs_glob.remove(ovs_cleanup_service) |
1621 | + old_files_ovs = bool(old_ovs_glob) |
1622 | + old_nm_glob = glob.glob('/run/NetworkManager/system-connections/netplan-*') |
1623 | + nm_ifaces = utils.nm_interfaces(old_nm_glob, netifaces.interfaces()) |
1624 | + old_files_nm = bool(old_nm_glob) |
1625 | |
1626 | generator_call = [] |
1627 | generate_out = None |
1628 | @@ -89,7 +117,6 @@ class NetplanApply(utils.NetplanCommand): |
1629 | else: |
1630 | raise ConfigurationError("the configuration could not be generated") |
1631 | |
1632 | - config_manager = ConfigManager() |
1633 | devices = netifaces.interfaces() |
1634 | |
1635 | # Re-start service when |
1636 | @@ -101,14 +128,36 @@ class NetplanApply(utils.NetplanCommand): |
1637 | restart_networkd = bool(glob.glob('/run/systemd/network/*netplan-*')) |
1638 | if not restart_networkd and old_files_networkd: |
1639 | restart_networkd = True |
1640 | - restart_nm = bool(glob.glob('/run/NetworkManager/system-connections/netplan-*')) |
1641 | + restart_ovs_glob = glob.glob('/run/systemd/system/netplan-ovs-*') |
1642 | + # Ignore netplan-ovs-cleanup.service, as it can always be there |
1643 | + if ovs_cleanup_service in restart_ovs_glob: |
1644 | + restart_ovs_glob.remove(ovs_cleanup_service) |
1645 | + restart_ovs = bool(restart_ovs_glob) |
1646 | + if not restart_ovs and old_files_ovs: |
1647 | + # OVS is managed via systemd units |
1648 | + restart_networkd = True |
1649 | + |
1650 | + restart_nm_glob = glob.glob('/run/NetworkManager/system-connections/netplan-*') |
1651 | + nm_ifaces.update(utils.nm_interfaces(restart_nm_glob, devices)) |
1652 | + restart_nm = bool(restart_nm_glob) |
1653 | if not restart_nm and old_files_nm: |
1654 | restart_nm = True |
1655 | |
1656 | # stop backends |
1657 | if restart_networkd: |
1658 | logging.debug('netplan generated networkd configuration changed, restarting networkd') |
1659 | - utils.systemctl_networkd('stop', sync=sync, extra_services=['netplan-wpa@*.service']) |
1660 | + # Running 'systemctl daemon-reload' will re-run the netplan systemd generator, |
1661 | + # so let's make sure we only run it iff we're willing to run 'netplan generate' |
1662 | + if run_generate: |
1663 | + utils.systemctl_daemon_reload() |
1664 | + # Clean up any old netplan related OVS ports/bonds/bridges, if applicable |
1665 | + NetplanApply.process_ovs_cleanup(config_manager, old_files_ovs, restart_ovs, exit_on_error) |
1666 | + wpa_services = ['netplan-wpa-*.service'] |
1667 | + # Historically (up to v0.98) we had netplan-wpa@*.service files, in case of an |
1668 | + # upgraded system, we need to make sure to stop those. |
1669 | + if utils.systemctl_is_active('netplan-wpa@*.service'): |
1670 | + wpa_services.insert(0, 'netplan-wpa@*.service') |
1671 | + utils.systemctl_networkd('stop', sync=sync, extra_services=wpa_services) |
1672 | else: |
1673 | logging.debug('no netplan generated networkd configuration exists') |
1674 | |
1675 | @@ -117,6 +166,8 @@ class NetplanApply(utils.NetplanCommand): |
1676 | if utils.nm_running(): |
1677 | # restarting NM does not cause new config to be applied, need to shut down devices first |
1678 | for device in devices: |
1679 | + if device not in nm_ifaces: |
1680 | + continue # do not touch this interface |
1681 | # ignore failures here -- some/many devices might not be managed by NM |
1682 | try: |
1683 | utils.nmcli(['device', 'disconnect', device]) |
1684 | @@ -135,14 +186,6 @@ class NetplanApply(utils.NetplanCommand): |
1685 | config_manager.parse() |
1686 | changes = NetplanApply.process_link_changes(devices, config_manager) |
1687 | |
1688 | - # apply any SR-IOV related changes, if applicable |
1689 | - try: |
1690 | - apply_sriov_config(devices, config_manager) |
1691 | - except (ConfigurationError, RuntimeError) as e: |
1692 | - logging.error(str(e)) |
1693 | - if exit_on_error: |
1694 | - sys.exit(1) |
1695 | - |
1696 | # if the interface is up, we can still apply some .link file changes |
1697 | devices = netifaces.interfaces() |
1698 | for device in devices: |
1699 | @@ -167,11 +210,21 @@ class NetplanApply(utils.NetplanCommand): |
1700 | |
1701 | subprocess.check_call(['udevadm', 'settle']) |
1702 | |
1703 | + # apply any SR-IOV related changes, if applicable |
1704 | + NetplanApply.process_sriov_config(config_manager, exit_on_error) |
1705 | + |
1706 | # (re)start backends |
1707 | if restart_networkd: |
1708 | - netplan_wpa = [os.path.basename(f) for f in glob.glob('/run/systemd/system/*.wants/netplan-wpa@*.service')] |
1709 | - utils.systemctl_networkd('start', sync=sync, extra_services=netplan_wpa) |
1710 | + netplan_wpa = [os.path.basename(f) for f in glob.glob('/run/systemd/system/*.wants/netplan-wpa-*.service')] |
1711 | + netplan_ovs = [os.path.basename(f) for f in glob.glob('/run/systemd/system/*.wants/netplan-ovs-*.service')] |
1712 | + # Run 'systemctl start' command synchronously, to avoid race conditions |
1713 | + # with 'oneshot' systemd service units, e.g. netplan-ovs-*.service. |
1714 | + utils.systemctl_networkd('start', sync=True, extra_services=netplan_wpa + netplan_ovs) |
1715 | if restart_nm: |
1716 | + # Flush all IP addresses of NM managed interfaces, to avoid NM creating |
1717 | + # new, non netplan-* connection profiles, using the existing IPs. |
1718 | + for iface in utils.nm_interfaces(restart_nm_glob, devices): |
1719 | + utils.ip_addr_flush(iface) |
1720 | utils.systemctl_network_manager('start', sync=sync) |
1721 | |
1722 | @staticmethod |
1723 | @@ -238,6 +291,9 @@ class NetplanApply(utils.NetplanCommand): |
1724 | continue |
1725 | |
1726 | driver_name = utils.get_interface_driver_name(interface, only_down=True) |
1727 | + if not driver_name: |
1728 | + # don't allow up interfaces to match by mac |
1729 | + continue |
1730 | macaddress = utils.get_interface_macaddress(interface) |
1731 | if driver_name in matches['by-driver']: |
1732 | new_name = matches['by-driver'][driver_name] |
1733 | @@ -252,3 +308,21 @@ class NetplanApply(utils.NetplanCommand): |
1734 | |
1735 | logging.debug(changes) |
1736 | return changes |
1737 | + |
1738 | + @staticmethod |
1739 | + def process_sriov_config(config_manager, exit_on_error=True): # pragma: nocover (covered in autopkgtest) |
1740 | + try: |
1741 | + apply_sriov_config(config_manager) |
1742 | + except (ConfigurationError, RuntimeError) as e: |
1743 | + logging.error(str(e)) |
1744 | + if exit_on_error: |
1745 | + sys.exit(1) |
1746 | + |
1747 | + @staticmethod |
1748 | + def process_ovs_cleanup(config_manager, ovs_old, ovs_current, exit_on_error=True): # pragma: nocover (autopkgtest) |
1749 | + try: |
1750 | + apply_ovs_cleanup(config_manager, ovs_old, ovs_current) |
1751 | + except (OSError, RuntimeError) as e: |
1752 | + logging.error(str(e)) |
1753 | + if exit_on_error: |
1754 | + sys.exit(1) |
1755 | diff --git a/netplan/cli/commands/try_command.py b/netplan/cli/commands/try_command.py |
1756 | index 0f70566..756fa49 100644 |
1757 | --- a/netplan/cli/commands/try_command.py |
1758 | +++ b/netplan/cli/commands/try_command.py |
1759 | @@ -80,7 +80,7 @@ class NetplanTry(utils.NetplanCommand): |
1760 | self.backup() |
1761 | self.setup() |
1762 | |
1763 | - NetplanApply.command_apply(run_generate=True, sync=True, exit_on_error=False) |
1764 | + NetplanApply().command_apply(run_generate=True, sync=True, exit_on_error=False) |
1765 | |
1766 | self.t.get_confirmation_input(timeout=self.timeout) |
1767 | except netplan.terminal.InputRejected: |
1768 | @@ -114,7 +114,7 @@ class NetplanTry(utils.NetplanCommand): |
1769 | |
1770 | def revert(self): # pragma: nocover (requires user input) |
1771 | self.config_manager.revert() |
1772 | - NetplanApply.command_apply(run_generate=False, sync=True, exit_on_error=False) |
1773 | + NetplanApply().command_apply(run_generate=False, sync=True, exit_on_error=False) |
1774 | for ifname in self.new_interfaces: |
1775 | if ifname not in self.config_manager.bonds and \ |
1776 | ifname not in self.config_manager.bridges and \ |
1777 | diff --git a/netplan/cli/ovs.py b/netplan/cli/ovs.py |
1778 | new file mode 100644 |
1779 | index 0000000..a7ae6c8 |
1780 | --- /dev/null |
1781 | +++ b/netplan/cli/ovs.py |
1782 | @@ -0,0 +1,168 @@ |
1783 | +#!/usr/bin/python3 |
1784 | +# |
1785 | +# Copyright (C) 2020 Canonical, Ltd. |
1786 | +# Author: Lukas 'slyon' Märdian <lukas.maerdian@canonical.com> |
1787 | +# |
1788 | +# This program is free software; you can redistribute it and/or modify |
1789 | +# it under the terms of the GNU General Public License as published by |
1790 | +# the Free Software Foundation; version 3. |
1791 | +# |
1792 | +# This program is distributed in the hope that it will be useful, |
1793 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1794 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1795 | +# GNU General Public License for more details. |
1796 | +# |
1797 | +# You should have received a copy of the GNU General Public License |
1798 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1799 | + |
1800 | +import logging |
1801 | +import os |
1802 | +import subprocess |
1803 | + |
1804 | +OPENVSWITCH_OVS_VSCTL = '/usr/bin/ovs-vsctl' |
1805 | +# Defaults for non-optional settings, as defined here: |
1806 | +# http://www.openvswitch.org/ovs-vswitchd.conf.db.5.pdf |
1807 | +DEFAULTS = { |
1808 | + # Mandatory columns: |
1809 | + 'mcast_snooping_enable': 'false', |
1810 | + 'rstp_enable': 'false', |
1811 | +} |
1812 | +GLOBALS = { |
1813 | + # Global commands: |
1814 | + 'set-ssl': ('del-ssl', 'get-ssl'), |
1815 | + 'set-fail-mode': ('del-fail-mode', 'get-fail-mode'), |
1816 | + 'set-controller': ('del-controller', 'get-controller'), |
1817 | +} |
1818 | + |
1819 | + |
1820 | +def _del_col(type, iface, column, value): |
1821 | + """Cleanup values from a column (i.e. "column=value")""" |
1822 | + default = DEFAULTS.get(column) |
1823 | + if default is None: |
1824 | + # removes the exact value only if it was set by netplan |
1825 | + subprocess.check_call([OPENVSWITCH_OVS_VSCTL, 'remove', type, iface, column, value]) |
1826 | + elif default and default != value: |
1827 | + # reset to default, if its not the default already |
1828 | + subprocess.check_call([OPENVSWITCH_OVS_VSCTL, 'set', type, iface, '%s=%s' % (column, default)]) |
1829 | + |
1830 | + |
1831 | +def _del_dict(type, iface, column, key, value): |
1832 | + """Cleanup values from a dictionary (i.e. "column:key=value")""" |
1833 | + # removes the exact value only if it was set by netplan |
1834 | + subprocess.check_call([OPENVSWITCH_OVS_VSCTL, 'remove', type, iface, column, key, value]) |
1835 | + |
1836 | + |
1837 | +def _del_global(type, iface, key, value): |
1838 | + """Cleanup commands from the global namespace""" |
1839 | + del_cmd, get_cmd = GLOBALS.get(key, (None, None)) |
1840 | + if del_cmd == 'del-ssl': |
1841 | + iface = None |
1842 | + |
1843 | + if del_cmd: |
1844 | + args_get = [OPENVSWITCH_OVS_VSCTL, get_cmd] |
1845 | + args_del = [OPENVSWITCH_OVS_VSCTL, del_cmd] |
1846 | + if iface: |
1847 | + args_get.append(iface) |
1848 | + args_del.append(iface) |
1849 | + # Check the current value of a global command and compare it to the tag-value, e.g.: |
1850 | + # * get-ssl: netplan/global/set-ssl=/private/key.pem,/another/cert.pem,/some/ca-cert.pem |
1851 | + # Private key: /private/key.pem |
1852 | + # Certificate: /another/cert.pem |
1853 | + # CA Certificate: /some/ca-cert.pem |
1854 | + # Bootstrap: false |
1855 | + # * get-fail-mode: netplan/global/set-fail-mode=secure |
1856 | + # secure |
1857 | + # * get-controller: netplan/global/set-controller=tcp:127.0.0.1:1337,unix:/some/socket |
1858 | + # tcp:127.0.0.1:1337 |
1859 | + # unix:/some/socket |
1860 | + out = subprocess.check_output(args_get, universal_newlines=True) |
1861 | + # Clean it only if the exact same value(s) were set by netplan. |
1862 | + # Don't touch it if other values were set by another integration. |
1863 | + if all(item in out for item in value.split(',')): |
1864 | + subprocess.check_call(args_del) |
1865 | + else: |
1866 | + raise Exception('Reset command unkown for:', key) |
1867 | + |
1868 | + |
1869 | +def clear_setting(type, iface, setting, value): |
1870 | + """Check if this setting is in a dict or a colum and delete accordingly""" |
1871 | + split = setting.split('/', 2) |
1872 | + col = split[1] |
1873 | + if col == 'global' and len(split) > 2: |
1874 | + _del_global(type, iface, split[2], value) |
1875 | + elif len(split) > 2: |
1876 | + _del_dict(type, iface, split[1], split[2], value) |
1877 | + else: |
1878 | + _del_col(type, iface, split[1], value) |
1879 | + # Cleanup the tag itself (i.e. "netplan/column[/key]") |
1880 | + subprocess.check_call([OPENVSWITCH_OVS_VSCTL, 'remove', type, iface, 'external-ids', setting]) |
1881 | + |
1882 | + |
1883 | +def is_ovs_interface(iface, interfaces): |
1884 | + if interfaces[iface].get('openvswitch') is not None: |
1885 | + return True |
1886 | + else: |
1887 | + return any(is_ovs_interface(i, interfaces) for i in interfaces[iface].get('interfaces', [])) |
1888 | + |
1889 | + |
1890 | +def apply_ovs_cleanup(config_manager, ovs_old, ovs_current): # pragma: nocover (covered in autopkgtest) |
1891 | + """ |
1892 | + Query OpenVSwitch state through 'ovs-vsctl' and filter for netplan=true |
1893 | + tagged ports/bonds and bridges. Delete interfaces which are not defined |
1894 | + in the current configuration. |
1895 | + Also filter for individual settings tagged netplan/<column>[/<key]=value |
1896 | + in external-ids and clear them if they have been set by netplan. |
1897 | + """ |
1898 | + config_manager.parse() |
1899 | + ovs_ifaces = set() |
1900 | + for i in config_manager.interfaces.keys(): |
1901 | + if (is_ovs_interface(i, config_manager.interfaces)): |
1902 | + ovs_ifaces.add(i) |
1903 | + |
1904 | + # Tear down old OVS interfaces, not defined in the current config. |
1905 | + # Use 'del-br' on the Interface table, to delete any netplan created VLAN fake bridges. |
1906 | + # Use 'del-bond-iface' on the Interface table, to delete netplan created patch port interfaces |
1907 | + if os.path.isfile(OPENVSWITCH_OVS_VSCTL): |
1908 | + # Step 1: Delete all interfaces, which are not part of the current OVS config |
1909 | + for t in (('Port', 'del-port'), ('Bridge', 'del-br'), ('Interface', 'del-br')): |
1910 | + out = subprocess.check_output([OPENVSWITCH_OVS_VSCTL, '--columns=name,external-ids', |
1911 | + '-f', 'csv', '-d', 'bare', '--no-headings', 'list', t[0]], |
1912 | + universal_newlines=True) |
1913 | + for line in out.splitlines(): |
1914 | + if 'netplan=true' in line: |
1915 | + iface = line.split(',')[0] |
1916 | + # Skip cleanup if this OVS interface is part of the current netplan OVS config |
1917 | + if iface in ovs_ifaces: |
1918 | + continue |
1919 | + if t[0] == 'Interface' and subprocess.run([OPENVSWITCH_OVS_VSCTL, 'iface-to-br', iface]).returncode > 0: |
1920 | + subprocess.check_call([OPENVSWITCH_OVS_VSCTL, '--if-exists', 'del-bond-iface', iface]) |
1921 | + else: |
1922 | + subprocess.check_call([OPENVSWITCH_OVS_VSCTL, '--if-exists', t[1], iface]) |
1923 | + |
1924 | + # Step 2: Clean up the settings of the remaining interfaces |
1925 | + for t in ('Port', 'Bridge', 'Interface', 'Open_vSwitch', 'Controller'): |
1926 | + cols = 'name,external-ids' |
1927 | + if t == 'Open_vSwitch': |
1928 | + cols = 'external-ids' |
1929 | + elif t == 'Controller': |
1930 | + cols = '_uuid,external-ids' # handle _uuid as if it would be the iface 'name' |
1931 | + out = subprocess.check_output([OPENVSWITCH_OVS_VSCTL, '--columns=%s' % cols, |
1932 | + '-f', 'csv', '-d', 'bare', '--no-headings', 'list', t], |
1933 | + universal_newlines=True) |
1934 | + for line in out.splitlines(): |
1935 | + if 'netplan/' in line: |
1936 | + iface = '.' |
1937 | + extids = line |
1938 | + if t != 'Open_vSwitch': |
1939 | + iface, extids = line.split(',', 1) |
1940 | + # Check each line (interface) if it contains any netplan tagged settings, e.g.: |
1941 | + # ovs0,"iface-id=myhostname netplan=true netplan/external-ids/iface-id=myhostname" |
1942 | + # ovs1,"netplan=true netplan/global/set-fail-mode=standalone netplan/mcast_snooping_enable=false" |
1943 | + for entry in extids.strip('"').split(' '): |
1944 | + if entry.startswith('netplan/') and '=' in entry: |
1945 | + setting, val = entry.split('=', 1) |
1946 | + clear_setting(t, iface, setting, val) |
1947 | + |
1948 | + # Show the warning only if we are or have been working with OVS definitions |
1949 | + elif ovs_old or ovs_current: |
1950 | + logging.warning('ovs-vsctl is missing, cannot tear down old OpenVSwitch interfaces') |
1951 | diff --git a/netplan/cli/sriov.py b/netplan/cli/sriov.py |
1952 | index 8feacf1..43e0259 100644 |
1953 | --- a/netplan/cli/sriov.py |
1954 | +++ b/netplan/cli/sriov.py |
1955 | @@ -30,25 +30,36 @@ import netifaces |
1956 | def _get_target_interface(interfaces, config_manager, pf_link, pfs): |
1957 | if pf_link not in pfs: |
1958 | # handle the match: syntax, get the actual device name |
1959 | - pf_match = config_manager.ethernets[pf_link].get('match') |
1960 | + pf_dev = config_manager.ethernets[pf_link] |
1961 | + pf_match = pf_dev.get('match') |
1962 | if pf_match: |
1963 | - by_name = pf_match.get('name') |
1964 | - by_mac = pf_match.get('macaddress') |
1965 | - by_driver = pf_match.get('driver') |
1966 | - |
1967 | - for interface in interfaces: |
1968 | - if ((by_name and not utils.is_interface_matching_name(interface, by_name)) or |
1969 | - (by_mac and not utils.is_interface_matching_macaddress(interface, by_mac)) or |
1970 | - (by_driver and not utils.is_interface_matching_driver_name(interface, by_driver))): |
1971 | - continue |
1972 | - # we have a matching PF |
1973 | - # store the matching interface in the dictionary of |
1974 | - # active PFs, but error out if we matched more than one |
1975 | - if pf_link in pfs: |
1976 | - raise ConfigurationError('matched more than one interface for a PF device: %s' % pf_link) |
1977 | - pfs[pf_link] = interface |
1978 | + # now here it's a bit tricky |
1979 | + set_name = pf_dev.get('set-name') |
1980 | + if set_name and set_name in interfaces: |
1981 | + # if we had a match: stanza and set-name: this means we should |
1982 | + # assume that, if found, the interface has already been |
1983 | + # renamed - use the new name |
1984 | + pfs[pf_link] = set_name |
1985 | + else: |
1986 | + # no set-name (or interfaces not yet renamed) so we need to do |
1987 | + # the matching ourselves |
1988 | + by_name = pf_match.get('name') |
1989 | + by_mac = pf_match.get('macaddress') |
1990 | + by_driver = pf_match.get('driver') |
1991 | + |
1992 | + for interface in interfaces: |
1993 | + if ((by_name and not utils.is_interface_matching_name(interface, by_name)) or |
1994 | + (by_mac and not utils.is_interface_matching_macaddress(interface, by_mac)) or |
1995 | + (by_driver and not utils.is_interface_matching_driver_name(interface, by_driver))): |
1996 | + continue |
1997 | + # we have a matching PF |
1998 | + # store the matching interface in the dictionary of |
1999 | + # active PFs, but error out if we matched more than one |
2000 | + if pf_link in pfs: |
2001 | + raise ConfigurationError('matched more than one interface for a PF device: %s' % pf_link) |
2002 | + pfs[pf_link] = interface |
2003 | else: |
2004 | - # no match field, assume entry name is interface name |
2005 | + # no match field, assume entry name is the interface name |
2006 | if pf_link in interfaces: |
2007 | pfs[pf_link] = pf_link |
2008 | |
2009 | @@ -219,11 +230,13 @@ def apply_vlan_filter_for_vf(pf, vf, vlan_name, vlan_id, prefix='/'): |
2010 | 'failed setting SR-IOV VLAN filter for vlan %s (ip link set command failed)' % vlan_name) |
2011 | |
2012 | |
2013 | -def apply_sriov_config(interfaces, config_manager): |
2014 | +def apply_sriov_config(config_manager): |
2015 | """ |
2016 | Go through all interfaces, identify which ones are SR-IOV VFs, create |
2017 | them and perform all other necessary setup. |
2018 | """ |
2019 | + config_manager.parse() |
2020 | + interfaces = netifaces.interfaces() |
2021 | |
2022 | # for sr-iov devices, we identify VFs by them having a link: field |
2023 | # pointing to an PF. So let's browse through all ethernet devices, |
2024 | @@ -316,5 +329,6 @@ def apply_sriov_config(interfaces, config_manager): |
2025 | raise ConfigurationError( |
2026 | 'interface %s for netplan device %s (%s) already has an SR-IOV vlan defined' % (vf, link, vlan)) |
2027 | |
2028 | + # TODO: make sure that we don't apply the filter twice |
2029 | apply_vlan_filter_for_vf(pf, vf, vlan, vlan_id) |
2030 | filtered_vlans_set.add(vf) |
2031 | diff --git a/netplan/cli/utils.py b/netplan/cli/utils.py |
2032 | index 5f54b1a..f076c25 100644 |
2033 | --- a/netplan/cli/utils.py |
2034 | +++ b/netplan/cli/utils.py |
2035 | @@ -23,6 +23,7 @@ import fnmatch |
2036 | import argparse |
2037 | import subprocess |
2038 | import netifaces |
2039 | +import re |
2040 | |
2041 | NM_SERVICE_NAME = 'NetworkManager.service' |
2042 | NM_SNAP_SERVICE_NAME = 'snap.network-manager.networkmanager.service' |
2043 | @@ -55,6 +56,20 @@ def nm_running(): # pragma: nocover (covered in autopkgtest) |
2044 | return False |
2045 | |
2046 | |
2047 | +def nm_interfaces(paths, devices): |
2048 | + pat = re.compile('^interface-name=(.*)$') |
2049 | + interfaces = set() |
2050 | + for path in paths: |
2051 | + with open(path, 'r') as f: |
2052 | + for line in f: |
2053 | + m = pat.match(line) |
2054 | + if m: |
2055 | + # Expand/match globbing of interface names, to real devices |
2056 | + interfaces.update(set(fnmatch.filter(devices, m.group(1)))) |
2057 | + break # skip to next file |
2058 | + return interfaces |
2059 | + |
2060 | + |
2061 | def systemctl_network_manager(action, sync=False): # pragma: nocover (covered in autopkgtest) |
2062 | service_name = NM_SERVICE_NAME |
2063 | |
2064 | @@ -86,6 +101,23 @@ def systemctl_networkd(action, sync=False, extra_services=[]): # pragma: nocove |
2065 | subprocess.check_call(command) |
2066 | |
2067 | |
2068 | +def systemctl_is_active(unit_pattern): # pragma: nocover (covered in autopkgtest) |
2069 | + '''Return True if at least one matching unit is running''' |
2070 | + if subprocess.call(['systemctl', '--quiet', 'is-active', unit_pattern]) == 0: |
2071 | + return True |
2072 | + return False |
2073 | + |
2074 | + |
2075 | +def systemctl_daemon_reload(): # pragma: nocover (covered in autopkgtest) |
2076 | + '''Reload systemd unit files from disk and re-calculate its dependencies''' |
2077 | + subprocess.check_call(['systemctl', 'daemon-reload']) |
2078 | + |
2079 | + |
2080 | +def ip_addr_flush(iface): # pragma: nocover (covered in autopkgtest) |
2081 | + '''Flush all IP addresses of a given interface via iproute2''' |
2082 | + subprocess.check_call(['ip', 'addr', 'flush', iface], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) |
2083 | + |
2084 | + |
2085 | def get_interface_driver_name(interface, only_down=False): # pragma: nocover (covered in autopkgtest) |
2086 | devdir = os.path.join('/sys/class/net', interface) |
2087 | if only_down: |
2088 | diff --git a/netplan/configmanager.py b/netplan/configmanager.py |
2089 | index 0f9e28b..ce050a2 100644 |
2090 | --- a/netplan/configmanager.py |
2091 | +++ b/netplan/configmanager.py |
2092 | @@ -44,6 +44,7 @@ class ConfigManager(object): |
2093 | @property |
2094 | def interfaces(self): |
2095 | interfaces = {} |
2096 | + interfaces.update(self.ovs_ports) |
2097 | interfaces.update(self.ethernets) |
2098 | interfaces.update(self.wifis) |
2099 | interfaces.update(self.bridges) |
2100 | @@ -59,6 +60,10 @@ class ConfigManager(object): |
2101 | return interfaces |
2102 | |
2103 | @property |
2104 | + def ovs_ports(self): |
2105 | + return self.network['ovs_ports'] |
2106 | + |
2107 | + @property |
2108 | def ethernets(self): |
2109 | return self.network['ethernets'] |
2110 | |
2111 | @@ -101,6 +106,7 @@ class ConfigManager(object): |
2112 | files = [names_to_paths[name] for name in sorted(names_to_paths.keys())] |
2113 | |
2114 | self.config['network'] = { |
2115 | + 'ovs_ports': {}, |
2116 | 'ethernets': {}, |
2117 | 'wifis': {}, |
2118 | 'bridges': {}, |
2119 | @@ -172,6 +178,29 @@ class ConfigManager(object): |
2120 | else: |
2121 | raise |
2122 | |
2123 | + def _merge_ovs_ports_config(self, orig, new): |
2124 | + new_interfaces = set() |
2125 | + ports = dict() |
2126 | + if 'ports' in new: |
2127 | + for p1, p2 in new.get('ports'): |
2128 | + # Spoof an interface config for patch ports, which are usually |
2129 | + # just strings. Add 'peer' and mark it via 'openvswitch' key. |
2130 | + ports[p1] = {'peer': p2, 'openvswitch': {}} |
2131 | + ports[p2] = {'peer': p1, 'openvswitch': {}} |
2132 | + changed_ifaces = list(ports.keys()) |
2133 | + |
2134 | + for ifname in changed_ifaces: |
2135 | + iface = ports.pop(ifname) |
2136 | + if ifname in orig: |
2137 | + logging.debug("{} exists in {}".format(ifname, orig)) |
2138 | + orig[ifname].update(iface) |
2139 | + else: |
2140 | + logging.debug("{} not found in {}".format(ifname, orig)) |
2141 | + orig[ifname] = iface |
2142 | + new_interfaces.add(ifname) |
2143 | + |
2144 | + return new_interfaces |
2145 | + |
2146 | def _merge_interface_config(self, orig, new): |
2147 | new_interfaces = set() |
2148 | changed_ifaces = list(new.keys()) |
2149 | @@ -198,6 +227,9 @@ class ConfigManager(object): |
2150 | if yaml_data is not None: |
2151 | network = yaml_data.get('network') |
2152 | if network: |
2153 | + if 'openvswitch' in network: |
2154 | + new = self._merge_ovs_ports_config(self.ovs_ports, network.get('openvswitch')) |
2155 | + new_interfaces |= new |
2156 | if 'ethernets' in network: |
2157 | new = self._merge_interface_config(self.ethernets, network.get('ethernets')) |
2158 | new_interfaces |= new |
2159 | diff --git a/src/generate.c b/src/generate.c |
2160 | index c020d8f..e88dd7f 100644 |
2161 | --- a/src/generate.c |
2162 | +++ b/src/generate.c |
2163 | @@ -30,10 +30,13 @@ |
2164 | #include "parse.h" |
2165 | #include "networkd.h" |
2166 | #include "nm.h" |
2167 | +#include "openvswitch.h" |
2168 | +#include "sriov.h" |
2169 | |
2170 | static gchar* rootdir; |
2171 | static gchar** files; |
2172 | static gboolean any_networkd; |
2173 | +static gboolean any_sriov; |
2174 | static gchar* mapping_iface; |
2175 | |
2176 | static GOptionEntry options[] = { |
2177 | @@ -53,9 +56,14 @@ reload_udevd(void) |
2178 | static void |
2179 | nd_iterator_list(gpointer value, gpointer user_data) |
2180 | { |
2181 | - if (write_networkd_conf((NetplanNetDefinition*) value, (const char*) user_data)) |
2182 | + NetplanNetDefinition* def = (NetplanNetDefinition*) value; |
2183 | + if (write_networkd_conf(def, (const char*) user_data)) |
2184 | any_networkd = TRUE; |
2185 | - write_nm_conf((NetplanNetDefinition*) value, (const char*) user_data); |
2186 | + |
2187 | + write_ovs_conf(def, (const char*) user_data); |
2188 | + write_nm_conf(def, (const char*) user_data); |
2189 | + if (def->sriov_explicit_vf_count < G_MAXUINT || def->sriov_link) |
2190 | + any_sriov = TRUE; |
2191 | } |
2192 | |
2193 | |
2194 | @@ -246,6 +254,8 @@ int main(int argc, char** argv) |
2195 | /* Clean up generated config from previous runs */ |
2196 | cleanup_networkd_conf(rootdir); |
2197 | cleanup_nm_conf(rootdir); |
2198 | + cleanup_ovs_conf(rootdir); |
2199 | + cleanup_sriov_conf(rootdir); |
2200 | |
2201 | if (mapping_iface && netdefs) { |
2202 | return find_interface(mapping_iface); |
2203 | @@ -256,6 +266,8 @@ int main(int argc, char** argv) |
2204 | g_debug("Generating output files.."); |
2205 | g_list_foreach (netdefs_ordered, nd_iterator_list, rootdir); |
2206 | write_nm_conf_finish(rootdir); |
2207 | + write_ovs_conf_finish(rootdir); |
2208 | + if (any_sriov) write_sriov_conf_finish(rootdir); |
2209 | /* We may have written .rules & .link files, thus we must |
2210 | * invalidate udevd cache of its config as by default it only |
2211 | * invalidates cache at most every 3 seconds. Not sure if this |
2212 | diff --git a/src/networkd.c b/src/networkd.c |
2213 | index e2bb111..a1b31d8 100644 |
2214 | --- a/src/networkd.c |
2215 | +++ b/src/networkd.c |
2216 | @@ -28,6 +28,7 @@ |
2217 | #include "networkd.h" |
2218 | #include "parse.h" |
2219 | #include "util.h" |
2220 | +#include "validation.h" |
2221 | |
2222 | /** |
2223 | * Append WiFi frequencies to wpa_supplicant's freq_list= |
2224 | @@ -152,6 +153,69 @@ write_tunnel_params(GString* s, const NetplanNetDefinition* def) |
2225 | } |
2226 | |
2227 | static void |
2228 | +write_wireguard_params(GString* s, const NetplanNetDefinition* def) |
2229 | +{ |
2230 | + GString *params = NULL; |
2231 | + params = g_string_sized_new(200); |
2232 | + |
2233 | + g_assert(def->tunnel.private_key); |
2234 | + /* The "PrivateKeyFile=" setting is available as of systemd-netwokrd v242+ |
2235 | + * Base64 encoded PrivateKey= or absolute PrivateKeyFile= fields are mandatory. |
2236 | + * |
2237 | + * The key was already validated via validate_tunnel_grammar(), but we need |
2238 | + * to differentiate between base64 key VS absolute path key-file. And a base64 |
2239 | + * string could (theoretically) start with '/', so we use is_wireguard_key() |
2240 | + * as well to check for more specific characteristics (if needed). */ |
2241 | + if (def->tunnel.private_key[0] == '/' && !is_wireguard_key(def->tunnel.private_key)) |
2242 | + g_string_append_printf(params, "PrivateKeyFile=%s\n", def->tunnel.private_key); |
2243 | + else |
2244 | + g_string_append_printf(params, "PrivateKey=%s\n", def->tunnel.private_key); |
2245 | + |
2246 | + if (def->tunnel.port) |
2247 | + g_string_append_printf(params, "ListenPort=%u\n", def->tunnel.port); |
2248 | + /* This is called FirewallMark= as of systemd v243, but we keep calling it FwMark= for |
2249 | + backwards compatibility. FwMark= is still supported, but deprecated: |
2250 | + https://github.com/systemd/systemd/pull/12478 */ |
2251 | + if (def->tunnel.fwmark) |
2252 | + g_string_append_printf(params, "FwMark=%u\n", def->tunnel.fwmark); |
2253 | + |
2254 | + g_string_append_printf(s, "\n[WireGuard]\n%s", params->str); |
2255 | + g_string_free(params, TRUE); |
2256 | + |
2257 | + for (guint i = 0; i < def->wireguard_peers->len; i++) { |
2258 | + NetplanWireguardPeer *peer = g_array_index (def->wireguard_peers, NetplanWireguardPeer*, i); |
2259 | + GString *peer_s = g_string_sized_new(200); |
2260 | + |
2261 | + g_string_append_printf(peer_s, "PublicKey=%s\n", peer->public_key); |
2262 | + g_string_append(peer_s, "AllowedIPs="); |
2263 | + for (guint i = 0; i < peer->allowed_ips->len; ++i) { |
2264 | + if (i > 0 ) |
2265 | + g_string_append_c(peer_s, ','); |
2266 | + g_string_append_printf(peer_s, "%s", g_array_index(peer->allowed_ips, char*, i)); |
2267 | + } |
2268 | + g_string_append_c(peer_s, '\n'); |
2269 | + |
2270 | + if (peer->keepalive) |
2271 | + g_string_append_printf(peer_s, "PersistentKeepalive=%d\n", peer->keepalive); |
2272 | + if (peer->endpoint) |
2273 | + g_string_append_printf(peer_s, "Endpoint=%s\n", peer->endpoint); |
2274 | + /* The key was already validated via validate_tunnel_grammar(), but we need |
2275 | + * to differentiate between base64 key VS absolute path key-file. And a base64 |
2276 | + * string could (theoretically) start with '/', so we use is_wireguard_key() |
2277 | + * as well to check for more specific characteristics (if needed). */ |
2278 | + if (peer->preshared_key) { |
2279 | + if (peer->preshared_key[0] == '/' && !is_wireguard_key(peer->preshared_key)) |
2280 | + g_string_append_printf(peer_s, "PresharedKeyFile=%s\n", peer->preshared_key); |
2281 | + else |
2282 | + g_string_append_printf(peer_s, "PresharedKey=%s\n", peer->preshared_key); |
2283 | + } |
2284 | + |
2285 | + g_string_append_printf(s, "\n[WireGuardPeer]\n%s", peer_s->str); |
2286 | + g_string_free(peer_s, TRUE); |
2287 | + } |
2288 | +} |
2289 | + |
2290 | +static void |
2291 | write_link_file(const NetplanNetDefinition* def, const char* rootdir, const char* path) |
2292 | { |
2293 | GString* s = NULL; |
2294 | @@ -326,9 +390,9 @@ write_netdev_file(const NetplanNetDefinition* def, const char* rootdir, const ch |
2295 | case NETPLAN_TUNNEL_MODE_SIT: |
2296 | case NETPLAN_TUNNEL_MODE_VTI: |
2297 | case NETPLAN_TUNNEL_MODE_VTI6: |
2298 | - g_string_append_printf(s, |
2299 | - "Kind=%s\n", |
2300 | - tunnel_mode_to_string(def->tunnel.mode)); |
2301 | + case NETPLAN_TUNNEL_MODE_WIREGUARD: |
2302 | + g_string_append_printf(s, "Kind=%s\n", |
2303 | + tunnel_mode_to_string(def->tunnel.mode)); |
2304 | break; |
2305 | |
2306 | case NETPLAN_TUNNEL_MODE_IP6IP6: |
2307 | @@ -341,14 +405,13 @@ write_netdev_file(const NetplanNetDefinition* def, const char* rootdir, const ch |
2308 | g_assert_not_reached(); |
2309 | // LCOV_EXCL_STOP |
2310 | } |
2311 | - |
2312 | - write_tunnel_params(s, def); |
2313 | + if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_WIREGUARD) |
2314 | + write_wireguard_params(s, def); |
2315 | + else |
2316 | + write_tunnel_params(s, def); |
2317 | break; |
2318 | |
2319 | - // LCOV_EXCL_START |
2320 | - default: |
2321 | - g_assert_not_reached(); |
2322 | - // LCOV_EXCL_STOP |
2323 | + default: g_assert_not_reached(); // LCOV_EXCL_LINE |
2324 | } |
2325 | |
2326 | /* these do not contain secrets and need to be readable by |
2327 | @@ -402,6 +465,19 @@ write_ip_rule(NetplanIPRule* r, GString* s) |
2328 | g_string_append_printf(s, "TypeOfService=%d\n", r->tos); |
2329 | } |
2330 | |
2331 | +static void |
2332 | +write_addr_option(NetplanAddressOptions* o, GString* s) |
2333 | +{ |
2334 | + g_string_append_printf(s, "\n[Address]\n"); |
2335 | + g_assert(o->address); |
2336 | + g_string_append_printf(s, "Address=%s\n", o->address); |
2337 | + |
2338 | + if (o->lifetime) |
2339 | + g_string_append_printf(s, "PreferredLifetime=%s\n", o->lifetime); |
2340 | + if (o->label) |
2341 | + g_string_append_printf(s, "Label=%s\n", o->label); |
2342 | +} |
2343 | + |
2344 | #define DHCP_OVERRIDES_ERROR \ |
2345 | "ERROR: %s: networkd requires that %s has the same value in both " \ |
2346 | "dhcp4_overrides and dhcp6_overrides\n" |
2347 | @@ -459,7 +535,10 @@ combine_dhcp_overrides(const NetplanNetDefinition* def, NetplanDHCPOverrides* co |
2348 | } |
2349 | } |
2350 | |
2351 | -static void |
2352 | +/** |
2353 | + * Write the needed networkd .network configuration for the selected netplan definition. |
2354 | + */ |
2355 | +void |
2356 | write_network_file(const NetplanNetDefinition* def, const char* rootdir, const char* path) |
2357 | { |
2358 | GString* network = NULL; |
2359 | @@ -524,13 +603,13 @@ write_network_file(const NetplanNetDefinition* def, const char* rootdir, const c |
2360 | if (def->ip6_addresses) |
2361 | for (unsigned i = 0; i < def->ip6_addresses->len; ++i) |
2362 | g_string_append_printf(network, "Address=%s\n", g_array_index(def->ip6_addresses, char*, i)); |
2363 | - if (def->ip6_addr_gen_mode) { |
2364 | - /* TODO: Figure out how we can configure ipv6-address-generation for networkd. |
2365 | - * IPv6Token= seems to be the corresponding option, but it doesn't do |
2366 | - * exactly what we need and has quite some restrictions, c.f.: |
2367 | - * https://github.com/systemd/systemd/issues/4625 |
2368 | - * https://github.com/systemd/systemd/pull/14415 */ |
2369 | - g_fprintf(stderr, "ERROR: %s: ipv6-address-generation is not supported by networkd\n", def->id); |
2370 | + if (def->ip6_addr_gen_token) { |
2371 | + g_string_append_printf(network, "IPv6Token=static:%s\n", def->ip6_addr_gen_token); |
2372 | + } else if (def->ip6_addr_gen_mode > NETPLAN_ADDRGEN_EUI64) { |
2373 | + /* EUI-64 mode is enabled by default, if no IPv6Token= is specified */ |
2374 | + /* TODO: Enable stable-privacy mode for networkd, once PR#16618 has been released: |
2375 | + * https://github.com/systemd/systemd/pull/16618 */ |
2376 | + g_fprintf(stderr, "ERROR: %s: ipv6-address-generation mode is not supported by networkd\n", def->id); |
2377 | exit(1); |
2378 | } |
2379 | if (def->accept_ra == NETPLAN_RA_MODE_ENABLED) |
2380 | @@ -563,7 +642,7 @@ write_network_file(const NetplanNetDefinition* def, const char* rootdir, const c |
2381 | if (def->type >= NETPLAN_DEF_TYPE_VIRTUAL) |
2382 | g_string_append(network, "ConfigureWithoutCarrier=yes\n"); |
2383 | |
2384 | - if (def->bridge) { |
2385 | + if (def->bridge && def->backend != NETPLAN_BACKEND_OVS) { |
2386 | g_string_append_printf(network, "Bridge=%s\n", def->bridge); |
2387 | |
2388 | if (def->bridge_params.path_cost || def->bridge_params.port_priority) |
2389 | @@ -573,14 +652,14 @@ write_network_file(const NetplanNetDefinition* def, const char* rootdir, const c |
2390 | if (def->bridge_params.port_priority) |
2391 | g_string_append_printf(network, "Priority=%u\n", def->bridge_params.port_priority); |
2392 | } |
2393 | - if (def->bond) { |
2394 | + if (def->bond && def->backend != NETPLAN_BACKEND_OVS) { |
2395 | g_string_append_printf(network, "Bond=%s\n", def->bond); |
2396 | |
2397 | if (def->bond_params.primary_slave) |
2398 | g_string_append_printf(network, "PrimarySlave=true\n"); |
2399 | } |
2400 | |
2401 | - if (def->has_vlans) { |
2402 | + if (def->has_vlans && def->backend != NETPLAN_BACKEND_OVS) { |
2403 | /* iterate over all netdefs to find VLANs attached to us */ |
2404 | GList *l = netdefs_ordered; |
2405 | const NetplanNetDefinition* nd; |
2406 | @@ -604,6 +683,13 @@ write_network_file(const NetplanNetDefinition* def, const char* rootdir, const c |
2407 | } |
2408 | } |
2409 | |
2410 | + if (def->address_options) { |
2411 | + for (unsigned i = 0; i < def->address_options->len; ++i) { |
2412 | + NetplanAddressOptions* opts = g_array_index(def->address_options, NetplanAddressOptions*, i); |
2413 | + write_addr_option(opts, network); |
2414 | + } |
2415 | + } |
2416 | + |
2417 | if (def->dhcp4 || def->dhcp6 || def->critical) { |
2418 | /* NetworkManager compatible route metrics */ |
2419 | g_string_append(network, "\n[DHCP]\n"); |
2420 | @@ -812,19 +898,8 @@ write_wpa_unit(const NetplanNetDefinition* def, const char* rootdir) |
2421 | { |
2422 | g_autoptr(GError) err = NULL; |
2423 | g_autofree gchar *stdouth = NULL; |
2424 | - g_autofree gchar *stderrh = NULL; |
2425 | - gint exit_status = 0; |
2426 | |
2427 | - gchar *argv[] = {"bin" "/" "systemd-escape", def->id, NULL}; |
2428 | - g_spawn_sync("/", argv, NULL, 0, NULL, NULL, &stdouth, &stderrh, &exit_status, &err); |
2429 | - g_spawn_check_exit_status(exit_status, &err); |
2430 | - if (err != NULL) { |
2431 | - // LCOV_EXCL_START |
2432 | - g_fprintf(stderr, "failed to ask systemd to escape %s; exit %d\nstdout: '%s'\nstderr: '%s'", def->id, exit_status, stdouth, stderrh); |
2433 | - exit(1); |
2434 | - // LCOV_EXCL_STOP |
2435 | - } |
2436 | - g_strstrip(stdouth); |
2437 | + stdouth = systemd_escape(def->id); |
2438 | |
2439 | GString* s = g_string_new("[Unit]\n"); |
2440 | g_autofree char* path = g_strjoin(NULL, "/run/systemd/system/netplan-wpa-", stdouth, ".service", NULL); |
2441 | @@ -863,6 +938,9 @@ write_wpa_conf(const NetplanNetDefinition* def, const char* rootdir) |
2442 | if (ap->bssid) { |
2443 | g_string_append_printf(s, " bssid=%s\n", ap->bssid); |
2444 | } |
2445 | + if (ap->hidden) { |
2446 | + g_string_append(s, " scan_ssid=1\n"); |
2447 | + } |
2448 | if (ap->band == NETPLAN_WIFI_BAND_24) { |
2449 | // initialize 2.4GHz frequency hashtable |
2450 | if(!wifi_frequency_24) |
2451 | @@ -990,8 +1068,12 @@ cleanup_networkd_conf(const char* rootdir) |
2452 | { |
2453 | unlink_glob(rootdir, "/run/systemd/network/10-netplan-*"); |
2454 | unlink_glob(rootdir, "/run/netplan/wpa-*.conf"); |
2455 | + unlink_glob(rootdir, "/run/systemd/system/systemd-networkd.service.wants/netplan-wpa-*.service"); |
2456 | unlink_glob(rootdir, "/run/systemd/system/netplan-wpa-*.service"); |
2457 | unlink_glob(rootdir, "/run/udev/rules.d/99-netplan-*"); |
2458 | + /* Historically (up to v0.98) we had netplan-wpa@*.service files, in case of an |
2459 | + * upgraded system, we need to make sure to clean those up. */ |
2460 | + unlink_glob(rootdir, "/run/systemd/system/systemd-networkd.service.wants/netplan-wpa@*.service"); |
2461 | } |
2462 | |
2463 | /** |
2464 | diff --git a/src/networkd.h b/src/networkd.h |
2465 | index 5629a04..41ab125 100644 |
2466 | --- a/src/networkd.h |
2467 | +++ b/src/networkd.h |
2468 | @@ -22,3 +22,5 @@ |
2469 | gboolean write_networkd_conf(const NetplanNetDefinition* def, const char* rootdir); |
2470 | void cleanup_networkd_conf(const char* rootdir); |
2471 | void enable_networkd(const char* generator_dir); |
2472 | + |
2473 | +void write_network_file(const NetplanNetDefinition* def, const char* rootdir, const char* path); |
2474 | diff --git a/src/nm.c b/src/nm.c |
2475 | index 2cd1d52..9a377d0 100644 |
2476 | --- a/src/nm.c |
2477 | +++ b/src/nm.c |
2478 | @@ -28,6 +28,7 @@ |
2479 | #include "nm.h" |
2480 | #include "parse.h" |
2481 | #include "util.h" |
2482 | +#include "validation.h" |
2483 | |
2484 | GString* udev_rules; |
2485 | |
2486 | @@ -111,6 +112,8 @@ type_str(const NetplanNetDefinition* def) |
2487 | case NETPLAN_DEF_TYPE_VLAN: |
2488 | return "vlan"; |
2489 | case NETPLAN_DEF_TYPE_TUNNEL: |
2490 | + if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_WIREGUARD) |
2491 | + return "wireguard"; |
2492 | return "ip-tunnel"; |
2493 | // LCOV_EXCL_START |
2494 | default: |
2495 | @@ -201,23 +204,14 @@ write_routes(const NetplanNetDefinition* def, GString *s, int family) |
2496 | exit(1); |
2497 | } |
2498 | |
2499 | - if (cur_route->scope && g_ascii_strcasecmp(cur_route->scope, "global") != 0) { |
2500 | - g_fprintf(stderr, "ERROR: %s: NetworkManager only supports global scoped routes\n", def->id); |
2501 | - exit(1); |
2502 | - } |
2503 | - |
2504 | - if (cur_route->table != NETPLAN_ROUTE_TABLE_UNSPEC) { |
2505 | - g_fprintf(stderr, "ERROR: %s: NetworkManager does not support non-default routing tables\n", def->id); |
2506 | - exit(1); |
2507 | - } |
2508 | - |
2509 | - if (cur_route->from) { |
2510 | - g_fprintf(stderr, "ERROR: %s: NetworkManager does not support routes with 'from'\n", def->id); |
2511 | - exit(1); |
2512 | - } |
2513 | - |
2514 | - if (cur_route->onlink) { |
2515 | - g_fprintf(stderr, "ERROR: %s: NetworkManager does not support on-link routes\n", def->id); |
2516 | + if (!g_strcmp0(cur_route->scope, "global")) { |
2517 | + /* For IPv6 addresses, kernel and NetworkManager don't support a scope. |
2518 | + * For IPv4 addresses, NetworkManager determines the scope of addresses on its own |
2519 | + * ("link" for addresses without gateway, "global" for addresses with next-hop). */ |
2520 | + g_debug("%s: NetworkManager does not support setting a scope for routes, it will auto-detect them.", def->id); |
2521 | + } else if (cur_route->scope) { |
2522 | + /* Error out if scope is not set to its default value of 'global' */ |
2523 | + g_fprintf(stderr, "ERROR: %s: NetworkManager does not support setting a scope for routes\n", def->id); |
2524 | exit(1); |
2525 | } |
2526 | |
2527 | @@ -226,6 +220,21 @@ write_routes(const NetplanNetDefinition* def, GString *s, int family) |
2528 | if (cur_route->metric != NETPLAN_METRIC_UNSPEC) |
2529 | g_string_append_printf(s, ",%d", cur_route->metric); |
2530 | g_string_append(s, "\n"); |
2531 | + |
2532 | + if ( cur_route->onlink |
2533 | + || cur_route->table != NETPLAN_ROUTE_TABLE_UNSPEC |
2534 | + || cur_route->from) { |
2535 | + g_string_append_printf(s, "route%d_options=", j); |
2536 | + if (cur_route->onlink) { |
2537 | + /* onlink for IPv6 addresses is only supported since nm-1.18.0. */ |
2538 | + g_string_append_printf(s, "onlink=true,"); |
2539 | + } |
2540 | + if (cur_route->table != NETPLAN_ROUTE_TABLE_UNSPEC) |
2541 | + g_string_append_printf(s, "table=%u,", cur_route->table); |
2542 | + if (cur_route->from) |
2543 | + g_string_append_printf(s, "src=%s,", cur_route->from); |
2544 | + s->str[s->len - 1] = '\n'; |
2545 | + } |
2546 | j++; |
2547 | } |
2548 | } |
2549 | @@ -322,6 +331,60 @@ write_bridge_params(const NetplanNetDefinition* def, GString *s) |
2550 | } |
2551 | |
2552 | static void |
2553 | +write_wireguard_params(const NetplanNetDefinition* def, GString *s) |
2554 | +{ |
2555 | + g_assert(def->tunnel.private_key); |
2556 | + g_string_append(s, "\n[wireguard]\n"); |
2557 | + |
2558 | + /* The key was already validated via validate_tunnel_grammar(), but we need |
2559 | + * to differentiate between base64 key VS absolute path key-file. And a base64 |
2560 | + * string could (theoretically) start with '/', so we use is_wireguard_key() |
2561 | + * as well to check for more specific characteristics (if needed). */ |
2562 | + if (def->tunnel.private_key[0] == '/' && !is_wireguard_key(def->tunnel.private_key)) { |
2563 | + g_fprintf(stderr, "%s: private key needs to be base64 encoded when using the NM backend\n", def->id); |
2564 | + exit(1); |
2565 | + } else |
2566 | + g_string_append_printf(s, "private-key=%s\n", def->tunnel.private_key); |
2567 | + |
2568 | + if (def->tunnel.port) |
2569 | + g_string_append_printf(s, "listen-port=%u\n", def->tunnel.port); |
2570 | + if (def->tunnel.fwmark) |
2571 | + g_string_append_printf(s, "fwmark=%u\n", def->tunnel.fwmark); |
2572 | + |
2573 | + for (guint i = 0; i < def->wireguard_peers->len; i++) { |
2574 | + NetplanWireguardPeer *peer = g_array_index (def->wireguard_peers, NetplanWireguardPeer*, i); |
2575 | + g_assert(peer->public_key); |
2576 | + g_string_append_printf(s, "\n[wireguard-peer.%s]\n", peer->public_key); |
2577 | + |
2578 | + if (peer->keepalive) |
2579 | + g_string_append_printf(s, "persistent-keepalive=%d\n", peer->keepalive); |
2580 | + if (peer->endpoint) |
2581 | + g_string_append_printf(s, "endpoint=%s\n", peer->endpoint); |
2582 | + /* The key was already validated via validate_tunnel_grammar(), but we need |
2583 | + * to differentiate between base64 key VS absolute path key-file. And a base64 |
2584 | + * string could (theoretically) start with '/', so we use is_wireguard_key() |
2585 | + * as well to check for more specific characteristics (if needed). */ |
2586 | + if (peer->preshared_key) { |
2587 | + if (peer->preshared_key[0] == '/' && !is_wireguard_key(peer->preshared_key)) { |
2588 | + g_fprintf(stderr, "%s: shared key needs to be base64 encoded when using the NM backend\n", def->id); |
2589 | + exit(1); |
2590 | + } else { |
2591 | + g_string_append_printf(s, "preshared-key=%s\n", peer->preshared_key); |
2592 | + g_string_append(s, "preshared-key-flags=0\n"); |
2593 | + } |
2594 | + } |
2595 | + if (peer->allowed_ips && peer->allowed_ips->len > 0) { |
2596 | + g_string_append(s, "allowed-ips="); |
2597 | + for (guint i = 0; i < peer->allowed_ips->len; ++i) { |
2598 | + if (i > 0 ) g_string_append_c(s, ';'); |
2599 | + g_string_append_printf(s, "%s", g_array_index(peer->allowed_ips, char*, i)); |
2600 | + } |
2601 | + g_string_append_c(s, '\n'); |
2602 | + } |
2603 | + } |
2604 | +} |
2605 | + |
2606 | +static void |
2607 | write_tunnel_params(const NetplanNetDefinition* def, GString *s) |
2608 | { |
2609 | g_string_append(s, "\n[ip-tunnel]\n"); |
2610 | @@ -437,6 +500,7 @@ write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const |
2611 | g_autofree char* conf_path = NULL; |
2612 | mode_t orig_umask; |
2613 | char uuidstr[37]; |
2614 | + const char *match_interface_name = NULL; |
2615 | |
2616 | if (def->type == NETPLAN_DEF_TYPE_WIFI) |
2617 | g_assert(ap); |
2618 | @@ -472,12 +536,10 @@ write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const |
2619 | else if (!def->has_match) |
2620 | g_string_append_printf(s, "interface-name=%s\n", def->id); |
2621 | else if (def->match.original_name) { |
2622 | - /* NM does not support interface name globbing */ |
2623 | - if (strpbrk(def->match.original_name, "*[]?")) { |
2624 | - g_fprintf(stderr, "ERROR: %s: NetworkManager definitions do not support name globbing\n", def->id); |
2625 | - exit(1); |
2626 | - } |
2627 | - g_string_append_printf(s, "interface-name=%s\n", def->match.original_name); |
2628 | + if (strpbrk(def->match.original_name, "*[]?")) |
2629 | + match_interface_name = def->match.original_name; |
2630 | + else |
2631 | + g_string_append_printf(s, "interface-name=%s\n", def->match.original_name); |
2632 | } |
2633 | /* else matches on something other than the name, do not restrict interface-name */ |
2634 | } else { |
2635 | @@ -609,8 +671,17 @@ write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const |
2636 | if (def->type == NETPLAN_DEF_TYPE_BOND) |
2637 | write_bond_parameters(def, s); |
2638 | |
2639 | - if (def->type == NETPLAN_DEF_TYPE_TUNNEL) |
2640 | - write_tunnel_params(def, s); |
2641 | + if (def->type == NETPLAN_DEF_TYPE_TUNNEL) { |
2642 | + if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_WIREGUARD) |
2643 | + write_wireguard_params(def, s); |
2644 | + else |
2645 | + write_tunnel_params(def, s); |
2646 | + } |
2647 | + |
2648 | + if (match_interface_name) { |
2649 | + g_string_append(s, "\n[match]\n"); |
2650 | + g_string_append_printf(s, "interface-name=%s;\n", match_interface_name); |
2651 | + } |
2652 | |
2653 | g_string_append(s, "\n[ipv4]\n"); |
2654 | |
2655 | @@ -660,7 +731,11 @@ write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const |
2656 | if (def->ip6_addresses) |
2657 | for (unsigned i = 0; i < def->ip6_addresses->len; ++i) |
2658 | g_string_append_printf(s, "address%i=%s\n", i+1, g_array_index(def->ip6_addresses, char*, i)); |
2659 | - if (def->ip6_addr_gen_mode) { |
2660 | + if (def->ip6_addr_gen_token) { |
2661 | + /* Token implies EUI-64, i.e mode=0 */ |
2662 | + g_string_append(s, "addr-gen-mode=0\n"); |
2663 | + g_string_append_printf(s, "token=%s\n", def->ip6_addr_gen_token); |
2664 | + } else if (def->ip6_addr_gen_mode) { |
2665 | g_string_append_printf(s, "addr-gen-mode=%s\n", addr_gen_mode_str(def->ip6_addr_gen_mode)); |
2666 | } |
2667 | if (def->ip6_privacy) |
2668 | @@ -700,6 +775,9 @@ write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const |
2669 | if (ap->bssid) { |
2670 | g_string_append_printf(s, "bssid=%s\n", ap->bssid); |
2671 | } |
2672 | + if (ap->hidden) { |
2673 | + g_string_append(s, "hidden=true\n"); |
2674 | + } |
2675 | if (ap->band == NETPLAN_WIFI_BAND_5 || ap->band == NETPLAN_WIFI_BAND_24) { |
2676 | g_string_append_printf(s, "band=%s\n", wifi_band_str(ap->band)); |
2677 | /* Channel is only unambiguous, if band is set. */ |
2678 | @@ -747,6 +825,11 @@ write_nm_conf(NetplanNetDefinition* def, const char* rootdir) |
2679 | exit(1); |
2680 | } |
2681 | |
2682 | + if (def->address_options) { |
2683 | + g_fprintf(stderr, "ERROR: %s: NetworkManager does not support address options\n", def->id); |
2684 | + exit(1); |
2685 | + } |
2686 | + |
2687 | /* for wifi we need to create a separate connection file for every SSID */ |
2688 | if (def->type == NETPLAN_DEF_TYPE_WIFI) { |
2689 | GHashTableIter iter; |
2690 | diff --git a/src/openvswitch.c b/src/openvswitch.c |
2691 | new file mode 100644 |
2692 | index 0000000..79068d1 |
2693 | --- /dev/null |
2694 | +++ b/src/openvswitch.c |
2695 | @@ -0,0 +1,484 @@ |
2696 | +/* |
2697 | + * Copyright (C) 2020 Canonical, Ltd. |
2698 | + * Author: Łukasz 'sil2100' Zemczak <lukasz.zemczak@ubuntu.com> |
2699 | + * Lukas 'slyon' Märdian <lukas.maerdian@canonical.com> |
2700 | + * |
2701 | + * This program is free software; you can redistribute it and/or modify |
2702 | + * it under the terms of the GNU General Public License as published by |
2703 | + * the Free Software Foundation; version 3. |
2704 | + * |
2705 | + * This program is distributed in the hope that it will be useful, |
2706 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2707 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2708 | + * GNU General Public License for more details. |
2709 | + * |
2710 | + * You should have received a copy of the GNU General Public License |
2711 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2712 | + */ |
2713 | + |
2714 | +#include <unistd.h> |
2715 | +#include <errno.h> |
2716 | + |
2717 | +#include <glib.h> |
2718 | +#include <glib/gprintf.h> |
2719 | + |
2720 | +#include "openvswitch.h" |
2721 | +#include "networkd.h" |
2722 | +#include "parse.h" |
2723 | +#include "util.h" |
2724 | + |
2725 | +static void |
2726 | +write_ovs_systemd_unit(const char* id, const GString* cmds, const char* rootdir, gboolean physical, gboolean cleanup, const char* dependency) |
2727 | +{ |
2728 | + g_autofree gchar* id_escaped = NULL; |
2729 | + g_autofree char* link = g_strjoin(NULL, rootdir ?: "", "/run/systemd/system/systemd-networkd.service.wants/netplan-ovs-", id, ".service", NULL); |
2730 | + g_autofree char* path = g_strjoin(NULL, "/run/systemd/system/netplan-ovs-", id, ".service", NULL); |
2731 | + |
2732 | + GString* s = g_string_new("[Unit]\n"); |
2733 | + g_string_append_printf(s, "Description=OpenVSwitch configuration for %s\n", id); |
2734 | + g_string_append(s, "DefaultDependencies=no\n"); |
2735 | + /* run any ovs-netplan unit only after openvswitch-switch.service is ready */ |
2736 | + g_string_append_printf(s, "Wants=openvswitch-switch.service\n"); |
2737 | + g_string_append_printf(s, "After=openvswitch-switch.service\n"); |
2738 | + if (physical) { |
2739 | + id_escaped = systemd_escape((char*) id); |
2740 | + g_string_append_printf(s, "Requires=sys-subsystem-net-devices-%s.device\n", id_escaped); |
2741 | + g_string_append_printf(s, "After=sys-subsystem-net-devices-%s.device\n", id_escaped); |
2742 | + } |
2743 | + if (!cleanup) { |
2744 | + g_string_append_printf(s, "After=netplan-ovs-cleanup.service\n"); |
2745 | + } else { |
2746 | + /* The netplan-ovs-cleanup unit shall not run on systems where openvswitch is not installed. */ |
2747 | + g_string_append(s, "ConditionFileIsExecutable=" OPENVSWITCH_OVS_VSCTL "\n"); |
2748 | + } |
2749 | + g_string_append(s, "Before=network.target\nWants=network.target\n"); |
2750 | + if (dependency) { |
2751 | + g_string_append_printf(s, "Requires=netplan-ovs-%s.service\n", dependency); |
2752 | + g_string_append_printf(s, "After=netplan-ovs-%s.service\n", dependency); |
2753 | + } |
2754 | + |
2755 | + g_string_append(s, "\n[Service]\nType=oneshot\n"); |
2756 | + g_string_append(s, cmds->str); |
2757 | + |
2758 | + g_string_free_to_file(s, rootdir, path, NULL); |
2759 | + |
2760 | + safe_mkdir_p_dir(link); |
2761 | + if (symlink(path, link) < 0 && errno != EEXIST) { |
2762 | + // LCOV_EXCL_START |
2763 | + g_fprintf(stderr, "failed to create enablement symlink: %m\n"); |
2764 | + exit(1); |
2765 | + // LCOV_EXCL_STOP |
2766 | + } |
2767 | +} |
2768 | + |
2769 | +#define append_systemd_cmd(s, command, ...) \ |
2770 | +{ \ |
2771 | + g_string_append(s, "ExecStart="); \ |
2772 | + g_string_append_printf(s, command, __VA_ARGS__); \ |
2773 | + g_string_append(s, "\n"); \ |
2774 | +} |
2775 | + |
2776 | +static char* |
2777 | +netplan_type_to_table_name(const NetplanDefType type) |
2778 | +{ |
2779 | + switch (type) { |
2780 | + case NETPLAN_DEF_TYPE_BRIDGE: |
2781 | + return "Bridge"; |
2782 | + case NETPLAN_DEF_TYPE_BOND: |
2783 | + case NETPLAN_DEF_TYPE_PORT: |
2784 | + return "Port"; |
2785 | + default: /* For regular interfaces and others */ |
2786 | + return "Interface"; |
2787 | + } |
2788 | +} |
2789 | + |
2790 | +static gboolean |
2791 | +netplan_type_is_physical(const NetplanDefType type) |
2792 | +{ |
2793 | + switch (type) { |
2794 | + case NETPLAN_DEF_TYPE_ETHERNET: |
2795 | + // case NETPLAN_DEF_TYPE_WIFI: |
2796 | + // case NETPLAN_DEF_TYPE_MODEM: |
2797 | + return TRUE; |
2798 | + default: |
2799 | + return FALSE; |
2800 | + } |
2801 | +} |
2802 | + |
2803 | +static void |
2804 | +write_ovs_tag_setting(const gchar* id, const char* type, const char* col, const char* key, const char* value, GString* cmds) |
2805 | +{ |
2806 | + g_assert(col); |
2807 | + g_assert(value); |
2808 | + g_autofree char *clean_value = g_strdup(value); |
2809 | + /* Replace " " -> "," if value contains spaces */ |
2810 | + if (strchr(value, ' ')) { |
2811 | + char **split = g_strsplit(value, " ", -1); |
2812 | + g_free(clean_value); |
2813 | + clean_value = g_strjoinv(",", split); |
2814 | + g_strfreev(split); |
2815 | + } |
2816 | + |
2817 | + GString* s = g_string_new("external-ids:netplan/"); |
2818 | + g_string_append_printf(s, "%s", col); |
2819 | + if (key) |
2820 | + g_string_append_printf(s, "/%s", key); |
2821 | + g_string_append_printf(s, "=%s", clean_value); |
2822 | + append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set %s %s %s", type, id, s->str); |
2823 | + g_string_free(s, TRUE); |
2824 | +} |
2825 | + |
2826 | +static void |
2827 | +write_ovs_additional_data(GHashTable *data, const char* type, const gchar* id, GString* cmds, const char* setting) |
2828 | +{ |
2829 | + GHashTableIter iter; |
2830 | + gchar* key; |
2831 | + gchar* value; |
2832 | + |
2833 | + g_hash_table_iter_init(&iter, data); |
2834 | + while (g_hash_table_iter_next(&iter, (gpointer) &key, (gpointer) &value)) { |
2835 | + /* XXX: we need to check what happens when an invalid key=value pair |
2836 | + gets supplied here. We might want to handle this somehow. */ |
2837 | + append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set %s %s %s:%s=%s", |
2838 | + type, id, setting, key, value); |
2839 | + write_ovs_tag_setting(id, type, setting, key, value, cmds); |
2840 | + } |
2841 | +} |
2842 | + |
2843 | +static void |
2844 | +setup_patch_port(GString* s, const NetplanNetDefinition* def) |
2845 | +{ |
2846 | + /* Execute the setup commands to create an OVS patch port atomically within |
2847 | + * the same command where this virtual interface is created. Either as a |
2848 | + * Port+Interface of an OVS bridge or as a Interface of an OVS bond. This |
2849 | + * avoids delays in the PatchPort creation and thus potential races. */ |
2850 | + g_assert(def->type == NETPLAN_DEF_TYPE_PORT); |
2851 | + g_string_append_printf(s, " -- set Interface %s type=patch options:peer=%s", |
2852 | + def->id, def->peer); |
2853 | +} |
2854 | + |
2855 | +static char* |
2856 | +write_ovs_bond_interfaces(const NetplanNetDefinition* def, GString* cmds) |
2857 | +{ |
2858 | + NetplanNetDefinition* tmp_nd; |
2859 | + GHashTableIter iter; |
2860 | + gchar* key; |
2861 | + guint i = 0; |
2862 | + GString* s = NULL; |
2863 | + GString* patch_ports = g_string_new(""); |
2864 | + |
2865 | + if (!def->bridge) { |
2866 | + g_fprintf(stderr, "Bond %s needs to be a slave of an OpenVSwitch bridge\n", def->id); |
2867 | + exit(1); |
2868 | + } |
2869 | + |
2870 | + s = g_string_new(OPENVSWITCH_OVS_VSCTL " --may-exist add-bond"); |
2871 | + g_string_append_printf(s, " %s %s", def->bridge, def->id); |
2872 | + |
2873 | + g_hash_table_iter_init(&iter, netdefs); |
2874 | + while (g_hash_table_iter_next(&iter, (gpointer) &key, (gpointer) &tmp_nd)) { |
2875 | + if (!g_strcmp0(def->id, tmp_nd->bond)) { |
2876 | + /* Append and count bond interfaces */ |
2877 | + g_string_append_printf(s, " %s", tmp_nd->id); |
2878 | + i++; |
2879 | + if (tmp_nd->type == NETPLAN_DEF_TYPE_PORT) |
2880 | + setup_patch_port(patch_ports, tmp_nd); |
2881 | + } |
2882 | + } |
2883 | + if (i < 2) { |
2884 | + g_fprintf(stderr, "Bond %s needs to have at least 2 slave interfaces\n", def->id); |
2885 | + exit(1); |
2886 | + } |
2887 | + |
2888 | + g_string_append(s, patch_ports->str); |
2889 | + g_string_free(patch_ports, TRUE); |
2890 | + append_systemd_cmd(cmds, s->str, def->bridge, def->id); |
2891 | + g_string_free(s, TRUE); |
2892 | + return def->bridge; |
2893 | +} |
2894 | + |
2895 | +static void |
2896 | +write_ovs_tag_netplan(const gchar* id, const char* type, GString* cmds) |
2897 | +{ |
2898 | + /* Mark this bridge/port/interface as created by netplan */ |
2899 | + append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set %s %s external-ids:netplan=true", |
2900 | + type, id); |
2901 | +} |
2902 | + |
2903 | +static void |
2904 | +write_ovs_bond_mode(const NetplanNetDefinition* def, GString* cmds) |
2905 | +{ |
2906 | + char* value = NULL; |
2907 | + /* OVS supports only "active-backup", "balance-tcp" and "balance-slb": |
2908 | + * http://www.openvswitch.org/support/dist-docs/ovs-vswitchd.conf.db.5.txt */ |
2909 | + if (!strcmp(def->bond_params.mode, "active-backup") || |
2910 | + !strcmp(def->bond_params.mode, "balance-tcp") || |
2911 | + !strcmp(def->bond_params.mode, "balance-slb")) { |
2912 | + value = def->bond_params.mode; |
2913 | + append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set Port %s bond_mode=%s", def->id, value); |
2914 | + write_ovs_tag_setting(def->id, "Port", "bond_mode", NULL, value, cmds); |
2915 | + } else { |
2916 | + g_fprintf(stderr, "%s: bond mode '%s' not supported by openvswitch\n", |
2917 | + def->id, def->bond_params.mode); |
2918 | + exit(1); |
2919 | + } |
2920 | +} |
2921 | + |
2922 | +static void |
2923 | +write_ovs_bridge_interfaces(const NetplanNetDefinition* def, GString* cmds) |
2924 | +{ |
2925 | + NetplanNetDefinition* tmp_nd; |
2926 | + GHashTableIter iter; |
2927 | + gchar* key; |
2928 | + |
2929 | + append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " --may-exist add-br %s", def->id); |
2930 | + |
2931 | + g_hash_table_iter_init(&iter, netdefs); |
2932 | + while (g_hash_table_iter_next(&iter, (gpointer) &key, (gpointer) &tmp_nd)) { |
2933 | + /* OVS bonds will connect to their OVS bridge and create the interface/port themselves */ |
2934 | + if ((tmp_nd->type != NETPLAN_DEF_TYPE_BOND || tmp_nd->backend != NETPLAN_BACKEND_OVS) |
2935 | + && !g_strcmp0(def->id, tmp_nd->bridge)) { |
2936 | + GString * patch_ports = g_string_new(""); |
2937 | + if (tmp_nd->type == NETPLAN_DEF_TYPE_PORT) |
2938 | + setup_patch_port(patch_ports, tmp_nd); |
2939 | + append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " --may-exist add-port %s %s%s", |
2940 | + def->id, tmp_nd->id, patch_ports->str); |
2941 | + g_string_free(patch_ports, TRUE); |
2942 | + } |
2943 | + } |
2944 | +} |
2945 | + |
2946 | +static void |
2947 | +write_ovs_protocols(const NetplanOVSSettings* ovs_settings, const gchar* bridge, GString* cmds) |
2948 | +{ |
2949 | + g_assert(bridge); |
2950 | + GString* s = g_string_new(g_array_index(ovs_settings->protocols, char*, 0)); |
2951 | + |
2952 | + for (unsigned i = 1; i < ovs_settings->protocols->len; ++i) |
2953 | + g_string_append_printf(s, ",%s", g_array_index(ovs_settings->protocols, char*, i)); |
2954 | + |
2955 | + append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set Bridge %s protocols=%s", bridge, s->str); |
2956 | + write_ovs_tag_setting(bridge, "Bridge", "protocols", NULL, s->str, cmds); |
2957 | + g_string_free(s, TRUE); |
2958 | +} |
2959 | + |
2960 | +static gboolean |
2961 | +check_ovs_ssl(gchar* target) |
2962 | +{ |
2963 | + /* Check if target needs ssl */ |
2964 | + if (g_str_has_prefix(target, "ssl:") || g_str_has_prefix(target, "pssl:")) { |
2965 | + /* Check if SSL is configured in ovs_settings_global.ssl */ |
2966 | + if (!ovs_settings_global.ssl.ca_certificate || !ovs_settings_global.ssl.client_certificate || |
2967 | + !ovs_settings_global.ssl.client_key) { |
2968 | + g_fprintf(stderr, "ERROR: openvswitch bridge controller target '%s' needs SSL configuration, but global 'openvswitch.ssl' settings are not set\n", target); |
2969 | + exit(1); |
2970 | + } |
2971 | + return TRUE; |
2972 | + } |
2973 | + return FALSE; |
2974 | +} |
2975 | + |
2976 | +static void |
2977 | +write_ovs_bridge_controller_targets(const NetplanOVSController* controller, const gchar* bridge, GString* cmds) |
2978 | +{ |
2979 | + gchar* target = g_array_index(controller->addresses, char*, 0); |
2980 | + gboolean needs_ssl = check_ovs_ssl(target); |
2981 | + GString* s = g_string_new(target); |
2982 | + |
2983 | + for (unsigned i = 1; i < controller->addresses->len; ++i) { |
2984 | + target = g_array_index(controller->addresses, char*, i); |
2985 | + if (!needs_ssl) |
2986 | + needs_ssl = check_ovs_ssl(target); |
2987 | + g_string_append_printf(s, " %s", target); |
2988 | + } |
2989 | + |
2990 | + append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set-controller %s %s", bridge, s->str); |
2991 | + write_ovs_tag_setting(bridge, "Bridge", "global", "set-controller", s->str, cmds); |
2992 | + g_string_free(s, TRUE); |
2993 | +} |
2994 | + |
2995 | +/** |
2996 | + * Generate the OpenVSwitch systemd units for configuration of the selected netdef |
2997 | + * @rootdir: If not %NULL, generate configuration in this root directory |
2998 | + * (useful for testing). |
2999 | + */ |
3000 | +void |
3001 | +write_ovs_conf(const NetplanNetDefinition* def, const char* rootdir) |
3002 | +{ |
3003 | + GString* cmds = g_string_new(NULL); |
3004 | + gchar* dependency = NULL; |
3005 | + const char* type = netplan_type_to_table_name(def->type); |
3006 | + g_autofree char* base_config_path = NULL; |
3007 | + char* value = NULL; |
3008 | + |
3009 | + /* TODO: maybe dynamically query the ovs-vsctl tool path? */ |
3010 | + |
3011 | + /* For OVS specific settings, we expect the backend to be set to OVS. |
3012 | + * The OVS backend is implicitly set, if an interface contains an empty "openvswitch: {}" |
3013 | + * key, or an "openvswitch:" key, containing more than "external-ids" and/or "other-config". */ |
3014 | + if (def->backend == NETPLAN_BACKEND_OVS) { |
3015 | + switch (def->type) { |
3016 | + case NETPLAN_DEF_TYPE_BOND: |
3017 | + dependency = write_ovs_bond_interfaces(def, cmds); |
3018 | + write_ovs_tag_netplan(def->id, type, cmds); |
3019 | + /* Set LACP mode, default to "off" */ |
3020 | + value = def->ovs_settings.lacp? def->ovs_settings.lacp : "off"; |
3021 | + append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set Port %s lacp=%s", def->id, value); |
3022 | + write_ovs_tag_setting(def->id, type, "lacp", NULL, value, cmds); |
3023 | + if (def->bond_params.mode) { |
3024 | + write_ovs_bond_mode(def, cmds); |
3025 | + } |
3026 | + break; |
3027 | + |
3028 | + case NETPLAN_DEF_TYPE_BRIDGE: |
3029 | + write_ovs_bridge_interfaces(def, cmds); |
3030 | + write_ovs_tag_netplan(def->id, type, cmds); |
3031 | + /* Set fail-mode, default to "standalone" */ |
3032 | + value = def->ovs_settings.fail_mode? def->ovs_settings.fail_mode : "standalone"; |
3033 | + append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set-fail-mode %s %s", def->id, value); |
3034 | + write_ovs_tag_setting(def->id, type, "global", "set-fail-mode", value, cmds); |
3035 | + /* Enable/disable mcast-snooping */ |
3036 | + value = def->ovs_settings.mcast_snooping? "true" : "false"; |
3037 | + append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set Bridge %s mcast_snooping_enable=%s", def->id, value); |
3038 | + write_ovs_tag_setting(def->id, type, "mcast_snooping_enable", NULL, value, cmds); |
3039 | + /* Enable/disable rstp */ |
3040 | + value = def->ovs_settings.rstp? "true" : "false"; |
3041 | + append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set Bridge %s rstp_enable=%s", def->id, value); |
3042 | + write_ovs_tag_setting(def->id, type, "rstp_enable", NULL, value, cmds); |
3043 | + /* Set protocols */ |
3044 | + if (def->ovs_settings.protocols && def->ovs_settings.protocols->len > 0) { |
3045 | + write_ovs_protocols(&(def->ovs_settings), def->id, cmds); |
3046 | + } else if (ovs_settings_global.protocols && ovs_settings_global.protocols->len > 0) { |
3047 | + write_ovs_protocols(&(ovs_settings_global), def->id, cmds); |
3048 | + } |
3049 | + /* Set controller target addresses */ |
3050 | + if (def->ovs_settings.controller.addresses && def->ovs_settings.controller.addresses->len > 0) { |
3051 | + write_ovs_bridge_controller_targets(&(def->ovs_settings.controller), def->id, cmds); |
3052 | + /* Set controller connection mode, only applicable if at least one controller target address was set */ |
3053 | + if (def->ovs_settings.controller.connection_mode) { |
3054 | + value = def->ovs_settings.controller.connection_mode; |
3055 | + append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set Controller %s connection-mode=%s", def->id, value); |
3056 | + write_ovs_tag_setting(def->id, "Controller", "connection-mode", NULL, value, cmds); |
3057 | + } |
3058 | + } |
3059 | + break; |
3060 | + |
3061 | + case NETPLAN_DEF_TYPE_PORT: |
3062 | + g_assert(def->peer); |
3063 | + dependency = def->bridge?: def->bond; |
3064 | + if (!dependency) { |
3065 | + g_fprintf(stderr, "%s: OpenVSwitch patch port needs to be assigned to a bridge/bond\n", def->id); |
3066 | + exit(1); |
3067 | + } |
3068 | + /* There is no OVS Port which we could tag netplan=true if this |
3069 | + * patch port is assigned as an OVS bond interface. Tag the |
3070 | + * Interface instead, to clean it up from a bond. */ |
3071 | + if (def->bond) |
3072 | + write_ovs_tag_netplan(def->id, "Interface", cmds); |
3073 | + else |
3074 | + write_ovs_tag_netplan(def->id, type, cmds); |
3075 | + break; |
3076 | + |
3077 | + case NETPLAN_DEF_TYPE_VLAN: |
3078 | + g_assert(def->vlan_link); |
3079 | + dependency = def->vlan_link->id; |
3080 | + /* Create a fake VLAN bridge */ |
3081 | + append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " --may-exist add-br %s %s %i", def->id, def->vlan_link->id, def->vlan_id) |
3082 | + write_ovs_tag_netplan(def->id, type, cmds); |
3083 | + break; |
3084 | + |
3085 | + default: |
3086 | + g_fprintf(stderr, "%s: This device type is not supported with the OpenVSwitch backend\n", def->id); |
3087 | + exit(1); |
3088 | + break; |
3089 | + } |
3090 | + |
3091 | + /* Try writing out a base config */ |
3092 | + base_config_path = g_strjoin(NULL, "run/systemd/network/10-netplan-", def->id, NULL); |
3093 | + write_network_file(def, rootdir, base_config_path); |
3094 | + } else { |
3095 | + /* Other interfaces must be part of an OVS bridge or bond to carry additional data */ |
3096 | + if ( (def->ovs_settings.external_ids && g_hash_table_size(def->ovs_settings.external_ids) > 0) |
3097 | + || (def->ovs_settings.other_config && g_hash_table_size(def->ovs_settings.other_config) > 0)) { |
3098 | + dependency = def->bridge?: def->bond; |
3099 | + if (!dependency) { |
3100 | + g_fprintf(stderr, "%s: Interface needs to be assigned to an OVS bridge/bond to carry external-ids/other-config\n", def->id); |
3101 | + exit(1); |
3102 | + } |
3103 | + } else { |
3104 | + g_debug("openvswitch: definition %s is not for us (backend %i)", def->id, def->backend); |
3105 | + return; |
3106 | + } |
3107 | + } |
3108 | + |
3109 | + /* Set "external-ids" and "other-config" after NETPLAN_BACKEND_OVS interfaces, as bonds, |
3110 | + * bridges, etc. might just be created before.*/ |
3111 | + |
3112 | + /* Common OVS settings can be specified even for non-OVS interfaces */ |
3113 | + if (def->ovs_settings.external_ids && g_hash_table_size(def->ovs_settings.external_ids) > 0) { |
3114 | + write_ovs_additional_data(def->ovs_settings.external_ids, type, |
3115 | + def->id, cmds, "external-ids"); |
3116 | + } |
3117 | + |
3118 | + if (def->ovs_settings.other_config && g_hash_table_size(def->ovs_settings.other_config) > 0) { |
3119 | + write_ovs_additional_data(def->ovs_settings.other_config, type, |
3120 | + def->id, cmds, "other-config"); |
3121 | + } |
3122 | + |
3123 | + /* If we need to configure anything for this netdef, write the required systemd unit */ |
3124 | + if (cmds->len > 0) |
3125 | + write_ovs_systemd_unit(def->id, cmds, rootdir, netplan_type_is_physical(def->type), FALSE, dependency); |
3126 | + g_string_free(cmds, TRUE); |
3127 | +} |
3128 | + |
3129 | +/** |
3130 | + * Finalize the OpenVSwitch configuration (global config) |
3131 | + */ |
3132 | +void |
3133 | +write_ovs_conf_finish(const char* rootdir) |
3134 | +{ |
3135 | + GString* cmds = g_string_new(NULL); |
3136 | + |
3137 | + /* Global external-ids and other-config settings */ |
3138 | + if (ovs_settings_global.external_ids && g_hash_table_size(ovs_settings_global.external_ids) > 0) { |
3139 | + write_ovs_additional_data(ovs_settings_global.external_ids, "open_vswitch", |
3140 | + ".", cmds, "external-ids"); |
3141 | + } |
3142 | + |
3143 | + if (ovs_settings_global.other_config && g_hash_table_size(ovs_settings_global.other_config) > 0) { |
3144 | + write_ovs_additional_data(ovs_settings_global.other_config, "open_vswitch", |
3145 | + ".", cmds, "other-config"); |
3146 | + } |
3147 | + |
3148 | + if (ovs_settings_global.ssl.client_key && ovs_settings_global.ssl.client_certificate && |
3149 | + ovs_settings_global.ssl.ca_certificate) { |
3150 | + GString* value = g_string_new(NULL); |
3151 | + g_string_printf(value, "%s %s %s", |
3152 | + ovs_settings_global.ssl.client_key, |
3153 | + ovs_settings_global.ssl.client_certificate, |
3154 | + ovs_settings_global.ssl.ca_certificate); |
3155 | + append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set-ssl %s", value->str); |
3156 | + write_ovs_tag_setting(".", "open_vswitch", "global", "set-ssl", value->str, cmds); |
3157 | + g_string_free(value, TRUE); |
3158 | + } |
3159 | + |
3160 | + if (cmds->len > 0) |
3161 | + write_ovs_systemd_unit("global", cmds, rootdir, FALSE, FALSE, NULL); |
3162 | + g_string_free(cmds, TRUE); |
3163 | + |
3164 | + /* Clear all netplan=true tagged ports/bonds and bridges, via 'netplan apply --only-ovs-cleanup' */ |
3165 | + cmds = g_string_new(NULL); |
3166 | + append_systemd_cmd(cmds, "/usr/sbin/netplan apply %s", "--only-ovs-cleanup"); |
3167 | + write_ovs_systemd_unit("cleanup", cmds, rootdir, FALSE, TRUE, NULL); |
3168 | + g_string_free(cmds, TRUE); |
3169 | +} |
3170 | + |
3171 | +/** |
3172 | + * Clean up all generated configurations in @rootdir from previous runs. |
3173 | + */ |
3174 | +void |
3175 | +cleanup_ovs_conf(const char* rootdir) |
3176 | +{ |
3177 | + unlink_glob(rootdir, "/run/systemd/system/systemd-networkd.service.wants/netplan-ovs-*.service"); |
3178 | + unlink_glob(rootdir, "/run/systemd/system/netplan-ovs-*.service"); |
3179 | +} |
3180 | diff --git a/src/openvswitch.h b/src/openvswitch.h |
3181 | new file mode 100644 |
3182 | index 0000000..69bd6ee |
3183 | --- /dev/null |
3184 | +++ b/src/openvswitch.h |
3185 | @@ -0,0 +1,24 @@ |
3186 | +/* |
3187 | + * Copyright (C) 2020 Canonical, Ltd. |
3188 | + * Author: Łukasz 'sil2100' Zemczak <lukasz.zemczak@ubuntu.com> |
3189 | + * |
3190 | + * This program is free software; you can redistribute it and/or modify |
3191 | + * it under the terms of the GNU General Public License as published by |
3192 | + * the Free Software Foundation; version 3. |
3193 | + * |
3194 | + * This program is distributed in the hope that it will be useful, |
3195 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3196 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3197 | + * GNU General Public License for more details. |
3198 | + * |
3199 | + * You should have received a copy of the GNU General Public License |
3200 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
3201 | + */ |
3202 | + |
3203 | +#pragma once |
3204 | + |
3205 | +#include "parse.h" |
3206 | + |
3207 | +void write_ovs_conf(const NetplanNetDefinition* def, const char* rootdir); |
3208 | +void write_ovs_conf_finish(const char* rootdir); |
3209 | +void cleanup_ovs_conf(const char* rootdir); |
3210 | diff --git a/src/parse.c b/src/parse.c |
3211 | index c1dc175..2732f78 100644 |
3212 | --- a/src/parse.c |
3213 | +++ b/src/parse.c |
3214 | @@ -1,6 +1,7 @@ |
3215 | /* |
3216 | * Copyright (C) 2016 Canonical, Ltd. |
3217 | * Author: Martin Pitt <martin.pitt@ubuntu.com> |
3218 | + * Lukas Märdian <lukas.maerdian@canonical.com> |
3219 | * |
3220 | * This program is free software; you can redistribute it and/or modify |
3221 | * it under the terms of the GNU General Public License as published by |
3222 | @@ -31,11 +32,14 @@ |
3223 | #include "validation.h" |
3224 | |
3225 | /* convenience macro to put the offset of a NetplanNetDefinition field into "void* data" */ |
3226 | +#define access_point_offset(field) GUINT_TO_POINTER(offsetof(NetplanWifiAccessPoint, field)) |
3227 | +#define addr_option_offset(field) GUINT_TO_POINTER(offsetof(NetplanAddressOptions, field)) |
3228 | +#define auth_offset(field) GUINT_TO_POINTER(offsetof(NetplanAuthenticationSettings, field)) |
3229 | +#define ip_rule_offset(field) GUINT_TO_POINTER(offsetof(NetplanIPRule, field)) |
3230 | #define netdef_offset(field) GUINT_TO_POINTER(offsetof(NetplanNetDefinition, field)) |
3231 | +#define ovs_settings_offset(field) GUINT_TO_POINTER(offsetof(NetplanOVSSettings, field)) |
3232 | #define route_offset(field) GUINT_TO_POINTER(offsetof(NetplanIPRoute, field)) |
3233 | -#define ip_rule_offset(field) GUINT_TO_POINTER(offsetof(NetplanIPRule, field)) |
3234 | -#define auth_offset(field) GUINT_TO_POINTER(offsetof(NetplanAuthenticationSettings, field)) |
3235 | -#define access_point_offset(field) GUINT_TO_POINTER(offsetof(NetplanWifiAccessPoint, field)) |
3236 | +#define wireguard_peer_offset(field) GUINT_TO_POINTER(offsetof(NetplanWireguardPeer, field)) |
3237 | |
3238 | /* NetplanNetDefinition that is currently being processed */ |
3239 | static NetplanNetDefinition* cur_netdef; |
3240 | @@ -43,14 +47,22 @@ static NetplanNetDefinition* cur_netdef; |
3241 | /* NetplanWifiAccessPoint that is currently being processed */ |
3242 | static NetplanWifiAccessPoint* cur_access_point; |
3243 | |
3244 | -/* authentication options that are currently being processed */ |
3245 | +/* NetplanAuthenticationSettings that are currently being processed */ |
3246 | static NetplanAuthenticationSettings* cur_auth; |
3247 | |
3248 | +/* NetplanWireguardPeer that is currently being processed */ |
3249 | +static NetplanWireguardPeer* cur_wireguard_peer; |
3250 | + |
3251 | +static NetplanAddressOptions* cur_addr_option; |
3252 | + |
3253 | static NetplanIPRoute* cur_route; |
3254 | static NetplanIPRule* cur_ip_rule; |
3255 | |
3256 | static NetplanBackend backend_global, backend_cur_type; |
3257 | |
3258 | +/* global OpenVSwitch settings */ |
3259 | +NetplanOVSSettings ovs_settings_global; |
3260 | + |
3261 | /* Global ID → NetplanNetDefinition* map for all parsed config files */ |
3262 | GHashTable* netdefs; |
3263 | |
3264 | @@ -62,6 +74,11 @@ GList* netdefs_ordered; |
3265 | * existing definition */ |
3266 | static GHashTable* ids_in_file; |
3267 | |
3268 | +/* Global variables, defined in this file */ |
3269 | +int missing_ids_found; |
3270 | +const char* current_file; |
3271 | +GHashTable* missing_id; |
3272 | + |
3273 | /** |
3274 | * Load YAML file name into a yaml_document_t. |
3275 | * |
3276 | @@ -173,6 +190,57 @@ assert_valid_id(yaml_node_t* node, GError** error) |
3277 | return TRUE; |
3278 | } |
3279 | |
3280 | +static void |
3281 | +initialize_dhcp_overrides(NetplanDHCPOverrides* overrides) |
3282 | +{ |
3283 | + overrides->use_dns = TRUE; |
3284 | + overrides->use_domains = NULL; |
3285 | + overrides->use_ntp = TRUE; |
3286 | + overrides->send_hostname = TRUE; |
3287 | + overrides->use_hostname = TRUE; |
3288 | + overrides->use_mtu = TRUE; |
3289 | + overrides->use_routes = TRUE; |
3290 | + overrides->hostname = NULL; |
3291 | + overrides->metric = NETPLAN_METRIC_UNSPEC; |
3292 | +} |
3293 | + |
3294 | +static void |
3295 | +initialize_ovs_settings(NetplanOVSSettings* ovs_settings) |
3296 | +{ |
3297 | + ovs_settings->mcast_snooping = FALSE; |
3298 | + ovs_settings->rstp = FALSE; |
3299 | +} |
3300 | + |
3301 | +static NetplanNetDefinition* |
3302 | +netplan_netdef_new(const char* id, NetplanDefType type, NetplanBackend backend) |
3303 | +{ |
3304 | + /* create new network definition */ |
3305 | + cur_netdef = g_new0(NetplanNetDefinition, 1); |
3306 | + cur_netdef->type = type; |
3307 | + cur_netdef->backend = backend ?: NETPLAN_BACKEND_NONE; |
3308 | + cur_netdef->id = g_strdup(id); |
3309 | + |
3310 | + /* Set some default values */ |
3311 | + cur_netdef->vlan_id = G_MAXUINT; /* 0 is a valid ID */ |
3312 | + cur_netdef->tunnel.mode = NETPLAN_TUNNEL_MODE_UNKNOWN; |
3313 | + cur_netdef->dhcp_identifier = g_strdup("duid"); /* keep networkd's default */ |
3314 | + /* systemd-networkd defaults to IPv6 LL enabled; keep that default */ |
3315 | + cur_netdef->linklocal.ipv6 = TRUE; |
3316 | + cur_netdef->sriov_vlan_filter = FALSE; |
3317 | + cur_netdef->sriov_explicit_vf_count = G_MAXUINT; /* 0 is a valid number of VFs */ |
3318 | + |
3319 | + /* DHCP override defaults */ |
3320 | + initialize_dhcp_overrides(&cur_netdef->dhcp4_overrides); |
3321 | + initialize_dhcp_overrides(&cur_netdef->dhcp6_overrides); |
3322 | + |
3323 | + /* OpenVSwitch defaults */ |
3324 | + initialize_ovs_settings(&cur_netdef->ovs_settings); |
3325 | + |
3326 | + g_hash_table_insert(netdefs, cur_netdef->id, cur_netdef); |
3327 | + netdefs_ordered = g_list_append(netdefs_ordered, cur_netdef); |
3328 | + return cur_netdef; |
3329 | +} |
3330 | + |
3331 | /**************************************************** |
3332 | * Data types and functions for interpreting YAML nodes |
3333 | ****************************************************/ |
3334 | @@ -216,7 +284,7 @@ get_handler(const mapping_entry_handler* handlers, const char* key) |
3335 | * Returns: TRUE on success, FALSE on error (@error gets set then). |
3336 | */ |
3337 | static gboolean |
3338 | -process_mapping(yaml_document_t* doc, yaml_node_t* node, const mapping_entry_handler* handlers, GError** error) |
3339 | +process_mapping(yaml_document_t* doc, yaml_node_t* node, const mapping_entry_handler* handlers, GList** out_values, GError** error) |
3340 | { |
3341 | yaml_node_pair_t* entry; |
3342 | |
3343 | @@ -235,10 +303,12 @@ process_mapping(yaml_document_t* doc, yaml_node_t* node, const mapping_entry_han |
3344 | if (!h) |
3345 | return yaml_error(key, error, "unknown key '%s'", scalar(key)); |
3346 | assert_type(value, h->type); |
3347 | + if (out_values) |
3348 | + *out_values = g_list_prepend(*out_values, g_strdup(scalar(key))); |
3349 | if (h->map_handlers) { |
3350 | g_assert(h->handler == NULL); |
3351 | g_assert(h->type == YAML_MAPPING_NODE); |
3352 | - if (!process_mapping(doc, value, h->map_handlers, error)) |
3353 | + if (!process_mapping(doc, value, h->map_handlers, NULL, error)) |
3354 | return FALSE; |
3355 | } else { |
3356 | if (!h->handler(doc, value, h->data, error)) |
3357 | @@ -317,6 +387,65 @@ handle_generic_mac(yaml_document_t* doc, yaml_node_t* node, void* entryptr, cons |
3358 | return handle_generic_str(doc, node, entryptr, data, error); |
3359 | } |
3360 | |
3361 | +/* |
3362 | + * Handler for setting a boolean field from a scalar node, inside a given struct |
3363 | + * @entryptr: pointer to the beginning of the to-be-modified data structure |
3364 | + * @data: offset into entryptr struct where the boolean field to write is located |
3365 | + */ |
3366 | +static gboolean |
3367 | +handle_generic_bool(yaml_document_t* doc, yaml_node_t* node, void* entryptr, const void* data, GError** error) |
3368 | +{ |
3369 | + g_assert(entryptr); |
3370 | + guint offset = GPOINTER_TO_UINT(data); |
3371 | + gboolean v; |
3372 | + |
3373 | + if (g_ascii_strcasecmp(scalar(node), "true") == 0 || |
3374 | + g_ascii_strcasecmp(scalar(node), "on") == 0 || |
3375 | + g_ascii_strcasecmp(scalar(node), "yes") == 0 || |
3376 | + g_ascii_strcasecmp(scalar(node), "y") == 0) |
3377 | + v = TRUE; |
3378 | + else if (g_ascii_strcasecmp(scalar(node), "false") == 0 || |
3379 | + g_ascii_strcasecmp(scalar(node), "off") == 0 || |
3380 | + g_ascii_strcasecmp(scalar(node), "no") == 0 || |
3381 | + g_ascii_strcasecmp(scalar(node), "n") == 0) |
3382 | + v = FALSE; |
3383 | + else |
3384 | + return yaml_error(node, error, "invalid boolean value '%s'", scalar(node)); |
3385 | + |
3386 | + *((gboolean*) ((void*) entryptr + offset)) = v; |
3387 | + return TRUE; |
3388 | +} |
3389 | + |
3390 | +/* |
3391 | + * Handler for setting a HashTable field from a mapping node, inside a given struct |
3392 | + * @entryptr: pointer to the beginning of the to-be-modified data structure |
3393 | + * @data: offset into entryptr struct where the boolean field to write is located |
3394 | +*/ |
3395 | +static gboolean |
3396 | +handle_generic_map(yaml_document_t* doc, yaml_node_t* node, void* entryptr, const void* data, GError** error) |
3397 | +{ |
3398 | + guint offset = GPOINTER_TO_UINT(data); |
3399 | + GHashTable** map = (GHashTable**) ((void*) entryptr + offset); |
3400 | + if (!*map) |
3401 | + *map = g_hash_table_new(g_str_hash, g_str_equal); |
3402 | + |
3403 | + for (yaml_node_pair_t* entry = node->data.mapping.pairs.start; entry < node->data.mapping.pairs.top; entry++) { |
3404 | + yaml_node_t* key, *value; |
3405 | + |
3406 | + key = yaml_document_get_node(doc, entry->key); |
3407 | + value = yaml_document_get_node(doc, entry->value); |
3408 | + |
3409 | + assert_type(key, YAML_SCALAR_NODE); |
3410 | + assert_type(value, YAML_SCALAR_NODE); |
3411 | + |
3412 | + /* TODO: make sure we free all the memory here */ |
3413 | + if (!g_hash_table_insert(*map, g_strdup(scalar(key)), g_strdup(scalar(value)))) |
3414 | + return yaml_error(node, error, "duplicate map entry '%s'", scalar(key)); |
3415 | + } |
3416 | + |
3417 | + return TRUE; |
3418 | +} |
3419 | + |
3420 | /** |
3421 | * Generic handler for setting a cur_netdef string field from a scalar node |
3422 | * @data: offset into NetplanNetDefinition where the const char* field to write is |
3423 | @@ -343,7 +472,9 @@ handle_netdef_id(yaml_document_t* doc, yaml_node_t* node, const void* data, GErr |
3424 | |
3425 | /** |
3426 | * Generic handler for setting a cur_netdef ID/iface name field referring to an |
3427 | - * existing ID from a scalar node |
3428 | + * existing ID from a scalar node. This handler also includes a special case |
3429 | + * handler for OVS VLANs, switching the backend implicitly to OVS for such |
3430 | + * interfaces |
3431 | * @data: offset into NetplanNetDefinition where the NetplanNetDefinition* field to write is |
3432 | * located |
3433 | */ |
3434 | @@ -358,6 +489,11 @@ handle_netdef_id_ref(yaml_document_t* doc, yaml_node_t* node, const void* data, |
3435 | add_missing_node(node); |
3436 | } else { |
3437 | *((NetplanNetDefinition**) ((void*) cur_netdef + offset)) = ref; |
3438 | + |
3439 | + if (cur_netdef->type == NETPLAN_DEF_TYPE_VLAN && ref->backend == NETPLAN_BACKEND_OVS) { |
3440 | + g_debug("%s: VLAN defined for openvswitch interface, choosing OVS backend", cur_netdef->id); |
3441 | + cur_netdef->backend = NETPLAN_BACKEND_OVS; |
3442 | + } |
3443 | } |
3444 | return TRUE; |
3445 | } |
3446 | @@ -381,24 +517,7 @@ handle_netdef_mac(yaml_document_t* doc, yaml_node_t* node, const void* data, GEr |
3447 | static gboolean |
3448 | handle_netdef_bool(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
3449 | { |
3450 | - guint offset = GPOINTER_TO_UINT(data); |
3451 | - gboolean v; |
3452 | - |
3453 | - if (g_ascii_strcasecmp(scalar(node), "true") == 0 || |
3454 | - g_ascii_strcasecmp(scalar(node), "on") == 0 || |
3455 | - g_ascii_strcasecmp(scalar(node), "yes") == 0 || |
3456 | - g_ascii_strcasecmp(scalar(node), "y") == 0) |
3457 | - v = TRUE; |
3458 | - else if (g_ascii_strcasecmp(scalar(node), "false") == 0 || |
3459 | - g_ascii_strcasecmp(scalar(node), "off") == 0 || |
3460 | - g_ascii_strcasecmp(scalar(node), "no") == 0 || |
3461 | - g_ascii_strcasecmp(scalar(node), "n") == 0) |
3462 | - v = FALSE; |
3463 | - else |
3464 | - return yaml_error(node, error, "invalid boolean value '%s'", scalar(node)); |
3465 | - |
3466 | - *((gboolean*) ((void*) cur_netdef + offset)) = v; |
3467 | - return TRUE; |
3468 | + return handle_generic_bool(doc, node, cur_netdef, data, error); |
3469 | } |
3470 | |
3471 | /** |
3472 | @@ -484,6 +603,22 @@ handle_netdef_addrgen(yaml_document_t* doc, yaml_node_t* node, const void* _, GE |
3473 | return TRUE; |
3474 | } |
3475 | |
3476 | +static gboolean |
3477 | +handle_netdef_addrtok(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
3478 | +{ |
3479 | + g_assert(cur_netdef); |
3480 | + gboolean ret = handle_netdef_str(doc, node, data, error); |
3481 | + if (!is_ip6_address(cur_netdef->ip6_addr_gen_token)) |
3482 | + return yaml_error(node, error, "invalid ipv6-address-token '%s'", scalar(node)); |
3483 | + return ret; |
3484 | +} |
3485 | + |
3486 | +static gboolean |
3487 | +handle_netdef_map(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
3488 | +{ |
3489 | + g_assert(cur_netdef); |
3490 | + return handle_generic_map(doc, node, cur_netdef, data, error); |
3491 | +} |
3492 | |
3493 | /**************************************************** |
3494 | * Grammar and handlers for network config "match" entry |
3495 | @@ -585,6 +720,12 @@ handle_access_point_mac(yaml_document_t* doc, yaml_node_t* node, const void* dat |
3496 | } |
3497 | |
3498 | static gboolean |
3499 | +handle_access_point_bool(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
3500 | +{ |
3501 | + return handle_generic_bool(doc, node, cur_access_point, data, error); |
3502 | +} |
3503 | + |
3504 | +static gboolean |
3505 | handle_access_point_password(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) |
3506 | { |
3507 | g_assert(cur_access_point); |
3508 | @@ -605,7 +746,7 @@ handle_access_point_auth(yaml_document_t* doc, yaml_node_t* node, const void* _, |
3509 | cur_access_point->has_auth = TRUE; |
3510 | |
3511 | cur_auth = &cur_access_point->auth; |
3512 | - ret = process_mapping(doc, node, auth_handlers, error); |
3513 | + ret = process_mapping(doc, node, auth_handlers, NULL, error); |
3514 | cur_auth = NULL; |
3515 | |
3516 | return ret; |
3517 | @@ -642,6 +783,7 @@ handle_access_point_band(yaml_document_t* doc, yaml_node_t* node, const void* _, |
3518 | static const mapping_entry_handler wifi_access_point_handlers[] = { |
3519 | {"band", YAML_SCALAR_NODE, handle_access_point_band}, |
3520 | {"bssid", YAML_SCALAR_NODE, handle_access_point_mac, NULL, access_point_offset(bssid)}, |
3521 | + {"hidden", YAML_SCALAR_NODE, handle_access_point_bool, NULL, access_point_offset(hidden)}, |
3522 | {"channel", YAML_SCALAR_NODE, handle_access_point_guint, NULL, access_point_offset(channel)}, |
3523 | {"mode", YAML_SCALAR_NODE, handle_access_point_mode}, |
3524 | {"password", YAML_SCALAR_NODE, handle_access_point_password}, |
3525 | @@ -680,27 +822,19 @@ handle_netdef_renderer(yaml_document_t* doc, yaml_node_t* node, const void* _, G |
3526 | static gboolean |
3527 | handle_accept_ra(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
3528 | { |
3529 | - if (g_ascii_strcasecmp(scalar(node), "true") == 0 || |
3530 | - g_ascii_strcasecmp(scalar(node), "on") == 0 || |
3531 | - g_ascii_strcasecmp(scalar(node), "yes") == 0 || |
3532 | - g_ascii_strcasecmp(scalar(node), "y") == 0) |
3533 | + gboolean ret = handle_generic_bool(doc, node, cur_netdef, data, error); |
3534 | + if (cur_netdef->accept_ra) |
3535 | cur_netdef->accept_ra = NETPLAN_RA_MODE_ENABLED; |
3536 | - else if (g_ascii_strcasecmp(scalar(node), "false") == 0 || |
3537 | - g_ascii_strcasecmp(scalar(node), "off") == 0 || |
3538 | - g_ascii_strcasecmp(scalar(node), "no") == 0 || |
3539 | - g_ascii_strcasecmp(scalar(node), "n") == 0) |
3540 | - cur_netdef->accept_ra = NETPLAN_RA_MODE_DISABLED; |
3541 | else |
3542 | - return yaml_error(node, error, "invalid boolean value '%s'", scalar(node)); |
3543 | - |
3544 | - return TRUE; |
3545 | + cur_netdef->accept_ra = NETPLAN_RA_MODE_DISABLED; |
3546 | + return ret; |
3547 | } |
3548 | |
3549 | static gboolean |
3550 | handle_match(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) |
3551 | { |
3552 | cur_netdef->has_match = TRUE; |
3553 | - return process_mapping(doc, node, match_handlers, error); |
3554 | + return process_mapping(doc, node, match_handlers, NULL, error); |
3555 | } |
3556 | |
3557 | struct NetplanWifiWowlanType NETPLAN_WIFI_WOWLAN_TYPES[] = { |
3558 | @@ -747,20 +881,61 @@ handle_auth(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** err |
3559 | cur_netdef->has_auth = TRUE; |
3560 | |
3561 | cur_auth = &cur_netdef->auth; |
3562 | - ret = process_mapping(doc, node, auth_handlers, error); |
3563 | + ret = process_mapping(doc, node, auth_handlers, NULL, error); |
3564 | cur_auth = NULL; |
3565 | |
3566 | return ret; |
3567 | } |
3568 | |
3569 | static gboolean |
3570 | -handle_addresses(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) |
3571 | +handle_address_option_lifetime(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
3572 | { |
3573 | + if (g_ascii_strcasecmp(scalar(node), "0") != 0 && |
3574 | + g_ascii_strcasecmp(scalar(node), "forever") != 0) { |
3575 | + return yaml_error(node, error, "invalid lifetime value '%s'", scalar(node)); |
3576 | + } |
3577 | + return handle_generic_str(doc, node, cur_addr_option, data, error); |
3578 | +} |
3579 | + |
3580 | +static gboolean |
3581 | +handle_address_option_label(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
3582 | +{ |
3583 | + return handle_generic_str(doc, node, cur_addr_option, data, error); |
3584 | +} |
3585 | + |
3586 | +const mapping_entry_handler address_option_handlers[] = { |
3587 | + {"lifetime", YAML_SCALAR_NODE, handle_address_option_lifetime, NULL, addr_option_offset(lifetime)}, |
3588 | + {"label", YAML_SCALAR_NODE, handle_address_option_label, NULL, addr_option_offset(label)}, |
3589 | + {NULL} |
3590 | +}; |
3591 | + |
3592 | +/* |
3593 | + * Handler for setting an array of IP addresses from a sequence node, inside a given struct |
3594 | + * @entryptr: pointer to the beginning of the do-be-modified data structure |
3595 | + * @data: offset into entryptr struct where the array to write is located |
3596 | + */ |
3597 | +static gboolean |
3598 | +handle_generic_addresses(yaml_document_t* doc, yaml_node_t* node, gboolean check_zero_prefix, GArray** ip4, GArray** ip6, GError** error) |
3599 | +{ |
3600 | + g_assert(ip4); |
3601 | + g_assert(ip6); |
3602 | for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) { |
3603 | g_autofree char* addr = NULL; |
3604 | char* prefix_len; |
3605 | guint64 prefix_len_num; |
3606 | yaml_node_t *entry = yaml_document_get_node(doc, *i); |
3607 | + yaml_node_t *key = NULL; |
3608 | + yaml_node_t *value = NULL; |
3609 | + |
3610 | + if (entry->type != YAML_SCALAR_NODE && entry->type != YAML_MAPPING_NODE) { |
3611 | + return yaml_error(entry, error, "expected either scalar or mapping (check indentation)"); |
3612 | + } |
3613 | + |
3614 | + if (entry->type == YAML_MAPPING_NODE) { |
3615 | + key = yaml_document_get_node(doc, entry->data.mapping.pairs.start->key); |
3616 | + value = yaml_document_get_node(doc, entry->data.mapping.pairs.start->value); |
3617 | + entry = key; |
3618 | + } |
3619 | assert_type(entry, YAML_SCALAR_NODE); |
3620 | |
3621 | /* split off /prefix_len */ |
3622 | @@ -772,26 +947,62 @@ handle_addresses(yaml_document_t* doc, yaml_node_t* node, const void* _, GError* |
3623 | prefix_len++; /* skip former '/' into first char of prefix */ |
3624 | prefix_len_num = g_ascii_strtoull(prefix_len, NULL, 10); |
3625 | |
3626 | + if (value) { |
3627 | + if (!is_ip4_address(addr) && !is_ip6_address(addr)) |
3628 | + return yaml_error(node, error, "malformed address '%s', must be X.X.X.X/NN or X:X:X:X:X:X:X:X/NN", scalar(entry)); |
3629 | + |
3630 | + if (!cur_netdef->address_options) |
3631 | + cur_netdef->address_options = g_array_new(FALSE, FALSE, sizeof(NetplanAddressOptions*)); |
3632 | + |
3633 | + for (unsigned i = 0; i < cur_netdef->address_options->len; ++i) { |
3634 | + NetplanAddressOptions* opts = g_array_index(cur_netdef->address_options, NetplanAddressOptions*, i); |
3635 | + /* check for multi-pass parsing, return early if options for this address already exist */ |
3636 | + if (!g_strcmp0(scalar(key), opts->address)) |
3637 | + return TRUE; |
3638 | + } |
3639 | + |
3640 | + cur_addr_option = g_new0(NetplanAddressOptions, 1); |
3641 | + cur_addr_option->address = g_strdup(scalar(key)); |
3642 | + |
3643 | + if (!process_mapping(doc, value, address_option_handlers, NULL, error)) |
3644 | + return FALSE; |
3645 | + |
3646 | + g_array_append_val(cur_netdef->address_options, cur_addr_option); |
3647 | + continue; |
3648 | + } |
3649 | + |
3650 | /* is it an IPv4 address? */ |
3651 | if (is_ip4_address(addr)) { |
3652 | - if (prefix_len_num == 0 || prefix_len_num > 32) |
3653 | + if ((check_zero_prefix && prefix_len_num == 0) || prefix_len_num > 32) |
3654 | return yaml_error(node, error, "invalid prefix length in address '%s'", scalar(entry)); |
3655 | |
3656 | - if (!cur_netdef->ip4_addresses) |
3657 | - cur_netdef->ip4_addresses = g_array_new(FALSE, FALSE, sizeof(char*)); |
3658 | + if (!*ip4) |
3659 | + *ip4 = g_array_new(FALSE, FALSE, sizeof(char*)); |
3660 | + |
3661 | + /* Do not append the same IP (on multiple passes), if it is already contained */ |
3662 | + for (unsigned i = 0; i < (*ip4)->len; ++i) |
3663 | + if (!g_strcmp0(scalar(entry), g_array_index(*ip4, char*, i))) |
3664 | + goto skip_ip4; |
3665 | char* s = g_strdup(scalar(entry)); |
3666 | - g_array_append_val(cur_netdef->ip4_addresses, s); |
3667 | + g_array_append_val(*ip4, s); |
3668 | +skip_ip4: |
3669 | continue; |
3670 | } |
3671 | |
3672 | /* is it an IPv6 address? */ |
3673 | if (is_ip6_address(addr)) { |
3674 | - if (prefix_len_num == 0 || prefix_len_num > 128) |
3675 | + if ((check_zero_prefix && prefix_len_num == 0) || prefix_len_num > 128) |
3676 | return yaml_error(node, error, "invalid prefix length in address '%s'", scalar(entry)); |
3677 | - if (!cur_netdef->ip6_addresses) |
3678 | - cur_netdef->ip6_addresses = g_array_new(FALSE, FALSE, sizeof(char*)); |
3679 | + if (!*ip6) |
3680 | + *ip6 = g_array_new(FALSE, FALSE, sizeof(char*)); |
3681 | + |
3682 | + /* Do not append the same IP (on multiple passes), if it is already contained */ |
3683 | + for (unsigned i = 0; i < (*ip6)->len; ++i) |
3684 | + if (!g_strcmp0(scalar(entry), g_array_index(*ip6, char*, i))) |
3685 | + goto skip_ip6; |
3686 | char* s = g_strdup(scalar(entry)); |
3687 | - g_array_append_val(cur_netdef->ip6_addresses, s); |
3688 | + g_array_append_val(*ip6, s); |
3689 | +skip_ip6: |
3690 | continue; |
3691 | } |
3692 | |
3693 | @@ -802,6 +1013,12 @@ handle_addresses(yaml_document_t* doc, yaml_node_t* node, const void* _, GError* |
3694 | } |
3695 | |
3696 | static gboolean |
3697 | +handle_addresses(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) |
3698 | +{ |
3699 | + return handle_generic_addresses(doc, node, TRUE, &(cur_netdef->ip4_addresses), &(cur_netdef->ip6_addresses), error); |
3700 | +} |
3701 | + |
3702 | +static gboolean |
3703 | handle_gateway4(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) |
3704 | { |
3705 | if (!is_ip4_address(scalar(node))) |
3706 | @@ -847,7 +1064,7 @@ handle_wifi_access_points(yaml_document_t* doc, yaml_node_t* node, const void* d |
3707 | return ret; |
3708 | } |
3709 | |
3710 | - if (!process_mapping(doc, value, wifi_access_point_handlers, error)) { |
3711 | + if (!process_mapping(doc, value, wifi_access_point_handlers, NULL, error)) { |
3712 | cur_access_point = NULL; |
3713 | return FALSE; |
3714 | } |
3715 | @@ -881,7 +1098,11 @@ handle_bridge_interfaces(yaml_document_t* doc, yaml_node_t* node, const void* da |
3716 | if (component->bond) |
3717 | return yaml_error(node, error, "%s: interface '%s' is already assigned to bond %s", |
3718 | cur_netdef->id, scalar(entry), component->bond); |
3719 | - component->bridge = g_strdup(cur_netdef->id); |
3720 | + component->bridge = g_strdup(cur_netdef->id); |
3721 | + if (component->backend == NETPLAN_BACKEND_OVS) { |
3722 | + g_debug("%s: Bridge contains openvswitch interface, choosing OVS backend", cur_netdef->id); |
3723 | + cur_netdef->backend = NETPLAN_BACKEND_OVS; |
3724 | + } |
3725 | } |
3726 | } |
3727 | |
3728 | @@ -902,9 +1123,19 @@ handle_bond_mode(yaml_document_t* doc, yaml_node_t* node, const void* data, GErr |
3729 | strcmp(scalar(node), "broadcast") == 0 || |
3730 | strcmp(scalar(node), "802.3ad") == 0 || |
3731 | strcmp(scalar(node), "balance-tlb") == 0 || |
3732 | - strcmp(scalar(node), "balance-alb") == 0)) |
3733 | + strcmp(scalar(node), "balance-alb") == 0 || |
3734 | + strcmp(scalar(node), "balance-tcp") == 0 || // only supported for OVS |
3735 | + strcmp(scalar(node), "balance-slb") == 0)) // only supported for OVS |
3736 | return yaml_error(node, error, "unknown bond mode '%s'", scalar(node)); |
3737 | |
3738 | + /* Implicitly set NETPLAN_BACKEND_OVS if ovs-only mode selected */ |
3739 | + if (!strcmp(scalar(node), "balance-tcp") || |
3740 | + !strcmp(scalar(node), "balance-slb")) { |
3741 | + g_debug("%s: mode '%s' only supported with openvswitch, choosing this backend", |
3742 | + cur_netdef->id, scalar(node)); |
3743 | + cur_netdef->backend = NETPLAN_BACKEND_OVS; |
3744 | + } |
3745 | + |
3746 | return handle_netdef_str(doc, node, data, error); |
3747 | } |
3748 | |
3749 | @@ -932,6 +1163,10 @@ handle_bond_interfaces(yaml_document_t* doc, yaml_node_t* node, const void* data |
3750 | return yaml_error(node, error, "%s: interface '%s' is already assigned to bond %s", |
3751 | cur_netdef->id, scalar(entry), component->bond); |
3752 | component->bond = g_strdup(cur_netdef->id); |
3753 | + if (component->backend == NETPLAN_BACKEND_OVS) { |
3754 | + g_debug("%s: Bond contains openvswitch interface, choosing OVS backend", cur_netdef->id); |
3755 | + cur_netdef->backend = NETPLAN_BACKEND_OVS; |
3756 | + } |
3757 | } |
3758 | } |
3759 | |
3760 | @@ -1076,24 +1311,8 @@ check_and_set_family(int family, guint* dest) |
3761 | static gboolean |
3762 | handle_routes_bool(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
3763 | { |
3764 | - guint offset = GPOINTER_TO_UINT(data); |
3765 | - gboolean v; |
3766 | - |
3767 | - if (g_ascii_strcasecmp(scalar(node), "true") == 0 || |
3768 | - g_ascii_strcasecmp(scalar(node), "on") == 0 || |
3769 | - g_ascii_strcasecmp(scalar(node), "yes") == 0 || |
3770 | - g_ascii_strcasecmp(scalar(node), "y") == 0) |
3771 | - v = TRUE; |
3772 | - else if (g_ascii_strcasecmp(scalar(node), "false") == 0 || |
3773 | - g_ascii_strcasecmp(scalar(node), "off") == 0 || |
3774 | - g_ascii_strcasecmp(scalar(node), "no") == 0 || |
3775 | - g_ascii_strcasecmp(scalar(node), "n") == 0) |
3776 | - v = FALSE; |
3777 | - else |
3778 | - return yaml_error(node, error, "invalid boolean value '%s'", scalar(node)); |
3779 | - |
3780 | - *((gboolean*) ((void*) cur_route + offset)) = v; |
3781 | - return TRUE; |
3782 | + g_assert(cur_route); |
3783 | + return handle_generic_bool(doc, node, cur_route, data, error); |
3784 | } |
3785 | |
3786 | static gboolean |
3787 | @@ -1166,87 +1385,26 @@ handle_ip_rule_ip(yaml_document_t* doc, yaml_node_t* node, const void* data, GEr |
3788 | } |
3789 | |
3790 | static gboolean |
3791 | -handle_ip_rule_prio(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
3792 | +handle_ip_rule_guint(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
3793 | { |
3794 | - guint64 v; |
3795 | - gchar* endptr; |
3796 | - |
3797 | - v = g_ascii_strtoull(scalar(node), &endptr, 10); |
3798 | - if (*endptr != '\0' || v > G_MAXUINT) |
3799 | - return yaml_error(node, error, "invalid priority value '%s'", scalar(node)); |
3800 | - |
3801 | - cur_ip_rule->priority = (guint) v; |
3802 | - return TRUE; |
3803 | + g_assert(cur_ip_rule); |
3804 | + return handle_generic_guint(doc, node, cur_ip_rule, data, error); |
3805 | } |
3806 | |
3807 | static gboolean |
3808 | -handle_ip_rule_tos(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
3809 | +handle_routes_guint(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
3810 | { |
3811 | - guint64 v; |
3812 | - gchar* endptr; |
3813 | - |
3814 | - v = g_ascii_strtoull(scalar(node), &endptr, 10); |
3815 | - if (*endptr != '\0' || v > 255) |
3816 | - return yaml_error(node, error, "invalid ToS (must be between 0 and 255): %s", scalar(node)); |
3817 | - |
3818 | - cur_ip_rule->tos = (guint) v; |
3819 | - return TRUE; |
3820 | -} |
3821 | - |
3822 | -static gboolean |
3823 | -handle_routes_table(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
3824 | -{ |
3825 | - guint64 v; |
3826 | - gchar* endptr; |
3827 | - |
3828 | - v = g_ascii_strtoull(scalar(node), &endptr, 10); |
3829 | - if (*endptr != '\0' || v > G_MAXUINT) |
3830 | - return yaml_error(node, error, "invalid routing table '%s'", scalar(node)); |
3831 | - |
3832 | - cur_route->table = (guint) v; |
3833 | - return TRUE; |
3834 | -} |
3835 | - |
3836 | -static gboolean |
3837 | -handle_ip_rule_table(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
3838 | -{ |
3839 | - guint64 v; |
3840 | - gchar* endptr; |
3841 | - |
3842 | - v = g_ascii_strtoull(scalar(node), &endptr, 10); |
3843 | - if (*endptr != '\0' || v > G_MAXUINT) |
3844 | - return yaml_error(node, error, "invalid routing table '%s'", scalar(node)); |
3845 | - |
3846 | - cur_ip_rule->table = (guint) v; |
3847 | - return TRUE; |
3848 | -} |
3849 | - |
3850 | -static gboolean |
3851 | -handle_ip_rule_fwmark(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
3852 | -{ |
3853 | - guint64 v; |
3854 | - gchar* endptr; |
3855 | - |
3856 | - v = g_ascii_strtoull(scalar(node), &endptr, 10); |
3857 | - if (*endptr != '\0' || v > G_MAXUINT) |
3858 | - return yaml_error(node, error, "invalid fwmark value '%s'", scalar(node)); |
3859 | - |
3860 | - cur_ip_rule->fwmark = (guint) v; |
3861 | - return TRUE; |
3862 | + g_assert(cur_route); |
3863 | + return handle_generic_guint(doc, node, cur_route, data, error); |
3864 | } |
3865 | |
3866 | static gboolean |
3867 | -handle_routes_metric(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) |
3868 | +handle_ip_rule_tos(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
3869 | { |
3870 | - guint64 v; |
3871 | - gchar* endptr; |
3872 | - |
3873 | - v = g_ascii_strtoull(scalar(node), &endptr, 10); |
3874 | - if (*endptr != '\0' || v > G_MAXUINT) |
3875 | - return yaml_error(node, error, "invalid unsigned int value '%s'", scalar(node)); |
3876 | - |
3877 | - cur_route->metric = (guint) v; |
3878 | - return TRUE; |
3879 | + gboolean ret = handle_generic_guint(doc, node, cur_ip_rule, data, error); |
3880 | + if (cur_ip_rule->tos > 255) |
3881 | + return yaml_error(node, error, "invalid ToS (must be between 0 and 255): %s", scalar(node)); |
3882 | + return ret; |
3883 | } |
3884 | |
3885 | /**************************************************** |
3886 | @@ -1343,7 +1501,7 @@ handle_bridge(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** e |
3887 | { |
3888 | cur_netdef->custom_bridging = TRUE; |
3889 | cur_netdef->bridge_params.stp = TRUE; |
3890 | - return process_mapping(doc, node, bridge_params_handlers, error); |
3891 | + return process_mapping(doc, node, bridge_params_handlers, NULL, error); |
3892 | } |
3893 | |
3894 | /**************************************************** |
3895 | @@ -1354,44 +1512,57 @@ static const mapping_entry_handler routes_handlers[] = { |
3896 | {"from", YAML_SCALAR_NODE, handle_routes_ip, NULL, route_offset(from)}, |
3897 | {"on-link", YAML_SCALAR_NODE, handle_routes_bool, NULL, route_offset(onlink)}, |
3898 | {"scope", YAML_SCALAR_NODE, handle_routes_scope}, |
3899 | - {"table", YAML_SCALAR_NODE, handle_routes_table}, |
3900 | + {"table", YAML_SCALAR_NODE, handle_routes_guint, NULL, route_offset(table)}, |
3901 | {"to", YAML_SCALAR_NODE, handle_routes_ip, NULL, route_offset(to)}, |
3902 | {"type", YAML_SCALAR_NODE, handle_routes_type}, |
3903 | {"via", YAML_SCALAR_NODE, handle_routes_ip, NULL, route_offset(via)}, |
3904 | - {"metric", YAML_SCALAR_NODE, handle_routes_metric}, |
3905 | + {"metric", YAML_SCALAR_NODE, handle_routes_guint, NULL, route_offset(metric)}, |
3906 | {NULL} |
3907 | }; |
3908 | |
3909 | static gboolean |
3910 | handle_routes(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) |
3911 | { |
3912 | + if (!cur_netdef->routes) |
3913 | + cur_netdef->routes = g_array_new(FALSE, TRUE, sizeof(NetplanIPRoute*)); |
3914 | + |
3915 | + /* Avoid adding the same routes in a 2nd parsing pass by comparing |
3916 | + * the array size to the YAML sequence size. Skip if they are equal. */ |
3917 | + guint item_count = node->data.sequence.items.top - node->data.sequence.items.start; |
3918 | + if (cur_netdef->routes->len == item_count) { |
3919 | + g_debug("%s: all routes have already been added", cur_netdef->id); |
3920 | + return TRUE; |
3921 | + } |
3922 | + |
3923 | for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) { |
3924 | yaml_node_t *entry = yaml_document_get_node(doc, *i); |
3925 | + assert_type(entry, YAML_MAPPING_NODE); |
3926 | |
3927 | + g_assert(cur_route == NULL); |
3928 | cur_route = g_new0(NetplanIPRoute, 1); |
3929 | cur_route->type = g_strdup("unicast"); |
3930 | cur_route->scope = g_strdup("global"); |
3931 | cur_route->family = G_MAXUINT; /* 0 is a valid family ID */ |
3932 | cur_route->metric = NETPLAN_METRIC_UNSPEC; /* 0 is a valid metric */ |
3933 | + g_debug("%s: adding new route", cur_netdef->id); |
3934 | |
3935 | - if (process_mapping(doc, entry, routes_handlers, error)) { |
3936 | - if (!cur_netdef->routes) { |
3937 | - cur_netdef->routes = g_array_new(FALSE, FALSE, sizeof(NetplanIPRoute*)); |
3938 | - } |
3939 | - |
3940 | + if (process_mapping(doc, entry, routes_handlers, NULL, error)) |
3941 | g_array_append_val(cur_netdef->routes, cur_route); |
3942 | - } |
3943 | |
3944 | if ( ( g_ascii_strcasecmp(cur_route->scope, "link") == 0 |
3945 | || g_ascii_strcasecmp(cur_route->scope, "host") == 0) |
3946 | - && !cur_route->to) |
3947 | + && !cur_route->to) { |
3948 | + cur_route = NULL; |
3949 | return yaml_error(node, error, "link and host routes must specify a 'to' IP"); |
3950 | - else if ( g_ascii_strcasecmp(cur_route->type, "unicast") == 0 |
3951 | + } else if ( g_ascii_strcasecmp(cur_route->type, "unicast") == 0 |
3952 | && g_ascii_strcasecmp(cur_route->scope, "global") == 0 |
3953 | - && (!cur_route->to || !cur_route->via)) |
3954 | + && (!cur_route->to || !cur_route->via)) { |
3955 | + cur_route = NULL; |
3956 | return yaml_error(node, error, "unicast route must include both a 'to' and 'via' IP"); |
3957 | - else if (g_ascii_strcasecmp(cur_route->type, "unicast") != 0 && !cur_route->to) |
3958 | + } else if (g_ascii_strcasecmp(cur_route->type, "unicast") != 0 && !cur_route->to) { |
3959 | + cur_route = NULL; |
3960 | return yaml_error(node, error, "non-unicast routes must specify a 'to' IP"); |
3961 | + } |
3962 | |
3963 | cur_route = NULL; |
3964 | |
3965 | @@ -1403,11 +1574,11 @@ handle_routes(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** e |
3966 | |
3967 | static const mapping_entry_handler ip_rules_handlers[] = { |
3968 | {"from", YAML_SCALAR_NODE, handle_ip_rule_ip, NULL, ip_rule_offset(from)}, |
3969 | - {"mark", YAML_SCALAR_NODE, handle_ip_rule_fwmark}, |
3970 | - {"priority", YAML_SCALAR_NODE, handle_ip_rule_prio}, |
3971 | - {"table", YAML_SCALAR_NODE, handle_ip_rule_table}, |
3972 | + {"mark", YAML_SCALAR_NODE, handle_ip_rule_guint, NULL, ip_rule_offset(fwmark)}, |
3973 | + {"priority", YAML_SCALAR_NODE, handle_ip_rule_guint, NULL, ip_rule_offset(priority)}, |
3974 | + {"table", YAML_SCALAR_NODE, handle_ip_rule_guint, NULL, ip_rule_offset(table)}, |
3975 | {"to", YAML_SCALAR_NODE, handle_ip_rule_ip, NULL, ip_rule_offset(to)}, |
3976 | - {"type-of-service", YAML_SCALAR_NODE, handle_ip_rule_tos}, |
3977 | + {"type-of-service", YAML_SCALAR_NODE, handle_ip_rule_tos, NULL, ip_rule_offset(tos)}, |
3978 | {NULL} |
3979 | }; |
3980 | |
3981 | @@ -1424,7 +1595,7 @@ handle_ip_rules(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** |
3982 | cur_ip_rule->tos = NETPLAN_IP_RULE_TOS_UNSPEC; |
3983 | cur_ip_rule->fwmark = NETPLAN_IP_RULE_FW_MARK_UNSPEC; |
3984 | |
3985 | - if (process_mapping(doc, entry, ip_rules_handlers, error)) { |
3986 | + if (process_mapping(doc, entry, ip_rules_handlers, NULL, error)) { |
3987 | if (!cur_netdef->ip_rules) { |
3988 | cur_netdef->ip_rules = g_array_new(FALSE, FALSE, sizeof(NetplanIPRule*)); |
3989 | } |
3990 | @@ -1482,7 +1653,10 @@ handle_bond_primary_slave(yaml_document_t* doc, yaml_node_t* node, const void* d |
3991 | if (!component) { |
3992 | add_missing_node(node); |
3993 | } else { |
3994 | - if (cur_netdef->bond_params.primary_slave) |
3995 | + /* If this is not the primary pass, the primary slave might already be equally set. */ |
3996 | + if (!g_strcmp0(cur_netdef->bond_params.primary_slave, scalar(node))) { |
3997 | + return TRUE; |
3998 | + } else if (cur_netdef->bond_params.primary_slave) |
3999 | return yaml_error(node, error, "%s: bond already has a primary slave: %s", |
4000 | cur_netdef->id, cur_netdef->bond_params.primary_slave); |
4001 | |
4002 | @@ -1525,7 +1699,7 @@ static const mapping_entry_handler bond_params_handlers[] = { |
4003 | static gboolean |
4004 | handle_bonding(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) |
4005 | { |
4006 | - return process_mapping(doc, node, bond_params_handlers, error); |
4007 | + return process_mapping(doc, node, bond_params_handlers, NULL, error); |
4008 | } |
4009 | |
4010 | static gboolean |
4011 | @@ -1592,56 +1766,155 @@ handle_tunnel_mode(yaml_document_t* doc, yaml_node_t* node, const void* _, GErro |
4012 | return yaml_error(node, error, "%s: tunnel mode '%s' is not supported", cur_netdef->id, key); |
4013 | } |
4014 | |
4015 | +static const mapping_entry_handler tunnel_keys_handlers[] = { |
4016 | + {"input", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(tunnel.input_key)}, |
4017 | + {"output", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(tunnel.output_key)}, |
4018 | + {"private", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(tunnel.private_key)}, |
4019 | + {NULL} |
4020 | +}; |
4021 | + |
4022 | static gboolean |
4023 | -handle_tunnel_key(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
4024 | +handle_tunnel_key_mapping(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) |
4025 | { |
4026 | - /* Tunnel key should be a number or dotted quad. */ |
4027 | - guint offset = GPOINTER_TO_UINT(data); |
4028 | - char** dest = (char**) ((void*) cur_netdef + offset); |
4029 | - guint64 v; |
4030 | - gchar* endptr; |
4031 | + gboolean ret = FALSE; |
4032 | |
4033 | - v = g_ascii_strtoull(scalar(node), &endptr, 10); |
4034 | - if (*endptr != '\0' || v > G_MAXUINT) { |
4035 | - /* Not a simple uint, try for a dotted quad */ |
4036 | - if (!is_ip4_address(scalar(node))) |
4037 | - return yaml_error(node, error, "invalid tunnel key '%s'", scalar(node)); |
4038 | + /* We overload the 'key[s]' setting for tunnels; such that it can either be a |
4039 | + * single scalar with the same key to use for both input, output and private |
4040 | + * keys, or a mapping where one can specify each. */ |
4041 | + if (node->type == YAML_SCALAR_NODE) { |
4042 | + ret = handle_netdef_str(doc, node, netdef_offset(tunnel.input_key), error); |
4043 | + if (ret) |
4044 | + ret = handle_netdef_str(doc, node, netdef_offset(tunnel.output_key), error); |
4045 | + if (ret) |
4046 | + ret = handle_netdef_str(doc, node, netdef_offset(tunnel.private_key), error); |
4047 | + } else if (node->type == YAML_MAPPING_NODE) |
4048 | + ret = process_mapping(doc, node, tunnel_keys_handlers, NULL, error); |
4049 | + else |
4050 | + return yaml_error(node, error, "invalid type for 'key[s]': must be a scalar or mapping"); |
4051 | + |
4052 | + return ret; |
4053 | +} |
4054 | + |
4055 | +/** |
4056 | + * Handler for setting a NetplanWireguardPeer string field from a scalar node |
4057 | + * @data: pointer to the const char* field to write |
4058 | + */ |
4059 | +static gboolean |
4060 | +handle_wireguard_peer_str(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
4061 | +{ |
4062 | + g_assert(cur_wireguard_peer); |
4063 | + return handle_generic_str(doc, node, cur_wireguard_peer, data, error); |
4064 | +} |
4065 | + |
4066 | +/** |
4067 | + * Handler for setting a NetplanWireguardPeer string field from a scalar node |
4068 | + * @data: pointer to the guint field to write |
4069 | + */ |
4070 | +static gboolean |
4071 | +handle_wireguard_peer_guint(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
4072 | +{ |
4073 | + g_assert(cur_wireguard_peer); |
4074 | + return handle_generic_guint(doc, node, cur_wireguard_peer, data, error); |
4075 | +} |
4076 | + |
4077 | +static gboolean |
4078 | +handle_wireguard_allowed_ips(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) |
4079 | +{ |
4080 | + return handle_generic_addresses(doc, node, FALSE, &(cur_wireguard_peer->allowed_ips), |
4081 | + &(cur_wireguard_peer->allowed_ips), error); |
4082 | +} |
4083 | + |
4084 | +static gboolean |
4085 | +handle_wireguard_endpoint(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) |
4086 | +{ |
4087 | + g_autofree char* endpoint = NULL; |
4088 | + char* port; |
4089 | + char* address; |
4090 | + guint64 port_num; |
4091 | + |
4092 | + endpoint = g_strdup(scalar(node)); |
4093 | + /* absolute minimal length of endpoint is 3 chars: 'h:8' */ |
4094 | + if (strlen(endpoint) < 3) { |
4095 | + return yaml_error(node, error, "invalid endpoint address or hostname '%s'", scalar(node)); |
4096 | + } |
4097 | + if (endpoint[0] == '[') { |
4098 | + /* this is an ipv6 endpoint in [ad:rr:ee::ss]:port form */ |
4099 | + char *endbrace = strrchr(endpoint, ']'); |
4100 | + if (!endbrace) |
4101 | + return yaml_error(node, error, "invalid address in endpoint '%s'", scalar(node)); |
4102 | + address = endpoint + 1; |
4103 | + *endbrace = '\0'; |
4104 | + port = strrchr(endbrace + 1, ':'); |
4105 | + } else { |
4106 | + address = endpoint; |
4107 | + port = strrchr(endpoint, ':'); |
4108 | + } |
4109 | + /* split off :port */ |
4110 | + if (!port) |
4111 | + return yaml_error(node, error, "endpoint '%s' is missing :port", scalar(node)); |
4112 | + *port = '\0'; |
4113 | + port++; /* skip former ':' into first char of port */ |
4114 | + port_num = g_ascii_strtoull(port, NULL, 10); |
4115 | + if (port_num > 65535) |
4116 | + return yaml_error(node, error, "invalid port in endpoint '%s'", scalar(node)); |
4117 | + if (is_ip4_address(address) || is_ip6_address(address) || is_hostname(address)) { |
4118 | + return handle_wireguard_peer_str(doc, node, wireguard_peer_offset(endpoint), error); |
4119 | } |
4120 | + return yaml_error(node, error, "invalid endpoint address or hostname '%s'", scalar(node)); |
4121 | +} |
4122 | |
4123 | - g_free(*dest); |
4124 | - *dest = g_strdup(scalar(node)); |
4125 | +static const mapping_entry_handler wireguard_peer_keys_handlers[] = { |
4126 | + {"public", YAML_SCALAR_NODE, handle_wireguard_peer_str, NULL, wireguard_peer_offset(public_key)}, |
4127 | + {"shared", YAML_SCALAR_NODE, handle_wireguard_peer_str, NULL, wireguard_peer_offset(preshared_key)}, |
4128 | + {NULL} |
4129 | +}; |
4130 | |
4131 | - return TRUE; |
4132 | +static gboolean |
4133 | +handle_wireguard_peer_key_mapping(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) |
4134 | +{ |
4135 | + return process_mapping(doc, node, wireguard_peer_keys_handlers, NULL, error); |
4136 | } |
4137 | |
4138 | -static const mapping_entry_handler tunnel_keys_handlers[] = { |
4139 | - {"input", YAML_SCALAR_NODE, handle_tunnel_key, NULL, netdef_offset(tunnel.input_key)}, |
4140 | - {"output", YAML_SCALAR_NODE, handle_tunnel_key, NULL, netdef_offset(tunnel.output_key)}, |
4141 | +const mapping_entry_handler wireguard_peer_handlers[] = { |
4142 | + {"keys", YAML_MAPPING_NODE, handle_wireguard_peer_key_mapping}, |
4143 | + {"keepalive", YAML_SCALAR_NODE, handle_wireguard_peer_guint, NULL, wireguard_peer_offset(keepalive)}, |
4144 | + {"endpoint", YAML_SCALAR_NODE, handle_wireguard_endpoint}, |
4145 | + {"allowed-ips", YAML_SEQUENCE_NODE, handle_wireguard_allowed_ips}, |
4146 | {NULL} |
4147 | }; |
4148 | |
4149 | static gboolean |
4150 | -handle_tunnel_key_mapping(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) |
4151 | +handle_wireguard_peers(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) |
4152 | { |
4153 | - gboolean ret = FALSE; |
4154 | + if (!cur_netdef->wireguard_peers) |
4155 | + cur_netdef->wireguard_peers = g_array_new(FALSE, TRUE, sizeof(NetplanWireguardPeer*)); |
4156 | |
4157 | - /* We overload the key 'key' for tunnels; such that it can either be a |
4158 | - * single scalar with the same key to use for both input and output keys, |
4159 | - * or a mapping where one can specify each. |
4160 | - */ |
4161 | - if (node->type == YAML_SCALAR_NODE) { |
4162 | - ret = handle_tunnel_key(doc, node, netdef_offset(tunnel.input_key), error); |
4163 | - if (ret) |
4164 | - ret = handle_tunnel_key(doc, node, netdef_offset(tunnel.output_key), error); |
4165 | - } |
4166 | - else if (node->type == YAML_MAPPING_NODE) { |
4167 | - ret = process_mapping(doc, node, tunnel_keys_handlers, error); |
4168 | - } |
4169 | - else { |
4170 | - return yaml_error(node, error, "invalid type for 'keys': must be a scalar or mapping"); |
4171 | + /* Avoid adding the same peers in a 2nd parsing pass by comparing |
4172 | + * the array size to the YAML sequence size. Skip if they are equal. */ |
4173 | + guint item_count = node->data.sequence.items.top - node->data.sequence.items.start; |
4174 | + if (cur_netdef->wireguard_peers->len == item_count) { |
4175 | + g_debug("%s: all wireguard peers have already been added", cur_netdef->id); |
4176 | + return TRUE; |
4177 | } |
4178 | |
4179 | - return ret; |
4180 | + for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) { |
4181 | + g_autofree char* addr = NULL; |
4182 | + yaml_node_t *entry = yaml_document_get_node(doc, *i); |
4183 | + assert_type(entry, YAML_MAPPING_NODE); |
4184 | + |
4185 | + g_assert(cur_wireguard_peer == NULL); |
4186 | + cur_wireguard_peer = g_new0(NetplanWireguardPeer, 1); |
4187 | + cur_wireguard_peer->allowed_ips = g_array_new(FALSE, FALSE, sizeof(char*)); |
4188 | + g_debug("%s: adding new wireguard peer", cur_netdef->id); |
4189 | + |
4190 | + g_array_append_val(cur_netdef->wireguard_peers, cur_wireguard_peer); |
4191 | + if (!process_mapping(doc, entry, wireguard_peer_handlers, NULL, error)) { |
4192 | + cur_wireguard_peer = NULL; |
4193 | + return FALSE; |
4194 | + } |
4195 | + cur_wireguard_peer = NULL; |
4196 | + } |
4197 | + return TRUE; |
4198 | } |
4199 | |
4200 | /**************************************************** |
4201 | @@ -1656,6 +1929,187 @@ static const mapping_entry_handler nm_backend_settings_handlers[] = { |
4202 | {NULL} |
4203 | }; |
4204 | |
4205 | +static gboolean |
4206 | +handle_ovs_bond_lacp(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
4207 | +{ |
4208 | + if (cur_netdef->type != NETPLAN_DEF_TYPE_BOND) |
4209 | + return yaml_error(node, error, "Key 'lacp' is only valid for iterface type 'openvswitch bond'"); |
4210 | + |
4211 | + if (g_strcmp0(scalar(node), "active") && g_strcmp0(scalar(node), "passive") && g_strcmp0(scalar(node), "off")) |
4212 | + return yaml_error(node, error, "Value of 'lacp' needs to be 'active', 'passive' or 'off"); |
4213 | + |
4214 | + return handle_netdef_str(doc, node, data, error); |
4215 | +} |
4216 | + |
4217 | +static gboolean |
4218 | +handle_ovs_bridge_bool(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
4219 | +{ |
4220 | + if (cur_netdef->type != NETPLAN_DEF_TYPE_BRIDGE) |
4221 | + return yaml_error(node, error, "Key is only valid for iterface type 'openvswitch bridge'"); |
4222 | + |
4223 | + return handle_netdef_bool(doc, node, data, error); |
4224 | +} |
4225 | + |
4226 | +static gboolean |
4227 | +handle_ovs_bridge_fail_mode(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
4228 | +{ |
4229 | + if (cur_netdef->type != NETPLAN_DEF_TYPE_BRIDGE) |
4230 | + return yaml_error(node, error, "Key 'fail-mode' is only valid for iterface type 'openvswitch bridge'"); |
4231 | + |
4232 | + if (g_strcmp0(scalar(node), "standalone") && g_strcmp0(scalar(node), "secure")) |
4233 | + return yaml_error(node, error, "Value of 'fail-mode' needs to be 'standalone' or 'secure'"); |
4234 | + |
4235 | + return handle_netdef_str(doc, node, data, error); |
4236 | +} |
4237 | + |
4238 | +static gboolean |
4239 | +handle_ovs_protocol(yaml_document_t* doc, yaml_node_t* node, void* entryptr, const void* data, GError** error) |
4240 | +{ |
4241 | + const char* supported[] = { |
4242 | + "OpenFlow10", "OpenFlow11", "OpenFlow12", "OpenFlow13", "OpenFlow14", "OpenFlow15", "OpenFlow16", NULL |
4243 | + }; |
4244 | + unsigned i = 0; |
4245 | + guint offset = GPOINTER_TO_UINT(data); |
4246 | + GArray** protocols = (GArray**) ((void*) entryptr + offset); |
4247 | + |
4248 | + for (yaml_node_item_t *iter = node->data.sequence.items.start; iter < node->data.sequence.items.top; iter++) { |
4249 | + yaml_node_t *entry = yaml_document_get_node(doc, *iter); |
4250 | + assert_type(entry, YAML_SCALAR_NODE); |
4251 | + |
4252 | + for (i = 0; supported[i] != NULL; ++i) |
4253 | + if (!g_strcmp0(scalar(entry), supported[i])) |
4254 | + break; |
4255 | + |
4256 | + if (supported[i] == NULL) |
4257 | + return yaml_error(node, error, "Unsupported OVS 'protocol' value: %s", scalar(entry)); |
4258 | + |
4259 | + if (!*protocols) |
4260 | + *protocols = g_array_new(FALSE, FALSE, sizeof(char*)); |
4261 | + char* s = g_strdup(scalar(entry)); |
4262 | + g_array_append_val(*protocols, s); |
4263 | + } |
4264 | + |
4265 | + return TRUE; |
4266 | +} |
4267 | + |
4268 | +static gboolean |
4269 | +handle_ovs_bridge_protocol(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
4270 | +{ |
4271 | + if (cur_netdef->type != NETPLAN_DEF_TYPE_BRIDGE) |
4272 | + return yaml_error(node, error, "Key 'protocols' is only valid for iterface type 'openvswitch bridge'"); |
4273 | + |
4274 | + return handle_ovs_protocol(doc, node, cur_netdef, data, error); |
4275 | +} |
4276 | + |
4277 | +static gboolean |
4278 | +handle_ovs_bridge_controller_connection_mode(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
4279 | +{ |
4280 | + if (cur_netdef->type != NETPLAN_DEF_TYPE_BRIDGE) |
4281 | + return yaml_error(node, error, "Key 'controller.connection-mode' is only valid for iterface type 'openvswitch bridge'"); |
4282 | + |
4283 | + if (g_strcmp0(scalar(node), "in-band") && g_strcmp0(scalar(node), "out-of-band")) |
4284 | + return yaml_error(node, error, "Value of 'connection-mode' needs to be 'in-band' or 'out-of-band'"); |
4285 | + |
4286 | + return handle_netdef_str(doc, node, data, error); |
4287 | +} |
4288 | + |
4289 | +static gboolean |
4290 | +handle_ovs_bridge_controller_addresses(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
4291 | +{ |
4292 | + if (cur_netdef->type != NETPLAN_DEF_TYPE_BRIDGE) |
4293 | + return yaml_error(node, error, "Key 'controller.addresses' is only valid for iterface type 'openvswitch bridge'"); |
4294 | + |
4295 | + for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) { |
4296 | + gchar** vec = NULL; |
4297 | + gboolean is_host = FALSE; |
4298 | + gboolean is_port = FALSE; |
4299 | + gboolean is_unix = FALSE; |
4300 | + |
4301 | + yaml_node_t *entry = yaml_document_get_node(doc, *i); |
4302 | + assert_type(entry, YAML_SCALAR_NODE); |
4303 | + /* We always need at least one colon */ |
4304 | + if (!g_strrstr(scalar(entry), ":")) |
4305 | + return yaml_error(node, error, "Unsupported OVS controller target: %s", scalar(entry)); |
4306 | + |
4307 | + vec = g_strsplit (scalar(entry), ":", 2); |
4308 | + |
4309 | + is_host = !g_strcmp0(vec[0], "tcp") || !g_strcmp0(vec[0], "ssl"); |
4310 | + is_port = !g_strcmp0(vec[0], "ptcp") || !g_strcmp0(vec[0], "pssl"); |
4311 | + is_unix = !g_strcmp0(vec[0], "unix") || !g_strcmp0(vec[0], "punix"); |
4312 | + |
4313 | + if (!cur_netdef->ovs_settings.controller.addresses) |
4314 | + cur_netdef->ovs_settings.controller.addresses = g_array_new(FALSE, FALSE, sizeof(char*)); |
4315 | + |
4316 | + /* Format: [p]unix:file */ |
4317 | + if (is_unix && vec[1] != NULL && vec[2] == NULL) { |
4318 | + char* s = g_strdup(scalar(entry)); |
4319 | + g_array_append_val(cur_netdef->ovs_settings.controller.addresses, s); |
4320 | + g_strfreev(vec); |
4321 | + continue; |
4322 | + /* Format tcp:host[:port] or ssl:host[:port] */ |
4323 | + } else if (is_host && validate_ovs_target(TRUE, vec[1])) { |
4324 | + char* s = g_strdup(scalar(entry)); |
4325 | + g_array_append_val(cur_netdef->ovs_settings.controller.addresses, s); |
4326 | + g_strfreev(vec); |
4327 | + continue; |
4328 | + /* Format ptcp:[port][:host] or pssl:[port][:host] */ |
4329 | + } else if (is_port && validate_ovs_target(FALSE, vec[1])) { |
4330 | + char* s = g_strdup(scalar(entry)); |
4331 | + g_array_append_val(cur_netdef->ovs_settings.controller.addresses, s); |
4332 | + g_strfreev(vec); |
4333 | + continue; |
4334 | + } |
4335 | + |
4336 | + g_strfreev(vec); |
4337 | + return yaml_error(node, error, "Unsupported OVS controller target: %s", scalar(entry)); |
4338 | + } |
4339 | + |
4340 | + return TRUE; |
4341 | +} |
4342 | + |
4343 | +static const mapping_entry_handler ovs_controller_handlers[] = { |
4344 | + {"addresses", YAML_SEQUENCE_NODE, handle_ovs_bridge_controller_addresses, NULL, netdef_offset(ovs_settings.controller.addresses)}, |
4345 | + {"connection-mode", YAML_SCALAR_NODE, handle_ovs_bridge_controller_connection_mode, NULL, netdef_offset(ovs_settings.controller.connection_mode)}, |
4346 | + {NULL}, |
4347 | +}; |
4348 | + |
4349 | +static const mapping_entry_handler ovs_backend_settings_handlers[] = { |
4350 | + {"external-ids", YAML_MAPPING_NODE, handle_netdef_map, NULL, netdef_offset(ovs_settings.external_ids)}, |
4351 | + {"other-config", YAML_MAPPING_NODE, handle_netdef_map, NULL, netdef_offset(ovs_settings.other_config)}, |
4352 | + {"lacp", YAML_SCALAR_NODE, handle_ovs_bond_lacp, NULL, netdef_offset(ovs_settings.lacp)}, |
4353 | + {"fail-mode", YAML_SCALAR_NODE, handle_ovs_bridge_fail_mode, NULL, netdef_offset(ovs_settings.fail_mode)}, |
4354 | + {"mcast-snooping", YAML_SCALAR_NODE, handle_ovs_bridge_bool, NULL, netdef_offset(ovs_settings.mcast_snooping)}, |
4355 | + {"rstp", YAML_SCALAR_NODE, handle_ovs_bridge_bool, NULL, netdef_offset(ovs_settings.rstp)}, |
4356 | + {"protocols", YAML_SEQUENCE_NODE, handle_ovs_bridge_protocol, NULL, netdef_offset(ovs_settings.protocols)}, |
4357 | + {"controller", YAML_MAPPING_NODE, NULL, ovs_controller_handlers}, |
4358 | + {NULL} |
4359 | +}; |
4360 | + |
4361 | +static gboolean |
4362 | +handle_ovs_backend(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) |
4363 | +{ |
4364 | + GList* values = NULL; |
4365 | + gboolean ret = process_mapping(doc, node, ovs_backend_settings_handlers, &values, error); |
4366 | + guint len = g_list_length(values); |
4367 | + |
4368 | + if (cur_netdef->type != NETPLAN_DEF_TYPE_BOND && cur_netdef->type != NETPLAN_DEF_TYPE_BRIDGE) { |
4369 | + GList *other_config = g_list_find_custom(values, "other-config", (GCompareFunc) strcmp); |
4370 | + GList *external_ids = g_list_find_custom(values, "external-ids", (GCompareFunc) strcmp); |
4371 | + /* Non-bond/non-bridge interfaces might still be handled by the networkd backend */ |
4372 | + if (len == 1 && (other_config || external_ids)) |
4373 | + return ret; |
4374 | + else if (len == 2 && other_config && external_ids) |
4375 | + return ret; |
4376 | + } |
4377 | + g_list_free_full(values, g_free); |
4378 | + |
4379 | + /* Set the renderer for this device to NETPLAN_BACKEND_OVS, implicitly. |
4380 | + * But only if empty "openvswitch: {}" or "openvswitch:" with more than |
4381 | + * "other-config" or "external-ids" keys is given. */ |
4382 | + cur_netdef->backend = NETPLAN_BACKEND_OVS; |
4383 | + return ret; |
4384 | +} |
4385 | + |
4386 | static const mapping_entry_handler nameservers_handlers[] = { |
4387 | {"search", YAML_SEQUENCE_NODE, handle_nameservers_search}, |
4388 | {"addresses", YAML_SEQUENCE_NODE, handle_nameservers_addresses}, |
4389 | @@ -1668,7 +2122,7 @@ static const mapping_entry_handler nameservers_handlers[] = { |
4390 | {"route-metric", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(overrides.metric)}, \ |
4391 | {"send-hostname", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(overrides.send_hostname)}, \ |
4392 | {"use-dns", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(overrides.use_dns)}, \ |
4393 | - {"use-domains", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(overrides.use_domains)}, \ |
4394 | + {"use-domains", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(overrides.use_domains)}, \ |
4395 | {"use-hostname", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(overrides.use_hostname)}, \ |
4396 | {"use-mtu", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(overrides.use_mtu)}, \ |
4397 | {"use-ntp", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(overrides.use_ntp)}, \ |
4398 | @@ -1686,7 +2140,7 @@ static const mapping_entry_handler dhcp6_overrides_handlers[] = { |
4399 | |
4400 | /* Handlers shared by all link types */ |
4401 | #define COMMON_LINK_HANDLERS \ |
4402 | - {"accept-ra", YAML_SCALAR_NODE, handle_accept_ra}, \ |
4403 | + {"accept-ra", YAML_SCALAR_NODE, handle_accept_ra, NULL, netdef_offset(accept_ra)}, \ |
4404 | {"addresses", YAML_SEQUENCE_NODE, handle_addresses}, \ |
4405 | {"critical", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(critical)}, \ |
4406 | {"dhcp4", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(dhcp4)}, \ |
4407 | @@ -1696,7 +2150,8 @@ static const mapping_entry_handler dhcp6_overrides_handlers[] = { |
4408 | {"dhcp6-overrides", YAML_MAPPING_NODE, NULL, dhcp6_overrides_handlers}, \ |
4409 | {"gateway4", YAML_SCALAR_NODE, handle_gateway4}, \ |
4410 | {"gateway6", YAML_SCALAR_NODE, handle_gateway6}, \ |
4411 | - {"ipv6-address-generation", YAML_SCALAR_NODE, handle_netdef_addrgen}, \ |
4412 | + {"ipv6-address-generation", YAML_SCALAR_NODE, handle_netdef_addrgen}, \ |
4413 | + {"ipv6-address-token", YAML_SCALAR_NODE, handle_netdef_addrtok, NULL, netdef_offset(ip6_addr_gen_token)}, \ |
4414 | {"ipv6-mtu", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(ipv6_mtubytes)}, \ |
4415 | {"ipv6-privacy", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(ip6_privacy)}, \ |
4416 | {"link-local", YAML_SEQUENCE_NODE, handle_link_local}, \ |
4417 | @@ -1709,15 +2164,16 @@ static const mapping_entry_handler dhcp6_overrides_handlers[] = { |
4418 | {"routes", YAML_SEQUENCE_NODE, handle_routes}, \ |
4419 | {"routing-policy", YAML_SEQUENCE_NODE, handle_ip_rules} |
4420 | |
4421 | -#define COMMON_BACKEND_HANDLERS \ |
4422 | - {"networkmanager", YAML_MAPPING_NODE, NULL, nm_backend_settings_handlers} |
4423 | +#define COMMON_BACKEND_HANDLERS \ |
4424 | + {"networkmanager", YAML_MAPPING_NODE, NULL, nm_backend_settings_handlers}, \ |
4425 | + {"openvswitch", YAML_MAPPING_NODE, handle_ovs_backend} |
4426 | |
4427 | /* Handlers for physical links */ |
4428 | -#define PHYSICAL_LINK_HANDLERS \ |
4429 | - {"match", YAML_MAPPING_NODE, handle_match}, \ |
4430 | - {"set-name", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(set_name)}, \ |
4431 | - {"wakeonlan", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(wake_on_lan)}, \ |
4432 | - {"wakeonwlan", YAML_SEQUENCE_NODE, handle_wowlan, NULL, netdef_offset(wowlan)}, \ |
4433 | +#define PHYSICAL_LINK_HANDLERS \ |
4434 | + {"match", YAML_MAPPING_NODE, handle_match}, \ |
4435 | + {"set-name", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(set_name)}, \ |
4436 | + {"wakeonlan", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(wake_on_lan)}, \ |
4437 | + {"wakeonwlan", YAML_SEQUENCE_NODE, handle_wowlan, NULL, netdef_offset(wowlan)}, \ |
4438 | {"emit-lldp", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(emit_lldp)} |
4439 | |
4440 | static const mapping_entry_handler ethernet_def_handlers[] = { |
4441 | @@ -1789,6 +2245,11 @@ static const mapping_entry_handler tunnel_def_handlers[] = { |
4442 | */ |
4443 | {"key", YAML_NO_NODE, handle_tunnel_key_mapping}, |
4444 | {"keys", YAML_NO_NODE, handle_tunnel_key_mapping}, |
4445 | + |
4446 | + /* wireguard */ |
4447 | + {"mark", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(tunnel.fwmark)}, |
4448 | + {"port", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(tunnel.port)}, |
4449 | + {"peers", YAML_SEQUENCE_NODE, handle_wireguard_peers}, |
4450 | {NULL} |
4451 | }; |
4452 | |
4453 | @@ -1814,22 +2275,74 @@ handle_network_renderer(yaml_document_t* doc, yaml_node_t* node, const void* _, |
4454 | return parse_renderer(node, &backend_global, error); |
4455 | } |
4456 | |
4457 | -static void |
4458 | -initialize_dhcp_overrides(NetplanDHCPOverrides* overrides) |
4459 | +static gboolean |
4460 | +handle_network_ovs_settings_global(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
4461 | { |
4462 | - overrides->use_dns = TRUE; |
4463 | - overrides->use_domains = NULL; |
4464 | - overrides->use_ntp = TRUE; |
4465 | - overrides->send_hostname = TRUE; |
4466 | - overrides->use_hostname = TRUE; |
4467 | - overrides->use_mtu = TRUE; |
4468 | - overrides->use_routes = TRUE; |
4469 | - overrides->hostname = NULL; |
4470 | - overrides->metric = NETPLAN_METRIC_UNSPEC; |
4471 | + return handle_generic_map(doc, node, &ovs_settings_global, data, error); |
4472 | +} |
4473 | + |
4474 | +static gboolean |
4475 | +handle_network_ovs_settings_global_protocol(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
4476 | +{ |
4477 | + return handle_ovs_protocol(doc, node, &ovs_settings_global, data, error); |
4478 | +} |
4479 | + |
4480 | +static gboolean |
4481 | +handle_network_ovs_settings_global_ports(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
4482 | +{ |
4483 | + yaml_node_t* port = NULL; |
4484 | + yaml_node_t* peer = NULL; |
4485 | + yaml_node_t* pair = NULL; |
4486 | + yaml_node_item_t *item = NULL; |
4487 | + NetplanNetDefinition *component = NULL; |
4488 | + |
4489 | + for (yaml_node_item_t *iter = node->data.sequence.items.start; iter < node->data.sequence.items.top; iter++) { |
4490 | + pair = yaml_document_get_node(doc, *iter); |
4491 | + assert_type(pair, YAML_SEQUENCE_NODE); |
4492 | + |
4493 | + item = pair->data.sequence.items.start; |
4494 | + /* A peer port definition must contain exactly 2 ports */ |
4495 | + if (item+2 != pair->data.sequence.items.top) { |
4496 | + return yaml_error(pair, error, "An openvswitch peer port sequence must have exactly two entries"); |
4497 | + } |
4498 | + |
4499 | + port = yaml_document_get_node(doc, *item); |
4500 | + assert_type(port, YAML_SCALAR_NODE); |
4501 | + peer = yaml_document_get_node(doc, *(item+1)); |
4502 | + assert_type(peer, YAML_SCALAR_NODE); |
4503 | + |
4504 | + /* Create port 1 netdef */ |
4505 | + component = g_hash_table_lookup(netdefs, scalar(port)); |
4506 | + if (!component) { |
4507 | + component = netplan_netdef_new(scalar(port), NETPLAN_DEF_TYPE_PORT, NETPLAN_BACKEND_OVS); |
4508 | + if (g_hash_table_remove(missing_id, scalar(port))) |
4509 | + missing_ids_found++; |
4510 | + } |
4511 | + |
4512 | + if (component->peer && g_strcmp0(component->peer, scalar(peer))) |
4513 | + return yaml_error(port, error, "openvswitch port '%s' is already assigned to peer '%s'", |
4514 | + component->id, component->peer); |
4515 | + component->peer = g_strdup(scalar(peer)); |
4516 | + |
4517 | + /* Create port 2 (peer) netdef */ |
4518 | + component = NULL; |
4519 | + component = g_hash_table_lookup(netdefs, scalar(peer)); |
4520 | + if (!component) { |
4521 | + component = netplan_netdef_new(scalar(peer), NETPLAN_DEF_TYPE_PORT, NETPLAN_BACKEND_OVS); |
4522 | + if (g_hash_table_remove(missing_id, scalar(peer))) |
4523 | + missing_ids_found++; |
4524 | + } |
4525 | + |
4526 | + if (component->peer && g_strcmp0(component->peer, scalar(port))) |
4527 | + return yaml_error(peer, error, "openvswitch port '%s' is already assigned to peer '%s'", |
4528 | + component->id, component->peer); |
4529 | + component->peer = g_strdup(scalar(port)); |
4530 | + } |
4531 | + return TRUE; |
4532 | } |
4533 | |
4534 | /** |
4535 | - * Callback for a net device type entry like "ethernets:" in "networks:" |
4536 | + * Callback for a net device type entry like "ethernets:" in "network:" |
4537 | * @data: netdef_type (as pointer) |
4538 | */ |
4539 | static gboolean |
4540 | @@ -1869,27 +2382,7 @@ handle_network_type(yaml_document_t* doc, yaml_node_t* node, const void* data, G |
4541 | if (cur_netdef->type != GPOINTER_TO_UINT(data)) |
4542 | return yaml_error(key, error, "Updated definition '%s' changes device type", scalar(key)); |
4543 | } else { |
4544 | - /* create new network definition */ |
4545 | - cur_netdef = g_new0(NetplanNetDefinition, 1); |
4546 | - cur_netdef->type = GPOINTER_TO_UINT(data); |
4547 | - cur_netdef->backend = backend_cur_type ?: NETPLAN_BACKEND_NONE; |
4548 | - cur_netdef->id = g_strdup(scalar(key)); |
4549 | - |
4550 | - /* Set some default values */ |
4551 | - cur_netdef->vlan_id = G_MAXUINT; /* 0 is a valid ID */ |
4552 | - cur_netdef->tunnel.mode = NETPLAN_TUNNEL_MODE_UNKNOWN; |
4553 | - cur_netdef->dhcp_identifier = g_strdup("duid"); /* keep networkd's default */ |
4554 | - /* systemd-networkd defaults to IPv6 LL enabled; keep that default */ |
4555 | - cur_netdef->linklocal.ipv6 = TRUE; |
4556 | - g_hash_table_insert(netdefs, cur_netdef->id, cur_netdef); |
4557 | - netdefs_ordered = g_list_append(netdefs_ordered, cur_netdef); |
4558 | - cur_netdef->sriov_vlan_filter = FALSE; |
4559 | - |
4560 | - /* DHCP override defaults */ |
4561 | - initialize_dhcp_overrides(&cur_netdef->dhcp4_overrides); |
4562 | - initialize_dhcp_overrides(&cur_netdef->dhcp6_overrides); |
4563 | - |
4564 | - g_hash_table_insert(netdefs, cur_netdef->id, cur_netdef); |
4565 | + netplan_netdef_new(scalar(key), GPOINTER_TO_UINT(data), backend_cur_type); |
4566 | } |
4567 | |
4568 | // XXX: breaks multi-pass parsing. |
4569 | @@ -1907,7 +2400,7 @@ handle_network_type(yaml_document_t* doc, yaml_node_t* node, const void* data, G |
4570 | case NETPLAN_DEF_TYPE_WIFI: handlers = wifi_def_handlers; break; |
4571 | default: g_assert_not_reached(); // LCOV_EXCL_LINE |
4572 | } |
4573 | - if (!process_mapping(doc, value, handlers, error)) |
4574 | + if (!process_mapping(doc, value, handlers, NULL, error)) |
4575 | return FALSE; |
4576 | |
4577 | /* validate definition-level conditions */ |
4578 | @@ -1923,6 +2416,34 @@ handle_network_type(yaml_document_t* doc, yaml_node_t* node, const void* data, G |
4579 | return TRUE; |
4580 | } |
4581 | |
4582 | +static const mapping_entry_handler ovs_global_ssl_handlers[] = { |
4583 | + {"ca-cert", YAML_SCALAR_NODE, handle_auth_str, NULL, auth_offset(ca_certificate)}, |
4584 | + {"certificate", YAML_SCALAR_NODE, handle_auth_str, NULL, auth_offset(client_certificate)}, |
4585 | + {"private-key", YAML_SCALAR_NODE, handle_auth_str, NULL, auth_offset(client_key)}, |
4586 | + {NULL} |
4587 | +}; |
4588 | + |
4589 | +static gboolean |
4590 | +handle_ovs_global_ssl(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) |
4591 | +{ |
4592 | + gboolean ret; |
4593 | + |
4594 | + cur_auth = &(ovs_settings_global.ssl); |
4595 | + ret = process_mapping(doc, node, ovs_global_ssl_handlers, NULL, error); |
4596 | + cur_auth = NULL; |
4597 | + |
4598 | + return ret; |
4599 | +} |
4600 | + |
4601 | +static const mapping_entry_handler ovs_network_settings_handlers[] = { |
4602 | + {"external-ids", YAML_MAPPING_NODE, handle_network_ovs_settings_global, NULL, ovs_settings_offset(external_ids)}, |
4603 | + {"other-config", YAML_MAPPING_NODE, handle_network_ovs_settings_global, NULL, ovs_settings_offset(other_config)}, |
4604 | + {"protocols", YAML_SEQUENCE_NODE, handle_network_ovs_settings_global_protocol, NULL, ovs_settings_offset(protocols)}, |
4605 | + {"ports", YAML_SEQUENCE_NODE, handle_network_ovs_settings_global_ports}, |
4606 | + {"ssl", YAML_MAPPING_NODE, handle_ovs_global_ssl}, |
4607 | + {NULL} |
4608 | +}; |
4609 | + |
4610 | static const mapping_entry_handler network_handlers[] = { |
4611 | {"bonds", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_BOND)}, |
4612 | {"bridges", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_BRIDGE)}, |
4613 | @@ -1933,6 +2454,7 @@ static const mapping_entry_handler network_handlers[] = { |
4614 | {"vlans", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_VLAN)}, |
4615 | {"wifis", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_WIFI)}, |
4616 | {"modems", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_MODEM)}, |
4617 | + {"openvswitch", YAML_MAPPING_NODE, NULL, ovs_network_settings_handlers}, |
4618 | {NULL} |
4619 | }; |
4620 | |
4621 | @@ -1966,7 +2488,7 @@ process_document(yaml_document_t* doc, GError** error) |
4622 | |
4623 | g_clear_error(error); |
4624 | |
4625 | - ret = process_mapping(doc, yaml_document_get_root_node(doc), root_handlers, error); |
4626 | + ret = process_mapping(doc, yaml_document_get_root_node(doc), root_handlers, NULL, error); |
4627 | |
4628 | still_missing = g_hash_table_size(missing_id); |
4629 | |
4630 | diff --git a/src/parse.h b/src/parse.h |
4631 | index b84ff4c..9f57bb4 100644 |
4632 | --- a/src/parse.h |
4633 | +++ b/src/parse.h |
4634 | @@ -25,14 +25,14 @@ |
4635 | |
4636 | |
4637 | /* file that is currently being processed, for useful error messages */ |
4638 | -const char* current_file; |
4639 | +extern const char* current_file; |
4640 | |
4641 | /* List of "seen" ids not found in netdefs yet by the parser. |
4642 | * These are removed when it exists in this list and we reach the point of |
4643 | * creating a netdef for that id; so by the time we're done parsing the yaml |
4644 | * document it should be empty. */ |
4645 | -GHashTable *missing_id; |
4646 | -int missing_ids_found; |
4647 | +extern GHashTable *missing_id; |
4648 | +extern int missing_ids_found; |
4649 | |
4650 | /**************************************************** |
4651 | * Parsed definitions |
4652 | @@ -50,12 +50,14 @@ typedef enum { |
4653 | NETPLAN_DEF_TYPE_BOND, |
4654 | NETPLAN_DEF_TYPE_VLAN, |
4655 | NETPLAN_DEF_TYPE_TUNNEL, |
4656 | + NETPLAN_DEF_TYPE_PORT, |
4657 | } NetplanDefType; |
4658 | |
4659 | typedef enum { |
4660 | NETPLAN_BACKEND_NONE, |
4661 | NETPLAN_BACKEND_NETWORKD, |
4662 | NETPLAN_BACKEND_NM, |
4663 | + NETPLAN_BACKEND_OVS, |
4664 | NETPLAN_BACKEND_MAX_, |
4665 | } NetplanBackend; |
4666 | |
4667 | @@ -63,6 +65,7 @@ static const char* const netplan_backend_to_name[NETPLAN_BACKEND_MAX_] = { |
4668 | [NETPLAN_BACKEND_NONE] = "none", |
4669 | [NETPLAN_BACKEND_NETWORKD] = "networkd", |
4670 | [NETPLAN_BACKEND_NM] = "NetworkManager", |
4671 | + [NETPLAN_BACKEND_OVS] = "OpenVSwitch", |
4672 | }; |
4673 | |
4674 | typedef enum { |
4675 | @@ -111,6 +114,7 @@ typedef enum { |
4676 | /* systemd-only, apparently? */ |
4677 | NETPLAN_TUNNEL_MODE_GRETAP = 101, |
4678 | NETPLAN_TUNNEL_MODE_IP6GRETAP = 102, |
4679 | + NETPLAN_TUNNEL_MODE_WIREGUARD = 103, |
4680 | |
4681 | NETPLAN_TUNNEL_MODE_MAX_, |
4682 | } NetplanTunnelMode; |
4683 | @@ -130,6 +134,7 @@ netplan_tunnel_mode_table[NETPLAN_TUNNEL_MODE_MAX_] = { |
4684 | |
4685 | [NETPLAN_TUNNEL_MODE_GRETAP] = "gretap", |
4686 | [NETPLAN_TUNNEL_MODE_IP6GRETAP] = "ip6gretap", |
4687 | + [NETPLAN_TUNNEL_MODE_WIREGUARD] = "wireguard", |
4688 | }; |
4689 | |
4690 | typedef enum { |
4691 | @@ -196,6 +201,23 @@ typedef struct dhcp_overrides { |
4692 | guint metric; |
4693 | } NetplanDHCPOverrides; |
4694 | |
4695 | +typedef struct ovs_controller { |
4696 | + char* connection_mode; |
4697 | + GArray* addresses; |
4698 | +} NetplanOVSController; |
4699 | + |
4700 | +typedef struct ovs_settings { |
4701 | + GHashTable* external_ids; |
4702 | + GHashTable* other_config; |
4703 | + char* lacp; |
4704 | + char* fail_mode; |
4705 | + gboolean mcast_snooping; |
4706 | + GArray* protocols; |
4707 | + gboolean rstp; |
4708 | + NetplanOVSController controller; |
4709 | + NetplanAuthenticationSettings ssl; |
4710 | +} NetplanOVSSettings; |
4711 | + |
4712 | /** |
4713 | * Represent a configuration stanza |
4714 | */ |
4715 | @@ -225,8 +247,10 @@ struct net_definition { |
4716 | NetplanRAMode accept_ra; |
4717 | GArray* ip4_addresses; |
4718 | GArray* ip6_addresses; |
4719 | + GArray* address_options; |
4720 | gboolean ip6_privacy; |
4721 | guint ip6_addr_gen_mode; |
4722 | + char* ip6_addr_gen_token; |
4723 | char* gateway4; |
4724 | char* gateway6; |
4725 | GArray* ip4_nameservers; |
4726 | @@ -234,6 +258,7 @@ struct net_definition { |
4727 | GArray* search_domains; |
4728 | GArray* routes; |
4729 | GArray* ip_rules; |
4730 | + GArray* wireguard_peers; |
4731 | struct { |
4732 | gboolean ipv4; |
4733 | gboolean ipv6; |
4734 | @@ -243,6 +268,9 @@ struct net_definition { |
4735 | char* bridge; |
4736 | char* bond; |
4737 | |
4738 | + /* peer ID for OVS patch ports */ |
4739 | + char* peer; |
4740 | + |
4741 | /* vlan */ |
4742 | guint vlan_id; |
4743 | NetplanNetDefinition* vlan_link; |
4744 | @@ -269,7 +297,6 @@ struct net_definition { |
4745 | NetplanWifiWowlanFlag wowlan; |
4746 | gboolean emit_lldp; |
4747 | |
4748 | - |
4749 | /* these properties are only valid for NETPLAN_DEF_TYPE_WIFI */ |
4750 | GHashTable* access_points; /* SSID → NetplanWifiAccessPoint* */ |
4751 | |
4752 | @@ -297,6 +324,7 @@ struct net_definition { |
4753 | char* primary_slave; |
4754 | } bond_params; |
4755 | |
4756 | + /* netplan-feature: modems */ |
4757 | struct { |
4758 | char* apn; |
4759 | gboolean auto_config; |
4760 | @@ -328,16 +356,24 @@ struct net_definition { |
4761 | char *remote_ip; |
4762 | char *input_key; |
4763 | char *output_key; |
4764 | + char *private_key; /* used for wireguard */ |
4765 | + guint fwmark; |
4766 | + guint port; |
4767 | } tunnel; |
4768 | |
4769 | NetplanAuthenticationSettings auth; |
4770 | gboolean has_auth; |
4771 | |
4772 | /* these properties are only valid for SR-IOV NICs */ |
4773 | + /* netplan-feature: sriov */ |
4774 | struct net_definition* sriov_link; |
4775 | gboolean sriov_vlan_filter; |
4776 | guint sriov_explicit_vf_count; |
4777 | |
4778 | + /* these properties are only valid for OpenVSwitch */ |
4779 | + /* netplan-feature: openvswitch */ |
4780 | + NetplanOVSSettings ovs_settings; |
4781 | + |
4782 | union { |
4783 | struct NetplanNMSettings { |
4784 | char *name; |
4785 | @@ -357,6 +393,14 @@ typedef enum { |
4786 | NETPLAN_WIFI_MODE_AP |
4787 | } NetplanWifiMode; |
4788 | |
4789 | +typedef struct { |
4790 | + char *endpoint; |
4791 | + char *public_key; |
4792 | + char *preshared_key; |
4793 | + GArray *allowed_ips; |
4794 | + guint keepalive; |
4795 | +} NetplanWireguardPeer; |
4796 | + |
4797 | typedef enum { |
4798 | NETPLAN_WIFI_BAND_DEFAULT, |
4799 | NETPLAN_WIFI_BAND_5, |
4800 | @@ -364,10 +408,17 @@ typedef enum { |
4801 | } NetplanWifiBand; |
4802 | |
4803 | typedef struct { |
4804 | + char* address; |
4805 | + char* lifetime; |
4806 | + char* label; |
4807 | +} NetplanAddressOptions; |
4808 | + |
4809 | +typedef struct { |
4810 | NetplanWifiMode mode; |
4811 | char* ssid; |
4812 | NetplanWifiBand band; |
4813 | char* bssid; |
4814 | + gboolean hidden; |
4815 | guint channel; |
4816 | |
4817 | NetplanAuthenticationSettings auth; |
4818 | @@ -415,6 +466,7 @@ typedef struct { |
4819 | /* Written/updated by parse_yaml(): char* id → net_definition */ |
4820 | extern GHashTable* netdefs; |
4821 | extern GList* netdefs_ordered; |
4822 | +extern NetplanOVSSettings ovs_settings_global; |
4823 | |
4824 | /**************************************************** |
4825 | * Functions |
4826 | diff --git a/src/sriov.c b/src/sriov.c |
4827 | new file mode 100644 |
4828 | index 0000000..60f9800 |
4829 | --- /dev/null |
4830 | +++ b/src/sriov.c |
4831 | @@ -0,0 +1,40 @@ |
4832 | +/* |
4833 | + * Copyright (C) 2020 Canonical, Ltd. |
4834 | + * Author: Łukasz 'sil2100' Zemczak <lukasz.zemczak@canonical.com> |
4835 | + * |
4836 | + * This program is free software; you can redistribute it and/or modify |
4837 | + * it under the terms of the GNU General Public License as published by |
4838 | + * the Free Software Foundation; version 3. |
4839 | + * |
4840 | + * This program is distributed in the hope that it will be useful, |
4841 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
4842 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4843 | + * GNU General Public License for more details. |
4844 | + * |
4845 | + * You should have received a copy of the GNU General Public License |
4846 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
4847 | + */ |
4848 | + |
4849 | +#include <unistd.h> |
4850 | + |
4851 | +#include <glib.h> |
4852 | +#include <glib/gstdio.h> |
4853 | +#include <glib-object.h> |
4854 | + |
4855 | +#include "util.h" |
4856 | + |
4857 | +void |
4858 | +write_sriov_conf_finish(const char* rootdir) |
4859 | +{ |
4860 | + /* For now we execute apply --sriov-only everytime there is a new |
4861 | + SR-IOV device appearing, which is fine as it's relatively fast */ |
4862 | + GString *udev_rule = g_string_new("ACTION==\"add\", SUBSYSTEM==\"net\", ATTRS{sriov_totalvfs}==\"?*\", RUN+=\"/usr/sbin/netplan apply --sriov-only\"\n"); |
4863 | + g_string_free_to_file(udev_rule, rootdir, "run/udev/rules.d/99-sriov-netplan-setup.rules", NULL); |
4864 | +} |
4865 | + |
4866 | +void |
4867 | +cleanup_sriov_conf(const char* rootdir) |
4868 | +{ |
4869 | + g_autofree char* rulepath = g_strjoin(NULL, rootdir ?: "", "/run/udev/rules.d/99-sriov-netplan-setup.rules", NULL); |
4870 | + unlink(rulepath); |
4871 | +} |
4872 | diff --git a/src/sriov.h b/src/sriov.h |
4873 | new file mode 100644 |
4874 | index 0000000..7cd5896 |
4875 | --- /dev/null |
4876 | +++ b/src/sriov.h |
4877 | @@ -0,0 +1,21 @@ |
4878 | +/* |
4879 | + * Copyright (C) 2020 Canonical, Ltd. |
4880 | + * Author: Łukasz 'sil2100' Zemczak <lukasz.zemczak@canonical.com> |
4881 | + * |
4882 | + * This program is free software; you can redistribute it and/or modify |
4883 | + * it under the terms of the GNU General Public License as published by |
4884 | + * the Free Software Foundation; version 3. |
4885 | + * |
4886 | + * This program is distributed in the hope that it will be useful, |
4887 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
4888 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4889 | + * GNU General Public License for more details. |
4890 | + * |
4891 | + * You should have received a copy of the GNU General Public License |
4892 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
4893 | + */ |
4894 | + |
4895 | +#pragma once |
4896 | + |
4897 | +void write_sriov_conf_finish(const char* rootdir); |
4898 | +void cleanup_sriov_conf(const char* rootdir); |
4899 | diff --git a/src/util.c b/src/util.c |
4900 | index 4b71ef1..edd2969 100644 |
4901 | --- a/src/util.c |
4902 | +++ b/src/util.c |
4903 | @@ -24,6 +24,9 @@ |
4904 | |
4905 | #include "util.h" |
4906 | |
4907 | +GHashTable* wifi_frequency_24; |
4908 | +GHashTable* wifi_frequency_5; |
4909 | + |
4910 | /** |
4911 | * Create the parent directories of given file path. Exit program on failure. |
4912 | */ |
4913 | @@ -148,3 +151,29 @@ wifi_get_freq5(int channel) |
4914 | return GPOINTER_TO_INT(g_hash_table_lookup(wifi_frequency_5, |
4915 | GINT_TO_POINTER(channel))); |
4916 | } |
4917 | + |
4918 | +/** |
4919 | + * Systemd-escape the given string. The caller is responsible for freeing |
4920 | + * the allocated escaped string. |
4921 | + */ |
4922 | +gchar* |
4923 | +systemd_escape(char* string) |
4924 | +{ |
4925 | + g_autoptr(GError) err = NULL; |
4926 | + g_autofree gchar* stderrh = NULL; |
4927 | + gint exit_status = 0; |
4928 | + gchar *escaped; |
4929 | + |
4930 | + gchar *argv[] = {"bin" "/" "systemd-escape", string, NULL}; |
4931 | + g_spawn_sync("/", argv, NULL, 0, NULL, NULL, &escaped, &stderrh, &exit_status, &err); |
4932 | + g_spawn_check_exit_status(exit_status, &err); |
4933 | + if (err != NULL) { |
4934 | + // LCOV_EXCL_START |
4935 | + g_fprintf(stderr, "failed to ask systemd to escape %s; exit %d\nstdout: '%s'\nstderr: '%s'", string, exit_status, escaped, stderrh); |
4936 | + exit(1); |
4937 | + // LCOV_EXCL_STOP |
4938 | + } |
4939 | + g_strstrip(escaped); |
4940 | + |
4941 | + return escaped; |
4942 | +} |
4943 | diff --git a/src/util.h b/src/util.h |
4944 | index 4ed1a60..ef6671b 100644 |
4945 | --- a/src/util.h |
4946 | +++ b/src/util.h |
4947 | @@ -17,8 +17,8 @@ |
4948 | |
4949 | #pragma once |
4950 | |
4951 | -GHashTable* wifi_frequency_24; |
4952 | -GHashTable* wifi_frequency_5; |
4953 | +extern GHashTable* wifi_frequency_24; |
4954 | +extern GHashTable* wifi_frequency_5; |
4955 | |
4956 | void safe_mkdir_p_dir(const char* file_path); |
4957 | void g_string_free_to_file(GString* s, const char* rootdir, const char* path, const char* suffix); |
4958 | @@ -26,3 +26,7 @@ void unlink_glob(const char* rootdir, const char* _glob); |
4959 | |
4960 | int wifi_get_freq24(int channel); |
4961 | int wifi_get_freq5(int channel); |
4962 | + |
4963 | +gchar* systemd_escape(char* string); |
4964 | + |
4965 | +#define OPENVSWITCH_OVS_VSCTL "/usr/bin/ovs-vsctl" |
4966 | diff --git a/src/validation.c b/src/validation.c |
4967 | index ad08988..12a0231 100644 |
4968 | --- a/src/validation.c |
4969 | +++ b/src/validation.c |
4970 | @@ -25,6 +25,7 @@ |
4971 | |
4972 | #include "parse.h" |
4973 | #include "error.h" |
4974 | +#include "util.h" |
4975 | |
4976 | |
4977 | /* Check sanity for address types */ |
4978 | @@ -57,15 +58,148 @@ is_ip6_address(const char* address) |
4979 | return FALSE; |
4980 | } |
4981 | |
4982 | +gboolean |
4983 | +is_hostname(const char *hostname) |
4984 | +{ |
4985 | + static const gchar *pattern = "^(([a-z0-9]|[a-z0-9][a-z0-9\\-]*[a-z0-9])\\.)*([a-z0-9]|[a-z0-9][a-z0-9\\-]*[a-z0-9])$"; |
4986 | + return g_regex_match_simple(pattern, hostname, G_REGEX_CASELESS, G_REGEX_MATCH_NOTEMPTY); |
4987 | +} |
4988 | + |
4989 | +gboolean |
4990 | +is_wireguard_key(const char* key) |
4991 | +{ |
4992 | + /* Check if this is (most likely) a 265bit, base64 encoded wireguard key */ |
4993 | + if (strlen(key) == 44 && key[43] == '=' && key[42] != '=') { |
4994 | + static const gchar *pattern = "^(?:[A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=)+$"; |
4995 | + return g_regex_match_simple(pattern, key, 0, G_REGEX_MATCH_NOTEMPTY); |
4996 | + } |
4997 | + return FALSE; |
4998 | +} |
4999 | + |
5000 | +/* Check sanity of OpenVSwitch controller targets */ |
The diff has been truncated for viewing.
I had to remove the Build-Dep on 'openvswitch- switch' for riscv64, due to that package (and thus the ovs-vsctl tool) missing in Focal. Therefore, OVS tests wouldn't pass on riscv64, but are disabled/ignored as well.