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