Import upstream version 0.99, md5 b9a732fca6f3505ab67f385ea6c6ef31
Debian Janitor
4 years ago
0 | NETPLAN_SOVER=0.0 | |
1 | ||
0 | 2 | BUILDFLAGS = \ |
3 | -g \ | |
4 | -fPIC \ | |
1 | 5 | -std=c99 \ |
2 | 6 | -D_XOPEN_SOURCE=500 \ |
3 | 7 | -DSBINDIR=\"$(SBINDIR)\" \ |
12 | 16 | GCOV ?= gcov |
13 | 17 | ROOTPREFIX ?= |
14 | 18 | PREFIX ?= /usr |
19 | LIBDIR ?= $(PREFIX)/lib | |
15 | 20 | ROOTLIBEXECDIR ?= $(ROOTPREFIX)/lib |
16 | 21 | LIBEXECDIR ?= $(PREFIX)/lib |
17 | 22 | SBINDIR ?= $(PREFIX)/sbin |
18 | 23 | DATADIR ?= $(PREFIX)/share |
19 | 24 | DOCDIR ?= $(DATADIR)/doc |
20 | 25 | MANDIR ?= $(DATADIR)/man |
26 | INCLUDEDIR ?= $(PREFIX)/include | |
21 | 27 | |
22 | 28 | PYCODE = netplan/ $(wildcard src/*.py) $(wildcard tests/*.py) $(wildcard tests/generator/*.py) $(wildcard tests/dbus/*.py) |
23 | 29 | |
28 | 34 | |
29 | 35 | default: netplan/_features.py generate netplan-dbus dbus/io.netplan.Netplan.service doc/netplan.html doc/netplan.5 doc/netplan-generate.8 doc/netplan-apply.8 doc/netplan-try.8 |
30 | 36 | |
31 | generate: src/generate.[hc] src/parse.[hc] src/util.[hc] src/networkd.[hc] src/nm.[hc] src/validation.[hc] src/error.[hc] | |
32 | $(CC) $(BUILDFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $(filter %.c, $^) `pkg-config --cflags --libs glib-2.0 gio-2.0 yaml-0.1 uuid` | |
37 | %.o: src/%.c | |
38 | $(CC) $(BUILDFLAGS) $(CFLAGS) $(LDFLAGS) -c $^ `pkg-config --cflags --libs glib-2.0 gio-2.0 yaml-0.1 uuid` | |
39 | ||
40 | libnetplan.so.$(NETPLAN_SOVER): parse.o util.o validation.o error.o | |
41 | $(CC) -shared -Wl,-soname,libnetplan.so.$(NETPLAN_SOVER) $(BUILDFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $^ `pkg-config --libs yaml-0.1` | |
42 | ln -snf libnetplan.so.$(NETPLAN_SOVER) libnetplan.so | |
43 | ||
44 | #generate: src/generate.[hc] src/parse.[hc] src/util.[hc] src/networkd.[hc] src/nm.[hc] src/validation.[hc] src/error.[hc] | |
45 | generate: libnetplan.so.$(NETPLAN_SOVER) nm.o networkd.o generate.o | |
46 | $(CC) $(BUILDFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $^ -L. -lnetplan `pkg-config --cflags --libs glib-2.0 gio-2.0 yaml-0.1 uuid` | |
33 | 47 | |
34 | 48 | netplan-dbus: src/dbus.c src/_features.h |
35 | $(CC) $(BUILDFLAGS) $(CFLAGS) -o $@ $^ `pkg-config --cflags --libs libsystemd glib-2.0` | |
49 | $(CC) $(BUILDFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $^ `pkg-config --cflags --libs libsystemd glib-2.0` | |
36 | 50 | |
37 | 51 | src/_features.h: src/[^_]*.[hc] |
38 | echo "#include <stddef.h>\nstatic const char *feature_flags[] __attribute__((__unused__)) = {" > $@ | |
52 | printf "#include <stddef.h>\nstatic const char *feature_flags[] __attribute__((__unused__)) = {\n" > $@ | |
39 | 53 | awk 'match ($$0, /netplan-feature:.*/ ) { $$0=substr($$0, RSTART, RLENGTH); print "\""$$2"\"," }' $^ >> $@ |
40 | 54 | echo "NULL, };" >> $@ |
41 | 55 | |
48 | 62 | clean: |
49 | 63 | rm -f netplan/_features.py src/_features.h |
50 | 64 | rm -f generate doc/*.html doc/*.[1-9] |
65 | rm -f *.o *.so* | |
51 | 66 | rm -f netplan-dbus dbus/*.service |
52 | 67 | rm -f *.gcda *.gcno generate.info |
53 | 68 | rm -rf test-coverage .coverage |
84 | 99 | python3-coverage xml --omit=/usr* || true |
85 | 100 | |
86 | 101 | install: default |
87 | mkdir -p $(DESTDIR)/$(SBINDIR) $(DESTDIR)/$(ROOTLIBEXECDIR)/netplan $(DESTDIR)/$(SYSTEMD_GENERATOR_DIR) | |
102 | mkdir -p $(DESTDIR)/$(SBINDIR) $(DESTDIR)/$(ROOTLIBEXECDIR)/netplan $(DESTDIR)/$(SYSTEMD_GENERATOR_DIR) $(DESTDIR)/$(LIBDIR) | |
88 | 103 | mkdir -p $(DESTDIR)/$(MANDIR)/man5 $(DESTDIR)/$(MANDIR)/man8 |
89 | 104 | mkdir -p $(DESTDIR)/$(DOCDIR)/netplan/examples |
90 | 105 | mkdir -p $(DESTDIR)/$(DATADIR)/netplan/netplan |
106 | mkdir -p $(DESTDIR)/$(INCLUDEDIR)/netplan | |
91 | 107 | install -m 755 generate $(DESTDIR)/$(ROOTLIBEXECDIR)/netplan/ |
92 | 108 | find netplan/ -name '*.py' -exec install -Dm 644 "{}" "$(DESTDIR)/$(DATADIR)/netplan/{}" \; |
93 | 109 | install -m 755 src/netplan.script $(DESTDIR)/$(DATADIR)/netplan/ |
94 | 110 | ln -srf $(DESTDIR)/$(DATADIR)/netplan/netplan.script $(DESTDIR)/$(SBINDIR)/netplan |
95 | 111 | ln -srf $(DESTDIR)/$(ROOTLIBEXECDIR)/netplan/generate $(DESTDIR)/$(SYSTEMD_GENERATOR_DIR)/netplan |
112 | # lib | |
113 | install -m 644 *.so.* $(DESTDIR)/$(LIBDIR)/ | |
114 | ln -snf libnetplan.so.$(NETPLAN_SOVER) $(DESTDIR)/$(LIBDIR)/libnetplan.so | |
115 | # headers, dev data | |
116 | install -m 644 src/*.h $(DESTDIR)/$(INCLUDEDIR)/netplan/ | |
117 | # TODO: install pkg-config once available | |
118 | # docs, data | |
96 | 119 | install -m 644 doc/*.html $(DESTDIR)/$(DOCDIR)/netplan/ |
97 | 120 | install -m 644 examples/*.yaml $(DESTDIR)/$(DOCDIR)/netplan/examples/ |
98 | 121 | install -m 644 doc/*.5 $(DESTDIR)/$(MANDIR)/man5/ |
99 | 122 | install -m 644 doc/*.8 $(DESTDIR)/$(MANDIR)/man8/ |
100 | install -D -m 644 src/netplan-wpa@.service $(DESTDIR)/$(SYSTEMD_UNIT_DIR)/netplan-wpa@.service | |
101 | 123 | install -T -D -m 644 netplan.completions $(DESTDIR)/$(BASH_COMPLETIONS_DIR)/netplan |
102 | 124 | # dbus |
103 | 125 | mkdir -p $(DESTDIR)/$(DATADIR)/dbus-1/system.d $(DESTDIR)/$(DATADIR)/dbus-1/system-services |
29 | 29 | The top-level node in a netplan configuration file is a ``network:`` mapping |
30 | 30 | that contains ``version: 2`` (the YAML currently being used by curtin, MaaS, |
31 | 31 | etc. is version 1), and then device definitions grouped by their type, such as |
32 | ``ethernets:``, ``wifis:``, or ``bridges:``. These are the types that our | |
32 | ``ethernets:``, ``modems:``, ``wifis:``, or ``bridges:``. These are the types that our | |
33 | 33 | renderer can understand and are supported by our backends. |
34 | 34 | |
35 | 35 | Each type block contains device definitions as a map where the keys (called |
51 | 51 | |
52 | 52 | Physical devices |
53 | 53 | |
54 | : (Examples: ethernet, wifi) These can dynamically come and go between | |
54 | : (Examples: ethernet, modem, wifi) These can dynamically come and go between | |
55 | 55 | reboots and even during runtime (hotplugging). In the generic case, they |
56 | 56 | can be selected by ``match:`` rules on desired properties, such as name/name |
57 | 57 | pattern, MAC address, driver, or device paths. In general these will match |
132 | 132 | |
133 | 133 | : Enable wake on LAN. Off by default. |
134 | 134 | |
135 | ``emit-lldp`` (bool) | |
136 | ||
137 | : (networkd backend only) Whether to emit LLDP packets. Off by default. | |
138 | ||
135 | 139 | |
136 | 140 | ## Common properties for all device types |
137 | 141 | |
141 | 145 | ``networkd`` and ``NetworkManager``. This property can be specified globally |
142 | 146 | in ``networks:``, for a device type (in e. g. ``ethernets:``) or |
143 | 147 | for a particular device definition. Default is ``networkd``. |
148 | ||
149 | The ``renderer`` property has one additional acceptable value for vlan objects | |
150 | (i. e. defined in ``vlans:``): ``sriov``. If a vlan is defined with the ``sriov`` | |
151 | renderer for an SR-IOV Virtual Function interface, this causes netplan to set | |
152 | up a hardware VLAN filter for it. There can be only one defined per VF. | |
144 | 153 | |
145 | 154 | ``dhcp4`` (bool) |
146 | 155 | |
197 | 206 | |
198 | 207 | : (networkd backend only) Designate the connection as "critical to the |
199 | 208 | system", meaning that special care will be taken by systemd-networkd to |
200 | not release the IP from DHCP when the daemon is restarted. | |
209 | not release the assigned IP when the daemon is restarted. | |
201 | 210 | |
202 | 211 | ``dhcp-identifier`` (scalar) |
203 | 212 | |
233 | 242 | but will not be addressable from the network. |
234 | 243 | |
235 | 244 | Example: ``addresses: [192.168.14.2/24, "2001:1::1/64"]`` |
245 | ||
246 | ``ipv6-address-generation`` (scalar) | |
247 | ||
248 | : Configure method for creating the address for use with RFC4862 IPv6 | |
249 | Stateless Address Autoconfiguration. Possible values are ``eui64`` | |
250 | or ``stable-privacy``. | |
236 | 251 | |
237 | 252 | ``gateway4``, ``gateway6`` (scalar) |
238 | 253 | |
539 | 554 | : Password to use to decrypt the private key specified in |
540 | 555 | ``client-key`` if it is encrypted. |
541 | 556 | |
557 | ``phase2-auth`` (scalar) | |
558 | : Phase 2 authentication mechanism. | |
559 | ||
542 | 560 | |
543 | 561 | ## Properties for device type ``ethernets:`` |
544 | Ethernet device definitions do not support any specific properties beyond the | |
545 | common ones described above. | |
562 | Ethernet device definitions, beyond common ones described above, also support | |
563 | some additional properties that can be used for SR-IOV devices. | |
564 | ||
565 | ``link`` (scalar) | |
566 | ||
567 | : (SR-IOV devices only) The ``link`` property declares the device as a | |
568 | Virtual Function of the selected Physical Function device, as identified | |
569 | by the given netplan id. | |
570 | ||
571 | Example: | |
572 | ||
573 | ethernets: | |
574 | enp1: {...} | |
575 | enp1s16f1: | |
576 | link: enp1 | |
577 | ||
578 | ``virtual-function-count`` (scalar) | |
579 | ||
580 | : (SR-IOV devices only) In certain special cases VFs might need to be | |
581 | configured outside of netplan. For such configurations ``virtual-function-count`` | |
582 | can be optionally used to set an explicit number of Virtual Functions for | |
583 | the given Physical Function. If unset, the default is to create only as many | |
584 | VFs as are defined in the netplan configuration. This should be used for special | |
585 | cases only. | |
586 | ||
587 | ## Properties for device type ``modems:`` | |
588 | GSM/CDMA modem configuration is only supported for the ``NetworkManager`` backend. ``systemd-networkd`` does | |
589 | not support modems. | |
590 | ||
591 | ``apn`` (scalar) | |
592 | : Set the carrier APN (Access Point Name). This can be omitted if ``auto-config`` is enabled. | |
593 | ||
594 | ``auto-config`` (bool) | |
595 | : Specify whether to try and autoconfigure the modem by doing a lookup of the carrier | |
596 | against the Mobile Broadband Provider database. This may not work for all carriers. | |
597 | ||
598 | ``device-id`` (scalar) | |
599 | : Specify the device ID (as given by the WWAN management service) of the modem to match. | |
600 | This can be found using ``mmcli``. | |
601 | ||
602 | ``network-id`` (scalar) | |
603 | : Specify the Network ID (GSM LAI format). If this is specified, the device will not roam networks. | |
604 | ||
605 | ``number`` (scalar) | |
606 | : The number to dial to establish the connection to the mobile broadband network. (Deprecated for GSM) | |
607 | ||
608 | ``password`` (scalar) | |
609 | : Specify the password used to authenticate with the carrier network. This can be omitted | |
610 | if ``auto-config`` is enabled. | |
611 | ||
612 | ``pin`` (scalar) | |
613 | : Specify the SIM PIN to allow it to operate if a PIN is set. | |
614 | ||
615 | ``sim-id`` (scalar) | |
616 | : Specify the SIM unique identifier (as given by the WWAN management service) which this | |
617 | connection applies to. If given, the connection will apply to any device also allowed by | |
618 | ``device-id`` which contains a SIM card matching the given identifier. | |
619 | ||
620 | ``sim-operator-id`` (scalar) | |
621 | : Specify the MCC/MNC string (such as "310260" or "21601") which identifies the carrier that | |
622 | this connection should apply to. If given, the connection will apply to any device also | |
623 | allowed by ``device-id`` and ``sim-id`` which contains a SIM card provisioned by the given operator. | |
624 | ||
625 | ``username`` (scalar) | |
626 | : Specify the username used to authentiate with the carrier network. This can be omitted if | |
627 | ``auto-config`` is enabled. | |
546 | 628 | |
547 | 629 | ## Properties for device type ``wifis:`` |
548 | 630 | Note that ``systemd-networkd`` does not natively support wifi, so you need |
573 | 655 | ``ap`` (create an access point to which other devices can connect), |
574 | 656 | and ``adhoc`` (peer to peer networks without a central access point). |
575 | 657 | ``ap`` is only supported with NetworkManager. |
658 | ||
659 | ``bssid`` (scalar) | |
660 | : If specified, directs the device to only associate with the given | |
661 | access point. | |
662 | ||
663 | ``band`` (scalar) | |
664 | : Possible bands are ``5GHz`` (for 5GHz 802.11a) and ``2.4GHz`` | |
665 | (for 2.4GHz 802.11), do not restrict the 802.11 frequency band of the | |
666 | network if unset (the default). | |
667 | ||
668 | ``channel`` (scalar) | |
669 | : Wireless channel to use for the Wi-Fi connection. Because channel | |
670 | numbers overlap between bands, this property takes effect only if | |
671 | the ``band`` property is also set. | |
672 | ||
673 | ``wakeonwlan`` (sequence of scalars) | |
674 | ||
675 | : This enables WakeOnWLan on supported devices. Not all drivers support all | |
676 | options. May be any combination of ``any``, ``disconnect``, ``magic_pkt``, | |
677 | ``gtk_rekey_failure``, ``eap_identity_req``, ``four_way_handshake``, | |
678 | ``rfkill_release`` or ``tcp`` (NetworkManager only). Or the exclusive | |
679 | ``default`` flag (the default). | |
576 | 680 | |
577 | 681 | ## Properties for device type ``bridges:`` |
578 | 682 | |
895 | 999 | id: 2 |
896 | 1000 | link: eno1 |
897 | 1001 | addresses: ... |
1002 | ||
1003 | ||
1004 | ## Backend-specific configuration parameters | |
1005 | ||
1006 | In addition to the other fields available to configure interfaces, some | |
1007 | backends may require to record some of their own parameters in netplan, | |
1008 | especially if the netplan definitions are generated automatically by the | |
1009 | consumer of that backend. Currently, this is only used with ``NetworkManager``. | |
1010 | ||
1011 | ``networkmanager`` (mapping) | |
1012 | ||
1013 | : Keeps the NetworkManager-specific configuration parameters used by the | |
1014 | daemon to recognize connections. | |
1015 | ||
1016 | ``name`` (scalar) | |
1017 | : Set the display name for the connection. | |
1018 | ||
1019 | ``uuid`` (scalar) | |
1020 | : Defines the UUID (unique identifier) for this connection, as | |
1021 | generated by NetworkManager itself. | |
1022 | ||
1023 | ``stable-id`` (scalar) | |
1024 | : Defines the stable ID (a different form of a connection name) used | |
1025 | by NetworkManager in case the name of the connection might otherwise | |
1026 | change, such as when sharing connections between users. | |
1027 | ||
1028 | ``device`` (scalar) | |
1029 | : Defines the interface name for which this connection applies. | |
898 | 1030 | |
899 | 1031 | |
900 | 1032 | ## Examples |
0 | network: | |
1 | version: 2 | |
2 | renderer: networkd | |
3 | ethernets: | |
4 | enp3s0: | |
5 | dhcp4: true | |
6 | auth: | |
7 | key-management: 802.1x | |
8 | method: ttls | |
9 | identity: fluffy@cisco.com | |
10 | password: hash:83...11 |
0 | network: | |
1 | version: 2 | |
2 | renderer: networkd | |
3 | ethernets: | |
4 | addresses: [ "2001:cafe:face:beef::dead:dead/64" ] | |
5 | routes: | |
6 | - to: "2001:cafe:face::1/128" | |
7 | scope: link | |
8 | - to: "::/0" | |
9 | via: "2001:cafe:face::1" | |
10 | on-link: true |
0 | network: | |
1 | version: 2 | |
2 | renderer: NetworkManager | |
3 | modems: | |
4 | cdc-wdm1: | |
5 | mtu: 1600 | |
6 | apn: ISP.CINGULAR | |
7 | username: ISP@CINGULARGPRS.COM | |
8 | password: CINGULAR1 | |
9 | number: "*99#" | |
10 | network-id: 24005 | |
11 | device-id: da812de91eec16620b06cd0ca5cbc7ea25245222 | |
12 | pin: 2345 | |
13 | sim-id: 89148000000060671234 | |
14 | sim-operator-id: 310260 |
0 | 0 | #!/usr/bin/python3 |
1 | 1 | # |
2 | # Copyright (C) 2018 Canonical, Ltd. | |
2 | # Copyright (C) 2018-2020 Canonical, Ltd. | |
3 | 3 | # Author: Mathieu Trudel-Lapierre <mathieu.trudel-lapierre@canonical.com> |
4 | # Author: Łukasz 'sil2100' Zemczak <lukasz.zemczak@canonical.com> | |
4 | 5 | # |
5 | 6 | # This program is free software; you can redistribute it and/or modify |
6 | 7 | # it under the terms of the GNU General Public License as published by |
25 | 26 | |
26 | 27 | import netplan.cli.utils as utils |
27 | 28 | from netplan.configmanager import ConfigManager, ConfigurationError |
29 | from netplan.cli.sriov import apply_sriov_config | |
28 | 30 | |
29 | 31 | import netifaces |
30 | 32 | |
73 | 75 | old_files_networkd = bool(glob.glob('/run/systemd/network/*netplan-*')) |
74 | 76 | old_files_nm = bool(glob.glob('/run/NetworkManager/system-connections/netplan-*')) |
75 | 77 | |
76 | if run_generate and subprocess.call([utils.get_generator_path()]) != 0: | |
78 | generator_call = [] | |
79 | generate_out = None | |
80 | if 'NETPLAN_PROFILE' in os.environ: | |
81 | generator_call.extend(['valgrind', '--leak-check=full']) | |
82 | generate_out = subprocess.STDOUT | |
83 | ||
84 | generator_call.append(utils.get_generator_path()) | |
85 | if run_generate and subprocess.call(generator_call, stderr=generate_out) != 0: | |
77 | 86 | if exit_on_error: |
78 | 87 | sys.exit(os.EX_CONFIG) |
79 | 88 | else: |
117 | 126 | else: |
118 | 127 | logging.debug('no netplan generated NM configuration exists') |
119 | 128 | |
129 | # Refresh devices now; restarting a backend might have made something appear. | |
130 | devices = netifaces.interfaces() | |
131 | ||
120 | 132 | # evaluate config for extra steps we need to take (like renaming) |
121 | 133 | # for now, only applies to non-virtual (real) devices. |
122 | 134 | config_manager.parse() |
123 | 135 | changes = NetplanApply.process_link_changes(devices, config_manager) |
136 | ||
137 | # apply any SR-IOV related changes, if applicable | |
138 | try: | |
139 | apply_sriov_config(devices, config_manager) | |
140 | except (ConfigurationError, RuntimeError) as e: | |
141 | logging.error(str(e)) | |
142 | if exit_on_error: | |
143 | sys.exit(1) | |
124 | 144 | |
125 | 145 | # if the interface is up, we can still apply some .link file changes |
126 | 146 | devices = netifaces.interfaces() |
215 | 235 | # do not rename members of virtual devices. MAC addresses |
216 | 236 | # may be the same for all interface members. |
217 | 237 | continue |
218 | # try to get the device's driver for matching. | |
219 | devdir = os.path.join('/sys/class/net', interface) | |
220 | try: | |
221 | with open(os.path.join(devdir, 'operstate')) as f: | |
222 | state = f.read().strip() | |
223 | if state != 'down': | |
224 | logging.debug('device %s operstate is %s, not changing', interface, state) | |
225 | continue | |
226 | except IOError as e: | |
227 | logging.error('Cannot determine operstate of %s: %s', interface, str(e)) | |
228 | continue | |
229 | ||
230 | try: | |
231 | driver = os.path.realpath(os.path.join(devdir, 'device', 'driver')) | |
232 | driver_name = os.path.basename(driver) | |
233 | except IOError as e: | |
234 | logging.debug('Cannot replug %s: cannot read link %s/device: %s', interface, devdir, str(e)) | |
235 | driver_name = None | |
236 | pass | |
237 | ||
238 | link = netifaces.ifaddresses(interface)[netifaces.AF_LINK][0] | |
239 | macaddress = link.get('addr') | |
238 | ||
239 | driver_name = utils.get_interface_driver_name(interface, only_down=True) | |
240 | macaddress = utils.get_interface_macaddress(interface) | |
240 | 241 | if driver_name in matches['by-driver']: |
241 | 242 | new_name = matches['by-driver'][driver_name] |
242 | 243 | logging.debug(new_name) |
0 | #!/usr/bin/python3 | |
1 | # | |
2 | # Copyright (C) 2020 Canonical, Ltd. | |
3 | # Author: Łukasz 'sil2100' Zemczak <lukasz.zemczak@canonical.com> | |
4 | # | |
5 | # This program is free software; you can redistribute it and/or modify | |
6 | # it under the terms of the GNU General Public License as published by | |
7 | # the Free Software Foundation; version 3. | |
8 | # | |
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. | |
13 | # | |
14 | # You should have received a copy of the GNU General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | ||
17 | import logging | |
18 | import os | |
19 | import subprocess | |
20 | ||
21 | from collections import defaultdict | |
22 | ||
23 | import netplan.cli.utils as utils | |
24 | from netplan.configmanager import ConfigurationError | |
25 | ||
26 | import netifaces | |
27 | ||
28 | ||
29 | def _get_target_interface(interfaces, config_manager, pf_link, pfs): | |
30 | if pf_link not in pfs: | |
31 | # handle the match: syntax, get the actual device name | |
32 | pf_match = config_manager.ethernets[pf_link].get('match') | |
33 | if pf_match: | |
34 | by_name = pf_match.get('name') | |
35 | by_mac = pf_match.get('macaddress') | |
36 | by_driver = pf_match.get('driver') | |
37 | ||
38 | for interface in interfaces: | |
39 | if ((by_name and not utils.is_interface_matching_name(interface, by_name)) or | |
40 | (by_mac and not utils.is_interface_matching_macaddress(interface, by_mac)) or | |
41 | (by_driver and not utils.is_interface_matching_driver_name(interface, by_driver))): | |
42 | continue | |
43 | # we have a matching PF | |
44 | # store the matching interface in the dictionary of | |
45 | # active PFs, but error out if we matched more than one | |
46 | if pf_link in pfs: | |
47 | raise ConfigurationError('matched more than one interface for a PF device: %s' % pf_link) | |
48 | pfs[pf_link] = interface | |
49 | else: | |
50 | # no match field, assume entry name is interface name | |
51 | if pf_link in interfaces: | |
52 | pfs[pf_link] = pf_link | |
53 | ||
54 | return pfs.get(pf_link, None) | |
55 | ||
56 | ||
57 | def get_vf_count_and_functions(interfaces, config_manager, | |
58 | vf_counts, vfs, pfs): | |
59 | """ | |
60 | Go through the list of netplan ethernet devices and identify which are | |
61 | PFs and VFs, matching the former with actual networking interfaces. | |
62 | Count how many VFs each PF will need. | |
63 | """ | |
64 | explicit_counts = {} | |
65 | for ethernet, settings in config_manager.ethernets.items(): | |
66 | if not settings: | |
67 | continue | |
68 | if ethernet == 'renderer': | |
69 | continue | |
70 | ||
71 | # we now also support explicitly stating how many VFs should be | |
72 | # allocated for a PF | |
73 | explicit_num = settings.get('virtual-function-count') | |
74 | if explicit_num: | |
75 | pf = _get_target_interface(interfaces, config_manager, ethernet, pfs) | |
76 | if pf: | |
77 | explicit_counts[pf] = explicit_num | |
78 | continue | |
79 | ||
80 | pf_link = settings.get('link') | |
81 | if pf_link and pf_link in config_manager.ethernets: | |
82 | _get_target_interface(interfaces, config_manager, pf_link, pfs) | |
83 | ||
84 | if pf_link in pfs: | |
85 | vf_counts[pfs[pf_link]] += 1 | |
86 | else: | |
87 | logging.warning('could not match physical interface for the defined PF: %s' % pf_link) | |
88 | # continue looking for other VFs | |
89 | continue | |
90 | ||
91 | # we can't yet perform matching on VFs as those are only | |
92 | # created later - but store, for convenience, all the valid | |
93 | # VFs that we encounter so far | |
94 | vfs[ethernet] = None | |
95 | ||
96 | # sanity check: since we can explicitly state the VF count, make sure | |
97 | # that this number isn't smaller than the actual number of VFs declared | |
98 | # the explicit number also overrides the number of actual VFs | |
99 | for pf, count in explicit_counts.items(): | |
100 | if pf in vf_counts and vf_counts[pf] > count: | |
101 | raise ConfigurationError( | |
102 | 'more VFs allocated than the explicit size declared: %s > %s' % (vf_counts[pf], count)) | |
103 | vf_counts[pf] = count | |
104 | ||
105 | ||
106 | def set_numvfs_for_pf(pf, vf_count): | |
107 | """ | |
108 | Allocate the required number of VFs for the selected PF. | |
109 | """ | |
110 | if vf_count > 256: | |
111 | raise ConfigurationError( | |
112 | 'cannot allocate more VFs for PF %s than the SR-IOV maximum: %s > 256' % (pf, vf_count)) | |
113 | ||
114 | devdir = os.path.join('/sys/class/net', pf, 'device') | |
115 | numvfs_path = os.path.join(devdir, 'sriov_numvfs') | |
116 | totalvfs_path = os.path.join(devdir, 'sriov_totalvfs') | |
117 | try: | |
118 | with open(totalvfs_path) as f: | |
119 | vf_max = int(f.read().strip()) | |
120 | except IOError as e: | |
121 | raise RuntimeError('failed parsing sriov_totalvfs for %s: %s' % (pf, str(e))) | |
122 | except ValueError: | |
123 | raise RuntimeError('invalid sriov_totalvfs value for %s' % pf) | |
124 | ||
125 | if vf_count > vf_max: | |
126 | raise ConfigurationError( | |
127 | 'cannot allocate more VFs for PF %s than supported: %s > %s (sriov_totalvfs)' % (pf, vf_count, vf_max)) | |
128 | ||
129 | try: | |
130 | with open(numvfs_path, 'w') as f: | |
131 | f.write(str(vf_count)) | |
132 | except IOError as e: | |
133 | bail = True | |
134 | if e.errno == 16: # device or resource busy | |
135 | logging.warning('device or resource busy while setting sriov_numvfs for %s, trying workaround' % pf) | |
136 | try: | |
137 | # doing this in two open/close sequences so that | |
138 | # it's as close to writing via shell as possible | |
139 | with open(numvfs_path, 'w') as f: | |
140 | f.write('0') | |
141 | with open(numvfs_path, 'w') as f: | |
142 | f.write(str(vf_count)) | |
143 | except IOError as e_inner: | |
144 | e = e_inner | |
145 | else: | |
146 | bail = False | |
147 | if bail: | |
148 | raise RuntimeError('failed setting sriov_numvfs to %s for %s: %s' % (vf_count, pf, str(e))) | |
149 | ||
150 | return True | |
151 | ||
152 | ||
153 | def perform_hardware_specific_quirks(pf): | |
154 | """ | |
155 | Perform any hardware-specific quirks for the given SR-IOV device to make | |
156 | sure all the VF-count changes are applied. | |
157 | """ | |
158 | devdir = os.path.join('/sys/class/net', pf, 'device') | |
159 | try: | |
160 | with open(os.path.join(devdir, 'vendor')) as f: | |
161 | device_id = f.read().strip()[2:] | |
162 | with open(os.path.join(devdir, 'device')) as f: | |
163 | vendor_id = f.read().strip()[2:] | |
164 | except IOError as e: | |
165 | raise RuntimeError('could not determine vendor and device ID of %s: %s' % (pf, str(e))) | |
166 | ||
167 | combined_id = ':'.join([vendor_id, device_id]) | |
168 | quirk_devices = () # TODO: add entries to the list | |
169 | if combined_id in quirk_devices: | |
170 | # some devices need special handling, so this is the place | |
171 | ||
172 | # Currently this part is empty, but has been added as a preemptive | |
173 | # measure, as apparently a lot of SR-IOV cards have issues with | |
174 | # dynamically allocating VFs. Some cards seem to require a full | |
175 | # kernel module reload cycle after changing the sriov_numvfs value | |
176 | # for the changes to come into effect. | |
177 | # Any identified card/vendor can then be special-cased here, if | |
178 | # needed. | |
179 | pass | |
180 | ||
181 | ||
182 | def apply_vlan_filter_for_vf(pf, vf, vlan_name, vlan_id, prefix='/'): | |
183 | """ | |
184 | Apply the hardware VLAN filtering for the selected VF. | |
185 | """ | |
186 | ||
187 | # this is more complicated, because to do this, we actually need to have | |
188 | # the vf index - just knowing the vf interface name is not enough | |
189 | vf_index = None | |
190 | # the prefix argument is here only for unit testing purposes | |
191 | vf_devdir = os.path.join(prefix, 'sys/class/net', vf, 'device') | |
192 | vf_dev_id = os.path.basename(os.readlink(vf_devdir)) | |
193 | pf_devdir = os.path.join(prefix, 'sys/class/net', pf, 'device') | |
194 | for f in os.listdir(pf_devdir): | |
195 | if 'virtfn' in f: | |
196 | dev_path = os.path.join(pf_devdir, f) | |
197 | dev_id = os.path.basename(os.readlink(dev_path)) | |
198 | if dev_id == vf_dev_id: | |
199 | vf_index = f[6:] | |
200 | break | |
201 | ||
202 | if not vf_index: | |
203 | raise RuntimeError( | |
204 | 'could not determine the VF index for %s while configuring vlan %s' % (vf, vlan_name)) | |
205 | ||
206 | # now, create the VLAN filter | |
207 | # TODO: would be best if we did this directl via python, without calling | |
208 | # the iproute tooling | |
209 | try: | |
210 | subprocess.check_call(['ip', 'link', 'set', | |
211 | 'dev', pf, | |
212 | 'vf', vf_index, | |
213 | 'vlan', str(vlan_id)], | |
214 | stdout=subprocess.DEVNULL, | |
215 | stderr=subprocess.DEVNULL) | |
216 | except subprocess.CalledProcessError: | |
217 | raise RuntimeError( | |
218 | 'failed setting SR-IOV VLAN filter for vlan %s (ip link set command failed)' % vlan_name) | |
219 | ||
220 | ||
221 | def apply_sriov_config(interfaces, config_manager): | |
222 | """ | |
223 | Go through all interfaces, identify which ones are SR-IOV VFs, create | |
224 | them and perform all other necessary setup. | |
225 | """ | |
226 | ||
227 | # for sr-iov devices, we identify VFs by them having a link: field | |
228 | # pointing to an PF. So let's browse through all ethernet devices, | |
229 | # find all that are VFs and count how many of those are linked to | |
230 | # particular PFs, as we need to then set the numvfs for each. | |
231 | vf_counts = defaultdict(int) | |
232 | # we also store all matches between VF/PF netplan entry names and | |
233 | # interface that they're currently matching to | |
234 | vfs = {} | |
235 | pfs = {} | |
236 | ||
237 | get_vf_count_and_functions( | |
238 | interfaces, config_manager, vf_counts, vfs, pfs) | |
239 | ||
240 | # setup the required number of VFs per PF | |
241 | # at the same time store which PFs got changed in case the NICs | |
242 | # require some special quirks for the VF number to change | |
243 | vf_count_changed = [] | |
244 | if vf_counts: | |
245 | for pf, vf_count in vf_counts.items(): | |
246 | if not set_numvfs_for_pf(pf, vf_count): | |
247 | continue | |
248 | ||
249 | vf_count_changed.append(pf) | |
250 | ||
251 | if vf_count_changed: | |
252 | # some cards need special treatment when we want to change the | |
253 | # number of enabled VFs | |
254 | for pf in vf_count_changed: | |
255 | perform_hardware_specific_quirks(pf) | |
256 | ||
257 | # also, since the VF number changed, the interfaces list also | |
258 | # changed, so we need to refresh it | |
259 | interfaces = netifaces.interfaces() | |
260 | ||
261 | # now in theory we should have all the new VFs set up and existing; | |
262 | # this is needed because we will have to now match the defined VF | |
263 | # entries to existing interfaces, otherwise we won't be able to set | |
264 | # filtered VLANs for those. | |
265 | # XXX: does matching those even make sense? | |
266 | for vf in vfs: | |
267 | settings = config_manager.ethernets.get(vf) | |
268 | match = settings.get('match') | |
269 | if match: | |
270 | # right now we only match by name, as I don't think matching per | |
271 | # driver and/or macaddress makes sense | |
272 | by_name = match.get('name') | |
273 | # by_mac = match.get('macaddress') | |
274 | # by_driver = match.get('driver') | |
275 | # TODO: print warning if other matches are provided | |
276 | ||
277 | for interface in interfaces: | |
278 | if by_name and not utils.is_interface_matching_name(interface, by_name): | |
279 | continue | |
280 | if vf in vfs and vfs[vf]: | |
281 | raise ConfigurationError('matched more than one interface for a VF device: %s' % vf) | |
282 | vfs[vf] = interface | |
283 | else: | |
284 | if vf in interfaces: | |
285 | vfs[vf] = vf | |
286 | ||
287 | filtered_vlans_set = set() | |
288 | for vlan, settings in config_manager.vlans.items(): | |
289 | # there is a special sriov vlan renderer that one can use to mark | |
290 | # a selected vlan to be done in hardware (VLAN filtering) | |
291 | if settings.get('renderer') == 'sriov': | |
292 | # this only works for SR-IOV VF interfaces | |
293 | link = settings.get('link') | |
294 | vlan_id = settings.get('id') | |
295 | if not vlan_id: | |
296 | raise ConfigurationError( | |
297 | 'no id property defined for SR-IOV vlan %s' % vlan) | |
298 | ||
299 | vf = vfs.get(link) | |
300 | if not vf: | |
301 | # it is possible this is not an error, for instance when | |
302 | # the configuration has been defined 'for the future' | |
303 | # XXX: but maybe we should error out here as well? | |
304 | logging.warning( | |
305 | 'SR-IOV vlan defined for %s but link %s is either not a VF or has no matches' % (vlan, link)) | |
306 | continue | |
307 | ||
308 | # get the parent pf interface | |
309 | # first we fetch the related vf netplan entry | |
310 | vf_parent_entry = config_manager.ethernets.get(link).get('link') | |
311 | # and finally, get the matched pf interface | |
312 | pf = pfs.get(vf_parent_entry) | |
313 | ||
314 | if vf in filtered_vlans_set: | |
315 | raise ConfigurationError( | |
316 | 'interface %s for netplan device %s (%s) already has an SR-IOV vlan defined' % (vf, link, vlan)) | |
317 | ||
318 | apply_vlan_filter_for_vf(pf, vf, vlan, vlan_id) | |
319 | filtered_vlans_set.add(vf) |
0 | 0 | #!/usr/bin/python3 |
1 | 1 | # |
2 | # Copyright (C) 2018 Canonical, Ltd. | |
2 | # Copyright (C) 2018-2020 Canonical, Ltd. | |
3 | 3 | # Author: Mathieu Trudel-Lapierre <mathieu.trudel-lapierre@canonical.com> |
4 | # Author: Łukasz 'sil2100' Zemczak <lukasz.zemczak@canonical.com> | |
4 | 5 | # |
5 | 6 | # This program is free software; you can redistribute it and/or modify |
6 | 7 | # it under the terms of the GNU General Public License as published by |
16 | 17 | |
17 | 18 | import sys |
18 | 19 | import os |
20 | import logging | |
21 | import fnmatch | |
19 | 22 | import argparse |
20 | 23 | import subprocess |
24 | import netifaces | |
21 | 25 | |
22 | 26 | NM_SERVICE_NAME = 'NetworkManager.service' |
23 | 27 | NM_SNAP_SERVICE_NAME = 'snap.network-manager.networkmanager.service' |
79 | 83 | command.append(service) |
80 | 84 | |
81 | 85 | subprocess.check_call(command) |
86 | ||
87 | ||
88 | def get_interface_driver_name(interface, only_down=False): # pragma: nocover (covered in autopkgtest) | |
89 | devdir = os.path.join('/sys/class/net', interface) | |
90 | if only_down: | |
91 | try: | |
92 | with open(os.path.join(devdir, 'operstate')) as f: | |
93 | state = f.read().strip() | |
94 | if state != 'down': | |
95 | logging.debug('device %s operstate is %s, not changing', interface, state) | |
96 | return None | |
97 | except IOError as e: | |
98 | logging.error('Cannot determine operstate of %s: %s', interface, str(e)) | |
99 | return None | |
100 | ||
101 | try: | |
102 | driver = os.path.realpath(os.path.join(devdir, 'device', 'driver')) | |
103 | driver_name = os.path.basename(driver) | |
104 | except IOError as e: | |
105 | logging.debug('Cannot replug %s: cannot read link %s/device: %s', interface, devdir, str(e)) | |
106 | return None | |
107 | ||
108 | return driver_name | |
109 | ||
110 | ||
111 | def get_interface_macaddress(interface): # pragma: nocover (covered in autopkgtest) | |
112 | link = netifaces.ifaddresses(interface)[netifaces.AF_LINK][0] | |
113 | ||
114 | return link.get('addr') | |
115 | ||
116 | ||
117 | def is_interface_matching_name(interface, match_driver): | |
118 | return fnmatch.fnmatchcase(interface, match_driver) | |
119 | ||
120 | ||
121 | def is_interface_matching_driver_name(interface, match_driver): | |
122 | driver_name = get_interface_driver_name(interface) | |
123 | ||
124 | return match_driver == driver_name | |
125 | ||
126 | ||
127 | def is_interface_matching_macaddress(interface, match_mac): | |
128 | macaddress = get_interface_macaddress(interface) | |
129 | ||
130 | return match_mac == macaddress | |
82 | 131 | |
83 | 132 | |
84 | 133 | class NetplanCommand(argparse.Namespace): |
52 | 52 | static void |
53 | 53 | nd_iterator_list(gpointer value, gpointer user_data) |
54 | 54 | { |
55 | if (write_networkd_conf((net_definition*) value, (const char*) user_data)) | |
55 | if (write_networkd_conf((NetplanNetDefinition*) value, (const char*) user_data)) | |
56 | 56 | any_networkd = TRUE; |
57 | write_nm_conf((net_definition*) value, (const char*) user_data); | |
57 | write_nm_conf((NetplanNetDefinition*) value, (const char*) user_data); | |
58 | 58 | } |
59 | 59 | |
60 | 60 | |
90 | 90 | |
91 | 91 | g_hash_table_iter_init (&iter, netdefs); |
92 | 92 | while (g_hash_table_iter_next (&iter, &key, &value)) { |
93 | net_definition *nd = (net_definition *) value; | |
93 | NetplanNetDefinition *nd = (NetplanNetDefinition *) value; | |
94 | 94 | if (!g_strcmp0(nd->set_name, interface)) |
95 | 95 | g_ptr_array_add (found, (gpointer) nd); |
96 | 96 | else if (!g_strcmp0(nd->id, interface)) |
103 | 103 | // LCOV_EXCL_START |
104 | 104 | g_hash_table_iter_init (&iter, netdefs); |
105 | 105 | while (g_hash_table_iter_next (&iter, &key, &value)) { |
106 | net_definition *nd = (net_definition *) value; | |
106 | NetplanNetDefinition *nd = (NetplanNetDefinition *) value; | |
107 | 107 | if (!g_strcmp0(nd->match.driver, driver)) |
108 | 108 | g_ptr_array_add (found, (gpointer) nd); |
109 | 109 | } |
117 | 117 | goto exit_find; |
118 | 118 | } |
119 | 119 | else { |
120 | net_definition *nd = (net_definition *)g_ptr_array_index (found, 0); | |
120 | const NetplanNetDefinition *nd = (NetplanNetDefinition *)g_ptr_array_index (found, 0); | |
121 | 121 | g_printf("id=%s, backend=%s, set_name=%s, match_name=%s, match_mac=%s, match_driver=%s\n", |
122 | 122 | nd->id, |
123 | netdef_backend_to_name[nd->backend], | |
123 | netplan_backend_to_name[nd->backend], | |
124 | 124 | nd->set_name, |
125 | 125 | nd->match.original_name, |
126 | 126 | nd->match.mac, |
140 | 140 | GError* error = NULL; |
141 | 141 | |
142 | 142 | g_debug("Processing input file %s..", f); |
143 | if (!parse_yaml(f, &error)) { | |
143 | if (!netplan_parse_yaml(f, &error)) { | |
144 | 144 | g_fprintf(stderr, "%s\n", error->message); |
145 | 145 | exit(1); |
146 | 146 | } |
236 | 236 | process_input_file(g_hash_table_lookup(configs, i->data)); |
237 | 237 | } |
238 | 238 | |
239 | if (!finish_parse(&error)) { | |
239 | netdefs = netplan_finish_parse(&error); | |
240 | if (error) { | |
240 | 241 | g_fprintf(stderr, "%s\n", error->message); |
241 | 242 | exit(1); |
242 | 243 | } |
265 | 266 | |
266 | 267 | /* Disable /usr/lib/NetworkManager/conf.d/10-globally-managed-devices.conf |
267 | 268 | * (which restricts NM to wifi and wwan) if global renderer is NM */ |
268 | if (get_global_backend() == BACKEND_NM) | |
269 | if (netplan_get_global_backend() == NETPLAN_BACKEND_NM) | |
269 | 270 | g_string_free_to_file(g_string_new(NULL), rootdir, "/run/NetworkManager/conf.d/10-globally-managed-devices.conf", NULL); |
270 | 271 | |
271 | 272 | if (called_as_generator) { |
0 | [Unit] | |
1 | Description=WPA supplicant for netplan %I | |
2 | DefaultDependencies=no | |
3 | Requires=sys-subsystem-net-devices-%i.device | |
4 | After=sys-subsystem-net-devices-%i.device | |
5 | Before=network.target | |
6 | Wants=network.target | |
7 | ||
8 | [Service] | |
9 | Type=simple | |
10 | ExecStart=/sbin/wpa_supplicant -c /run/netplan/wpa-%I.conf -i%I |
15 | 15 | */ |
16 | 16 | |
17 | 17 | #include <stdlib.h> |
18 | #include <string.h> | |
18 | 19 | #include <unistd.h> |
20 | #include <ctype.h> | |
19 | 21 | #include <errno.h> |
20 | 22 | #include <sys/stat.h> |
21 | 23 | |
26 | 28 | #include "parse.h" |
27 | 29 | #include "util.h" |
28 | 30 | |
31 | /** | |
32 | * Append WiFi frequencies to wpa_supplicant's freq_list= | |
33 | */ | |
34 | static void | |
35 | wifi_append_freq(gpointer key, gpointer value, gpointer user_data) | |
36 | { | |
37 | GString* s = user_data; | |
38 | g_string_append_printf(s, "%d ", GPOINTER_TO_INT(value)); | |
39 | } | |
40 | ||
41 | /** | |
42 | * append wowlan_triggers= string for wpa_supplicant.conf | |
43 | */ | |
44 | static void | |
45 | append_wifi_wowlan_flags(NetplanWifiWowlanFlag flag, GString* str) { | |
46 | if (flag & NETPLAN_WIFI_WOWLAN_TYPES[0].flag || flag >= NETPLAN_WIFI_WOWLAN_TCP) { | |
47 | g_fprintf(stderr, "ERROR: unsupported wowlan_triggers mask: 0x%x\n", flag); | |
48 | exit(1); | |
49 | } | |
50 | for (unsigned i = 0; NETPLAN_WIFI_WOWLAN_TYPES[i].name != NULL; ++i) { | |
51 | if (flag & NETPLAN_WIFI_WOWLAN_TYPES[i].flag) { | |
52 | g_string_append_printf(str, "%s ", NETPLAN_WIFI_WOWLAN_TYPES[i].name); | |
53 | } | |
54 | } | |
55 | /* replace trailing space with newline */ | |
56 | str = g_string_overwrite(str, str->len-1, "\n"); | |
57 | } | |
29 | 58 | |
30 | 59 | /** |
31 | 60 | * Append [Match] section of @def to @s. |
32 | 61 | */ |
33 | 62 | static void |
34 | append_match_section(net_definition* def, GString* s, gboolean match_rename) | |
63 | append_match_section(const NetplanNetDefinition* def, GString* s, gboolean match_rename) | |
35 | 64 | { |
36 | 65 | /* Note: an empty [Match] section is interpreted as matching all devices, |
37 | 66 | * which is what we want for the simple case that you only have one device |
47 | 76 | if (!match_rename && def->match.original_name) |
48 | 77 | g_string_append_printf(s, "OriginalName=%s\n", def->match.original_name); |
49 | 78 | if (match_rename) { |
50 | if (def->type >= ND_VIRTUAL) | |
79 | if (def->type >= NETPLAN_DEF_TYPE_VIRTUAL) | |
51 | 80 | g_string_append_printf(s, "Name=%s\n", def->id); |
52 | 81 | else if (def->set_name) |
53 | 82 | g_string_append_printf(s, "Name=%s\n", def->set_name); |
75 | 104 | } |
76 | 105 | |
77 | 106 | static void |
78 | write_bridge_params(GString* s, net_definition* def) | |
107 | write_bridge_params(GString* s, const NetplanNetDefinition* def) | |
79 | 108 | { |
80 | 109 | GString *params = NULL; |
81 | 110 | |
101 | 130 | } |
102 | 131 | |
103 | 132 | static void |
104 | write_tunnel_params(GString* s, net_definition* def) | |
133 | write_tunnel_params(GString* s, const NetplanNetDefinition* def) | |
105 | 134 | { |
106 | 135 | GString *params = NULL; |
107 | 136 | |
108 | 137 | params = g_string_sized_new(200); |
109 | 138 | |
110 | 139 | g_string_printf(params, "Independent=true\n"); |
111 | if (def->tunnel.mode == TUNNEL_MODE_IPIP6 || def->tunnel.mode == TUNNEL_MODE_IP6IP6) | |
140 | if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_IPIP6 || def->tunnel.mode == NETPLAN_TUNNEL_MODE_IP6IP6) | |
112 | 141 | g_string_append_printf(params, "Mode=%s\n", tunnel_mode_to_string(def->tunnel.mode)); |
113 | 142 | g_string_append_printf(params, "Local=%s\n", def->tunnel.local_ip); |
114 | 143 | g_string_append_printf(params, "Remote=%s\n", def->tunnel.remote_ip); |
122 | 151 | } |
123 | 152 | |
124 | 153 | static void |
125 | write_link_file(net_definition* def, const char* rootdir, const char* path) | |
154 | write_link_file(const NetplanNetDefinition* def, const char* rootdir, const char* path) | |
126 | 155 | { |
127 | 156 | GString* s = NULL; |
128 | 157 | mode_t orig_umask; |
129 | 158 | |
130 | /* Don't write .link files for virtual devices; they use .netdev instead */ | |
131 | if (def->type >= ND_VIRTUAL) | |
159 | /* Don't write .link files for virtual devices; they use .netdev instead. | |
160 | * Don't write .link files for MODEM devices, as they aren't supported by networkd. | |
161 | */ | |
162 | if (def->type >= NETPLAN_DEF_TYPE_VIRTUAL || def->type == NETPLAN_DEF_TYPE_MODEM) | |
132 | 163 | return; |
133 | 164 | |
134 | 165 | /* do we need to write a .link file? */ |
169 | 200 | |
170 | 201 | |
171 | 202 | static void |
172 | write_bond_parameters(net_definition* def, GString* s) | |
203 | write_bond_parameters(const NetplanNetDefinition* def, GString* s) | |
173 | 204 | { |
174 | 205 | GString* params = NULL; |
175 | 206 | |
248 | 279 | } |
249 | 280 | |
250 | 281 | static void |
251 | write_netdev_file(net_definition* def, const char* rootdir, const char* path) | |
282 | write_netdev_file(const NetplanNetDefinition* def, const char* rootdir, const char* path) | |
252 | 283 | { |
253 | 284 | GString* s = NULL; |
254 | 285 | mode_t orig_umask; |
255 | 286 | |
256 | g_assert(def->type >= ND_VIRTUAL); | |
287 | g_assert(def->type >= NETPLAN_DEF_TYPE_VIRTUAL); | |
288 | ||
289 | if (def->type == NETPLAN_DEF_TYPE_VLAN && def->sriov_vlan_filter) { | |
290 | g_debug("%s is defined as a hardware SR-IOV filtered VLAN, postponing creation", def->id); | |
291 | return; | |
292 | } | |
257 | 293 | |
258 | 294 | /* build file contents */ |
259 | 295 | s = g_string_sized_new(200); |
265 | 301 | g_string_append_printf(s, "MTUBytes=%u\n", def->mtubytes); |
266 | 302 | |
267 | 303 | switch (def->type) { |
268 | case ND_BRIDGE: | |
304 | case NETPLAN_DEF_TYPE_BRIDGE: | |
269 | 305 | g_string_append(s, "Kind=bridge\n"); |
270 | 306 | write_bridge_params(s, def); |
271 | 307 | break; |
272 | 308 | |
273 | case ND_BOND: | |
309 | case NETPLAN_DEF_TYPE_BOND: | |
274 | 310 | g_string_append(s, "Kind=bond\n"); |
275 | 311 | write_bond_parameters(def, s); |
276 | 312 | break; |
277 | 313 | |
278 | case ND_VLAN: | |
314 | case NETPLAN_DEF_TYPE_VLAN: | |
279 | 315 | g_string_append_printf(s, "Kind=vlan\n\n[VLAN]\nId=%u\n", def->vlan_id); |
280 | 316 | break; |
281 | 317 | |
282 | case ND_TUNNEL: | |
318 | case NETPLAN_DEF_TYPE_TUNNEL: | |
283 | 319 | switch(def->tunnel.mode) { |
284 | case TUNNEL_MODE_GRE: | |
285 | case TUNNEL_MODE_GRETAP: | |
286 | case TUNNEL_MODE_IPIP: | |
287 | case TUNNEL_MODE_IP6GRE: | |
288 | case TUNNEL_MODE_IP6GRETAP: | |
289 | case TUNNEL_MODE_SIT: | |
290 | case TUNNEL_MODE_VTI: | |
291 | case TUNNEL_MODE_VTI6: | |
320 | case NETPLAN_TUNNEL_MODE_GRE: | |
321 | case NETPLAN_TUNNEL_MODE_GRETAP: | |
322 | case NETPLAN_TUNNEL_MODE_IPIP: | |
323 | case NETPLAN_TUNNEL_MODE_IP6GRE: | |
324 | case NETPLAN_TUNNEL_MODE_IP6GRETAP: | |
325 | case NETPLAN_TUNNEL_MODE_SIT: | |
326 | case NETPLAN_TUNNEL_MODE_VTI: | |
327 | case NETPLAN_TUNNEL_MODE_VTI6: | |
292 | 328 | g_string_append_printf(s, |
293 | 329 | "Kind=%s\n", |
294 | 330 | tunnel_mode_to_string(def->tunnel.mode)); |
295 | 331 | break; |
296 | 332 | |
297 | case TUNNEL_MODE_IP6IP6: | |
298 | case TUNNEL_MODE_IPIP6: | |
333 | case NETPLAN_TUNNEL_MODE_IP6IP6: | |
334 | case NETPLAN_TUNNEL_MODE_IPIP6: | |
299 | 335 | g_string_append(s, "Kind=ip6tnl\n"); |
300 | 336 | break; |
301 | 337 | |
322 | 358 | } |
323 | 359 | |
324 | 360 | static void |
325 | write_route(ip_route* r, GString* s) | |
361 | write_route(NetplanIPRoute* r, GString* s) | |
326 | 362 | { |
327 | 363 | g_string_append_printf(s, "\n[Route]\n"); |
328 | 364 | |
339 | 375 | g_string_append_printf(s, "Type=%s\n", r->type); |
340 | 376 | if (r->onlink) |
341 | 377 | g_string_append_printf(s, "GatewayOnlink=true\n"); |
342 | if (r->metric != METRIC_UNSPEC) | |
378 | if (r->metric != NETPLAN_METRIC_UNSPEC) | |
343 | 379 | g_string_append_printf(s, "Metric=%d\n", r->metric); |
344 | if (r->table != ROUTE_TABLE_UNSPEC) | |
380 | if (r->table != NETPLAN_ROUTE_TABLE_UNSPEC) | |
345 | 381 | g_string_append_printf(s, "Table=%d\n", r->table); |
346 | 382 | } |
347 | 383 | |
348 | 384 | static void |
349 | write_ip_rule(ip_rule* r, GString* s) | |
385 | write_ip_rule(NetplanIPRule* r, GString* s) | |
350 | 386 | { |
351 | 387 | g_string_append_printf(s, "\n[RoutingPolicyRule]\n"); |
352 | 388 | |
355 | 391 | if (r->to) |
356 | 392 | g_string_append_printf(s, "To=%s\n", r->to); |
357 | 393 | |
358 | if (r->table != ROUTE_TABLE_UNSPEC) | |
394 | if (r->table != NETPLAN_ROUTE_TABLE_UNSPEC) | |
359 | 395 | g_string_append_printf(s, "Table=%d\n", r->table); |
360 | if (r->priority != IP_RULE_PRIO_UNSPEC) | |
396 | if (r->priority != NETPLAN_IP_RULE_PRIO_UNSPEC) | |
361 | 397 | g_string_append_printf(s, "Priority=%d\n", r->priority); |
362 | if (r->fwmark != IP_RULE_FW_MARK_UNSPEC) | |
398 | if (r->fwmark != NETPLAN_IP_RULE_FW_MARK_UNSPEC) | |
363 | 399 | g_string_append_printf(s, "FirewallMark=%d\n", r->fwmark); |
364 | if (r->tos != IP_RULE_TOS_UNSPEC) | |
400 | if (r->tos != NETPLAN_IP_RULE_TOS_UNSPEC) | |
365 | 401 | g_string_append_printf(s, "TypeOfService=%d\n", r->tos); |
366 | 402 | } |
367 | 403 | |
370 | 406 | "dhcp4_overrides and dhcp6_overrides\n" |
371 | 407 | |
372 | 408 | static void |
373 | combine_dhcp_overrides(net_definition* def, dhcp_overrides* combined_dhcp_overrides) | |
409 | combine_dhcp_overrides(const NetplanNetDefinition* def, NetplanDHCPOverrides* combined_dhcp_overrides) | |
374 | 410 | { |
375 | 411 | /* if only one of dhcp4 or dhcp6 is enabled, those overrides are used */ |
376 | 412 | if (def->dhcp4 && !def->dhcp6) { |
423 | 459 | } |
424 | 460 | |
425 | 461 | static void |
426 | write_network_file(net_definition* def, const char* rootdir, const char* path) | |
462 | write_network_file(const NetplanNetDefinition* def, const char* rootdir, const char* path) | |
427 | 463 | { |
428 | 464 | GString* network = NULL; |
429 | 465 | GString* link = NULL; |
430 | 466 | GString* s = NULL; |
431 | 467 | mode_t orig_umask; |
432 | 468 | |
469 | if (def->type == NETPLAN_DEF_TYPE_VLAN && def->sriov_vlan_filter) { | |
470 | g_debug("%s is defined as a hardware SR-IOV filtered VLAN, postponing creation", def->id); | |
471 | return; | |
472 | } | |
473 | ||
433 | 474 | /* Prepare the [Link] section of the .network file. */ |
434 | 475 | link = g_string_sized_new(200); |
435 | 476 | |
440 | 481 | if (def->optional) { |
441 | 482 | g_string_append(link, "RequiredForOnline=no\n"); |
442 | 483 | } |
443 | for (unsigned i = 0; optional_address_options[i].name != NULL; ++i) { | |
444 | if (def->optional_addresses & optional_address_options[i].flag) { | |
445 | g_string_append_printf(link, "OptionalAddresses=%s\n", optional_address_options[i].name); | |
484 | for (unsigned i = 0; NETPLAN_OPTIONAL_ADDRESS_TYPES[i].name != NULL; ++i) { | |
485 | if (def->optional_addresses & NETPLAN_OPTIONAL_ADDRESS_TYPES[i].flag) { | |
486 | g_string_append_printf(link, "OptionalAddresses=%s\n", NETPLAN_OPTIONAL_ADDRESS_TYPES[i].name); | |
446 | 487 | } |
447 | 488 | } |
448 | 489 | } |
449 | 490 | |
491 | if (def->mtubytes) { | |
492 | g_string_append_printf(link, "MTUBytes=%d\n", def->mtubytes); | |
493 | } | |
494 | ||
495 | if (def->emit_lldp) { | |
496 | g_string_append(network, "EmitLLDP=true\n"); | |
497 | } | |
450 | 498 | |
451 | 499 | if (def->dhcp4 && def->dhcp6) |
452 | 500 | g_string_append(network, "DHCP=yes\n"); |
475 | 523 | if (def->ip6_addresses) |
476 | 524 | for (unsigned i = 0; i < def->ip6_addresses->len; ++i) |
477 | 525 | g_string_append_printf(network, "Address=%s\n", g_array_index(def->ip6_addresses, char*, i)); |
478 | if (def->accept_ra == ACCEPT_RA_ENABLED) | |
526 | if (def->ip6_addr_gen_mode) { | |
527 | /* TODO: Figure out how we can configure ipv6-address-generation for networkd. | |
528 | * IPv6Token= seems to be the corresponding option, but it doesn't do | |
529 | * exactly what we need and has quite some restrictions, c.f.: | |
530 | * https://github.com/systemd/systemd/issues/4625 | |
531 | * https://github.com/systemd/systemd/pull/14415 */ | |
532 | g_fprintf(stderr, "ERROR: %s: ipv6-address-generation is not supported by networkd\n", def->id); | |
533 | exit(1); | |
534 | } | |
535 | if (def->accept_ra == NETPLAN_RA_MODE_ENABLED) | |
479 | 536 | g_string_append_printf(network, "IPv6AcceptRA=yes\n"); |
480 | else if (def->accept_ra == ACCEPT_RA_DISABLED) | |
537 | else if (def->accept_ra == NETPLAN_RA_MODE_DISABLED) | |
481 | 538 | g_string_append_printf(network, "IPv6AcceptRA=no\n"); |
482 | 539 | if (def->ip6_privacy) |
483 | 540 | g_string_append(network, "IPv6PrivacyExtensions=yes\n"); |
502 | 559 | g_string_append_printf(network, "IPv6MTUBytes=%d\n", def->ipv6_mtubytes); |
503 | 560 | } |
504 | 561 | |
505 | if (def->type >= ND_VIRTUAL) | |
562 | if (def->type >= NETPLAN_DEF_TYPE_VIRTUAL) | |
506 | 563 | g_string_append(network, "ConfigureWithoutCarrier=yes\n"); |
507 | 564 | |
508 | 565 | if (def->bridge) { |
525 | 582 | if (def->has_vlans) { |
526 | 583 | /* iterate over all netdefs to find VLANs attached to us */ |
527 | 584 | GList *l = netdefs_ordered; |
528 | net_definition* nd; | |
585 | const NetplanNetDefinition* nd; | |
529 | 586 | for (; l != NULL; l = l->next) { |
530 | 587 | nd = l->data; |
531 | if (nd->vlan_link == def) | |
588 | if (nd->vlan_link == def && !nd->sriov_vlan_filter) | |
532 | 589 | g_string_append_printf(network, "VLAN=%s\n", nd->id); |
533 | 590 | } |
534 | 591 | } |
535 | 592 | |
536 | 593 | if (def->routes != NULL) { |
537 | 594 | for (unsigned i = 0; i < def->routes->len; ++i) { |
538 | ip_route* cur_route = g_array_index (def->routes, ip_route*, i); | |
595 | NetplanIPRoute* cur_route = g_array_index (def->routes, NetplanIPRoute*, i); | |
539 | 596 | write_route(cur_route, network); |
540 | 597 | } |
541 | 598 | } |
542 | 599 | if (def->ip_rules != NULL) { |
543 | 600 | for (unsigned i = 0; i < def->ip_rules->len; ++i) { |
544 | ip_rule* cur_rule = g_array_index (def->ip_rules, ip_rule*, i); | |
601 | NetplanIPRule* cur_rule = g_array_index (def->ip_rules, NetplanIPRule*, i); | |
545 | 602 | write_ip_rule(cur_rule, network); |
546 | 603 | } |
547 | 604 | } |
548 | 605 | |
549 | if (def->dhcp4 || def->dhcp6) { | |
606 | if (def->dhcp4 || def->dhcp6 || def->critical) { | |
550 | 607 | /* NetworkManager compatible route metrics */ |
551 | 608 | g_string_append(network, "\n[DHCP]\n"); |
552 | ||
609 | } | |
610 | ||
611 | if (def->critical) | |
612 | g_string_append_printf(network, "CriticalConnection=true\n"); | |
613 | ||
614 | if (def->dhcp4 || def->dhcp6) { | |
553 | 615 | if (g_strcmp0(def->dhcp_identifier, "duid") != 0) |
554 | 616 | g_string_append_printf(network, "ClientIdentifier=%s\n", def->dhcp_identifier); |
555 | if (def->critical) | |
556 | g_string_append_printf(network, "CriticalConnection=true\n"); | |
557 | ||
558 | dhcp_overrides combined_dhcp_overrides; | |
617 | ||
618 | NetplanDHCPOverrides combined_dhcp_overrides; | |
559 | 619 | combine_dhcp_overrides(def, &combined_dhcp_overrides); |
560 | 620 | |
561 | if (combined_dhcp_overrides.metric == METRIC_UNSPEC) { | |
562 | g_string_append_printf(network, "RouteMetric=%i\n", (def->type == ND_WIFI ? 600 : 100)); | |
621 | if (combined_dhcp_overrides.metric == NETPLAN_METRIC_UNSPEC) { | |
622 | g_string_append_printf(network, "RouteMetric=%i\n", (def->type == NETPLAN_DEF_TYPE_WIFI ? 600 : 100)); | |
563 | 623 | } else { |
564 | 624 | g_string_append_printf(network, "RouteMetric=%u\n", |
565 | 625 | combined_dhcp_overrides.metric); |
612 | 672 | } |
613 | 673 | |
614 | 674 | static void |
615 | write_rules_file(net_definition* def, const char* rootdir) | |
675 | write_rules_file(const NetplanNetDefinition* def, const char* rootdir) | |
616 | 676 | { |
617 | 677 | GString* s = NULL; |
618 | 678 | g_autofree char* path = g_strjoin(NULL, "run/udev/rules.d/99-netplan-", def->id, ".rules", NULL); |
621 | 681 | /* do we need to write a .rules file? |
622 | 682 | * It's only required for reliably setting the name of a physical device |
623 | 683 | * until systemd issue #9006 is resolved. */ |
624 | if (def->type >= ND_VIRTUAL) | |
684 | if (def->type >= NETPLAN_DEF_TYPE_VIRTUAL) | |
625 | 685 | return; |
626 | 686 | |
627 | 687 | /* Matching by name does not work. |
656 | 716 | } |
657 | 717 | |
658 | 718 | static void |
659 | append_wpa_auth_conf(GString* s, const authentication_settings* auth) | |
719 | append_wpa_auth_conf(GString* s, const NetplanAuthenticationSettings* auth, const char* id) | |
660 | 720 | { |
661 | 721 | switch (auth->key_management) { |
662 | case KEY_MANAGEMENT_NONE: | |
722 | case NETPLAN_AUTH_KEY_MANAGEMENT_NONE: | |
663 | 723 | g_string_append(s, " key_mgmt=NONE\n"); |
664 | 724 | break; |
665 | 725 | |
666 | case KEY_MANAGEMENT_WPA_PSK: | |
726 | case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK: | |
667 | 727 | g_string_append(s, " key_mgmt=WPA-PSK\n"); |
668 | 728 | break; |
669 | 729 | |
670 | case KEY_MANAGEMENT_WPA_EAP: | |
730 | case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAP: | |
671 | 731 | g_string_append(s, " key_mgmt=WPA-EAP\n"); |
672 | 732 | break; |
673 | 733 | |
674 | case KEY_MANAGEMENT_8021X: | |
734 | case NETPLAN_AUTH_KEY_MANAGEMENT_8021X: | |
675 | 735 | g_string_append(s, " key_mgmt=IEEE8021X\n"); |
676 | 736 | break; |
677 | 737 | } |
678 | 738 | |
679 | 739 | switch (auth->eap_method) { |
680 | case EAP_NONE: | |
681 | break; | |
682 | ||
683 | case EAP_TLS: | |
740 | case NETPLAN_AUTH_EAP_NONE: | |
741 | break; | |
742 | ||
743 | case NETPLAN_AUTH_EAP_TLS: | |
684 | 744 | g_string_append(s, " eap=TLS\n"); |
685 | 745 | break; |
686 | 746 | |
687 | case EAP_PEAP: | |
747 | case NETPLAN_AUTH_EAP_PEAP: | |
688 | 748 | g_string_append(s, " eap=PEAP\n"); |
689 | 749 | break; |
690 | 750 | |
691 | case EAP_TTLS: | |
751 | case NETPLAN_AUTH_EAP_TTLS: | |
692 | 752 | g_string_append(s, " eap=TTLS\n"); |
693 | 753 | break; |
694 | 754 | } |
700 | 760 | g_string_append_printf(s, " anonymous_identity=\"%s\"\n", auth->anonymous_identity); |
701 | 761 | } |
702 | 762 | if (auth->password) { |
703 | if (auth->key_management == KEY_MANAGEMENT_WPA_PSK) { | |
704 | g_string_append_printf(s, " psk=\"%s\"\n", auth->password); | |
763 | if (auth->key_management == NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK) { | |
764 | size_t len = strlen(auth->password); | |
765 | if (len == 64) { | |
766 | /* must be a hex-digit key representation */ | |
767 | for (unsigned i = 0; i < 64; ++i) | |
768 | if (!isxdigit(auth->password[i])) { | |
769 | g_fprintf(stderr, "ERROR: %s: PSK length of 64 is only supported for hex-digit representation\n", id); | |
770 | exit(1); | |
771 | } | |
772 | /* this is required to be unquoted */ | |
773 | g_string_append_printf(s, " psk=%s\n", auth->password); | |
774 | } else if (len < 8 || len > 63) { | |
775 | /* per wpa_supplicant spec, passphrase needs to be between 8 | |
776 | and 63 characters */ | |
777 | g_fprintf(stderr, "ERROR: %s: ASCII passphrase must be between 8 and 63 characters (inclusive)\n", id); | |
778 | exit(1); | |
779 | } else { | |
780 | g_string_append_printf(s, " psk=\"%s\"\n", auth->password); | |
781 | } | |
705 | 782 | } else { |
706 | 783 | if (strncmp(auth->password, "hash:", 5) == 0) { |
707 | 784 | g_string_append_printf(s, " password=%s\n", auth->password); |
722 | 799 | if (auth->client_key_password) { |
723 | 800 | g_string_append_printf(s, " private_key_passwd=\"%s\"\n", auth->client_key_password); |
724 | 801 | } |
725 | } | |
726 | ||
727 | static void | |
728 | write_wpa_conf(net_definition* def, const char* rootdir) | |
802 | if (auth->phase2_auth) { | |
803 | g_string_append_printf(s, " phase2=\"auth=%s\"\n", auth->phase2_auth); | |
804 | } | |
805 | ||
806 | } | |
807 | ||
808 | /* netplan-feature: generated-supplicant */ | |
809 | static void | |
810 | write_wpa_unit(const NetplanNetDefinition* def, const char* rootdir) | |
811 | { | |
812 | g_autoptr(GError) err = NULL; | |
813 | g_autofree gchar *stdouth = NULL; | |
814 | g_autofree gchar *stderrh = NULL; | |
815 | gint exit_status = 0; | |
816 | ||
817 | gchar *argv[] = {"bin" "/" "systemd-escape", def->id, NULL}; | |
818 | g_spawn_sync("/", argv, NULL, 0, NULL, NULL, &stdouth, &stderrh, &exit_status, &err); | |
819 | g_spawn_check_exit_status(exit_status, &err); | |
820 | if (err != NULL) { | |
821 | // LCOV_EXCL_START | |
822 | g_fprintf(stderr, "failed to ask systemd to escape %s; exit %d\nstdout: '%s'\nstderr: '%s'", def->id, exit_status, stdouth, stderrh); | |
823 | exit(1); | |
824 | // LCOV_EXCL_STOP | |
825 | } | |
826 | g_strstrip(stdouth); | |
827 | ||
828 | GString* s = g_string_new("[Unit]\n"); | |
829 | g_autofree char* path = g_strjoin(NULL, "/run/systemd/system/netplan-wpa-", stdouth, ".service", NULL); | |
830 | g_string_append_printf(s, "Description=WPA supplicant for netplan %s\n", stdouth); | |
831 | g_string_append(s, "DefaultDependencies=no\n"); | |
832 | g_string_append_printf(s, "Requires=sys-subsystem-net-devices-%s.device\n", stdouth); | |
833 | g_string_append_printf(s, "After=sys-subsystem-net-devices-%s.device\n", stdouth); | |
834 | g_string_append(s, "Before=network.target\nWants=network.target\n\n"); | |
835 | g_string_append(s, "[Service]\nType=simple\n"); | |
836 | g_string_append_printf(s, "ExecStart=/sbin/wpa_supplicant -c /run/netplan/wpa-%s.conf -i%s", stdouth, stdouth); | |
837 | ||
838 | if (def->type != NETPLAN_DEF_TYPE_WIFI) { | |
839 | g_string_append(s, " -Dwired\n"); | |
840 | } | |
841 | g_string_free_to_file(s, rootdir, path, NULL); | |
842 | } | |
843 | ||
844 | static void | |
845 | write_wpa_conf(const NetplanNetDefinition* def, const char* rootdir) | |
729 | 846 | { |
730 | 847 | GHashTableIter iter; |
731 | 848 | GString* s = g_string_new("ctrl_interface=/run/wpa_supplicant\n\n"); |
733 | 850 | mode_t orig_umask; |
734 | 851 | |
735 | 852 | g_debug("%s: Creating wpa_supplicant configuration file %s", def->id, path); |
736 | if (def->type == ND_WIFI) { | |
737 | wifi_access_point* ap; | |
853 | if (def->type == NETPLAN_DEF_TYPE_WIFI) { | |
854 | if (def->wowlan && def->wowlan > NETPLAN_WIFI_WOWLAN_DEFAULT) { | |
855 | g_string_append(s, "wowlan_triggers="); | |
856 | append_wifi_wowlan_flags(def->wowlan, s); | |
857 | } | |
858 | NetplanWifiAccessPoint* ap; | |
738 | 859 | g_hash_table_iter_init(&iter, def->access_points); |
739 | 860 | while (g_hash_table_iter_next(&iter, NULL, (gpointer) &ap)) { |
740 | 861 | g_string_append_printf(s, "network={\n ssid=\"%s\"\n", ap->ssid); |
862 | if (ap->bssid) { | |
863 | g_string_append_printf(s, " bssid=%s\n", ap->bssid); | |
864 | } | |
865 | if (ap->band == NETPLAN_WIFI_BAND_24) { | |
866 | // initialize 2.4GHz frequency hashtable | |
867 | if(!wifi_frequency_24) | |
868 | wifi_get_freq24(1); | |
869 | if (ap->channel) { | |
870 | g_string_append_printf(s, " freq_list=%d\n", wifi_get_freq24(ap->channel)); | |
871 | } else { | |
872 | g_string_append_printf(s, " freq_list="); | |
873 | g_hash_table_foreach(wifi_frequency_24, wifi_append_freq, s); | |
874 | // overwrite last whitespace with newline | |
875 | s = g_string_overwrite(s, s->len-1, "\n"); | |
876 | } | |
877 | } else if (ap->band == NETPLAN_WIFI_BAND_5) { | |
878 | // initialize 5GHz frequency hashtable | |
879 | if(!wifi_frequency_5) | |
880 | wifi_get_freq5(7); | |
881 | if (ap->channel) { | |
882 | g_string_append_printf(s, " freq_list=%d\n", wifi_get_freq5(ap->channel)); | |
883 | } else { | |
884 | g_string_append_printf(s, " freq_list="); | |
885 | g_hash_table_foreach(wifi_frequency_5, wifi_append_freq, s); | |
886 | // overwrite last whitespace with newline | |
887 | s = g_string_overwrite(s, s->len-1, "\n"); | |
888 | } | |
889 | } | |
741 | 890 | switch (ap->mode) { |
742 | case WIFI_MODE_INFRASTRUCTURE: | |
891 | case NETPLAN_WIFI_MODE_INFRASTRUCTURE: | |
743 | 892 | /* default in wpasupplicant */ |
744 | 893 | break; |
745 | case WIFI_MODE_ADHOC: | |
894 | case NETPLAN_WIFI_MODE_ADHOC: | |
746 | 895 | g_string_append(s, " mode=1\n"); |
747 | 896 | break; |
748 | case WIFI_MODE_AP: | |
897 | case NETPLAN_WIFI_MODE_AP: | |
749 | 898 | g_fprintf(stderr, "ERROR: %s: networkd does not support wifi in access point mode\n", def->id); |
750 | 899 | exit(1); |
751 | 900 | } |
752 | 901 | |
753 | 902 | /* wifi auth trumps netdef auth */ |
754 | 903 | if (ap->has_auth) { |
755 | append_wpa_auth_conf(s, &ap->auth); | |
904 | append_wpa_auth_conf(s, &ap->auth, ap->ssid); | |
756 | 905 | } |
757 | 906 | else { |
758 | 907 | g_string_append(s, " key_mgmt=NONE\n"); |
763 | 912 | else { |
764 | 913 | /* wired 802.1x auth or similar */ |
765 | 914 | g_string_append(s, "network={\n"); |
766 | append_wpa_auth_conf(s, &def->auth); | |
915 | append_wpa_auth_conf(s, &def->auth, def->id); | |
767 | 916 | g_string_append(s, "}\n"); |
768 | 917 | } |
769 | 918 | |
781 | 930 | * Returns: TRUE if @def applies to networkd, FALSE otherwise. |
782 | 931 | */ |
783 | 932 | gboolean |
784 | write_networkd_conf(net_definition* def, const char* rootdir) | |
933 | write_networkd_conf(const NetplanNetDefinition* def, const char* rootdir) | |
785 | 934 | { |
786 | 935 | g_autofree char* path_base = g_strjoin(NULL, "run/systemd/network/10-netplan-", def->id, NULL); |
787 | 936 | |
790 | 939 | write_link_file(def, rootdir, path_base); |
791 | 940 | write_rules_file(def, rootdir); |
792 | 941 | |
793 | if (def->backend != BACKEND_NETWORKD) { | |
942 | if (def->backend != NETPLAN_BACKEND_NETWORKD) { | |
794 | 943 | g_debug("networkd: definition %s is not for us (backend %i)", def->id, def->backend); |
795 | 944 | return FALSE; |
796 | 945 | } |
797 | 946 | |
798 | if (def->type == ND_WIFI || def->has_auth) { | |
799 | g_autofree char* link = g_strjoin(NULL, rootdir ?: "", "/run/systemd/system/systemd-networkd.service.wants/netplan-wpa@", def->id, ".service", NULL); | |
800 | if (def->type == ND_WIFI && def->has_match) { | |
947 | if (def->type == NETPLAN_DEF_TYPE_MODEM) { | |
948 | g_fprintf(stderr, "ERROR: %s: networkd backend does not support GSM/CDMA modem configuration\n", def->id); | |
949 | exit(1); | |
950 | } | |
951 | ||
952 | if (def->type == NETPLAN_DEF_TYPE_WIFI || def->has_auth) { | |
953 | g_autofree char* link = g_strjoin(NULL, rootdir ?: "", "/run/systemd/system/systemd-networkd.service.wants/netplan-wpa-", def->id, ".service", NULL); | |
954 | g_autofree char* slink = g_strjoin(NULL, "/run/systemd/system/netplan-wpa-", def->id, ".service", NULL); | |
955 | if (def->type == NETPLAN_DEF_TYPE_WIFI && def->has_match) { | |
801 | 956 | g_fprintf(stderr, "ERROR: %s: networkd backend does not support wifi with match:, only by interface name\n", def->id); |
802 | 957 | exit(1); |
803 | 958 | } |
804 | 959 | |
960 | g_debug("Creating wpa_supplicant config"); | |
805 | 961 | write_wpa_conf(def, rootdir); |
962 | ||
963 | g_debug("Creating wpa_supplicant unit %s", slink); | |
964 | write_wpa_unit(def, rootdir); | |
806 | 965 | |
807 | 966 | g_debug("Creating wpa_supplicant service enablement link %s", link); |
808 | 967 | safe_mkdir_p_dir(link); |
809 | if (symlink("/lib/systemd/system/netplan-wpa@.service", link) < 0 && errno != EEXIST) { | |
968 | ||
969 | if (symlink(slink, link) < 0 && errno != EEXIST) { | |
810 | 970 | // LCOV_EXCL_START |
811 | 971 | g_fprintf(stderr, "failed to create enablement symlink: %m\n"); |
812 | 972 | exit(1); |
815 | 975 | |
816 | 976 | } |
817 | 977 | |
818 | if (def->type >= ND_VIRTUAL) | |
978 | if (def->type >= NETPLAN_DEF_TYPE_VIRTUAL) | |
819 | 979 | write_netdev_file(def, rootdir, path_base); |
820 | 980 | write_network_file(def, rootdir, path_base); |
821 | 981 | return TRUE; |
829 | 989 | { |
830 | 990 | unlink_glob(rootdir, "/run/systemd/network/10-netplan-*"); |
831 | 991 | unlink_glob(rootdir, "/run/netplan/wpa-*.conf"); |
832 | unlink_glob(rootdir, "/run/systemd/system/systemd-networkd.service.wants/netplan-wpa@*.service"); | |
992 | unlink_glob(rootdir, "/run/systemd/system/netplan-wpa-*.service"); | |
833 | 993 | unlink_glob(rootdir, "/run/udev/rules.d/99-netplan-*"); |
834 | 994 | } |
835 | 995 |
18 | 18 | |
19 | 19 | #include "parse.h" |
20 | 20 | |
21 | gboolean write_networkd_conf(net_definition* def, const char* rootdir); | |
21 | gboolean write_networkd_conf(const NetplanNetDefinition* def, const char* rootdir); | |
22 | 22 | void cleanup_networkd_conf(const char* rootdir); |
23 | 23 | void enable_networkd(const char* generator_dir); |
35 | 35 | * Append NM device specifier of @def to @s. |
36 | 36 | */ |
37 | 37 | static void |
38 | g_string_append_netdef_match(GString* s, const net_definition* def) | |
38 | g_string_append_netdef_match(GString* s, const NetplanNetDefinition* def) | |
39 | 39 | { |
40 | 40 | g_assert(!def->match.driver || def->set_name); |
41 | 41 | if (def->match.mac) { |
42 | 42 | g_string_append_printf(s, "mac:%s", def->match.mac); |
43 | } else if (def->match.original_name || def->set_name || def->type >= ND_VIRTUAL) { | |
43 | } else if (def->match.original_name || def->set_name || def->type >= NETPLAN_DEF_TYPE_VIRTUAL) { | |
44 | 44 | /* we always have the renamed name here */ |
45 | 45 | g_string_append_printf(s, "interface-name:%s", |
46 | (def->type >= ND_VIRTUAL) ? def->id | |
46 | (def->type >= NETPLAN_DEF_TYPE_VIRTUAL) ? def->id | |
47 | 47 | : (def->set_name ?: def->match.original_name)); |
48 | 48 | } else { |
49 | 49 | /* no matches → match all devices of that type */ |
50 | 50 | switch (def->type) { |
51 | case ND_ETHERNET: | |
51 | case NETPLAN_DEF_TYPE_ETHERNET: | |
52 | 52 | g_string_append(s, "type:ethernet"); |
53 | 53 | break; |
54 | 54 | /* This cannot be reached with just NM and networkd backends, as |
56 | 56 | * wifi device from NM. This would become relevant with another |
57 | 57 | * wifi-supporting backend, but until then this just spoils 100% |
58 | 58 | * code coverage. |
59 | case ND_WIFI: | |
59 | case NETPLAN_DEF_TYPE_WIFI: | |
60 | 60 | g_string_append(s, "type:wifi"); |
61 | 61 | break; |
62 | 62 | */ |
70 | 70 | } |
71 | 71 | |
72 | 72 | /** |
73 | * Infer if this is a modem netdef of type GSM. | |
74 | * This is done by checking for certain modem_params, which are only | |
75 | * applicable to GSM connections. | |
76 | */ | |
77 | static const gboolean | |
78 | modem_is_gsm(const NetplanNetDefinition* def) | |
79 | { | |
80 | if (def->type == NETPLAN_DEF_TYPE_MODEM && (def->modem_params.apn || | |
81 | def->modem_params.auto_config || def->modem_params.device_id || | |
82 | def->modem_params.network_id || def->modem_params.pin || | |
83 | def->modem_params.sim_id || def->modem_params.sim_operator_id)) | |
84 | return TRUE; | |
85 | ||
86 | return FALSE; | |
87 | } | |
88 | ||
89 | /** | |
73 | 90 | * Return NM "type=" string. |
74 | 91 | */ |
75 | 92 | static const char* |
76 | type_str(netdef_type type) | |
77 | { | |
93 | type_str(const NetplanNetDefinition* def) | |
94 | { | |
95 | const NetplanDefType type = def->type; | |
78 | 96 | switch (type) { |
79 | case ND_ETHERNET: | |
97 | case NETPLAN_DEF_TYPE_ETHERNET: | |
80 | 98 | return "ethernet"; |
81 | case ND_WIFI: | |
99 | case NETPLAN_DEF_TYPE_MODEM: | |
100 | if (modem_is_gsm(def)) | |
101 | return "gsm"; | |
102 | else | |
103 | return "cdma"; | |
104 | case NETPLAN_DEF_TYPE_WIFI: | |
82 | 105 | return "wifi"; |
83 | case ND_BRIDGE: | |
106 | case NETPLAN_DEF_TYPE_BRIDGE: | |
84 | 107 | return "bridge"; |
85 | case ND_BOND: | |
108 | case NETPLAN_DEF_TYPE_BOND: | |
86 | 109 | return "bond"; |
87 | case ND_VLAN: | |
110 | case NETPLAN_DEF_TYPE_VLAN: | |
88 | 111 | return "vlan"; |
89 | case ND_TUNNEL: | |
112 | case NETPLAN_DEF_TYPE_TUNNEL: | |
90 | 113 | return "ip-tunnel"; |
91 | 114 | // LCOV_EXCL_START |
92 | 115 | default: |
99 | 122 | * Return NM wifi "mode=" string. |
100 | 123 | */ |
101 | 124 | static const char* |
102 | wifi_mode_str(wifi_mode mode) | |
125 | wifi_mode_str(const NetplanWifiMode mode) | |
103 | 126 | { |
104 | 127 | switch (mode) { |
105 | case WIFI_MODE_INFRASTRUCTURE: | |
128 | case NETPLAN_WIFI_MODE_INFRASTRUCTURE: | |
106 | 129 | return "infrastructure"; |
107 | case WIFI_MODE_ADHOC: | |
130 | case NETPLAN_WIFI_MODE_ADHOC: | |
108 | 131 | return "adhoc"; |
109 | case WIFI_MODE_AP: | |
132 | case NETPLAN_WIFI_MODE_AP: | |
110 | 133 | return "ap"; |
111 | 134 | // LCOV_EXCL_START |
112 | 135 | default: |
115 | 138 | } |
116 | 139 | } |
117 | 140 | |
118 | static void | |
119 | write_search_domains(const net_definition* def, GString *s) | |
141 | /** | |
142 | * Return NM wifi "band=" string. | |
143 | */ | |
144 | static const char* | |
145 | wifi_band_str(const NetplanWifiBand band) | |
146 | { | |
147 | switch (band) { | |
148 | case NETPLAN_WIFI_BAND_5: | |
149 | return "a"; | |
150 | case NETPLAN_WIFI_BAND_24: | |
151 | return "bg"; | |
152 | // LCOV_EXCL_START | |
153 | default: | |
154 | g_assert_not_reached(); | |
155 | // LCOV_EXCL_STOP | |
156 | } | |
157 | } | |
158 | ||
159 | /** | |
160 | * Return NM addr-gen-mode string. | |
161 | */ | |
162 | static const char* | |
163 | addr_gen_mode_str(const NetplanAddrGenMode mode) | |
164 | { | |
165 | switch (mode) { | |
166 | case NETPLAN_ADDRGEN_EUI64: | |
167 | return "0"; | |
168 | case NETPLAN_ADDRGEN_STABLEPRIVACY: | |
169 | return "1"; | |
170 | // LCOV_EXCL_START | |
171 | default: | |
172 | g_assert_not_reached(); | |
173 | // LCOV_EXCL_STOP | |
174 | } | |
175 | } | |
176 | ||
177 | static void | |
178 | write_search_domains(const NetplanNetDefinition* def, GString *s) | |
120 | 179 | { |
121 | 180 | if (def->search_domains) { |
122 | 181 | g_string_append(s, "dns-search="); |
127 | 186 | } |
128 | 187 | |
129 | 188 | static void |
130 | write_routes(const net_definition* def, GString *s, int family) | |
189 | write_routes(const NetplanNetDefinition* def, GString *s, int family) | |
131 | 190 | { |
132 | 191 | if (def->routes != NULL) { |
133 | 192 | for (unsigned i = 0, j = 1; i < def->routes->len; ++i) { |
134 | ip_route *cur_route = g_array_index(def->routes, ip_route*, i); | |
193 | const NetplanIPRoute *cur_route = g_array_index(def->routes, NetplanIPRoute*, i); | |
135 | 194 | |
136 | 195 | if (cur_route->family != family) |
137 | 196 | continue; |
146 | 205 | exit(1); |
147 | 206 | } |
148 | 207 | |
149 | if (cur_route->table != ROUTE_TABLE_UNSPEC) { | |
208 | if (cur_route->table != NETPLAN_ROUTE_TABLE_UNSPEC) { | |
150 | 209 | g_fprintf(stderr, "ERROR: %s: NetworkManager does not support non-default routing tables\n", def->id); |
151 | 210 | exit(1); |
152 | 211 | } |
163 | 222 | |
164 | 223 | g_string_append_printf(s, "route%d=%s,%s", |
165 | 224 | j, cur_route->to, cur_route->via); |
166 | if (cur_route->metric != METRIC_UNSPEC) | |
225 | if (cur_route->metric != NETPLAN_METRIC_UNSPEC) | |
167 | 226 | g_string_append_printf(s, ",%d", cur_route->metric); |
168 | 227 | g_string_append(s, "\n"); |
169 | 228 | j++; |
172 | 231 | } |
173 | 232 | |
174 | 233 | static void |
175 | write_bond_parameters(const net_definition* def, GString *s) | |
234 | write_bond_parameters(const NetplanNetDefinition* def, GString *s) | |
176 | 235 | { |
177 | 236 | GString* params = NULL; |
178 | 237 | |
236 | 295 | } |
237 | 296 | |
238 | 297 | static void |
239 | write_bridge_params(const net_definition* def, GString *s) | |
298 | write_bridge_params(const NetplanNetDefinition* def, GString *s) | |
240 | 299 | { |
241 | 300 | GString* params = NULL; |
242 | 301 | |
262 | 321 | } |
263 | 322 | |
264 | 323 | static void |
265 | write_tunnel_params(const net_definition* def, GString *s) | |
324 | write_tunnel_params(const NetplanNetDefinition* def, GString *s) | |
266 | 325 | { |
267 | 326 | g_string_append(s, "\n[ip-tunnel]\n"); |
268 | 327 | |
277 | 336 | } |
278 | 337 | |
279 | 338 | static void |
280 | write_dot1x_auth_parameters(const authentication_settings* auth, GString *s) | |
281 | { | |
282 | if (auth->eap_method == EAP_NONE) { | |
339 | write_dot1x_auth_parameters(const NetplanAuthenticationSettings* auth, GString *s) | |
340 | { | |
341 | if (auth->eap_method == NETPLAN_AUTH_EAP_NONE) { | |
283 | 342 | return; |
284 | 343 | } |
285 | 344 | |
286 | 345 | g_string_append_printf(s, "\n[802-1x]\n"); |
287 | 346 | |
288 | 347 | switch (auth->eap_method) { |
289 | case EAP_NONE: break; // LCOV_EXCL_LINE | |
290 | case EAP_TLS: | |
348 | case NETPLAN_AUTH_EAP_NONE: break; // LCOV_EXCL_LINE | |
349 | case NETPLAN_AUTH_EAP_TLS: | |
291 | 350 | g_string_append(s, "eap=tls\n"); |
292 | 351 | break; |
293 | case EAP_PEAP: | |
352 | case NETPLAN_AUTH_EAP_PEAP: | |
294 | 353 | g_string_append(s, "eap=peap\n"); |
295 | 354 | break; |
296 | case EAP_TTLS: | |
355 | case NETPLAN_AUTH_EAP_TTLS: | |
297 | 356 | g_string_append(s, "eap=ttls\n"); |
298 | 357 | break; |
299 | 358 | } |
304 | 363 | if (auth->anonymous_identity) { |
305 | 364 | g_string_append_printf(s, "anonymous-identity=%s\n", auth->anonymous_identity); |
306 | 365 | } |
307 | if (auth->password && auth->key_management != KEY_MANAGEMENT_WPA_PSK) { | |
366 | if (auth->password && auth->key_management != NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK) { | |
308 | 367 | g_string_append_printf(s, "password=%s\n", auth->password); |
309 | 368 | } |
310 | 369 | if (auth->ca_certificate) { |
319 | 378 | if (auth->client_key_password) { |
320 | 379 | g_string_append_printf(s, "private-key-password=%s\n", auth->client_key_password); |
321 | 380 | } |
322 | } | |
323 | ||
324 | static void | |
325 | write_wifi_auth_parameters(const authentication_settings* auth, GString *s) | |
326 | { | |
327 | if (auth->key_management == KEY_MANAGEMENT_NONE) { | |
381 | if (auth->phase2_auth) { | |
382 | g_string_append_printf(s, "phase2-auth=%s\n", auth->phase2_auth); | |
383 | } | |
384 | ||
385 | } | |
386 | ||
387 | static void | |
388 | write_wifi_auth_parameters(const NetplanAuthenticationSettings* auth, GString *s) | |
389 | { | |
390 | if (auth->key_management == NETPLAN_AUTH_KEY_MANAGEMENT_NONE) { | |
328 | 391 | return; |
329 | 392 | } |
330 | 393 | |
331 | 394 | g_string_append(s, "\n[wifi-security]\n"); |
332 | 395 | |
333 | 396 | switch (auth->key_management) { |
334 | case KEY_MANAGEMENT_NONE: break; // LCOV_EXCL_LINE | |
335 | case KEY_MANAGEMENT_WPA_PSK: | |
397 | case NETPLAN_AUTH_KEY_MANAGEMENT_NONE: break; // LCOV_EXCL_LINE | |
398 | case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK: | |
336 | 399 | g_string_append(s, "key-mgmt=wpa-psk\n"); |
337 | 400 | if (auth->password) { |
338 | 401 | g_string_append_printf(s, "psk=%s\n", auth->password); |
339 | 402 | } |
340 | 403 | break; |
341 | case KEY_MANAGEMENT_WPA_EAP: | |
404 | case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAP: | |
342 | 405 | g_string_append(s, "key-mgmt=wpa-eap\n"); |
343 | 406 | break; |
344 | case KEY_MANAGEMENT_8021X: | |
407 | case NETPLAN_AUTH_KEY_MANAGEMENT_8021X: | |
345 | 408 | g_string_append(s, "key-mgmt=ieee8021x\n"); |
346 | 409 | break; |
347 | 410 | } |
350 | 413 | } |
351 | 414 | |
352 | 415 | static void |
353 | maybe_generate_uuid(net_definition* def) | |
416 | maybe_generate_uuid(NetplanNetDefinition* def) | |
354 | 417 | { |
355 | 418 | if (uuid_is_null(def->uuid)) |
356 | 419 | uuid_generate(def->uuid); |
358 | 421 | |
359 | 422 | /** |
360 | 423 | * Generate NetworkManager configuration in @rootdir/run/NetworkManager/ for a |
361 | * particular net_definition and wifi_access_point, as NM requires a separate | |
424 | * particular NetplanNetDefinition and NetplanWifiAccessPoint, as NM requires a separate | |
362 | 425 | * connection file for each SSID. |
363 | * @def: The net_definition for which to create a connection | |
426 | * @def: The NetplanNetDefinition for which to create a connection | |
364 | 427 | * @rootdir: If not %NULL, generate configuration in this root directory |
365 | 428 | * (useful for testing). |
366 | 429 | * @ap: The access point for which to create a connection. Must be %NULL for |
367 | 430 | * non-wifi types. |
368 | 431 | */ |
369 | 432 | static void |
370 | write_nm_conf_access_point(net_definition* def, const char* rootdir, const wifi_access_point* ap) | |
433 | write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const NetplanWifiAccessPoint* ap) | |
371 | 434 | { |
372 | 435 | GString *s = NULL; |
373 | 436 | g_autofree char* conf_path = NULL; |
374 | 437 | mode_t orig_umask; |
375 | 438 | char uuidstr[37]; |
376 | 439 | |
377 | if (def->type == ND_WIFI) | |
440 | if (def->type == NETPLAN_DEF_TYPE_WIFI) | |
378 | 441 | g_assert(ap); |
379 | 442 | else |
380 | 443 | g_assert(ap == NULL); |
444 | ||
445 | if (def->type == NETPLAN_DEF_TYPE_VLAN && def->sriov_vlan_filter) { | |
446 | g_debug("%s is defined as a hardware SR-IOV filtered VLAN, postponing creation", def->id); | |
447 | return; | |
448 | } | |
381 | 449 | |
382 | 450 | s = g_string_new(NULL); |
383 | 451 | g_string_append_printf(s, "[connection]\nid=netplan-%s", def->id); |
384 | 452 | if (ap) |
385 | 453 | g_string_append_printf(s, "-%s", ap->ssid); |
386 | g_string_append_printf(s, "\ntype=%s\n", type_str(def->type)); | |
454 | g_string_append_printf(s, "\ntype=%s\n", type_str(def)); | |
387 | 455 | |
388 | 456 | /* VLAN devices refer to us as their parent; if our ID is not a name but we |
389 | 457 | * have matches, parent= must be the connection UUID, so put it into the |
394 | 462 | g_string_append_printf(s, "uuid=%s\n", uuidstr); |
395 | 463 | } |
396 | 464 | |
397 | if (def->type < ND_VIRTUAL) { | |
465 | if (def->type < NETPLAN_DEF_TYPE_VIRTUAL) { | |
398 | 466 | /* physical (existing) devices use matching; driver matching is not |
399 | 467 | * supported, MAC matching is done below (different keyfile section), |
400 | 468 | * so only match names here */ |
415 | 483 | /* virtual (created) devices set a name */ |
416 | 484 | g_string_append_printf(s, "interface-name=%s\n", def->id); |
417 | 485 | |
418 | if (def->type == ND_BRIDGE) | |
486 | if (def->type == NETPLAN_DEF_TYPE_BRIDGE) | |
419 | 487 | write_bridge_params(def, s); |
488 | } | |
489 | if (def->type == NETPLAN_DEF_TYPE_MODEM) { | |
490 | if (modem_is_gsm(def)) | |
491 | g_string_append_printf(s, "\n[gsm]\n"); | |
492 | else | |
493 | g_string_append_printf(s, "\n[cdma]\n"); | |
494 | ||
495 | /* Use NetworkManager's auto configuration feature if no APN, username, or password is specified */ | |
496 | if (def->modem_params.auto_config || (!def->modem_params.apn && | |
497 | !def->modem_params.username && !def->modem_params.password)) { | |
498 | g_string_append_printf(s, "auto-config=true\n"); | |
499 | } else { | |
500 | if (def->modem_params.apn) | |
501 | g_string_append_printf(s, "apn=%s\n", def->modem_params.apn); | |
502 | if (def->modem_params.password) | |
503 | g_string_append_printf(s, "password=%s\n", def->modem_params.password); | |
504 | if (def->modem_params.username) | |
505 | g_string_append_printf(s, "username=%s\n", def->modem_params.username); | |
506 | } | |
507 | ||
508 | if (def->modem_params.device_id) | |
509 | g_string_append_printf(s, "device-id=%s\n", def->modem_params.device_id); | |
510 | if (def->mtubytes) | |
511 | g_string_append_printf(s, "mtu=%u\n", def->mtubytes); | |
512 | if (def->modem_params.network_id) | |
513 | g_string_append_printf(s, "network-id=%s\n", def->modem_params.network_id); | |
514 | if (def->modem_params.number) | |
515 | g_string_append_printf(s, "number=%s\n", def->modem_params.number); | |
516 | if (def->modem_params.pin) | |
517 | g_string_append_printf(s, "pin=%s\n", def->modem_params.pin); | |
518 | if (def->modem_params.sim_id) | |
519 | g_string_append_printf(s, "sim-id=%s\n", def->modem_params.sim_id); | |
520 | if (def->modem_params.sim_operator_id) | |
521 | g_string_append_printf(s, "sim-operator-id=%s\n", def->modem_params.sim_operator_id); | |
420 | 522 | } |
421 | 523 | if (def->bridge) { |
422 | 524 | g_string_append_printf(s, "slave-type=bridge\nmaster=%s\n", def->bridge); |
436 | 538 | exit(1); |
437 | 539 | } |
438 | 540 | |
439 | if (def->type < ND_VIRTUAL) { | |
541 | if (def->type < NETPLAN_DEF_TYPE_VIRTUAL) { | |
440 | 542 | GString *link_str = NULL; |
441 | 543 | |
442 | 544 | link_str = g_string_new(NULL); |
452 | 554 | if (def->mtubytes) { |
453 | 555 | g_string_append_printf(link_str, "mtu=%d\n", def->mtubytes); |
454 | 556 | } |
557 | if (def->wowlan && def->wowlan > NETPLAN_WIFI_WOWLAN_DEFAULT) | |
558 | g_string_append_printf(link_str, "wake-on-wlan=%u\n", def->wowlan); | |
455 | 559 | |
456 | 560 | if (link_str->len > 0) { |
457 | 561 | switch (def->type) { |
458 | case ND_WIFI: | |
562 | case NETPLAN_DEF_TYPE_WIFI: | |
459 | 563 | g_string_append_printf(s, "\n[802-11-wireless]\n%s", link_str->str); break; |
564 | case NETPLAN_DEF_TYPE_MODEM: | |
565 | /* Avoid adding an [ethernet] section into the [gsm/cdma] description. */ | |
566 | break; | |
460 | 567 | default: |
461 | 568 | g_string_append_printf(s, "\n[802-3-ethernet]\n%s", link_str->str); break; |
462 | 569 | } |
482 | 589 | g_string_free(link_str, TRUE); |
483 | 590 | } |
484 | 591 | |
485 | if (def->type == ND_VLAN) { | |
592 | if (def->type == NETPLAN_DEF_TYPE_VLAN) { | |
486 | 593 | g_assert(def->vlan_id < G_MAXUINT); |
487 | 594 | g_assert(def->vlan_link != NULL); |
488 | 595 | g_string_append_printf(s, "\n[vlan]\nid=%u\nparent=", def->vlan_id); |
498 | 605 | } |
499 | 606 | } |
500 | 607 | |
501 | if (def->type == ND_BOND) | |
608 | if (def->type == NETPLAN_DEF_TYPE_BOND) | |
502 | 609 | write_bond_parameters(def, s); |
503 | 610 | |
504 | if (def->type == ND_TUNNEL) | |
611 | if (def->type == NETPLAN_DEF_TYPE_TUNNEL) | |
505 | 612 | write_tunnel_params(def, s); |
506 | 613 | |
507 | 614 | g_string_append(s, "\n[ipv4]\n"); |
508 | 615 | |
509 | if (ap && ap->mode == WIFI_MODE_AP) | |
616 | if (ap && ap->mode == NETPLAN_WIFI_MODE_AP) | |
510 | 617 | g_string_append(s, "method=shared\n"); |
511 | 618 | else if (def->dhcp4) |
512 | 619 | g_string_append(s, "method=auto\n"); |
513 | 620 | else if (def->ip4_addresses) |
514 | 621 | /* This requires adding at least one address (done below) */ |
515 | 622 | g_string_append(s, "method=manual\n"); |
516 | else if (def->type == ND_TUNNEL) | |
623 | else if (def->type == NETPLAN_DEF_TYPE_TUNNEL) | |
517 | 624 | /* sit tunnels will not start in link-local apparently */ |
518 | 625 | g_string_append(s, "method=disabled\n"); |
519 | 626 | else |
543 | 650 | g_string_append(s, "never-default=true\n"); |
544 | 651 | } |
545 | 652 | |
546 | if (def->dhcp4 && def->dhcp4_overrides.metric != METRIC_UNSPEC) | |
653 | if (def->dhcp4 && def->dhcp4_overrides.metric != NETPLAN_METRIC_UNSPEC) | |
547 | 654 | g_string_append_printf(s, "route-metric=%u\n", def->dhcp4_overrides.metric); |
548 | 655 | |
549 | if (def->dhcp6 || def->ip6_addresses || def->gateway6 || def->ip6_nameservers) { | |
656 | if (def->dhcp6 || def->ip6_addresses || def->gateway6 || def->ip6_nameservers || def->ip6_addr_gen_mode) { | |
550 | 657 | g_string_append(s, "\n[ipv6]\n"); |
551 | 658 | g_string_append(s, def->dhcp6 ? "method=auto\n" : "method=manual\n"); |
552 | 659 | if (def->ip6_addresses) |
553 | 660 | for (unsigned i = 0; i < def->ip6_addresses->len; ++i) |
554 | 661 | g_string_append_printf(s, "address%i=%s\n", i+1, g_array_index(def->ip6_addresses, char*, i)); |
662 | if (def->ip6_addr_gen_mode) { | |
663 | g_string_append_printf(s, "addr-gen-mode=%s\n", addr_gen_mode_str(def->ip6_addr_gen_mode)); | |
664 | } | |
555 | 665 | if (def->ip6_privacy) |
556 | 666 | g_string_append(s, "ip6-privacy=2\n"); |
557 | 667 | if (def->gateway6) |
574 | 684 | g_string_append(s, "never-default=true\n"); |
575 | 685 | } |
576 | 686 | |
577 | if (def->dhcp6_overrides.metric != METRIC_UNSPEC) | |
687 | if (def->dhcp6_overrides.metric != NETPLAN_METRIC_UNSPEC) | |
578 | 688 | g_string_append_printf(s, "route-metric=%u\n", def->dhcp6_overrides.metric); |
579 | 689 | } |
580 | 690 | else { |
586 | 696 | conf_path = g_strjoin(NULL, "run/NetworkManager/system-connections/netplan-", def->id, "-", escaped_ssid, ".nmconnection", NULL); |
587 | 697 | |
588 | 698 | g_string_append_printf(s, "\n[wifi]\nssid=%s\nmode=%s\n", ap->ssid, wifi_mode_str(ap->mode)); |
699 | if (ap->bssid) { | |
700 | g_string_append_printf(s, "bssid=%s\n", ap->bssid); | |
701 | } | |
702 | if (ap->band == NETPLAN_WIFI_BAND_5 || ap->band == NETPLAN_WIFI_BAND_24) { | |
703 | g_string_append_printf(s, "band=%s\n", wifi_band_str(ap->band)); | |
704 | /* Channel is only unambiguous, if band is set. */ | |
705 | if (ap->channel) { | |
706 | /* Validate WiFi channel */ | |
707 | if (ap->band == NETPLAN_WIFI_BAND_5) | |
708 | wifi_get_freq5(ap->channel); | |
709 | else | |
710 | wifi_get_freq24(ap->channel); | |
711 | g_string_append_printf(s, "channel=%u\n", ap->channel); | |
712 | } | |
713 | } | |
589 | 714 | if (ap->has_auth) { |
590 | 715 | write_wifi_auth_parameters(&ap->auth, s); |
591 | 716 | } |
604 | 729 | |
605 | 730 | /** |
606 | 731 | * Generate NetworkManager configuration in @rootdir/run/NetworkManager/ for a |
607 | * particular net_definition. | |
732 | * particular NetplanNetDefinition. | |
608 | 733 | * @rootdir: If not %NULL, generate configuration in this root directory |
609 | 734 | * (useful for testing). |
610 | 735 | */ |
611 | 736 | void |
612 | write_nm_conf(net_definition* def, const char* rootdir) | |
613 | { | |
614 | if (def->backend != BACKEND_NM) { | |
737 | write_nm_conf(NetplanNetDefinition* def, const char* rootdir) | |
738 | { | |
739 | if (def->backend != NETPLAN_BACKEND_NM) { | |
615 | 740 | g_debug("NetworkManager: definition %s is not for us (backend %i)", def->id, def->backend); |
616 | 741 | return; |
617 | 742 | } |
622 | 747 | } |
623 | 748 | |
624 | 749 | /* for wifi we need to create a separate connection file for every SSID */ |
625 | if (def->type == ND_WIFI) { | |
750 | if (def->type == NETPLAN_DEF_TYPE_WIFI) { | |
626 | 751 | GHashTableIter iter; |
627 | 752 | gpointer key; |
628 | wifi_access_point* ap; | |
753 | const NetplanWifiAccessPoint* ap; | |
629 | 754 | g_assert(def->access_points); |
630 | 755 | g_hash_table_iter_init(&iter, def->access_points); |
631 | 756 | while (g_hash_table_iter_next(&iter, &key, (gpointer) &ap)) |
639 | 764 | static void |
640 | 765 | nd_append_non_nm_ids(gpointer data, gpointer str) |
641 | 766 | { |
642 | net_definition* nd = data; | |
643 | ||
644 | if (nd->backend != BACKEND_NM) { | |
767 | const NetplanNetDefinition* nd = data; | |
768 | ||
769 | if (nd->backend != NETPLAN_BACKEND_NM) { | |
645 | 770 | if (nd->match.driver) { |
646 | 771 | /* NM cannot match on drivers, so ignore these via udev rules */ |
647 | 772 | if (!udev_rules) |
18 | 18 | |
19 | 19 | #include "parse.h" |
20 | 20 | |
21 | void write_nm_conf(net_definition* def, const char* rootdir); | |
21 | void write_nm_conf(NetplanNetDefinition* def, const char* rootdir); | |
22 | 22 | void write_nm_conf_finish(const char* rootdir); |
23 | 23 | void cleanup_nm_conf(const char* rootdir); |
29 | 29 | #include "error.h" |
30 | 30 | #include "validation.h" |
31 | 31 | |
32 | /* convenience macro to put the offset of a net_definition field into "void* data" */ | |
33 | #define netdef_offset(field) GUINT_TO_POINTER(offsetof(net_definition, field)) | |
34 | #define route_offset(field) GUINT_TO_POINTER(offsetof(ip_route, field)) | |
35 | #define ip_rule_offset(field) GUINT_TO_POINTER(offsetof(ip_rule, field)) | |
36 | #define auth_offset(field) GUINT_TO_POINTER(offsetof(authentication_settings, field)) | |
37 | ||
38 | /* net_definition that is currently being processed */ | |
39 | net_definition* cur_netdef; | |
40 | ||
41 | /* wifi AP that is currently being processed */ | |
42 | wifi_access_point* cur_access_point; | |
32 | /* convenience macro to put the offset of a NetplanNetDefinition field into "void* data" */ | |
33 | #define netdef_offset(field) GUINT_TO_POINTER(offsetof(NetplanNetDefinition, field)) | |
34 | #define route_offset(field) GUINT_TO_POINTER(offsetof(NetplanIPRoute, field)) | |
35 | #define ip_rule_offset(field) GUINT_TO_POINTER(offsetof(NetplanIPRule, field)) | |
36 | #define auth_offset(field) GUINT_TO_POINTER(offsetof(NetplanAuthenticationSettings, field)) | |
37 | #define access_point_offset(field) GUINT_TO_POINTER(offsetof(NetplanWifiAccessPoint, field)) | |
38 | ||
39 | /* NetplanNetDefinition that is currently being processed */ | |
40 | static NetplanNetDefinition* cur_netdef; | |
41 | ||
42 | /* NetplanWifiAccessPoint that is currently being processed */ | |
43 | static NetplanWifiAccessPoint* cur_access_point; | |
43 | 44 | |
44 | 45 | /* authentication options that are currently being processed */ |
45 | authentication_settings* cur_auth; | |
46 | ||
47 | ip_route* cur_route; | |
48 | ip_rule* cur_ip_rule; | |
49 | ||
50 | netdef_backend backend_global, backend_cur_type; | |
51 | ||
52 | /* Global ID → net_definition* map for all parsed config files */ | |
46 | static NetplanAuthenticationSettings* cur_auth; | |
47 | ||
48 | static NetplanIPRoute* cur_route; | |
49 | static NetplanIPRule* cur_ip_rule; | |
50 | ||
51 | static NetplanBackend backend_global, backend_cur_type; | |
52 | ||
53 | /* Global ID → NetplanNetDefinition* map for all parsed config files */ | |
53 | 54 | GHashTable* netdefs; |
54 | 55 | |
55 | 56 | /* Contains the same objects as 'netdefs' but ordered by dependency */ |
58 | 59 | /* Set of IDs in currently parsed YAML file, for being able to detect |
59 | 60 | * "duplicate ID within one file" vs. allowing a drop-in to override/amend an |
60 | 61 | * existing definition */ |
61 | GHashTable* ids_in_file; | |
62 | static GHashTable* ids_in_file; | |
62 | 63 | |
63 | 64 | /** |
64 | 65 | * Load YAML file name into a yaml_document_t. |
86 | 87 | ret = parser_error(&parser, yaml, error); |
87 | 88 | } |
88 | 89 | |
90 | yaml_parser_delete(&parser); | |
89 | 91 | fclose(fyaml); |
90 | 92 | return ret; |
91 | 93 | } |
135 | 137 | static void |
136 | 138 | add_missing_node(const yaml_node_t* node) |
137 | 139 | { |
138 | missing_node* missing; | |
140 | NetplanMissingNode* missing; | |
139 | 141 | |
140 | 142 | /* Let's capture the current netdef we were playing with along with the |
141 | 143 | * actual yaml_node_t that errors (that is an identifier not previously |
142 | 144 | * seen by the compiler). We can use it later to write an sensible error |
143 | 145 | * message and point the user in the right direction. */ |
144 | missing = g_new0(missing_node, 1); | |
146 | missing = g_new0(NetplanMissingNode, 1); | |
145 | 147 | missing->netdef_id = cur_netdef->id; |
146 | 148 | missing->node = node; |
147 | 149 | |
246 | 248 | return TRUE; |
247 | 249 | } |
248 | 250 | |
249 | /** | |
250 | * Generic handler for setting a cur_netdef string field from a scalar node | |
251 | * @data: offset into net_definition where the const char* field to write is | |
251 | /************************************************************* | |
252 | * Generic helper functions to extract data from scalar nodes. | |
253 | *************************************************************/ | |
254 | ||
255 | /** | |
256 | * Handler for setting a guint field from a scalar node, inside a given struct | |
257 | * @entryptr: pointer to the begining of the to-be-modified data structure | |
258 | * @data: offset into entryptr struct where the guint field to write is located | |
259 | */ | |
260 | static gboolean | |
261 | handle_generic_guint(yaml_document_t* doc, yaml_node_t* node, const void* entryptr, const void* data, GError** error) | |
262 | { | |
263 | g_assert(entryptr); | |
264 | guint offset = GPOINTER_TO_UINT(data); | |
265 | guint64 v; | |
266 | gchar* endptr; | |
267 | ||
268 | v = g_ascii_strtoull(scalar(node), &endptr, 10); | |
269 | if (*endptr != '\0' || v > G_MAXUINT) | |
270 | return yaml_error(node, error, "invalid unsigned int value '%s'", scalar(node)); | |
271 | ||
272 | *((guint*) ((void*) entryptr + offset)) = (guint) v; | |
273 | return TRUE; | |
274 | } | |
275 | ||
276 | /** | |
277 | * Handler for setting a string field from a scalar node, inside a given struct | |
278 | * @entryptr: pointer to the beginning of the to-be-modified data structure | |
279 | * @data: offset into entryptr struct where the const char* field to write is | |
252 | 280 | * located |
253 | 281 | */ |
254 | 282 | static gboolean |
255 | handle_netdef_str(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) | |
256 | { | |
283 | handle_generic_str(yaml_document_t* doc, yaml_node_t* node, void* entryptr, const void* data, GError** error) | |
284 | { | |
285 | g_assert(entryptr); | |
257 | 286 | guint offset = GPOINTER_TO_UINT(data); |
258 | char** dest = (char**) ((void*) cur_netdef + offset); | |
287 | char** dest = (char**) ((void*) entryptr + offset); | |
259 | 288 | g_free(*dest); |
260 | 289 | *dest = g_strdup(scalar(node)); |
261 | 290 | return TRUE; |
262 | 291 | } |
263 | 292 | |
293 | /* | |
294 | * Handler for setting a MAC address field from a scalar node, inside a given struct | |
295 | * @entryptr: pointer to the beginning of the to-be-modified data structure | |
296 | * @data: offset into entryptr struct where the const char* field to write is | |
297 | * located | |
298 | */ | |
299 | static gboolean | |
300 | handle_generic_mac(yaml_document_t* doc, yaml_node_t* node, void* entryptr, const void* data, GError** error) | |
301 | { | |
302 | g_assert(entryptr); | |
303 | static regex_t re; | |
304 | static gboolean re_inited = FALSE; | |
305 | ||
306 | g_assert(node->type == YAML_SCALAR_NODE); | |
307 | ||
308 | if (!re_inited) { | |
309 | g_assert(regcomp(&re, "^[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]$", REG_EXTENDED|REG_NOSUB) == 0); | |
310 | re_inited = TRUE; | |
311 | } | |
312 | ||
313 | if (regexec(&re, scalar(node), 0, NULL, 0) != 0) | |
314 | return yaml_error(node, error, "Invalid MAC address '%s', must be XX:XX:XX:XX:XX:XX", scalar(node)); | |
315 | ||
316 | return handle_generic_str(doc, node, entryptr, data, error); | |
317 | } | |
318 | ||
319 | /** | |
320 | * Generic handler for setting a cur_netdef string field from a scalar node | |
321 | * @data: offset into NetplanNetDefinition where the const char* field to write is | |
322 | * located | |
323 | */ | |
324 | static gboolean | |
325 | handle_netdef_str(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) | |
326 | { | |
327 | return handle_generic_str(doc, node, cur_netdef, data, error); | |
328 | } | |
329 | ||
264 | 330 | /** |
265 | 331 | * Generic handler for setting a cur_netdef ID/iface name field from a scalar node |
266 | * @data: offset into net_definition where the const char* field to write is | |
332 | * @data: offset into NetplanNetDefinition where the const char* field to write is | |
267 | 333 | * located |
268 | 334 | */ |
269 | 335 | static gboolean |
277 | 343 | /** |
278 | 344 | * Generic handler for setting a cur_netdef ID/iface name field referring to an |
279 | 345 | * existing ID from a scalar node |
280 | * @data: offset into net_definition where the net_definition* field to write is | |
346 | * @data: offset into NetplanNetDefinition where the NetplanNetDefinition* field to write is | |
281 | 347 | * located |
282 | 348 | */ |
283 | 349 | static gboolean |
284 | 350 | handle_netdef_id_ref(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
285 | 351 | { |
286 | 352 | guint offset = GPOINTER_TO_UINT(data); |
287 | net_definition* ref = NULL; | |
353 | NetplanNetDefinition* ref = NULL; | |
288 | 354 | |
289 | 355 | ref = g_hash_table_lookup(netdefs, scalar(node)); |
290 | 356 | if (!ref) { |
291 | 357 | add_missing_node(node); |
292 | 358 | } else { |
293 | *((net_definition**) ((void*) cur_netdef + offset)) = ref; | |
359 | *((NetplanNetDefinition**) ((void*) cur_netdef + offset)) = ref; | |
294 | 360 | } |
295 | 361 | return TRUE; |
296 | 362 | } |
298 | 364 | |
299 | 365 | /** |
300 | 366 | * Generic handler for setting a cur_netdef MAC address field from a scalar node |
301 | * @data: offset into net_definition where the const char* field to write is | |
367 | * @data: offset into NetplanNetDefinition where the const char* field to write is | |
302 | 368 | * located |
303 | 369 | */ |
304 | 370 | static gboolean |
305 | 371 | handle_netdef_mac(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
306 | 372 | { |
307 | static regex_t re; | |
308 | static gboolean re_inited = FALSE; | |
309 | ||
310 | g_assert(node->type == YAML_SCALAR_NODE); | |
311 | ||
312 | if (!re_inited) { | |
313 | g_assert(regcomp(&re, "^[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]$", REG_EXTENDED|REG_NOSUB) == 0); | |
314 | re_inited = TRUE; | |
315 | } | |
316 | ||
317 | if (regexec(&re, scalar(node), 0, NULL, 0) != 0) | |
318 | return yaml_error(node, error, "Invalid MAC address '%s', must be XX:XX:XX:XX:XX:XX", scalar(node)); | |
319 | ||
320 | return handle_netdef_str(doc, node, data, error); | |
373 | return handle_generic_mac(doc, node, cur_netdef, data, error); | |
321 | 374 | } |
322 | 375 | |
323 | 376 | /** |
324 | 377 | * Generic handler for setting a cur_netdef gboolean field from a scalar node |
325 | * @data: offset into net_definition where the gboolean field to write is located | |
378 | * @data: offset into NetplanNetDefinition where the gboolean field to write is located | |
326 | 379 | */ |
327 | 380 | static gboolean |
328 | 381 | handle_netdef_bool(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
349 | 402 | |
350 | 403 | /** |
351 | 404 | * Generic handler for setting a cur_netdef guint field from a scalar node |
352 | * @data: offset into net_definition where the guint field to write is located | |
405 | * @data: offset into NetplanNetDefinition where the guint field to write is located | |
353 | 406 | */ |
354 | 407 | static gboolean |
355 | 408 | handle_netdef_guint(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
356 | 409 | { |
357 | guint offset = GPOINTER_TO_UINT(data); | |
358 | guint64 v; | |
359 | gchar* endptr; | |
360 | ||
361 | v = g_ascii_strtoull(scalar(node), &endptr, 10); | |
362 | if (*endptr != '\0' || v > G_MAXUINT) | |
363 | return yaml_error(node, error, "invalid unsigned int value '%s'", scalar(node)); | |
364 | ||
365 | *((guint*) ((void*) cur_netdef + offset)) = (guint) v; | |
366 | return TRUE; | |
410 | return handle_generic_guint(doc, node, cur_netdef, data, error); | |
367 | 411 | } |
368 | 412 | |
369 | 413 | static gboolean |
426 | 470 | return TRUE; |
427 | 471 | } |
428 | 472 | |
473 | static gboolean | |
474 | handle_netdef_addrgen(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) | |
475 | { | |
476 | g_assert(cur_netdef); | |
477 | if (strcmp(scalar(node), "eui64") == 0) | |
478 | cur_netdef->ip6_addr_gen_mode = NETPLAN_ADDRGEN_EUI64; | |
479 | else if (strcmp(scalar(node), "stable-privacy") == 0) | |
480 | cur_netdef->ip6_addr_gen_mode = NETPLAN_ADDRGEN_STABLEPRIVACY; | |
481 | else | |
482 | return yaml_error(node, error, "unknown ipv6-address-generation '%s'", scalar(node)); | |
483 | return TRUE; | |
484 | } | |
485 | ||
429 | 486 | |
430 | 487 | /**************************************************** |
431 | 488 | * Grammar and handlers for network config "match" entry |
432 | 489 | ****************************************************/ |
433 | 490 | |
434 | const mapping_entry_handler match_handlers[] = { | |
491 | static const mapping_entry_handler match_handlers[] = { | |
435 | 492 | {"driver", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(match.driver)}, |
436 | 493 | {"macaddress", YAML_SCALAR_NODE, handle_netdef_mac, NULL, netdef_offset(match.mac)}, |
437 | 494 | {"name", YAML_SCALAR_NODE, handle_netdef_id, NULL, netdef_offset(match.original_name)}, |
458 | 515 | { |
459 | 516 | g_assert(cur_auth); |
460 | 517 | if (strcmp(scalar(node), "none") == 0) |
461 | cur_auth->key_management = KEY_MANAGEMENT_NONE; | |
518 | cur_auth->key_management = NETPLAN_AUTH_KEY_MANAGEMENT_NONE; | |
462 | 519 | else if (strcmp(scalar(node), "psk") == 0) |
463 | cur_auth->key_management = KEY_MANAGEMENT_WPA_PSK; | |
520 | cur_auth->key_management = NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK; | |
464 | 521 | else if (strcmp(scalar(node), "eap") == 0) |
465 | cur_auth->key_management = KEY_MANAGEMENT_WPA_EAP; | |
522 | cur_auth->key_management = NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAP; | |
466 | 523 | else if (strcmp(scalar(node), "802.1x") == 0) |
467 | cur_auth->key_management = KEY_MANAGEMENT_8021X; | |
524 | cur_auth->key_management = NETPLAN_AUTH_KEY_MANAGEMENT_8021X; | |
468 | 525 | else |
469 | 526 | return yaml_error(node, error, "unknown key management type '%s'", scalar(node)); |
470 | 527 | return TRUE; |
475 | 532 | { |
476 | 533 | g_assert(cur_auth); |
477 | 534 | if (strcmp(scalar(node), "tls") == 0) |
478 | cur_auth->eap_method = EAP_TLS; | |
535 | cur_auth->eap_method = NETPLAN_AUTH_EAP_TLS; | |
479 | 536 | else if (strcmp(scalar(node), "peap") == 0) |
480 | cur_auth->eap_method = EAP_PEAP; | |
537 | cur_auth->eap_method = NETPLAN_AUTH_EAP_PEAP; | |
481 | 538 | else if (strcmp(scalar(node), "ttls") == 0) |
482 | cur_auth->eap_method = EAP_TTLS; | |
539 | cur_auth->eap_method = NETPLAN_AUTH_EAP_TTLS; | |
483 | 540 | else |
484 | 541 | return yaml_error(node, error, "unknown EAP method '%s'", scalar(node)); |
485 | 542 | return TRUE; |
486 | 543 | } |
487 | 544 | |
488 | const mapping_entry_handler auth_handlers[] = { | |
545 | static const mapping_entry_handler auth_handlers[] = { | |
489 | 546 | {"key-management", YAML_SCALAR_NODE, handle_auth_key_management}, |
490 | 547 | {"method", YAML_SCALAR_NODE, handle_auth_method}, |
491 | 548 | {"identity", YAML_SCALAR_NODE, handle_auth_str, NULL, auth_offset(identity)}, |
495 | 552 | {"client-certificate", YAML_SCALAR_NODE, handle_auth_str, NULL, auth_offset(client_certificate)}, |
496 | 553 | {"client-key", YAML_SCALAR_NODE, handle_auth_str, NULL, auth_offset(client_key)}, |
497 | 554 | {"client-key-password", YAML_SCALAR_NODE, handle_auth_str, NULL, auth_offset(client_key_password)}, |
555 | {"phase2-auth", YAML_SCALAR_NODE, handle_auth_str, NULL, auth_offset(phase2_auth)}, | |
498 | 556 | {NULL} |
499 | 557 | }; |
500 | 558 | |
502 | 560 | * Grammar and handlers for network device definition |
503 | 561 | ****************************************************/ |
504 | 562 | |
505 | static netdef_backend | |
506 | get_default_backend_for_type(netdef_type type) | |
507 | { | |
508 | if (backend_global != BACKEND_NONE) | |
563 | static NetplanBackend | |
564 | get_default_backend_for_type(NetplanDefType type) | |
565 | { | |
566 | if (backend_global != NETPLAN_BACKEND_NONE) | |
509 | 567 | return backend_global; |
510 | 568 | |
511 | 569 | /* networkd can handle all device types at the moment, so nothing |
512 | 570 | * type-specific */ |
513 | return BACKEND_NETWORKD; | |
571 | return NETPLAN_BACKEND_NETWORKD; | |
572 | } | |
573 | ||
574 | static gboolean | |
575 | handle_access_point_guint(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) | |
576 | { | |
577 | return handle_generic_guint(doc, node, cur_access_point, data, error); | |
578 | } | |
579 | ||
580 | static gboolean | |
581 | handle_access_point_mac(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) | |
582 | { | |
583 | return handle_generic_mac(doc, node, cur_access_point, data, error); | |
514 | 584 | } |
515 | 585 | |
516 | 586 | static gboolean |
519 | 589 | g_assert(cur_access_point); |
520 | 590 | /* shortcut for WPA-PSK */ |
521 | 591 | cur_access_point->has_auth = TRUE; |
522 | cur_access_point->auth.key_management = KEY_MANAGEMENT_WPA_PSK; | |
592 | cur_access_point->auth.key_management = NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK; | |
523 | 593 | g_free(cur_access_point->auth.password); |
524 | 594 | cur_access_point->auth.password = g_strdup(scalar(node)); |
525 | 595 | return TRUE; |
545 | 615 | { |
546 | 616 | g_assert(cur_access_point); |
547 | 617 | if (strcmp(scalar(node), "infrastructure") == 0) |
548 | cur_access_point->mode = WIFI_MODE_INFRASTRUCTURE; | |
618 | cur_access_point->mode = NETPLAN_WIFI_MODE_INFRASTRUCTURE; | |
549 | 619 | else if (strcmp(scalar(node), "adhoc") == 0) |
550 | cur_access_point->mode = WIFI_MODE_ADHOC; | |
620 | cur_access_point->mode = NETPLAN_WIFI_MODE_ADHOC; | |
551 | 621 | else if (strcmp(scalar(node), "ap") == 0) |
552 | cur_access_point->mode = WIFI_MODE_AP; | |
622 | cur_access_point->mode = NETPLAN_WIFI_MODE_AP; | |
553 | 623 | else |
554 | 624 | return yaml_error(node, error, "unknown wifi mode '%s'", scalar(node)); |
555 | 625 | return TRUE; |
556 | 626 | } |
557 | 627 | |
558 | const mapping_entry_handler wifi_access_point_handlers[] = { | |
628 | static gboolean | |
629 | handle_access_point_band(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) | |
630 | { | |
631 | g_assert(cur_access_point); | |
632 | if (strcmp(scalar(node), "5GHz") == 0 || strcmp(scalar(node), "5G") == 0) | |
633 | cur_access_point->band = NETPLAN_WIFI_BAND_5; | |
634 | else if (strcmp(scalar(node), "2.4GHz") == 0 || strcmp(scalar(node), "2.4G") == 0) | |
635 | cur_access_point->band = NETPLAN_WIFI_BAND_24; | |
636 | else | |
637 | return yaml_error(node, error, "unknown wifi band '%s'", scalar(node)); | |
638 | return TRUE; | |
639 | } | |
640 | ||
641 | static const mapping_entry_handler wifi_access_point_handlers[] = { | |
642 | {"band", YAML_SCALAR_NODE, handle_access_point_band}, | |
643 | {"bssid", YAML_SCALAR_NODE, handle_access_point_mac, NULL, access_point_offset(bssid)}, | |
644 | {"channel", YAML_SCALAR_NODE, handle_access_point_guint, NULL, access_point_offset(channel)}, | |
559 | 645 | {"mode", YAML_SCALAR_NODE, handle_access_point_mode}, |
560 | 646 | {"password", YAML_SCALAR_NODE, handle_access_point_password}, |
561 | 647 | {"auth", YAML_MAPPING_NODE, handle_access_point_auth}, |
566 | 652 | * Parse scalar node's string into a netdef_backend. |
567 | 653 | */ |
568 | 654 | static gboolean |
569 | parse_renderer(yaml_node_t* node, netdef_backend* backend, GError** error) | |
655 | parse_renderer(yaml_node_t* node, NetplanBackend* backend, GError** error) | |
570 | 656 | { |
571 | 657 | if (strcmp(scalar(node), "networkd") == 0) |
572 | *backend = BACKEND_NETWORKD; | |
658 | *backend = NETPLAN_BACKEND_NETWORKD; | |
573 | 659 | else if (strcmp(scalar(node), "NetworkManager") == 0) |
574 | *backend = BACKEND_NM; | |
660 | *backend = NETPLAN_BACKEND_NM; | |
575 | 661 | else |
576 | 662 | return yaml_error(node, error, "unknown renderer '%s'", scalar(node)); |
577 | 663 | return TRUE; |
580 | 666 | static gboolean |
581 | 667 | handle_netdef_renderer(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) |
582 | 668 | { |
669 | if (cur_netdef->type == NETPLAN_DEF_TYPE_VLAN) { | |
670 | if (strcmp(scalar(node), "sriov") == 0) { | |
671 | cur_netdef->sriov_vlan_filter = TRUE; | |
672 | return TRUE; | |
673 | } | |
674 | } | |
675 | ||
583 | 676 | return parse_renderer(node, &cur_netdef->backend, error); |
584 | 677 | } |
585 | 678 | |
590 | 683 | g_ascii_strcasecmp(scalar(node), "on") == 0 || |
591 | 684 | g_ascii_strcasecmp(scalar(node), "yes") == 0 || |
592 | 685 | g_ascii_strcasecmp(scalar(node), "y") == 0) |
593 | cur_netdef->accept_ra = ACCEPT_RA_ENABLED; | |
686 | cur_netdef->accept_ra = NETPLAN_RA_MODE_ENABLED; | |
594 | 687 | else if (g_ascii_strcasecmp(scalar(node), "false") == 0 || |
595 | 688 | g_ascii_strcasecmp(scalar(node), "off") == 0 || |
596 | 689 | g_ascii_strcasecmp(scalar(node), "no") == 0 || |
597 | 690 | g_ascii_strcasecmp(scalar(node), "n") == 0) |
598 | cur_netdef->accept_ra = ACCEPT_RA_DISABLED; | |
691 | cur_netdef->accept_ra = NETPLAN_RA_MODE_DISABLED; | |
599 | 692 | else |
600 | 693 | return yaml_error(node, error, "invalid boolean value '%s'", scalar(node)); |
601 | 694 | |
607 | 700 | { |
608 | 701 | cur_netdef->has_match = TRUE; |
609 | 702 | return process_mapping(doc, node, match_handlers, error); |
703 | } | |
704 | ||
705 | struct NetplanWifiWowlanType NETPLAN_WIFI_WOWLAN_TYPES[] = { | |
706 | {"default", NETPLAN_WIFI_WOWLAN_DEFAULT}, | |
707 | {"any", NETPLAN_WIFI_WOWLAN_ANY}, | |
708 | {"disconnect", NETPLAN_WIFI_WOWLAN_DISCONNECT}, | |
709 | {"magic_pkt", NETPLAN_WIFI_WOWLAN_MAGIC}, | |
710 | {"gtk_rekey_failure", NETPLAN_WIFI_WOWLAN_GTK_REKEY_FAILURE}, | |
711 | {"eap_identity_req", NETPLAN_WIFI_WOWLAN_EAP_IDENTITY_REQ}, | |
712 | {"four_way_handshake", NETPLAN_WIFI_WOWLAN_4WAY_HANDSHAKE}, | |
713 | {"rfkill_release", NETPLAN_WIFI_WOWLAN_RFKILL_RELEASE}, | |
714 | {"tcp", NETPLAN_WIFI_WOWLAN_TCP}, | |
715 | {NULL}, | |
716 | }; | |
717 | ||
718 | static gboolean | |
719 | handle_wowlan(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) | |
720 | { | |
721 | for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) { | |
722 | yaml_node_t *entry = yaml_document_get_node(doc, *i); | |
723 | assert_type(entry, YAML_SCALAR_NODE); | |
724 | int found = FALSE; | |
725 | ||
726 | for (unsigned i = 0; NETPLAN_WIFI_WOWLAN_TYPES[i].name != NULL; ++i) { | |
727 | if (g_ascii_strcasecmp(scalar(entry), NETPLAN_WIFI_WOWLAN_TYPES[i].name) == 0) { | |
728 | cur_netdef->wowlan |= NETPLAN_WIFI_WOWLAN_TYPES[i].flag; | |
729 | found = TRUE; | |
730 | break; | |
731 | } | |
732 | } | |
733 | if (!found) | |
734 | return yaml_error(node, error, "invalid value for wakeonwlan: '%s'", scalar(entry)); | |
735 | } | |
736 | if (cur_netdef->wowlan > NETPLAN_WIFI_WOWLAN_DEFAULT && cur_netdef->wowlan & NETPLAN_WIFI_WOWLAN_TYPES[0].flag) | |
737 | return yaml_error(node, error, "'default' is an exclusive flag for wakeonwlan"); | |
738 | return TRUE; | |
610 | 739 | } |
611 | 740 | |
612 | 741 | static gboolean |
701 | 830 | assert_type(value, YAML_MAPPING_NODE); |
702 | 831 | |
703 | 832 | g_assert(cur_access_point == NULL); |
704 | cur_access_point = g_new0(wifi_access_point, 1); | |
833 | cur_access_point = g_new0(NetplanWifiAccessPoint, 1); | |
705 | 834 | cur_access_point->ssid = g_strdup(scalar(key)); |
706 | 835 | g_debug("%s: adding wifi AP '%s'", cur_netdef->id, cur_access_point->ssid); |
707 | 836 | |
738 | 867 | /* all entries must refer to already defined IDs */ |
739 | 868 | for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) { |
740 | 869 | yaml_node_t *entry = yaml_document_get_node(doc, *i); |
741 | net_definition *component; | |
870 | NetplanNetDefinition *component; | |
742 | 871 | |
743 | 872 | assert_type(entry, YAML_SCALAR_NODE); |
744 | 873 | component = g_hash_table_lookup(netdefs, scalar(entry)); |
760 | 889 | |
761 | 890 | /** |
762 | 891 | * Handler for bond "mode" types. |
763 | * @data: offset into net_definition where the const char* field to write is | |
892 | * @data: offset into NetplanNetDefinition where the const char* field to write is | |
764 | 893 | * located |
765 | 894 | */ |
766 | 895 | static gboolean |
788 | 917 | /* all entries must refer to already defined IDs */ |
789 | 918 | for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) { |
790 | 919 | yaml_node_t *entry = yaml_document_get_node(doc, *i); |
791 | net_definition *component; | |
920 | NetplanNetDefinition *component; | |
792 | 921 | |
793 | 922 | assert_type(entry, YAML_SCALAR_NODE); |
794 | 923 | component = g_hash_table_lookup(netdefs, scalar(entry)); |
879 | 1008 | return TRUE; |
880 | 1009 | } |
881 | 1010 | |
882 | struct optional_address_option optional_address_options[] = { | |
883 | {"ipv4-ll", OPTIONAL_IPV4_LL}, | |
884 | {"ipv6-ra", OPTIONAL_IPV6_RA}, | |
885 | {"dhcp4", OPTIONAL_DHCP4}, | |
886 | {"dhcp6", OPTIONAL_DHCP6}, | |
887 | {"static", OPTIONAL_STATIC}, | |
1011 | struct NetplanOptionalAddressType NETPLAN_OPTIONAL_ADDRESS_TYPES[] = { | |
1012 | {"ipv4-ll", NETPLAN_OPTIONAL_IPV4_LL}, | |
1013 | {"ipv6-ra", NETPLAN_OPTIONAL_IPV6_RA}, | |
1014 | {"dhcp4", NETPLAN_OPTIONAL_DHCP4}, | |
1015 | {"dhcp6", NETPLAN_OPTIONAL_DHCP6}, | |
1016 | {"static", NETPLAN_OPTIONAL_STATIC}, | |
888 | 1017 | {NULL}, |
889 | 1018 | }; |
890 | 1019 | |
896 | 1025 | assert_type(entry, YAML_SCALAR_NODE); |
897 | 1026 | int found = FALSE; |
898 | 1027 | |
899 | for (unsigned i = 0; optional_address_options[i].name != NULL; ++i) { | |
900 | if (g_ascii_strcasecmp(scalar(entry), optional_address_options[i].name) == 0) { | |
901 | cur_netdef->optional_addresses |= optional_address_options[i].flag; | |
1028 | for (unsigned i = 0; NETPLAN_OPTIONAL_ADDRESS_TYPES[i].name != NULL; ++i) { | |
1029 | if (g_ascii_strcasecmp(scalar(entry), NETPLAN_OPTIONAL_ADDRESS_TYPES[i].name) == 0) { | |
1030 | cur_netdef->optional_addresses |= NETPLAN_OPTIONAL_ADDRESS_TYPES[i].flag; | |
902 | 1031 | found = TRUE; |
903 | 1032 | break; |
904 | 1033 | } |
1130 | 1259 | yaml_node_t* key, *value; |
1131 | 1260 | guint v; |
1132 | 1261 | gchar* endptr; |
1133 | net_definition *component; | |
1262 | NetplanNetDefinition *component; | |
1134 | 1263 | guint* ref_ptr; |
1135 | 1264 | |
1136 | 1265 | key = yaml_document_get_node(doc, entry->key); |
1166 | 1295 | yaml_node_t* key, *value; |
1167 | 1296 | guint v; |
1168 | 1297 | gchar* endptr; |
1169 | net_definition *component; | |
1298 | NetplanNetDefinition *component; | |
1170 | 1299 | guint* ref_ptr; |
1171 | 1300 | |
1172 | 1301 | key = yaml_document_get_node(doc, entry->key); |
1196 | 1325 | return TRUE; |
1197 | 1326 | } |
1198 | 1327 | |
1199 | const mapping_entry_handler bridge_params_handlers[] = { | |
1328 | static const mapping_entry_handler bridge_params_handlers[] = { | |
1200 | 1329 | {"ageing-time", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(bridge_params.ageing_time)}, |
1201 | 1330 | {"forward-delay", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(bridge_params.forward_delay)}, |
1202 | 1331 | {"hello-time", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(bridge_params.hello_time)}, |
1220 | 1349 | * Grammar and handlers for network config "routes" entry |
1221 | 1350 | ****************************************************/ |
1222 | 1351 | |
1223 | const mapping_entry_handler routes_handlers[] = { | |
1352 | static const mapping_entry_handler routes_handlers[] = { | |
1224 | 1353 | {"from", YAML_SCALAR_NODE, handle_routes_ip, NULL, route_offset(from)}, |
1225 | 1354 | {"on-link", YAML_SCALAR_NODE, handle_routes_bool, NULL, route_offset(onlink)}, |
1226 | 1355 | {"scope", YAML_SCALAR_NODE, handle_routes_scope}, |
1238 | 1367 | for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) { |
1239 | 1368 | yaml_node_t *entry = yaml_document_get_node(doc, *i); |
1240 | 1369 | |
1241 | cur_route = g_new0(ip_route, 1); | |
1370 | cur_route = g_new0(NetplanIPRoute, 1); | |
1242 | 1371 | cur_route->type = g_strdup("unicast"); |
1243 | 1372 | cur_route->scope = g_strdup("global"); |
1244 | 1373 | cur_route->family = G_MAXUINT; /* 0 is a valid family ID */ |
1245 | cur_route->metric = METRIC_UNSPEC; /* 0 is a valid metric */ | |
1374 | cur_route->metric = NETPLAN_METRIC_UNSPEC; /* 0 is a valid metric */ | |
1246 | 1375 | |
1247 | 1376 | if (process_mapping(doc, entry, routes_handlers, error)) { |
1248 | 1377 | if (!cur_netdef->routes) { |
1249 | cur_netdef->routes = g_array_new(FALSE, FALSE, sizeof(ip_route*)); | |
1378 | cur_netdef->routes = g_array_new(FALSE, FALSE, sizeof(NetplanIPRoute*)); | |
1250 | 1379 | } |
1251 | 1380 | |
1252 | 1381 | g_array_append_val(cur_netdef->routes, cur_route); |
1271 | 1400 | return TRUE; |
1272 | 1401 | } |
1273 | 1402 | |
1274 | const mapping_entry_handler ip_rules_handlers[] = { | |
1403 | static const mapping_entry_handler ip_rules_handlers[] = { | |
1275 | 1404 | {"from", YAML_SCALAR_NODE, handle_ip_rule_ip, NULL, ip_rule_offset(from)}, |
1276 | 1405 | {"mark", YAML_SCALAR_NODE, handle_ip_rule_fwmark}, |
1277 | 1406 | {"priority", YAML_SCALAR_NODE, handle_ip_rule_prio}, |
1287 | 1416 | for (yaml_node_item_t *i = node->data.sequence.items.start; i < node->data.sequence.items.top; i++) { |
1288 | 1417 | yaml_node_t *entry = yaml_document_get_node(doc, *i); |
1289 | 1418 | |
1290 | cur_ip_rule = g_new0(ip_rule, 1); | |
1419 | cur_ip_rule = g_new0(NetplanIPRule, 1); | |
1291 | 1420 | cur_ip_rule->family = G_MAXUINT; /* 0 is a valid family ID */ |
1292 | cur_ip_rule->priority = IP_RULE_PRIO_UNSPEC; | |
1293 | cur_ip_rule->table = ROUTE_TABLE_UNSPEC; | |
1294 | cur_ip_rule->tos = IP_RULE_TOS_UNSPEC; | |
1295 | cur_ip_rule->fwmark = IP_RULE_FW_MARK_UNSPEC; | |
1421 | cur_ip_rule->priority = NETPLAN_IP_RULE_PRIO_UNSPEC; | |
1422 | cur_ip_rule->table = NETPLAN_ROUTE_TABLE_UNSPEC; | |
1423 | cur_ip_rule->tos = NETPLAN_IP_RULE_TOS_UNSPEC; | |
1424 | cur_ip_rule->fwmark = NETPLAN_IP_RULE_FW_MARK_UNSPEC; | |
1296 | 1425 | |
1297 | 1426 | if (process_mapping(doc, entry, ip_rules_handlers, error)) { |
1298 | 1427 | if (!cur_netdef->ip_rules) { |
1299 | cur_netdef->ip_rules = g_array_new(FALSE, FALSE, sizeof(ip_rule*)); | |
1428 | cur_netdef->ip_rules = g_array_new(FALSE, FALSE, sizeof(NetplanIPRule*)); | |
1300 | 1429 | } |
1301 | 1430 | |
1302 | 1431 | g_array_append_val(cur_netdef->ip_rules, cur_ip_rule); |
1345 | 1474 | static gboolean |
1346 | 1475 | handle_bond_primary_slave(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error) |
1347 | 1476 | { |
1348 | net_definition *component; | |
1477 | NetplanNetDefinition *component; | |
1349 | 1478 | char** ref_ptr; |
1350 | 1479 | |
1351 | 1480 | component = g_hash_table_lookup(netdefs, scalar(node)); |
1364 | 1493 | return TRUE; |
1365 | 1494 | } |
1366 | 1495 | |
1367 | const mapping_entry_handler bond_params_handlers[] = { | |
1496 | static const mapping_entry_handler bond_params_handlers[] = { | |
1368 | 1497 | {"mode", YAML_SCALAR_NODE, handle_bond_mode, NULL, netdef_offset(bond_params.mode)}, |
1369 | 1498 | {"lacp-rate", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(bond_params.lacp_rate)}, |
1370 | 1499 | {"mii-monitor-interval", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(bond_params.monitor_interval)}, |
1417 | 1546 | ****************************************************/ |
1418 | 1547 | |
1419 | 1548 | const char* |
1420 | tunnel_mode_to_string(tunnel_mode mode) | |
1421 | { | |
1422 | return tunnel_mode_table[mode]; | |
1549 | tunnel_mode_to_string(NetplanTunnelMode mode) | |
1550 | { | |
1551 | return netplan_tunnel_mode_table[mode]; | |
1423 | 1552 | } |
1424 | 1553 | |
1425 | 1554 | static gboolean |
1449 | 1578 | handle_tunnel_mode(yaml_document_t* doc, yaml_node_t* node, const void* _, GError** error) |
1450 | 1579 | { |
1451 | 1580 | const char *key = scalar(node); |
1452 | tunnel_mode i; | |
1581 | NetplanTunnelMode i; | |
1453 | 1582 | |
1454 | 1583 | // Skip over unknown (0) tunnel mode. |
1455 | for (i = 1; i < _TUNNEL_MODE_MAX; ++i) { | |
1456 | if (g_strcmp0(tunnel_mode_table[i], key) == 0) { | |
1584 | for (i = 1; i < NETPLAN_TUNNEL_MODE_MAX_; ++i) { | |
1585 | if (g_strcmp0(netplan_tunnel_mode_table[i], key) == 0) { | |
1457 | 1586 | cur_netdef->tunnel.mode = i; |
1458 | 1587 | return TRUE; |
1459 | 1588 | } |
1484 | 1613 | return TRUE; |
1485 | 1614 | } |
1486 | 1615 | |
1487 | const mapping_entry_handler tunnel_keys_handlers[] = { | |
1616 | static const mapping_entry_handler tunnel_keys_handlers[] = { | |
1488 | 1617 | {"input", YAML_SCALAR_NODE, handle_tunnel_key, NULL, netdef_offset(tunnel.input_key)}, |
1489 | 1618 | {"output", YAML_SCALAR_NODE, handle_tunnel_key, NULL, netdef_offset(tunnel.output_key)}, |
1490 | 1619 | {NULL} |
1518 | 1647 | * Grammar and handlers for network devices |
1519 | 1648 | ****************************************************/ |
1520 | 1649 | |
1521 | const mapping_entry_handler nameservers_handlers[] = { | |
1650 | static const mapping_entry_handler nm_backend_settings_handlers[] = { | |
1651 | {"name", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.name)}, | |
1652 | {"uuid", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.uuid)}, | |
1653 | {"stable-id", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.stable_id)}, | |
1654 | {"device", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.device)}, | |
1655 | {NULL} | |
1656 | }; | |
1657 | ||
1658 | static const mapping_entry_handler nameservers_handlers[] = { | |
1522 | 1659 | {"search", YAML_SEQUENCE_NODE, handle_nameservers_search}, |
1523 | 1660 | {"addresses", YAML_SEQUENCE_NODE, handle_nameservers_addresses}, |
1524 | 1661 | {NULL} |
1536 | 1673 | {"use-ntp", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(overrides.use_ntp)}, \ |
1537 | 1674 | {"use-routes", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(overrides.use_routes)} |
1538 | 1675 | |
1539 | const mapping_entry_handler dhcp4_overrides_handlers[] = { | |
1676 | static const mapping_entry_handler dhcp4_overrides_handlers[] = { | |
1540 | 1677 | COMMON_DHCP_OVERRIDES_HANDLERS(dhcp4_overrides), |
1541 | 1678 | {NULL}, |
1542 | 1679 | }; |
1543 | 1680 | |
1544 | const mapping_entry_handler dhcp6_overrides_handlers[] = { | |
1681 | static const mapping_entry_handler dhcp6_overrides_handlers[] = { | |
1545 | 1682 | COMMON_DHCP_OVERRIDES_HANDLERS(dhcp6_overrides), |
1546 | 1683 | {NULL}, |
1547 | 1684 | }; |
1558 | 1695 | {"dhcp6-overrides", YAML_MAPPING_NODE, NULL, dhcp6_overrides_handlers}, \ |
1559 | 1696 | {"gateway4", YAML_SCALAR_NODE, handle_gateway4}, \ |
1560 | 1697 | {"gateway6", YAML_SCALAR_NODE, handle_gateway6}, \ |
1698 | {"ipv6-address-generation", YAML_SCALAR_NODE, handle_netdef_addrgen}, \ | |
1561 | 1699 | {"ipv6-mtu", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(ipv6_mtubytes)}, \ |
1562 | 1700 | {"ipv6-privacy", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(ip6_privacy)}, \ |
1563 | 1701 | {"link-local", YAML_SEQUENCE_NODE, handle_link_local}, \ |
1570 | 1708 | {"routes", YAML_SEQUENCE_NODE, handle_routes}, \ |
1571 | 1709 | {"routing-policy", YAML_SEQUENCE_NODE, handle_ip_rules} |
1572 | 1710 | |
1711 | #define COMMON_BACKEND_HANDLERS \ | |
1712 | {"networkmanager", YAML_MAPPING_NODE, NULL, nm_backend_settings_handlers} | |
1713 | ||
1573 | 1714 | /* Handlers for physical links */ |
1574 | 1715 | #define PHYSICAL_LINK_HANDLERS \ |
1575 | 1716 | {"match", YAML_MAPPING_NODE, handle_match}, \ |
1576 | 1717 | {"set-name", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(set_name)}, \ |
1577 | {"wakeonlan", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(wake_on_lan)} | |
1578 | ||
1579 | const mapping_entry_handler ethernet_def_handlers[] = { | |
1718 | {"wakeonlan", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(wake_on_lan)}, \ | |
1719 | {"wakeonwlan", YAML_SEQUENCE_NODE, handle_wowlan, NULL, netdef_offset(wowlan)}, \ | |
1720 | {"emit-lldp", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(emit_lldp)} | |
1721 | ||
1722 | static const mapping_entry_handler ethernet_def_handlers[] = { | |
1580 | 1723 | COMMON_LINK_HANDLERS, |
1724 | COMMON_BACKEND_HANDLERS, | |
1581 | 1725 | PHYSICAL_LINK_HANDLERS, |
1582 | 1726 | {"auth", YAML_MAPPING_NODE, handle_auth}, |
1727 | {"link", YAML_SCALAR_NODE, handle_netdef_id_ref, NULL, netdef_offset(sriov_link)}, | |
1728 | {"virtual-function-count", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(sriov_explicit_vf_count)}, | |
1583 | 1729 | {NULL} |
1584 | 1730 | }; |
1585 | 1731 | |
1586 | const mapping_entry_handler wifi_def_handlers[] = { | |
1732 | static const mapping_entry_handler wifi_def_handlers[] = { | |
1587 | 1733 | COMMON_LINK_HANDLERS, |
1734 | COMMON_BACKEND_HANDLERS, | |
1588 | 1735 | PHYSICAL_LINK_HANDLERS, |
1589 | 1736 | {"access-points", YAML_MAPPING_NODE, handle_wifi_access_points}, |
1590 | 1737 | {"auth", YAML_MAPPING_NODE, handle_auth}, |
1591 | 1738 | {NULL} |
1592 | 1739 | }; |
1593 | 1740 | |
1594 | const mapping_entry_handler bridge_def_handlers[] = { | |
1741 | static const mapping_entry_handler bridge_def_handlers[] = { | |
1595 | 1742 | COMMON_LINK_HANDLERS, |
1743 | COMMON_BACKEND_HANDLERS, | |
1596 | 1744 | {"interfaces", YAML_SEQUENCE_NODE, handle_bridge_interfaces, NULL, NULL}, |
1597 | 1745 | {"parameters", YAML_MAPPING_NODE, handle_bridge}, |
1598 | 1746 | {NULL} |
1599 | 1747 | }; |
1600 | 1748 | |
1601 | const mapping_entry_handler bond_def_handlers[] = { | |
1749 | static const mapping_entry_handler bond_def_handlers[] = { | |
1602 | 1750 | COMMON_LINK_HANDLERS, |
1751 | COMMON_BACKEND_HANDLERS, | |
1603 | 1752 | {"interfaces", YAML_SEQUENCE_NODE, handle_bond_interfaces, NULL, NULL}, |
1604 | 1753 | {"parameters", YAML_MAPPING_NODE, handle_bonding}, |
1605 | 1754 | {NULL} |
1606 | 1755 | }; |
1607 | 1756 | |
1608 | const mapping_entry_handler vlan_def_handlers[] = { | |
1757 | static const mapping_entry_handler vlan_def_handlers[] = { | |
1609 | 1758 | COMMON_LINK_HANDLERS, |
1759 | COMMON_BACKEND_HANDLERS, | |
1610 | 1760 | {"id", YAML_SCALAR_NODE, handle_netdef_guint, NULL, netdef_offset(vlan_id)}, |
1611 | 1761 | {"link", YAML_SCALAR_NODE, handle_netdef_id_ref, NULL, netdef_offset(vlan_link)}, |
1612 | 1762 | {NULL} |
1613 | 1763 | }; |
1614 | 1764 | |
1615 | const mapping_entry_handler tunnel_def_handlers[] = { | |
1765 | static const mapping_entry_handler modem_def_handlers[] = { | |
1616 | 1766 | COMMON_LINK_HANDLERS, |
1767 | {"apn", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(modem_params.apn)}, | |
1768 | {"auto-config", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(modem_params.auto_config)}, | |
1769 | {"device-id", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(modem_params.device_id)}, | |
1770 | {"network-id", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(modem_params.network_id)}, | |
1771 | {"number", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(modem_params.number)}, | |
1772 | {"password", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(modem_params.password)}, | |
1773 | {"pin", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(modem_params.pin)}, | |
1774 | {"sim-id", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(modem_params.sim_id)}, | |
1775 | {"sim-operator-id", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(modem_params.sim_operator_id)}, | |
1776 | {"username", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(modem_params.username)}, | |
1777 | }; | |
1778 | ||
1779 | static const mapping_entry_handler tunnel_def_handlers[] = { | |
1780 | COMMON_LINK_HANDLERS, | |
1781 | COMMON_BACKEND_HANDLERS, | |
1617 | 1782 | {"mode", YAML_SCALAR_NODE, handle_tunnel_mode}, |
1618 | 1783 | {"local", YAML_SCALAR_NODE, handle_tunnel_addr, NULL, netdef_offset(tunnel.local_ip)}, |
1619 | 1784 | {"remote", YAML_SCALAR_NODE, handle_tunnel_addr, NULL, netdef_offset(tunnel.remote_ip)}, |
1649 | 1814 | } |
1650 | 1815 | |
1651 | 1816 | static void |
1652 | initialize_dhcp_overrides(dhcp_overrides* overrides) | |
1817 | initialize_dhcp_overrides(NetplanDHCPOverrides* overrides) | |
1653 | 1818 | { |
1654 | 1819 | overrides->use_dns = TRUE; |
1655 | 1820 | overrides->use_domains = NULL; |
1659 | 1824 | overrides->use_mtu = TRUE; |
1660 | 1825 | overrides->use_routes = TRUE; |
1661 | 1826 | overrides->hostname = NULL; |
1662 | overrides->metric = METRIC_UNSPEC; | |
1827 | overrides->metric = NETPLAN_METRIC_UNSPEC; | |
1663 | 1828 | } |
1664 | 1829 | |
1665 | 1830 | /** |
1704 | 1869 | return yaml_error(key, error, "Updated definition '%s' changes device type", scalar(key)); |
1705 | 1870 | } else { |
1706 | 1871 | /* create new network definition */ |
1707 | cur_netdef = g_new0(net_definition, 1); | |
1872 | cur_netdef = g_new0(NetplanNetDefinition, 1); | |
1708 | 1873 | cur_netdef->type = GPOINTER_TO_UINT(data); |
1709 | cur_netdef->backend = backend_cur_type ?: BACKEND_NONE; | |
1874 | cur_netdef->backend = backend_cur_type ?: NETPLAN_BACKEND_NONE; | |
1710 | 1875 | cur_netdef->id = g_strdup(scalar(key)); |
1711 | 1876 | |
1712 | 1877 | /* Set some default values */ |
1713 | 1878 | cur_netdef->vlan_id = G_MAXUINT; /* 0 is a valid ID */ |
1714 | cur_netdef->tunnel.mode = TUNNEL_MODE_UNKNOWN; | |
1879 | cur_netdef->tunnel.mode = NETPLAN_TUNNEL_MODE_UNKNOWN; | |
1715 | 1880 | cur_netdef->dhcp_identifier = g_strdup("duid"); /* keep networkd's default */ |
1716 | 1881 | /* systemd-networkd defaults to IPv6 LL enabled; keep that default */ |
1717 | 1882 | cur_netdef->linklocal.ipv6 = TRUE; |
1718 | 1883 | g_hash_table_insert(netdefs, cur_netdef->id, cur_netdef); |
1719 | 1884 | netdefs_ordered = g_list_append(netdefs_ordered, cur_netdef); |
1885 | cur_netdef->sriov_vlan_filter = FALSE; | |
1720 | 1886 | |
1721 | 1887 | /* DHCP override defaults */ |
1722 | 1888 | initialize_dhcp_overrides(&cur_netdef->dhcp4_overrides); |
1731 | 1897 | |
1732 | 1898 | /* and fill it with definitions */ |
1733 | 1899 | switch (cur_netdef->type) { |
1734 | case ND_BOND: handlers = bond_def_handlers; break; | |
1735 | case ND_BRIDGE: handlers = bridge_def_handlers; break; | |
1736 | case ND_ETHERNET: handlers = ethernet_def_handlers; break; | |
1737 | case ND_TUNNEL: handlers = tunnel_def_handlers; break; | |
1738 | case ND_VLAN: handlers = vlan_def_handlers; break; | |
1739 | case ND_WIFI: handlers = wifi_def_handlers; break; | |
1900 | case NETPLAN_DEF_TYPE_BOND: handlers = bond_def_handlers; break; | |
1901 | case NETPLAN_DEF_TYPE_BRIDGE: handlers = bridge_def_handlers; break; | |
1902 | case NETPLAN_DEF_TYPE_ETHERNET: handlers = ethernet_def_handlers; break; | |
1903 | case NETPLAN_DEF_TYPE_MODEM: handlers = modem_def_handlers; break; | |
1904 | case NETPLAN_DEF_TYPE_TUNNEL: handlers = tunnel_def_handlers; break; | |
1905 | case NETPLAN_DEF_TYPE_VLAN: handlers = vlan_def_handlers; break; | |
1906 | case NETPLAN_DEF_TYPE_WIFI: handlers = wifi_def_handlers; break; | |
1740 | 1907 | default: g_assert_not_reached(); // LCOV_EXCL_LINE |
1741 | 1908 | } |
1742 | 1909 | if (!process_mapping(doc, value, handlers, error)) |
1748 | 1915 | |
1749 | 1916 | /* convenience shortcut: physical device without match: means match |
1750 | 1917 | * name on ID */ |
1751 | if (cur_netdef->type < ND_VIRTUAL && !cur_netdef->has_match) | |
1918 | if (cur_netdef->type < NETPLAN_DEF_TYPE_VIRTUAL && !cur_netdef->has_match) | |
1752 | 1919 | cur_netdef->match.original_name = cur_netdef->id; |
1753 | 1920 | } |
1754 | backend_cur_type = BACKEND_NONE; | |
1755 | return TRUE; | |
1756 | } | |
1757 | ||
1758 | const mapping_entry_handler network_handlers[] = { | |
1759 | {"bonds", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(ND_BOND)}, | |
1760 | {"bridges", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(ND_BRIDGE)}, | |
1761 | {"ethernets", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(ND_ETHERNET)}, | |
1921 | backend_cur_type = NETPLAN_BACKEND_NONE; | |
1922 | return TRUE; | |
1923 | } | |
1924 | ||
1925 | static const mapping_entry_handler network_handlers[] = { | |
1926 | {"bonds", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_BOND)}, | |
1927 | {"bridges", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_BRIDGE)}, | |
1928 | {"ethernets", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_ETHERNET)}, | |
1762 | 1929 | {"renderer", YAML_SCALAR_NODE, handle_network_renderer}, |
1763 | {"tunnels", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(ND_TUNNEL)}, | |
1930 | {"tunnels", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_TUNNEL)}, | |
1764 | 1931 | {"version", YAML_SCALAR_NODE, handle_network_version}, |
1765 | {"vlans", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(ND_VLAN)}, | |
1766 | {"wifis", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(ND_WIFI)}, | |
1932 | {"vlans", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_VLAN)}, | |
1933 | {"wifis", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_WIFI)}, | |
1934 | {"modems", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_MODEM)}, | |
1767 | 1935 | {NULL} |
1768 | 1936 | }; |
1769 | 1937 | |
1771 | 1939 | * Grammar and handlers for root node |
1772 | 1940 | ****************************************************/ |
1773 | 1941 | |
1774 | const mapping_entry_handler root_handlers[] = { | |
1942 | static const mapping_entry_handler root_handlers[] = { | |
1775 | 1943 | {"network", YAML_MAPPING_NODE, NULL, network_handlers}, |
1776 | 1944 | {NULL} |
1777 | 1945 | }; |
1808 | 1976 | if (g_hash_table_size(missing_id) > 0) { |
1809 | 1977 | GHashTableIter iter; |
1810 | 1978 | gpointer key, value; |
1811 | missing_node *missing; | |
1979 | NetplanMissingNode *missing; | |
1812 | 1980 | |
1813 | 1981 | g_clear_error(error); |
1814 | 1982 | |
1816 | 1984 | * approximate early failure and give the user a meaningful error. */ |
1817 | 1985 | g_hash_table_iter_init (&iter, missing_id); |
1818 | 1986 | g_hash_table_iter_next (&iter, &key, &value); |
1819 | missing = (missing_node*) value; | |
1987 | missing = (NetplanMissingNode*) value; | |
1820 | 1988 | |
1821 | 1989 | return yaml_error(missing->node, error, "%s: interface '%s' is not defined", |
1822 | 1990 | missing->netdef_id, |
1832 | 2000 | * Parse given YAML file and create/update global "netdefs" list. |
1833 | 2001 | */ |
1834 | 2002 | gboolean |
1835 | parse_yaml(const char* filename, GError** error) | |
2003 | netplan_parse_yaml(const char* filename, GError** error) | |
1836 | 2004 | { |
1837 | 2005 | yaml_document_t doc; |
1838 | 2006 | gboolean ret; |
1839 | 2007 | |
1840 | 2008 | if (!load_yaml(filename, &doc, error)) |
1841 | 2009 | return FALSE; |
2010 | ||
2011 | if (!netdefs) | |
2012 | netdefs = g_hash_table_new(g_str_hash, g_str_equal); | |
1842 | 2013 | |
1843 | 2014 | /* empty file? */ |
1844 | 2015 | if (yaml_document_get_root_node(&doc) == NULL) |
1845 | 2016 | return TRUE; |
1846 | ||
1847 | if (!netdefs) | |
1848 | netdefs = g_hash_table_new(g_str_hash, g_str_equal); | |
1849 | 2017 | |
1850 | 2018 | g_assert(ids_in_file == NULL); |
1851 | 2019 | ids_in_file = g_hash_table_new(g_str_hash, NULL); |
1863 | 2031 | finish_iterator(gpointer key, gpointer value, gpointer user_data) |
1864 | 2032 | { |
1865 | 2033 | GError **error = (GError **)user_data; |
1866 | net_definition* nd = value; | |
2034 | NetplanNetDefinition* nd = value; | |
1867 | 2035 | |
1868 | 2036 | /* Take more steps to make sure we always have a backend set for netdefs */ |
1869 | if (nd->backend == BACKEND_NONE) { | |
2037 | if (nd->backend == NETPLAN_BACKEND_NONE) { | |
1870 | 2038 | nd->backend = get_default_backend_for_type(nd->type); |
1871 | 2039 | g_debug("%s: setting default backend to %i", nd->id, nd->backend); |
1872 | 2040 | } |
1879 | 2047 | /** |
1880 | 2048 | * Post-processing after parsing all config files |
1881 | 2049 | */ |
1882 | gboolean | |
1883 | finish_parse(GError** error) | |
1884 | { | |
1885 | if (netdefs) | |
2050 | GHashTable * | |
2051 | netplan_finish_parse(GError** error) | |
2052 | { | |
2053 | if (netdefs) { | |
2054 | g_debug("We have some netdefs, pass them through a final round of validation"); | |
1886 | 2055 | g_hash_table_foreach(netdefs, finish_iterator, error); |
2056 | } | |
1887 | 2057 | |
1888 | 2058 | if (error && *error) |
1889 | return FALSE; | |
1890 | ||
1891 | return TRUE; | |
2059 | return NULL; | |
2060 | ||
2061 | return netdefs; | |
1892 | 2062 | } |
1893 | 2063 | |
1894 | 2064 | /** |
1895 | 2065 | * Return current global backend. |
1896 | 2066 | */ |
1897 | netdef_backend | |
1898 | get_global_backend() | |
2067 | NetplanBackend | |
2068 | netplan_get_global_backend() | |
1899 | 2069 | { |
1900 | 2070 | return backend_global; |
1901 | 2071 | } |
38 | 38 | ****************************************************/ |
39 | 39 | |
40 | 40 | typedef enum { |
41 | ND_NONE, | |
41 | NETPLAN_DEF_TYPE_NONE, | |
42 | 42 | /* physical devices */ |
43 | ND_ETHERNET, | |
44 | ND_WIFI, | |
43 | NETPLAN_DEF_TYPE_ETHERNET, | |
44 | NETPLAN_DEF_TYPE_WIFI, | |
45 | NETPLAN_DEF_TYPE_MODEM, | |
45 | 46 | /* virtual devices */ |
46 | ND_VIRTUAL, | |
47 | ND_BRIDGE = ND_VIRTUAL, | |
48 | ND_BOND, | |
49 | ND_VLAN, | |
50 | ND_TUNNEL, | |
51 | } netdef_type; | |
52 | ||
53 | typedef enum { | |
54 | BACKEND_NONE, | |
55 | BACKEND_NETWORKD, | |
56 | BACKEND_NM, | |
57 | _BACKEND_MAX, | |
58 | } netdef_backend; | |
59 | ||
60 | static const char* const netdef_backend_to_name[_BACKEND_MAX] = { | |
61 | [BACKEND_NONE] = "none", | |
62 | [BACKEND_NETWORKD] = "networkd", | |
63 | [BACKEND_NM] = "NetworkManager", | |
47 | NETPLAN_DEF_TYPE_VIRTUAL, | |
48 | NETPLAN_DEF_TYPE_BRIDGE = NETPLAN_DEF_TYPE_VIRTUAL, | |
49 | NETPLAN_DEF_TYPE_BOND, | |
50 | NETPLAN_DEF_TYPE_VLAN, | |
51 | NETPLAN_DEF_TYPE_TUNNEL, | |
52 | } NetplanDefType; | |
53 | ||
54 | typedef enum { | |
55 | NETPLAN_BACKEND_NONE, | |
56 | NETPLAN_BACKEND_NETWORKD, | |
57 | NETPLAN_BACKEND_NM, | |
58 | NETPLAN_BACKEND_MAX_, | |
59 | } NetplanBackend; | |
60 | ||
61 | static const char* const netplan_backend_to_name[NETPLAN_BACKEND_MAX_] = { | |
62 | [NETPLAN_BACKEND_NONE] = "none", | |
63 | [NETPLAN_BACKEND_NETWORKD] = "networkd", | |
64 | [NETPLAN_BACKEND_NM] = "NetworkManager", | |
64 | 65 | }; |
65 | 66 | |
66 | 67 | typedef enum { |
67 | ACCEPT_RA_KERNEL, | |
68 | ACCEPT_RA_ENABLED, | |
69 | ACCEPT_RA_DISABLED, | |
70 | } ra_mode; | |
71 | ||
72 | typedef enum { | |
73 | OPTIONAL_IPV4_LL = 1<<0, | |
74 | OPTIONAL_IPV6_RA = 1<<1, | |
75 | OPTIONAL_DHCP4 = 1<<2, | |
76 | OPTIONAL_DHCP6 = 1<<3, | |
77 | OPTIONAL_STATIC = 1<<4, | |
78 | } optional_addr; | |
68 | NETPLAN_RA_MODE_KERNEL, | |
69 | NETPLAN_RA_MODE_ENABLED, | |
70 | NETPLAN_RA_MODE_DISABLED, | |
71 | } NetplanRAMode; | |
72 | ||
73 | typedef enum { | |
74 | NETPLAN_OPTIONAL_IPV4_LL = 1<<0, | |
75 | NETPLAN_OPTIONAL_IPV6_RA = 1<<1, | |
76 | NETPLAN_OPTIONAL_DHCP4 = 1<<2, | |
77 | NETPLAN_OPTIONAL_DHCP6 = 1<<3, | |
78 | NETPLAN_OPTIONAL_STATIC = 1<<4, | |
79 | } NetplanOptionalAddressFlag; | |
80 | ||
81 | typedef enum { | |
82 | NETPLAN_ADDRGEN_DEFAULT, | |
83 | NETPLAN_ADDRGEN_EUI64, | |
84 | NETPLAN_ADDRGEN_STABLEPRIVACY, | |
85 | } NetplanAddrGenMode; | |
86 | ||
87 | struct NetplanOptionalAddressType { | |
88 | char* name; | |
89 | NetplanOptionalAddressFlag flag; | |
90 | }; | |
91 | ||
92 | extern struct NetplanOptionalAddressType NETPLAN_OPTIONAL_ADDRESS_TYPES[]; | |
79 | 93 | |
80 | 94 | /* Tunnel mode enum; sync with NetworkManager's DBUS API */ |
81 | 95 | /* TODO: figure out whether networkd's GRETAP and NM's ISATAP |
82 | 96 | * are the same thing. |
83 | 97 | */ |
84 | 98 | typedef enum { |
85 | TUNNEL_MODE_UNKNOWN = 0, | |
86 | TUNNEL_MODE_IPIP = 1, | |
87 | TUNNEL_MODE_GRE = 2, | |
88 | TUNNEL_MODE_SIT = 3, | |
89 | TUNNEL_MODE_ISATAP = 4, // NM only. | |
90 | TUNNEL_MODE_VTI = 5, | |
91 | TUNNEL_MODE_IP6IP6 = 6, | |
92 | TUNNEL_MODE_IPIP6 = 7, | |
93 | TUNNEL_MODE_IP6GRE = 8, | |
94 | TUNNEL_MODE_VTI6 = 9, | |
99 | NETPLAN_TUNNEL_MODE_UNKNOWN = 0, | |
100 | NETPLAN_TUNNEL_MODE_IPIP = 1, | |
101 | NETPLAN_TUNNEL_MODE_GRE = 2, | |
102 | NETPLAN_TUNNEL_MODE_SIT = 3, | |
103 | NETPLAN_TUNNEL_MODE_ISATAP = 4, // NM only. | |
104 | NETPLAN_TUNNEL_MODE_VTI = 5, | |
105 | NETPLAN_TUNNEL_MODE_IP6IP6 = 6, | |
106 | NETPLAN_TUNNEL_MODE_IPIP6 = 7, | |
107 | NETPLAN_TUNNEL_MODE_IP6GRE = 8, | |
108 | NETPLAN_TUNNEL_MODE_VTI6 = 9, | |
95 | 109 | |
96 | 110 | /* systemd-only, apparently? */ |
97 | TUNNEL_MODE_GRETAP = 101, | |
98 | TUNNEL_MODE_IP6GRETAP = 102, | |
99 | ||
100 | _TUNNEL_MODE_MAX, | |
101 | } tunnel_mode; | |
111 | NETPLAN_TUNNEL_MODE_GRETAP = 101, | |
112 | NETPLAN_TUNNEL_MODE_IP6GRETAP = 102, | |
113 | ||
114 | NETPLAN_TUNNEL_MODE_MAX_, | |
115 | } NetplanTunnelMode; | |
102 | 116 | |
103 | 117 | static const char* const |
104 | tunnel_mode_table[_TUNNEL_MODE_MAX] = { | |
105 | [TUNNEL_MODE_UNKNOWN] = "unknown", | |
106 | [TUNNEL_MODE_IPIP] = "ipip", | |
107 | [TUNNEL_MODE_GRE] = "gre", | |
108 | [TUNNEL_MODE_SIT] = "sit", | |
109 | [TUNNEL_MODE_ISATAP] = "isatap", | |
110 | [TUNNEL_MODE_VTI] = "vti", | |
111 | [TUNNEL_MODE_IP6IP6] = "ip6ip6", | |
112 | [TUNNEL_MODE_IPIP6] = "ipip6", | |
113 | [TUNNEL_MODE_IP6GRE] = "ip6gre", | |
114 | [TUNNEL_MODE_VTI6] = "vti6", | |
115 | ||
116 | [TUNNEL_MODE_GRETAP] = "gretap", | |
117 | [TUNNEL_MODE_IP6GRETAP] = "ip6gretap", | |
118 | netplan_tunnel_mode_table[NETPLAN_TUNNEL_MODE_MAX_] = { | |
119 | [NETPLAN_TUNNEL_MODE_UNKNOWN] = "unknown", | |
120 | [NETPLAN_TUNNEL_MODE_IPIP] = "ipip", | |
121 | [NETPLAN_TUNNEL_MODE_GRE] = "gre", | |
122 | [NETPLAN_TUNNEL_MODE_SIT] = "sit", | |
123 | [NETPLAN_TUNNEL_MODE_ISATAP] = "isatap", | |
124 | [NETPLAN_TUNNEL_MODE_VTI] = "vti", | |
125 | [NETPLAN_TUNNEL_MODE_IP6IP6] = "ip6ip6", | |
126 | [NETPLAN_TUNNEL_MODE_IPIP6] = "ipip6", | |
127 | [NETPLAN_TUNNEL_MODE_IP6GRE] = "ip6gre", | |
128 | [NETPLAN_TUNNEL_MODE_VTI6] = "vti6", | |
129 | ||
130 | [NETPLAN_TUNNEL_MODE_GRETAP] = "gretap", | |
131 | [NETPLAN_TUNNEL_MODE_IP6GRETAP] = "ip6gretap", | |
118 | 132 | }; |
119 | 133 | |
120 | struct optional_address_option { | |
134 | typedef enum { | |
135 | NETPLAN_WIFI_WOWLAN_DEFAULT = 1<<0, | |
136 | NETPLAN_WIFI_WOWLAN_ANY = 1<<1, | |
137 | NETPLAN_WIFI_WOWLAN_DISCONNECT = 1<<2, | |
138 | NETPLAN_WIFI_WOWLAN_MAGIC = 1<<3, | |
139 | NETPLAN_WIFI_WOWLAN_GTK_REKEY_FAILURE = 1<<4, | |
140 | NETPLAN_WIFI_WOWLAN_EAP_IDENTITY_REQ = 1<<5, | |
141 | NETPLAN_WIFI_WOWLAN_4WAY_HANDSHAKE = 1<<6, | |
142 | NETPLAN_WIFI_WOWLAN_RFKILL_RELEASE = 1<<7, | |
143 | NETPLAN_WIFI_WOWLAN_TCP = 1<<8, | |
144 | } NetplanWifiWowlanFlag; | |
145 | ||
146 | struct NetplanWifiWowlanType { | |
121 | 147 | char* name; |
122 | optional_addr flag; | |
148 | NetplanWifiWowlanFlag flag; | |
123 | 149 | }; |
124 | 150 | |
125 | extern struct optional_address_option optional_address_options[]; | |
126 | ||
127 | typedef enum { | |
128 | KEY_MANAGEMENT_NONE, | |
129 | KEY_MANAGEMENT_WPA_PSK, | |
130 | KEY_MANAGEMENT_WPA_EAP, | |
131 | KEY_MANAGEMENT_8021X, | |
132 | } auth_key_management_type; | |
133 | ||
134 | typedef enum { | |
135 | EAP_NONE, | |
136 | EAP_TLS, | |
137 | EAP_PEAP, | |
138 | EAP_TTLS, | |
139 | } auth_eap_method; | |
151 | extern struct NetplanWifiWowlanType NETPLAN_WIFI_WOWLAN_TYPES[]; | |
152 | ||
153 | typedef enum { | |
154 | NETPLAN_AUTH_KEY_MANAGEMENT_NONE, | |
155 | NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK, | |
156 | NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAP, | |
157 | NETPLAN_AUTH_KEY_MANAGEMENT_8021X, | |
158 | } NetplanAuthKeyManagementType; | |
159 | ||
160 | typedef enum { | |
161 | NETPLAN_AUTH_EAP_NONE, | |
162 | NETPLAN_AUTH_EAP_TLS, | |
163 | NETPLAN_AUTH_EAP_PEAP, | |
164 | NETPLAN_AUTH_EAP_TTLS, | |
165 | } NetplanAuthEAPMethod; | |
140 | 166 | |
141 | 167 | typedef struct missing_node { |
142 | 168 | char* netdef_id; |
143 | 169 | const yaml_node_t* node; |
144 | } missing_node; | |
170 | } NetplanMissingNode; | |
145 | 171 | |
146 | 172 | typedef struct authentication_settings { |
147 | auth_key_management_type key_management; | |
148 | auth_eap_method eap_method; | |
173 | NetplanAuthKeyManagementType key_management; | |
174 | NetplanAuthEAPMethod eap_method; | |
149 | 175 | char* identity; |
150 | 176 | char* anonymous_identity; |
151 | 177 | char* password; |
153 | 179 | char* client_certificate; |
154 | 180 | char* client_key; |
155 | 181 | char* client_key_password; |
156 | } authentication_settings; | |
182 | char* phase2_auth; /* netplan-feature: auth-phase2 */ | |
183 | } NetplanAuthenticationSettings; | |
157 | 184 | |
158 | 185 | /* Fields below are valid for dhcp4 and dhcp6 unless otherwise noted. */ |
159 | 186 | typedef struct dhcp_overrides { |
166 | 193 | char* use_domains; /* netplan-feature: dhcp-use-domains */ |
167 | 194 | char* hostname; |
168 | 195 | guint metric; |
169 | } dhcp_overrides; | |
196 | } NetplanDHCPOverrides; | |
170 | 197 | |
171 | 198 | /** |
172 | 199 | * Represent a configuration stanza |
173 | 200 | */ |
174 | typedef struct net_definition { | |
175 | netdef_type type; | |
176 | netdef_backend backend; | |
201 | ||
202 | struct net_definition; | |
203 | ||
204 | typedef struct net_definition NetplanNetDefinition; | |
205 | ||
206 | struct net_definition { | |
207 | NetplanDefType type; | |
208 | NetplanBackend backend; | |
177 | 209 | char* id; |
178 | 210 | /* only necessary for NetworkManager connection UUIDs in some cases */ |
179 | 211 | uuid_t uuid; |
180 | 212 | |
181 | 213 | /* status options */ |
182 | 214 | gboolean optional; |
183 | optional_addr optional_addresses; | |
215 | NetplanOptionalAddressFlag optional_addresses; | |
184 | 216 | gboolean critical; |
185 | 217 | |
186 | 218 | /* addresses */ |
187 | 219 | gboolean dhcp4; |
188 | 220 | gboolean dhcp6; |
189 | 221 | char* dhcp_identifier; |
190 | dhcp_overrides dhcp4_overrides; | |
191 | dhcp_overrides dhcp6_overrides; | |
192 | ra_mode accept_ra; | |
222 | NetplanDHCPOverrides dhcp4_overrides; | |
223 | NetplanDHCPOverrides dhcp6_overrides; | |
224 | NetplanRAMode accept_ra; | |
193 | 225 | GArray* ip4_addresses; |
194 | 226 | GArray* ip6_addresses; |
195 | 227 | gboolean ip6_privacy; |
228 | guint ip6_addr_gen_mode; | |
196 | 229 | char* gateway4; |
197 | 230 | char* gateway6; |
198 | 231 | GArray* ip4_nameservers; |
211 | 244 | |
212 | 245 | /* vlan */ |
213 | 246 | guint vlan_id; |
214 | struct net_definition* vlan_link; | |
247 | NetplanNetDefinition* vlan_link; | |
215 | 248 | gboolean has_vlans; |
216 | 249 | |
217 | 250 | /* Configured custom MAC address */ |
232 | 265 | } match; |
233 | 266 | gboolean has_match; |
234 | 267 | gboolean wake_on_lan; |
235 | ||
236 | /* these properties are only valid for ND_WIFI */ | |
237 | GHashTable* access_points; /* SSID → wifi_access_point* */ | |
268 | NetplanWifiWowlanFlag wowlan; | |
269 | gboolean emit_lldp; | |
270 | ||
271 | ||
272 | /* these properties are only valid for NETPLAN_DEF_TYPE_WIFI */ | |
273 | GHashTable* access_points; /* SSID → NetplanWifiAccessPoint* */ | |
238 | 274 | |
239 | 275 | struct { |
240 | 276 | char* mode; |
261 | 297 | } bond_params; |
262 | 298 | |
263 | 299 | struct { |
300 | char* apn; | |
301 | gboolean auto_config; | |
302 | char* device_id; | |
303 | char* network_id; | |
304 | char* number; | |
305 | char* password; | |
306 | char* pin; | |
307 | char* sim_id; | |
308 | char* sim_operator_id; | |
309 | char* username; | |
310 | } modem_params; | |
311 | ||
312 | struct { | |
264 | 313 | char* ageing_time; |
265 | 314 | guint priority; |
266 | 315 | guint port_priority; |
273 | 322 | gboolean custom_bridging; |
274 | 323 | |
275 | 324 | struct { |
276 | tunnel_mode mode; | |
325 | NetplanTunnelMode mode; | |
277 | 326 | char *local_ip; |
278 | 327 | char *remote_ip; |
279 | 328 | char *input_key; |
280 | 329 | char *output_key; |
281 | 330 | } tunnel; |
282 | 331 | |
283 | authentication_settings auth; | |
332 | NetplanAuthenticationSettings auth; | |
284 | 333 | gboolean has_auth; |
285 | } net_definition; | |
286 | ||
287 | typedef enum { | |
288 | WIFI_MODE_INFRASTRUCTURE, | |
289 | WIFI_MODE_ADHOC, | |
290 | WIFI_MODE_AP | |
291 | } wifi_mode; | |
334 | ||
335 | /* these properties are only valid for SR-IOV NICs */ | |
336 | struct net_definition* sriov_link; | |
337 | gboolean sriov_vlan_filter; | |
338 | guint sriov_explicit_vf_count; | |
339 | ||
340 | union { | |
341 | struct NetplanNMSettings { | |
342 | char *name; | |
343 | char *uuid; | |
344 | char *stable_id; | |
345 | char *device; | |
346 | } nm; | |
347 | struct NetplanNetworkdSettings { | |
348 | char *unit; | |
349 | } networkd; | |
350 | } backend_settings; | |
351 | }; | |
352 | ||
353 | typedef enum { | |
354 | NETPLAN_WIFI_MODE_INFRASTRUCTURE, | |
355 | NETPLAN_WIFI_MODE_ADHOC, | |
356 | NETPLAN_WIFI_MODE_AP | |
357 | } NetplanWifiMode; | |
358 | ||
359 | typedef enum { | |
360 | NETPLAN_WIFI_BAND_DEFAULT, | |
361 | NETPLAN_WIFI_BAND_5, | |
362 | NETPLAN_WIFI_BAND_24 | |
363 | } NetplanWifiBand; | |
292 | 364 | |
293 | 365 | typedef struct { |
294 | wifi_mode mode; | |
366 | NetplanWifiMode mode; | |
295 | 367 | char* ssid; |
296 | ||
297 | authentication_settings auth; | |
368 | NetplanWifiBand band; | |
369 | char* bssid; | |
370 | guint channel; | |
371 | ||
372 | NetplanAuthenticationSettings auth; | |
298 | 373 | gboolean has_auth; |
299 | } wifi_access_point; | |
300 | ||
301 | #define METRIC_UNSPEC G_MAXUINT | |
302 | #define ROUTE_TABLE_UNSPEC 0 | |
303 | #define IP_RULE_PRIO_UNSPEC G_MAXUINT | |
304 | #define IP_RULE_FW_MARK_UNSPEC 0 | |
305 | #define IP_RULE_TOS_UNSPEC G_MAXUINT | |
374 | } NetplanWifiAccessPoint; | |
375 | ||
376 | #define NETPLAN_METRIC_UNSPEC G_MAXUINT | |
377 | #define NETPLAN_ROUTE_TABLE_UNSPEC 0 | |
378 | #define NETPLAN_IP_RULE_PRIO_UNSPEC G_MAXUINT | |
379 | #define NETPLAN_IP_RULE_FW_MARK_UNSPEC 0 | |
380 | #define NETPLAN_IP_RULE_TOS_UNSPEC G_MAXUINT | |
306 | 381 | |
307 | 382 | typedef struct { |
308 | 383 | guint family; |
319 | 394 | /* valid metrics are valid positive integers. |
320 | 395 | * invalid metrics are represented by METRIC_UNSPEC */ |
321 | 396 | guint metric; |
322 | } ip_route; | |
397 | } NetplanIPRoute; | |
323 | 398 | |
324 | 399 | typedef struct { |
325 | 400 | guint family; |
334 | 409 | guint fwmark; |
335 | 410 | /* type-of-service: between 0 and 255 */ |
336 | 411 | guint tos; |
337 | } ip_rule; | |
412 | } NetplanIPRule; | |
338 | 413 | |
339 | 414 | /* Written/updated by parse_yaml(): char* id → net_definition */ |
340 | 415 | extern GHashTable* netdefs; |
344 | 419 | * Functions |
345 | 420 | ****************************************************/ |
346 | 421 | |
347 | gboolean parse_yaml(const char* filename, GError** error); | |
348 | gboolean finish_parse(GError** error); | |
349 | netdef_backend get_global_backend(); | |
350 | const char* tunnel_mode_to_string(tunnel_mode mode); | |
422 | gboolean netplan_parse_yaml(const char* filename, GError** error); | |
423 | GHashTable* netplan_finish_parse(GError** error); | |
424 | NetplanBackend netplan_get_global_backend(); | |
425 | const char* tunnel_mode_to_string(NetplanTunnelMode mode); |
85 | 85 | for (size_t i = 0; i < gl.gl_pathc; ++i) |
86 | 86 | unlink(gl.gl_pathv[i]); |
87 | 87 | } |
88 | ||
89 | /** | |
90 | * Get the frequency of a given 2.4GHz WiFi channel | |
91 | */ | |
92 | int | |
93 | wifi_get_freq24(int channel) | |
94 | { | |
95 | if (channel < 1 || channel > 14) { | |
96 | g_fprintf(stderr, "ERROR: invalid 2.4GHz WiFi channel: %d\n", channel); | |
97 | exit(1); | |
98 | } | |
99 | ||
100 | if (!wifi_frequency_24) { | |
101 | wifi_frequency_24 = g_hash_table_new(g_direct_hash, g_direct_equal); | |
102 | /* Initialize 2.4GHz frequencies, as of: | |
103 | * https://en.wikipedia.org/wiki/List_of_WLAN_channels#2.4_GHz_(802.11b/g/n/ax) */ | |
104 | for (unsigned i = 0; i < 13; i++) { | |
105 | g_hash_table_insert(wifi_frequency_24, GINT_TO_POINTER(i+1), | |
106 | GINT_TO_POINTER(2412+i*5)); | |
107 | } | |
108 | g_hash_table_insert(wifi_frequency_24, GINT_TO_POINTER(14), | |
109 | GINT_TO_POINTER(2484)); | |
110 | } | |
111 | return GPOINTER_TO_INT(g_hash_table_lookup(wifi_frequency_24, | |
112 | GINT_TO_POINTER(channel))); | |
113 | } | |
114 | ||
115 | /** | |
116 | * Get the frequency of a given 5GHz WiFi channel | |
117 | */ | |
118 | int | |
119 | wifi_get_freq5(int channel) | |
120 | { | |
121 | int channels[] = { 7, 8, 9, 11, 12, 16, 32, 34, 36, 38, 40, 42, 44, 46, 48, | |
122 | 50, 52, 54, 56, 58, 60, 62, 64, 68, 96, 100, 102, 104, | |
123 | 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, | |
124 | 128, 132, 134, 136, 138, 140, 142, 144, 149, 151, 153, | |
125 | 155, 157, 159, 161, 165, 169, 173 }; | |
126 | gboolean found = FALSE; | |
127 | for (unsigned i = 0; i < sizeof(channels) / sizeof(int); i++) { | |
128 | if (channel == channels[i]) { | |
129 | found = TRUE; | |
130 | break; | |
131 | } | |
132 | } | |
133 | if (!found) { | |
134 | g_fprintf(stderr, "ERROR: invalid 5GHz WiFi channel: %d\n", channel); | |
135 | exit(1); | |
136 | } | |
137 | if (!wifi_frequency_5) { | |
138 | wifi_frequency_5 = g_hash_table_new(g_direct_hash, g_direct_equal); | |
139 | /* Initialize 5GHz frequencies, as of: | |
140 | * https://en.wikipedia.org/wiki/List_of_WLAN_channels#5.0_GHz_(802.11j)_WLAN | |
141 | * Skipping channels 183-196. They are valid only in Japan with registration needed */ | |
142 | for (unsigned i = 0; i < sizeof(channels) / sizeof(int); i++) { | |
143 | g_hash_table_insert(wifi_frequency_5, GINT_TO_POINTER(channels[i]), | |
144 | GINT_TO_POINTER(5000+channels[i]*5)); | |
145 | } | |
146 | } | |
147 | return GPOINTER_TO_INT(g_hash_table_lookup(wifi_frequency_5, | |
148 | GINT_TO_POINTER(channel))); | |
149 | } |
16 | 16 | |
17 | 17 | #pragma once |
18 | 18 | |
19 | GHashTable* wifi_frequency_24; | |
20 | GHashTable* wifi_frequency_5; | |
21 | ||
19 | 22 | void safe_mkdir_p_dir(const char* file_path); |
20 | 23 | void g_string_free_to_file(GString* s, const char* rootdir, const char* path, const char* suffix); |
21 | 24 | void unlink_glob(const char* rootdir, const char* _glob); |
25 | ||
26 | int wifi_get_freq24(int channel); | |
27 | int wifi_get_freq5(int channel); |
18 | 18 | #include <glib/gstdio.h> |
19 | 19 | #include <gio/gio.h> |
20 | 20 | #include <arpa/inet.h> |
21 | #include <regex.h> | |
21 | 22 | |
22 | 23 | #include <yaml.h> |
23 | 24 | |
59 | 60 | * Validation for grammar and backend rules. |
60 | 61 | ************************************************/ |
61 | 62 | static gboolean |
62 | validate_tunnel_grammar(net_definition* nd, yaml_node_t* node, GError** error) | |
63 | { | |
64 | if (nd->tunnel.mode == TUNNEL_MODE_UNKNOWN) | |
63 | validate_tunnel_grammar(NetplanNetDefinition* nd, yaml_node_t* node, GError** error) | |
64 | { | |
65 | if (nd->tunnel.mode == NETPLAN_TUNNEL_MODE_UNKNOWN) | |
65 | 66 | return yaml_error(node, error, "%s: missing 'mode' property for tunnel", nd->id); |
66 | 67 | |
67 | 68 | /* Validate local/remote IPs */ |
71 | 72 | return yaml_error(node, error, "%s: missing 'remote' property for tunnel", nd->id); |
72 | 73 | |
73 | 74 | switch(nd->tunnel.mode) { |
74 | case TUNNEL_MODE_IPIP6: | |
75 | case TUNNEL_MODE_IP6IP6: | |
76 | case TUNNEL_MODE_IP6GRE: | |
77 | case TUNNEL_MODE_IP6GRETAP: | |
78 | case TUNNEL_MODE_VTI6: | |
75 | case NETPLAN_TUNNEL_MODE_IPIP6: | |
76 | case NETPLAN_TUNNEL_MODE_IP6IP6: | |
77 | case NETPLAN_TUNNEL_MODE_IP6GRE: | |
78 | case NETPLAN_TUNNEL_MODE_IP6GRETAP: | |
79 | case NETPLAN_TUNNEL_MODE_VTI6: | |
79 | 80 | if (!is_ip6_address(nd->tunnel.local_ip)) |
80 | 81 | return yaml_error(node, error, "%s: 'local' must be a valid IPv6 address for this tunnel type", nd->id); |
81 | 82 | if (!is_ip6_address(nd->tunnel.remote_ip)) |
94 | 95 | } |
95 | 96 | |
96 | 97 | static gboolean |
97 | validate_tunnel_backend_rules(net_definition* nd, yaml_node_t* node, GError** error) | |
98 | validate_tunnel_backend_rules(NetplanNetDefinition* nd, yaml_node_t* node, GError** error) | |
98 | 99 | { |
99 | 100 | /* Backend-specific validation rules for tunnels */ |
100 | 101 | switch (nd->backend) { |
101 | case BACKEND_NETWORKD: | |
102 | case NETPLAN_BACKEND_NETWORKD: | |
102 | 103 | switch (nd->tunnel.mode) { |
103 | case TUNNEL_MODE_VTI: | |
104 | case TUNNEL_MODE_VTI6: | |
104 | case NETPLAN_TUNNEL_MODE_VTI: | |
105 | case NETPLAN_TUNNEL_MODE_VTI6: | |
105 | 106 | break; |
106 | 107 | |
107 | 108 | /* TODO: Remove this exception and fix ISATAP handling with the |
108 | 109 | * networkd backend. |
109 | 110 | * systemd-networkd has grown ISATAP support in 918049a. |
110 | 111 | */ |
111 | case TUNNEL_MODE_ISATAP: | |
112 | case NETPLAN_TUNNEL_MODE_ISATAP: | |
112 | 113 | return yaml_error(node, error, |
113 | 114 | "%s: %s tunnel mode is not supported by networkd", |
114 | 115 | nd->id, |
124 | 125 | } |
125 | 126 | break; |
126 | 127 | |
127 | case BACKEND_NM: | |
128 | case NETPLAN_BACKEND_NM: | |
128 | 129 | switch (nd->tunnel.mode) { |
129 | case TUNNEL_MODE_GRE: | |
130 | case TUNNEL_MODE_IP6GRE: | |
131 | break; | |
132 | ||
133 | case TUNNEL_MODE_GRETAP: | |
134 | case TUNNEL_MODE_IP6GRETAP: | |
130 | case NETPLAN_TUNNEL_MODE_GRE: | |
131 | case NETPLAN_TUNNEL_MODE_IP6GRE: | |
132 | break; | |
133 | ||
134 | case NETPLAN_TUNNEL_MODE_GRETAP: | |
135 | case NETPLAN_TUNNEL_MODE_IP6GRETAP: | |
135 | 136 | return yaml_error(node, error, |
136 | 137 | "%s: %s tunnel mode is not supported by NetworkManager", |
137 | 138 | nd->id, |
157 | 158 | } |
158 | 159 | |
159 | 160 | gboolean |
160 | validate_netdef_grammar(net_definition* nd, yaml_node_t* node, GError** error) | |
161 | validate_netdef_grammar(NetplanNetDefinition* nd, yaml_node_t* node, GError** error) | |
161 | 162 | { |
162 | 163 | int missing_id_count = g_hash_table_size(missing_id); |
163 | 164 | gboolean valid = FALSE; |
164 | 165 | |
165 | g_assert(nd->type != ND_NONE); | |
166 | g_assert(nd->type != NETPLAN_DEF_TYPE_NONE); | |
166 | 167 | |
167 | 168 | /* Skip all validation if we're missing some definition IDs (devices). |
168 | 169 | * The ones we have yet to see may be necessary for validation to succeed, |
174 | 175 | if (nd->set_name && !nd->has_match) |
175 | 176 | return yaml_error(node, error, "%s: 'set-name:' requires 'match:' properties", nd->id); |
176 | 177 | |
177 | if (nd->type == ND_WIFI && nd->access_points == NULL) | |
178 | if (nd->type == NETPLAN_DEF_TYPE_WIFI && nd->access_points == NULL) | |
178 | 179 | return yaml_error(node, error, "%s: No access points defined", nd->id); |
179 | 180 | |
180 | if (nd->type == ND_VLAN) { | |
181 | if (nd->type == NETPLAN_DEF_TYPE_VLAN) { | |
181 | 182 | if (!nd->vlan_link) |
182 | 183 | return yaml_error(node, error, "%s: missing 'link' property", nd->id); |
183 | 184 | nd->vlan_link->has_vlans = TRUE; |
187 | 188 | return yaml_error(node, error, "%s: invalid id '%u' (allowed values are 0 to 4094)", nd->id, nd->vlan_id); |
188 | 189 | } |
189 | 190 | |
190 | if (nd->type == ND_TUNNEL) { | |
191 | if (nd->type == NETPLAN_DEF_TYPE_TUNNEL) { | |
191 | 192 | valid = validate_tunnel_grammar(nd, node, error); |
192 | 193 | if (!valid) |
193 | 194 | goto netdef_grammar_error; |
200 | 201 | } |
201 | 202 | |
202 | 203 | gboolean |
203 | validate_backend_rules(net_definition* nd, GError** error) | |
204 | validate_backend_rules(NetplanNetDefinition* nd, GError** error) | |
204 | 205 | { |
205 | 206 | gboolean valid = FALSE; |
206 | 207 | /* Set a dummy, NULL yaml_node_t for error reporting */ |
207 | 208 | yaml_node_t* node = NULL; |
208 | 209 | |
209 | g_assert(nd->type != ND_NONE); | |
210 | ||
211 | if (nd->type == ND_TUNNEL) { | |
210 | g_assert(nd->type != NETPLAN_DEF_TYPE_NONE); | |
211 | ||
212 | if (nd->type == NETPLAN_DEF_TYPE_TUNNEL) { | |
212 | 213 | valid = validate_tunnel_backend_rules(nd, node, error); |
213 | 214 | if (!valid) |
214 | 215 | goto backend_rules_error; |
23 | 23 | gboolean is_ip6_address(const char* address); |
24 | 24 | |
25 | 25 | gboolean |
26 | validate_netdef_grammar(net_definition* nd, yaml_node_t* node, GError** error); | |
26 | validate_netdef_grammar(NetplanNetDefinition* nd, yaml_node_t* node, GError** error); | |
27 | 27 | |
28 | 28 | gboolean |
29 | validate_backend_rules(net_definition* nd, GError** error); | |
29 | validate_backend_rules(NetplanNetDefinition* nd, GError** error); |
32 | 32 | |
33 | 33 | # Make sure we can import our development netplan. |
34 | 34 | os.environ.update({'PYTHONPATH': '.'}) |
35 | os.environ.update({'LD_LIBRARY_PATH': '.:{}'.format(os.environ.get('LD_LIBRARY_PATH'))}) | |
36 | ||
37 | ||
38 | def _load_yaml(text): | |
39 | return yaml.load(text, Loader=yaml.SafeLoader) | |
35 | 40 | |
36 | 41 | |
37 | 42 | class TestArgs(unittest.TestCase): |
62 | 67 | self.workdir = tempfile.TemporaryDirectory() |
63 | 68 | |
64 | 69 | def test_no_config(self): |
65 | out = subprocess.check_output(exe_cli + ['generate', '--root-dir', self.workdir.name]) | |
70 | p = subprocess.Popen(exe_cli + ['generate', '--root-dir', self.workdir.name], stdout=subprocess.PIPE, | |
71 | stderr=subprocess.PIPE) | |
72 | (out, err) = p.communicate() | |
66 | 73 | self.assertEqual(out, b'') |
67 | 74 | self.assertEqual(os.listdir(self.workdir.name), []) |
68 | 75 | |
144 | 151 | |
145 | 152 | |
146 | 153 | class TestIfupdownMigrate(unittest.TestCase): |
154 | ||
147 | 155 | def setUp(self): |
148 | 156 | self.workdir = tempfile.TemporaryDirectory() |
149 | 157 | self.ifaces_path = os.path.join(self.workdir.name, 'etc/network/interfaces') |
202 | 210 | |
203 | 211 | def test_dhcp4(self): |
204 | 212 | out = self.do_test('auto en1\niface en1 inet dhcp')[0] |
205 | self.assertEqual(yaml.load(out), {'network': { | |
213 | self.assertEqual(_load_yaml(out), {'network': { | |
206 | 214 | 'version': 2, |
207 | 215 | 'ethernets': {'en1': {'dhcp4': True}}}}, out.decode()) |
208 | 216 | |
209 | 217 | def test_dhcp6(self): |
210 | 218 | out = self.do_test('auto en1\niface en1 inet6 dhcp')[0] |
211 | self.assertEqual(yaml.load(out), {'network': { | |
219 | self.assertEqual(_load_yaml(out), {'network': { | |
212 | 220 | 'version': 2, |
213 | 221 | 'ethernets': {'en1': {'dhcp6': True}}}}, out.decode()) |
214 | 222 | |
215 | 223 | def test_dhcp4_and_6(self): |
216 | 224 | out = self.do_test('auto lo\niface lo inet loopback\n\n' |
217 | 225 | 'auto en1\niface en1 inet dhcp\niface en1 inet6 dhcp')[0] |
218 | self.assertEqual(yaml.load(out), {'network': { | |
226 | self.assertEqual(_load_yaml(out), {'network': { | |
219 | 227 | 'version': 2, |
220 | 228 | 'ethernets': {'en1': {'dhcp4': True, 'dhcp6': True}}}}, out.decode()) |
221 | 229 | |
223 | 231 | out = self.do_test('iface lo inet loopback\nauto lo\nsource-directory interfaces.d', |
224 | 232 | dropins={'interfaces.d/std': 'auto en1\niface en1 inet dhcp', |
225 | 233 | 'interfaces.d/std.bak': 'some_bogus dontreadme'})[0] |
226 | self.assertEqual(yaml.load(out), {'network': { | |
234 | self.assertEqual(_load_yaml(out), {'network': { | |
227 | 235 | 'version': 2, |
228 | 236 | 'ethernets': {'en1': {'dhcp4': True}}}}, out.decode()) |
229 | 237 | |
231 | 239 | out = self.do_test('iface lo inet loopback\nauto lo\nsource-directory /etc/network/defs/my', |
232 | 240 | dropins={'defs/my/std': 'auto en1\niface en1 inet dhcp', |
233 | 241 | 'defs/my/std.bak': 'some_bogus dontreadme'})[0] |
234 | self.assertEqual(yaml.load(out), {'network': { | |
242 | self.assertEqual(_load_yaml(out), {'network': { | |
235 | 243 | 'version': 2, |
236 | 244 | 'ethernets': {'en1': {'dhcp4': True}}}}, out.decode()) |
237 | 245 | |
239 | 247 | out = self.do_test('iface lo inet loopback\nauto lo\nsource interfaces.d/*.cfg', |
240 | 248 | dropins={'interfaces.d/std.cfg': 'auto en1\niface en1 inet dhcp', |
241 | 249 | 'interfaces.d/std.cfgold': 'some_bogus dontreadme'})[0] |
242 | self.assertEqual(yaml.load(out), {'network': { | |
250 | self.assertEqual(_load_yaml(out), {'network': { | |
243 | 251 | 'version': 2, |
244 | 252 | 'ethernets': {'en1': {'dhcp4': True}}}}, out.decode()) |
245 | 253 | |
247 | 255 | out = self.do_test('iface lo inet loopback\nauto lo\nsource /etc/network/*.cfg', |
248 | 256 | dropins={'std.cfg': 'auto en1\niface en1 inet dhcp', |
249 | 257 | 'std.cfgold': 'some_bogus dontreadme'})[0] |
250 | self.assertEqual(yaml.load(out), {'network': { | |
258 | self.assertEqual(_load_yaml(out), {'network': { | |
251 | 259 | 'version': 2, |
252 | 260 | 'ethernets': {'en1': {'dhcp4': True}}}}, out.decode()) |
253 | 261 | |
254 | 262 | def test_allow(self): |
255 | 263 | out = self.do_test('allow-hotplug en1\niface en1 inet dhcp\n' |
256 | 264 | 'allow-auto en2\niface en2 inet dhcp')[0] |
257 | self.assertEqual(yaml.load(out), {'network': { | |
265 | self.assertEqual(_load_yaml(out), {'network': { | |
258 | 266 | 'version': 2, |
259 | 267 | 'ethernets': {'en1': {'dhcp4': True}, |
260 | 268 | 'en2': {'dhcp4': True}}}}, out.decode()) |
261 | 269 | |
262 | 270 | def test_no_scripts(self): |
263 | 271 | out = self.do_test('auto en1\niface en1 inet dhcp\nno-scripts en1')[0] |
264 | self.assertEqual(yaml.load(out), {'network': { | |
272 | self.assertEqual(_load_yaml(out), {'network': { | |
265 | 273 | 'version': 2, |
266 | 274 | 'ethernets': {'en1': {'dhcp4': True}}}}, out.decode()) |
267 | 275 | |
275 | 283 | def test_write_file_haveconfig(self): |
276 | 284 | (out, err) = self.do_test('auto en1\niface en1 inet dhcp', dry_run=False) |
277 | 285 | with open(self.converted_path) as f: |
278 | config = yaml.load(f) | |
286 | config = _load_yaml(f) | |
279 | 287 | self.assertEqual(config, {'network': { |
280 | 288 | 'version': 2, |
281 | 289 | 'ethernets': {'en1': {'dhcp4': True}}}}) |
301 | 309 | |
302 | 310 | def test_static_ipv4_prefix(self): |
303 | 311 | out = self.do_test('auto en1\niface en1 inet static\naddress 1.2.3.4/8', dry_run=True)[0] |
304 | self.assertEqual(yaml.load(out), {'network': { | |
312 | self.assertEqual(_load_yaml(out), {'network': { | |
305 | 313 | 'version': 2, |
306 | 314 | 'ethernets': {'en1': {'addresses': ["1.2.3.4/8"]}}}}, out.decode()) |
307 | 315 | |
308 | 316 | def test_static_ipv4_netmask(self): |
309 | 317 | out = self.do_test('auto en1\niface en1 inet static\naddress 1.2.3.4\nnetmask 255.0.0.0', dry_run=True)[0] |
310 | self.assertEqual(yaml.load(out), {'network': { | |
318 | self.assertEqual(_load_yaml(out), {'network': { | |
311 | 319 | 'version': 2, |
312 | 320 | 'ethernets': {'en1': {'addresses': ["1.2.3.4/8"]}}}}, out.decode()) |
313 | 321 | |
348 | 356 | |
349 | 357 | def test_static_ipv6_prefix(self): |
350 | 358 | out = self.do_test('auto en1\niface en1 inet6 static\naddress fc00:0123:4567:89ab:cdef::1234/64', dry_run=True)[0] |
351 | self.assertEqual(yaml.load(out), {'network': { | |
359 | self.assertEqual(_load_yaml(out), {'network': { | |
352 | 360 | 'version': 2, |
353 | 361 | 'ethernets': {'en1': {'addresses': ["fc00:123:4567:89ab:cdef::1234/64"]}}}}, out.decode()) |
354 | 362 | |
355 | 363 | def test_static_ipv6_netmask(self): |
356 | 364 | out = self.do_test('auto en1\niface en1 inet6 static\n' |
357 | 365 | 'address fc00:0123:4567:89ab:cdef::1234\nnetmask 64', dry_run=True)[0] |
358 | self.assertEqual(yaml.load(out), {'network': { | |
366 | self.assertEqual(_load_yaml(out), {'network': { | |
359 | 367 | 'version': 2, |
360 | 368 | 'ethernets': {'en1': {'addresses': ["fc00:123:4567:89ab:cdef::1234/64"]}}}}, out.decode()) |
361 | 369 | |
403 | 411 | def test_static_ipv6_accept_ra_0(self): |
404 | 412 | out = self.do_test('auto en1\niface en1 inet6 static\n' |
405 | 413 | 'address fc00:0123:4567:89ab:cdef::1234/64\naccept_ra 0', dry_run=True)[0] |
406 | self.assertEqual(yaml.load(out), {'network': { | |
414 | self.assertEqual(_load_yaml(out), {'network': { | |
407 | 415 | 'version': 2, |
408 | 416 | 'ethernets': {'en1': {'addresses': ["fc00:123:4567:89ab:cdef::1234/64"], |
409 | 417 | 'accept_ra': False}}}}, out.decode()) |
411 | 419 | def test_static_ipv6_accept_ra_1(self): |
412 | 420 | out = self.do_test('auto en1\niface en1 inet6 static\n' |
413 | 421 | 'address fc00:0123:4567:89ab:cdef::1234/64\naccept_ra 1', dry_run=True)[0] |
414 | self.assertEqual(yaml.load(out), {'network': { | |
422 | self.assertEqual(_load_yaml(out), {'network': { | |
415 | 423 | 'version': 2, |
416 | 424 | 'ethernets': {'en1': {'addresses': ["fc00:123:4567:89ab:cdef::1234/64"], |
417 | 425 | 'accept_ra': True}}}}, out.decode()) |
437 | 445 | iface en1 inet6 static |
438 | 446 | address fc00:0123:4567:89ab:cdef::1234/64 |
439 | 447 | gateway fc00:0123:4567:89ab::1""", dry_run=True)[0] |
440 | self.assertEqual(yaml.load(out), {'network': { | |
448 | self.assertEqual(_load_yaml(out), {'network': { | |
441 | 449 | 'version': 2, |
442 | 450 | 'ethernets': {'en1': |
443 | 451 | {'addresses': ["1.2.3.4/8", "fc00:123:4567:89ab:cdef::1234/64"], |
454 | 462 | iface en1 inet6 static |
455 | 463 | address fc00:0123:4567:89ab:cdef::1234/64 |
456 | 464 | dns-nameservers fc00:0123:4567:89ab:1::1 fc00:0123:4567:89ab:2::1""", dry_run=True)[0] |
457 | self.assertEqual(yaml.load(out), {'network': { | |
465 | self.assertEqual(_load_yaml(out), {'network': { | |
458 | 466 | 'version': 2, |
459 | 467 | 'ethernets': {'en1': |
460 | 468 | {'addresses': ["1.2.3.4/8", "fc00:123:4567:89ab:cdef::1234/64"], |
466 | 474 | |
467 | 475 | def test_static_dns2(self): |
468 | 476 | out = self.do_test('auto en1\niface en1 inet static\naddress 1.2.3.4/8\ndns-search foo foo.bar', dry_run=True)[0] |
469 | self.assertEqual(yaml.load(out), {'network': { | |
477 | self.assertEqual(_load_yaml(out), {'network': { | |
470 | 478 | 'version': 2, |
471 | 479 | 'ethernets': {'en1': {'addresses': ["1.2.3.4/8"], |
472 | 480 | 'nameservers': { |
475 | 483 | |
476 | 484 | def test_static_mtu(self): |
477 | 485 | out = self.do_test('auto en1\niface en1 inet static\naddress 1.2.3.4/8\nmtu 1280', dry_run=True)[0] |
478 | self.assertEqual(yaml.load(out), {'network': { | |
486 | self.assertEqual(_load_yaml(out), {'network': { | |
479 | 487 | 'version': 2, |
480 | 488 | 'ethernets': {'en1': {'addresses': ["1.2.3.4/8"], |
481 | 489 | 'mtu': 1280}}}}, out.decode()) |
493 | 501 | |
494 | 502 | def test_static_hwaddress(self): |
495 | 503 | out = self.do_test('auto en1\niface en1 inet static\naddress 1.2.3.4/8\nhwaddress 52:54:00:6b:3c:59', dry_run=True)[0] |
496 | self.assertEqual(yaml.load(out), {'network': { | |
504 | self.assertEqual(_load_yaml(out), {'network': { | |
497 | 505 | 'version': 2, |
498 | 506 | 'ethernets': {'en1': {'addresses': ["1.2.3.4/8"], |
499 | 507 | 'macaddress': '52:54:00:6b:3c:59'}}}}, out.decode()) |
27 | 27 | |
28 | 28 | exe_generate = os.path.join(os.path.dirname(os.path.dirname( |
29 | 29 | os.path.dirname(os.path.abspath(__file__)))), 'generate') |
30 | ||
31 | # make sure we point to libnetplan properly. | |
32 | os.environ.update({'LD_LIBRARY_PATH': '.:{}'.format(os.environ.get('LD_LIBRARY_PATH'))}) | |
30 | 33 | |
31 | 34 | # make sure we fail on criticals |
32 | 35 | os.environ['G_DEBUG'] = 'fatal-criticals' |
30 | 30 | wl0: |
31 | 31 | access-points: |
32 | 32 | "Joe's Home": |
33 | password: "s3kr1t" | |
33 | password: "s0s3kr1t" | |
34 | 34 | "Luke's Home": |
35 | 35 | auth: |
36 | 36 | key-management: psk |
37 | 37 | password: "4lsos3kr1t" |
38 | "BobsHome": | |
39 | password: "e03ce667c87bc81ca968d9120ca37f89eb09aec3c55b80386e5d772efd6b926e" | |
40 | "BillsHome": | |
41 | auth: | |
42 | key-management: psk | |
43 | password: "db3b0acf5653aeaddd5fe034fb9f07175b2864f847b005aaa2f09182d9411b04" | |
38 | 44 | workplace: |
39 | 45 | auth: |
40 | 46 | key-management: eap |
97 | 103 | ssid="Luke's Home" |
98 | 104 | key_mgmt=WPA-PSK |
99 | 105 | psk="4lsos3kr1t" |
106 | } | |
107 | ''', new_config) | |
108 | self.assertIn(''' | |
109 | network={ | |
110 | ssid="BobsHome" | |
111 | key_mgmt=WPA-PSK | |
112 | psk=e03ce667c87bc81ca968d9120ca37f89eb09aec3c55b80386e5d772efd6b926e | |
113 | } | |
114 | ''', new_config) | |
115 | self.assertIn(''' | |
116 | network={ | |
117 | ssid="BillsHome" | |
118 | key_mgmt=WPA-PSK | |
119 | psk=db3b0acf5653aeaddd5fe034fb9f07175b2864f847b005aaa2f09182d9411b04 | |
100 | 120 | } |
101 | 121 | ''', new_config) |
102 | 122 | self.assertIn(''' |
152 | 172 | network={ |
153 | 173 | ssid="Joe's Home" |
154 | 174 | key_mgmt=WPA-PSK |
155 | psk="s3kr1t" | |
175 | psk="s0s3kr1t" | |
156 | 176 | } |
157 | 177 | ''', new_config) |
158 | 178 | self.assertEqual(stat.S_IMODE(os.fstat(f.fileno()).st_mode), 0o600) |
179 | self.assertTrue(os.path.isfile(os.path.join( | |
180 | self.workdir.name, 'run/systemd/system/netplan-wpa-wl0.service'))) | |
159 | 181 | self.assertTrue(os.path.islink(os.path.join( |
160 | self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa@wl0.service'))) | |
182 | self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa-wl0.service'))) | |
161 | 183 | |
162 | 184 | def test_auth_wired(self): |
163 | 185 | self.generate('''network: |
173 | 195 | client-certificate: /etc/ssl/cust-crt.pem |
174 | 196 | client-key: /etc/ssl/cust-key.pem |
175 | 197 | client-key-password: "d3cryptPr1v4t3K3y" |
198 | phase2-auth: MSCHAPV2 | |
176 | 199 | dhcp4: yes |
177 | 200 | ''') |
178 | 201 | |
195 | 218 | client_cert="/etc/ssl/cust-crt.pem" |
196 | 219 | private_key="/etc/ssl/cust-key.pem" |
197 | 220 | private_key_passwd="d3cryptPr1v4t3K3y" |
221 | phase2="auth=MSCHAPV2" | |
198 | 222 | } |
199 | 223 | ''') |
200 | 224 | self.assertEqual(stat.S_IMODE(os.fstat(f.fileno()).st_mode), 0o600) |
225 | self.assertTrue(os.path.isfile(os.path.join( | |
226 | self.workdir.name, 'run/systemd/system/netplan-wpa-eth0.service'))) | |
201 | 227 | self.assertTrue(os.path.islink(os.path.join( |
202 | self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa@eth0.service'))) | |
228 | self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa-eth0.service'))) | |
203 | 229 | |
204 | 230 | |
205 | 231 | class TestNetworkManager(TestBase): |
212 | 238 | wl0: |
213 | 239 | access-points: |
214 | 240 | "Joe's Home": |
215 | password: "s3kr1t" | |
241 | password: "s0s3kr1t" | |
216 | 242 | "Luke's Home": |
217 | 243 | auth: |
218 | 244 | key-management: psk |
248 | 274 | client-certificate: /etc/ssl/cust-crt.pem |
249 | 275 | client-key: /etc/ssl/cust-key.pem |
250 | 276 | client-key-password: "d3cryptPr1v4t3K3y" |
277 | phase2-auth: MSCHAPV2 | |
251 | 278 | opennet: |
252 | 279 | auth: |
253 | 280 | key-management: none |
278 | 305 | |
279 | 306 | [wifi-security] |
280 | 307 | key-mgmt=wpa-psk |
281 | psk=s3kr1t | |
308 | psk=s0s3kr1t | |
282 | 309 | ''', |
283 | 310 | 'wl0-Luke%27s%20Home': '''[connection] |
284 | 311 | id=netplan-wl0-Luke's Home |
412 | 439 | client-cert=/etc/ssl/cust-crt.pem |
413 | 440 | private-key=/etc/ssl/cust-key.pem |
414 | 441 | private-key-password=d3cryptPr1v4t3K3y |
442 | phase2-auth=MSCHAPV2 | |
415 | 443 | ''', |
416 | 444 | 'wl0-opennet': '''[connection] |
417 | 445 | id=netplan-wl0-opennet |
515 | 543 | auth: |
516 | 544 | method: bogus''', expect_fail=True) |
517 | 545 | self.assertIn("unknown EAP method 'bogus'", err) |
546 | ||
547 | def test_auth_networkd_wifi_psk_too_big(self): | |
548 | err = self.generate('''network: | |
549 | version: 2 | |
550 | wifis: | |
551 | wl0: | |
552 | access-points: | |
553 | "Joe's Home": | |
554 | password: "LoremipsumdolorsitametconsecteturadipiscingelitCrastemporvelitnunc" | |
555 | dhcp4: yes''', expect_fail=True) | |
556 | self.assertIn("ASCII passphrase must be between 8 and 63 characters (inclusive)", err) | |
557 | ||
558 | def test_auth_networkd_wifi_psk_too_small(self): | |
559 | err = self.generate('''network: | |
560 | version: 2 | |
561 | wifis: | |
562 | wl0: | |
563 | access-points: | |
564 | "Joe's Home": | |
565 | password: "p4ss" | |
566 | dhcp4: yes''', expect_fail=True) | |
567 | self.assertIn("ASCII passphrase must be between 8 and 63 characters (inclusive)", err) | |
568 | ||
569 | def test_auth_networkd_wifi_psk_64_non_hexdigit(self): | |
570 | err = self.generate('''network: | |
571 | version: 2 | |
572 | wifis: | |
573 | wl0: | |
574 | access-points: | |
575 | "Joe's Home": | |
576 | password: "LoremipsumdolorsitametconsecteturadipiscingelitCrastemporvelitnu" | |
577 | dhcp4: yes''', expect_fail=True) | |
578 | self.assertIn("PSK length of 64 is only supported for hex-digit representation", err) |
103 | 103 | 'bond0.network': '''[Match] |
104 | 104 | Name=bond0 |
105 | 105 | |
106 | [Link] | |
107 | MTUBytes=9000 | |
108 | ||
106 | 109 | [Network] |
107 | 110 | LinkLocalAddressing=ipv6 |
108 | 111 | ConfigureWithoutCarrier=yes |
109 | 112 | VLAN=bond0.108 |
110 | 113 | ''', |
111 | 114 | 'eth1.link': '[Match]\nOriginalName=eth1\n\n[Link]\nWakeOnLan=off\nMTUBytes=9000\n', |
112 | 'eth1.network': '[Match]\nName=eth1\n\n[Network]\nLinkLocalAddressing=no\nIPv6MTUBytes=2000\nBond=bond0\n' | |
115 | 'eth1.network': '''[Match] | |
116 | Name=eth1 | |
117 | ||
118 | [Link] | |
119 | MTUBytes=9000 | |
120 | ||
121 | [Network] | |
122 | LinkLocalAddressing=no | |
123 | IPv6MTUBytes=2000 | |
124 | Bond=bond0 | |
125 | ''' | |
113 | 126 | }) |
114 | 127 | self.assert_networkd_udev(None) |
115 | 128 | |
282 | 295 | version: 2 |
283 | 296 | ethernets: |
284 | 297 | engreen: |
285 | dhcp4: yes | |
286 | 298 | critical: yes |
287 | 299 | ''') |
288 | 300 | |
290 | 302 | Name=engreen |
291 | 303 | |
292 | 304 | [Network] |
293 | DHCP=ipv4 | |
294 | 305 | LinkLocalAddressing=ipv6 |
295 | 306 | |
296 | 307 | [DHCP] |
297 | 308 | CriticalConnection=true |
298 | RouteMetric=100 | |
299 | UseMTU=true | |
300 | 309 | '''}) |
301 | 310 | |
302 | 311 | def test_dhcp_identifier_mac(self): |
714 | 723 | method=auto |
715 | 724 | '''}) |
716 | 725 | |
726 | def test_ip6_addr_gen_mode(self): | |
727 | self.generate('''network: | |
728 | version: 2 | |
729 | renderer: NetworkManager | |
730 | ethernets: | |
731 | engreen: | |
732 | dhcp6: yes | |
733 | ipv6-address-generation: stable-privacy | |
734 | enblue: | |
735 | dhcp6: yes | |
736 | ipv6-address-generation: eui64''') | |
737 | self.assert_nm({'engreen': '''[connection] | |
738 | id=netplan-engreen | |
739 | type=ethernet | |
740 | interface-name=engreen | |
741 | ||
742 | [ethernet] | |
743 | wake-on-lan=0 | |
744 | ||
745 | [ipv4] | |
746 | method=link-local | |
747 | ||
748 | [ipv6] | |
749 | method=auto | |
750 | addr-gen-mode=1 | |
751 | ''', | |
752 | 'enblue': '''[connection] | |
753 | id=netplan-enblue | |
754 | type=ethernet | |
755 | interface-name=enblue | |
756 | ||
757 | [ethernet] | |
758 | wake-on-lan=0 | |
759 | ||
760 | [ipv4] | |
761 | method=link-local | |
762 | ||
763 | [ipv6] | |
764 | method=auto | |
765 | addr-gen-mode=0 | |
766 | '''}) | |
767 | ||
717 | 768 | def test_eth_manual_addresses(self): |
718 | 769 | self.generate('''network: |
719 | 770 | version: 2 |
209 | 209 | mode: bogus''', expect_fail=True) |
210 | 210 | self.assertIn("unknown wifi mode 'bogus'", err) |
211 | 211 | |
212 | def test_wifi_ap_unknown_band(self): | |
213 | err = self.generate('''network: | |
214 | version: 2 | |
215 | wifis: | |
216 | wl0: | |
217 | access-points: | |
218 | workplace: | |
219 | band: bogus''', expect_fail=True) | |
220 | self.assertIn("unknown wifi band 'bogus'", err) | |
221 | ||
222 | def test_wifi_ap_invalid_freq24(self): | |
223 | err = self.generate('''network: | |
224 | version: 2 | |
225 | renderer: NetworkManager | |
226 | wifis: | |
227 | wl0: | |
228 | access-points: | |
229 | workplace: | |
230 | band: 2.4GHz | |
231 | channel: 15''', expect_fail=True) | |
232 | self.assertIn("ERROR: invalid 2.4GHz WiFi channel: 15", err) | |
233 | ||
234 | def test_wifi_ap_invalid_freq5(self): | |
235 | err = self.generate('''network: | |
236 | version: 2 | |
237 | wifis: | |
238 | wl0: | |
239 | access-points: | |
240 | workplace: | |
241 | band: 5GHz | |
242 | channel: 14''', expect_fail=True) | |
243 | self.assertIn("ERROR: invalid 5GHz WiFi channel: 14", err) | |
244 | ||
212 | 245 | def test_invalid_ipv4_address(self): |
213 | 246 | err = self.generate('''network: |
214 | 247 | version: 2 |
286 | 319 | addresses: |
287 | 320 | - 2001::1/''', expect_fail=True) |
288 | 321 | self.assertIn("invalid prefix length in address '2001::1/'", err) |
322 | ||
323 | def test_invalid_addr_gen_mode(self): | |
324 | err = self.generate('''network: | |
325 | version: 2 | |
326 | renderer: NetworkManager | |
327 | ethernets: | |
328 | engreen: | |
329 | ipv6-address-generation: 0''', expect_fail=True) | |
330 | self.assertIn("unknown ipv6-address-generation '0'", err) | |
331 | ||
332 | def test_addr_gen_mode_not_supported(self): | |
333 | err = self.generate('''network: | |
334 | version: 2 | |
335 | ethernets: | |
336 | engreen: | |
337 | ipv6-address-generation: eui64''', expect_fail=True) | |
338 | self.assertIn("ERROR: engreen: ipv6-address-generation is not supported by networkd", err) | |
289 | 339 | |
290 | 340 | def test_invalid_gateway4(self): |
291 | 341 | for a in ['300.400.1.1', '1.2.3', '192.168.14.1/24']: |
362 | 412 | ena: {id: 1, link: en1}''', expect_fail=True) |
363 | 413 | self.assertIn("ena: interface 'en1' is not defined", err) |
364 | 414 | |
415 | def test_vlan_unknown_renderer(self): | |
416 | err = self.generate('''network: | |
417 | version: 2 | |
418 | ethernets: {en1: {}} | |
419 | vlans: | |
420 | ena: {id: 1, link: en1, renderer: foo}''', expect_fail=True) | |
421 | self.assertIn("unknown renderer 'foo'", err) | |
422 | ||
365 | 423 | def test_device_bad_route_to(self): |
366 | 424 | self.generate('''network: |
367 | 425 | version: 2 |
45 | 45 | # should not allow NM to manage everything |
46 | 46 | self.assertFalse(os.path.exists(self.nm_enable_all_conf)) |
47 | 47 | |
48 | def test_eth_lldp(self): | |
49 | self.generate('''network: | |
50 | version: 2 | |
51 | ethernets: | |
52 | eth0: | |
53 | dhcp4: n | |
54 | emit-lldp: true''') | |
55 | ||
56 | self.assert_networkd({'eth0.network': '''[Match] | |
57 | Name=eth0 | |
58 | ||
59 | [Network] | |
60 | EmitLLDP=true | |
61 | LinkLocalAddressing=ipv6 | |
62 | '''}) | |
63 | ||
48 | 64 | def test_eth_mtu(self): |
49 | 65 | self.generate('''network: |
50 | 66 | version: 2 |
56 | 72 | self.assert_networkd({'eth1.link': '[Match]\nOriginalName=eth1\n\n[Link]\nWakeOnLan=off\nMTUBytes=1280\n', |
57 | 73 | 'eth1.network': '''[Match] |
58 | 74 | Name=eth1 |
75 | ||
76 | [Link] | |
77 | MTUBytes=1280 | |
78 | ||
79 | [Network] | |
80 | LinkLocalAddressing=ipv6 | |
81 | '''}) | |
82 | self.assert_networkd_udev(None) | |
83 | ||
84 | def test_eth_sriov_vlan_filterv_link(self): | |
85 | self.generate('''network: | |
86 | version: 2 | |
87 | ethernets: | |
88 | enp1: | |
89 | dhcp4: n | |
90 | enp1s16f1: | |
91 | dhcp4: n | |
92 | link: enp1''') | |
93 | ||
94 | self.assert_networkd({'enp1.network': '''[Match] | |
95 | Name=enp1 | |
96 | ||
97 | [Network] | |
98 | LinkLocalAddressing=ipv6 | |
99 | ''', | |
100 | 'enp1s16f1.network': '''[Match] | |
101 | Name=enp1s16f1 | |
102 | ||
103 | [Network] | |
104 | LinkLocalAddressing=ipv6 | |
105 | '''}) | |
106 | self.assert_networkd_udev(None) | |
107 | ||
108 | def test_eth_sriov_virtual_functions(self): | |
109 | self.generate('''network: | |
110 | version: 2 | |
111 | ethernets: | |
112 | enp1: | |
113 | virtual-function-count: 8''') | |
114 | ||
115 | self.assert_networkd({'enp1.network': '''[Match] | |
116 | Name=enp1 | |
59 | 117 | |
60 | 118 | [Network] |
61 | 119 | LinkLocalAddressing=ipv6 |
306 | 364 | method=ignore |
307 | 365 | '''}) |
308 | 366 | |
367 | def test_eth_sriov_link(self): | |
368 | self.generate('''network: | |
369 | version: 2 | |
370 | renderer: NetworkManager | |
371 | ethernets: | |
372 | enp1: | |
373 | dhcp4: n | |
374 | enp1s16f1: | |
375 | dhcp4: n | |
376 | link: enp1''') | |
377 | ||
378 | self.assert_networkd({}) | |
379 | self.assert_nm({'enp1': '''[connection] | |
380 | id=netplan-enp1 | |
381 | type=ethernet | |
382 | interface-name=enp1 | |
383 | ||
384 | [ethernet] | |
385 | wake-on-lan=0 | |
386 | ||
387 | [ipv4] | |
388 | method=link-local | |
389 | ||
390 | [ipv6] | |
391 | method=ignore | |
392 | ''', | |
393 | 'enp1s16f1': '''[connection] | |
394 | id=netplan-enp1s16f1 | |
395 | type=ethernet | |
396 | interface-name=enp1s16f1 | |
397 | ||
398 | [ethernet] | |
399 | wake-on-lan=0 | |
400 | ||
401 | [ipv4] | |
402 | method=link-local | |
403 | ||
404 | [ipv6] | |
405 | method=ignore | |
406 | '''}) | |
407 | ||
408 | def test_eth_sriov_virtual_functions(self): | |
409 | self.generate('''network: | |
410 | version: 2 | |
411 | renderer: NetworkManager | |
412 | ethernets: | |
413 | enp1: | |
414 | dhcp4: n | |
415 | virtual-function-count: 8''') | |
416 | ||
417 | self.assert_networkd({}) | |
418 | self.assert_nm({'enp1': '''[connection] | |
419 | id=netplan-enp1 | |
420 | type=ethernet | |
421 | interface-name=enp1 | |
422 | ||
423 | [ethernet] | |
424 | wake-on-lan=0 | |
425 | ||
426 | [ipv4] | |
427 | method=link-local | |
428 | ||
429 | [ipv6] | |
430 | method=ignore | |
431 | '''}) | |
432 | ||
309 | 433 | def test_eth_set_mac(self): |
310 | 434 | self.generate('''network: |
311 | 435 | version: 2 |
0 | # | |
1 | # Tests for gsm/cdma modem devices config generated via netplan | |
2 | # | |
3 | # Copyright (C) 2020 Canonical, Ltd. | |
4 | # Author: Lukas Märdian <lukas.maerdian@canonical.com> | |
5 | # | |
6 | # This program is free software; you can redistribute it and/or modify | |
7 | # it under the terms of the GNU General Public License as published by | |
8 | # the Free Software Foundation; version 3. | |
9 | # | |
10 | # This program is distributed in the hope that it will be useful, | |
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | # GNU General Public License for more details. | |
14 | # | |
15 | # You should have received a copy of the GNU General Public License | |
16 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 | ||
18 | from .base import TestBase | |
19 | ||
20 | ||
21 | class TestNetworkd(TestBase): | |
22 | '''networkd output''' | |
23 | ||
24 | def test_not_supported(self): | |
25 | # does not produce any output, but fails with: | |
26 | # "networkd backend does not support GSM modem configuration" | |
27 | err = self.generate('''network: | |
28 | version: 2 | |
29 | modems: | |
30 | mobilephone: | |
31 | auto-config: true''', expect_fail=True) | |
32 | self.assertIn("ERROR: mobilephone: networkd backend does not support GSM/CDMA modem configuration", err) | |
33 | ||
34 | self.assert_networkd({}) | |
35 | self.assert_nm({}) | |
36 | ||
37 | ||
38 | class TestNetworkManager(TestBase): | |
39 | '''networkmanager output''' | |
40 | ||
41 | def test_cdma_config(self): | |
42 | self.generate('''network: | |
43 | version: 2 | |
44 | renderer: NetworkManager | |
45 | modems: | |
46 | mobilephone: | |
47 | mtu: 0 | |
48 | number: "#666" | |
49 | username: test-user | |
50 | password: s0s3kr1t''') | |
51 | self.assert_nm({'mobilephone': '''[connection] | |
52 | id=netplan-mobilephone | |
53 | type=cdma | |
54 | interface-name=mobilephone | |
55 | ||
56 | [cdma] | |
57 | password=s0s3kr1t | |
58 | username=test-user | |
59 | number=#666 | |
60 | ||
61 | [ethernet] | |
62 | wake-on-lan=0 | |
63 | ||
64 | [ipv4] | |
65 | method=link-local | |
66 | ||
67 | [ipv6] | |
68 | method=ignore | |
69 | '''}) | |
70 | self.assert_networkd({}) | |
71 | self.assert_nm_udev(None) | |
72 | ||
73 | def test_gsm_auto_config(self): | |
74 | self.generate('''network: | |
75 | version: 2 | |
76 | renderer: NetworkManager | |
77 | modems: | |
78 | mobilephone: | |
79 | auto-config: true''') | |
80 | self.assert_nm({'mobilephone': '''[connection] | |
81 | id=netplan-mobilephone | |
82 | type=gsm | |
83 | interface-name=mobilephone | |
84 | ||
85 | [gsm] | |
86 | auto-config=true | |
87 | ||
88 | [ethernet] | |
89 | wake-on-lan=0 | |
90 | ||
91 | [ipv4] | |
92 | method=link-local | |
93 | ||
94 | [ipv6] | |
95 | method=ignore | |
96 | '''}) | |
97 | self.assert_networkd({}) | |
98 | self.assert_nm_udev(None) | |
99 | ||
100 | def test_gsm_auto_config_implicit(self): | |
101 | self.generate('''network: | |
102 | version: 2 | |
103 | renderer: NetworkManager | |
104 | modems: | |
105 | mobilephone: | |
106 | number: "*99#" | |
107 | mtu: 1600 | |
108 | pin: "1234"''') | |
109 | self.assert_nm({'mobilephone': '''[connection] | |
110 | id=netplan-mobilephone | |
111 | type=gsm | |
112 | interface-name=mobilephone | |
113 | ||
114 | [gsm] | |
115 | auto-config=true | |
116 | mtu=1600 | |
117 | number=*99# | |
118 | pin=1234 | |
119 | ||
120 | [ethernet] | |
121 | wake-on-lan=0 | |
122 | ||
123 | [ipv4] | |
124 | method=link-local | |
125 | ||
126 | [ipv6] | |
127 | method=ignore | |
128 | '''}) | |
129 | self.assert_networkd({}) | |
130 | self.assert_nm_udev(None) | |
131 | ||
132 | def test_gsm_apn(self): | |
133 | self.generate('''network: | |
134 | version: 2 | |
135 | renderer: NetworkManager | |
136 | modems: | |
137 | mobilephone: | |
138 | apn: internet''') | |
139 | self.assert_nm({'mobilephone': '''[connection] | |
140 | id=netplan-mobilephone | |
141 | type=gsm | |
142 | interface-name=mobilephone | |
143 | ||
144 | [gsm] | |
145 | apn=internet | |
146 | ||
147 | [ethernet] | |
148 | wake-on-lan=0 | |
149 | ||
150 | [ipv4] | |
151 | method=link-local | |
152 | ||
153 | [ipv6] | |
154 | method=ignore | |
155 | '''}) | |
156 | self.assert_networkd({}) | |
157 | self.assert_nm_udev(None) | |
158 | ||
159 | def test_gsm_apn_username_password(self): | |
160 | self.generate('''network: | |
161 | version: 2 | |
162 | renderer: NetworkManager | |
163 | modems: | |
164 | mobilephone: | |
165 | apn: internet | |
166 | username: some-user | |
167 | password: some-pass''') | |
168 | self.assert_nm({'mobilephone': '''[connection] | |
169 | id=netplan-mobilephone | |
170 | type=gsm | |
171 | interface-name=mobilephone | |
172 | ||
173 | [gsm] | |
174 | apn=internet | |
175 | password=some-pass | |
176 | username=some-user | |
177 | ||
178 | [ethernet] | |
179 | wake-on-lan=0 | |
180 | ||
181 | [ipv4] | |
182 | method=link-local | |
183 | ||
184 | [ipv6] | |
185 | method=ignore | |
186 | '''}) | |
187 | self.assert_networkd({}) | |
188 | self.assert_nm_udev(None) | |
189 | ||
190 | def test_gsm_device_id(self): | |
191 | self.generate('''network: | |
192 | version: 2 | |
193 | renderer: NetworkManager | |
194 | modems: | |
195 | mobilephone: | |
196 | device-id: test''') | |
197 | self.assert_nm({'mobilephone': '''[connection] | |
198 | id=netplan-mobilephone | |
199 | type=gsm | |
200 | interface-name=mobilephone | |
201 | ||
202 | [gsm] | |
203 | auto-config=true | |
204 | device-id=test | |
205 | ||
206 | [ethernet] | |
207 | wake-on-lan=0 | |
208 | ||
209 | [ipv4] | |
210 | method=link-local | |
211 | ||
212 | [ipv6] | |
213 | method=ignore | |
214 | '''}) | |
215 | self.assert_networkd({}) | |
216 | self.assert_nm_udev(None) | |
217 | ||
218 | def test_gsm_network_id(self): | |
219 | self.generate('''network: | |
220 | version: 2 | |
221 | renderer: NetworkManager | |
222 | modems: | |
223 | mobilephone: | |
224 | network-id: test''') | |
225 | self.assert_nm({'mobilephone': '''[connection] | |
226 | id=netplan-mobilephone | |
227 | type=gsm | |
228 | interface-name=mobilephone | |
229 | ||
230 | [gsm] | |
231 | auto-config=true | |
232 | network-id=test | |
233 | ||
234 | [ethernet] | |
235 | wake-on-lan=0 | |
236 | ||
237 | [ipv4] | |
238 | method=link-local | |
239 | ||
240 | [ipv6] | |
241 | method=ignore | |
242 | '''}) | |
243 | self.assert_networkd({}) | |
244 | self.assert_nm_udev(None) | |
245 | ||
246 | def test_gsm_pin(self): | |
247 | self.generate('''network: | |
248 | version: 2 | |
249 | renderer: NetworkManager | |
250 | modems: | |
251 | mobilephone: | |
252 | pin: 1234''') | |
253 | self.assert_nm({'mobilephone': '''[connection] | |
254 | id=netplan-mobilephone | |
255 | type=gsm | |
256 | interface-name=mobilephone | |
257 | ||
258 | [gsm] | |
259 | auto-config=true | |
260 | pin=1234 | |
261 | ||
262 | [ethernet] | |
263 | wake-on-lan=0 | |
264 | ||
265 | [ipv4] | |
266 | method=link-local | |
267 | ||
268 | [ipv6] | |
269 | method=ignore | |
270 | '''}) | |
271 | self.assert_networkd({}) | |
272 | self.assert_nm_udev(None) | |
273 | ||
274 | def test_gsm_sim_id(self): | |
275 | self.generate('''network: | |
276 | version: 2 | |
277 | renderer: NetworkManager | |
278 | modems: | |
279 | mobilephone: | |
280 | sim-id: test''') | |
281 | self.assert_nm({'mobilephone': '''[connection] | |
282 | id=netplan-mobilephone | |
283 | type=gsm | |
284 | interface-name=mobilephone | |
285 | ||
286 | [gsm] | |
287 | auto-config=true | |
288 | sim-id=test | |
289 | ||
290 | [ethernet] | |
291 | wake-on-lan=0 | |
292 | ||
293 | [ipv4] | |
294 | method=link-local | |
295 | ||
296 | [ipv6] | |
297 | method=ignore | |
298 | '''}) | |
299 | self.assert_networkd({}) | |
300 | self.assert_nm_udev(None) | |
301 | ||
302 | def test_gsm_sim_operator_id(self): | |
303 | self.generate('''network: | |
304 | version: 2 | |
305 | renderer: NetworkManager | |
306 | modems: | |
307 | mobilephone: | |
308 | sim-operator-id: test''') | |
309 | self.assert_nm({'mobilephone': '''[connection] | |
310 | id=netplan-mobilephone | |
311 | type=gsm | |
312 | interface-name=mobilephone | |
313 | ||
314 | [gsm] | |
315 | auto-config=true | |
316 | sim-operator-id=test | |
317 | ||
318 | [ethernet] | |
319 | wake-on-lan=0 | |
320 | ||
321 | [ipv4] | |
322 | method=link-local | |
323 | ||
324 | [ipv6] | |
325 | method=ignore | |
326 | '''}) | |
327 | self.assert_networkd({}) | |
328 | self.assert_nm_udev(None) | |
329 | ||
330 | def test_gsm_example(self): | |
331 | self.generate('''network: | |
332 | version: 2 | |
333 | renderer: NetworkManager | |
334 | modems: | |
335 | cdc-wdm1: | |
336 | mtu: 1600 | |
337 | apn: ISP.CINGULAR | |
338 | username: ISP@CINGULARGPRS.COM | |
339 | password: CINGULAR1 | |
340 | number: "*99#" | |
341 | network-id: 24005 | |
342 | device-id: da812de91eec16620b06cd0ca5cbc7ea25245222 | |
343 | pin: 2345 | |
344 | sim-id: 89148000000060671234 | |
345 | sim-operator-id: 310260''') | |
346 | self.assert_nm({'cdc-wdm1': '''[connection] | |
347 | id=netplan-cdc-wdm1 | |
348 | type=gsm | |
349 | interface-name=cdc-wdm1 | |
350 | ||
351 | [gsm] | |
352 | apn=ISP.CINGULAR | |
353 | password=CINGULAR1 | |
354 | username=ISP@CINGULARGPRS.COM | |
355 | device-id=da812de91eec16620b06cd0ca5cbc7ea25245222 | |
356 | mtu=1600 | |
357 | network-id=24005 | |
358 | number=*99# | |
359 | pin=2345 | |
360 | sim-id=89148000000060671234 | |
361 | sim-operator-id=310260 | |
362 | ||
363 | [ethernet] | |
364 | wake-on-lan=0 | |
365 | ||
366 | [ipv4] | |
367 | method=link-local | |
368 | ||
369 | [ipv6] | |
370 | method=ignore | |
371 | '''}) | |
372 | self.assert_networkd({}) | |
373 | self.assert_nm_udev(None) |
104 | 104 | unmanaged-devices+=interface-name:en1,interface-name:enblue,interface-name:enred,interface-name:engreen,''') |
105 | 105 | self.assert_nm_udev(None) |
106 | 106 | |
107 | def test_vlan_sriov(self): | |
108 | # we need to make sure renderer: sriov vlans are not saved as part of | |
109 | # the NM/networkd config | |
110 | self.generate('''network: | |
111 | version: 2 | |
112 | ethernets: | |
113 | en1: {} | |
114 | vlans: | |
115 | enblue: | |
116 | id: 1 | |
117 | link: en1 | |
118 | renderer: sriov | |
119 | engreen: {id: 2, link: en1, dhcp6: true}''') | |
120 | ||
121 | self.assert_networkd({'en1.network': '''[Match] | |
122 | Name=en1 | |
123 | ||
124 | [Network] | |
125 | LinkLocalAddressing=ipv6 | |
126 | VLAN=engreen | |
127 | ''', | |
128 | 'engreen.netdev': '''[NetDev] | |
129 | Name=engreen | |
130 | Kind=vlan | |
131 | ||
132 | [VLAN] | |
133 | Id=2 | |
134 | ''', | |
135 | 'engreen.network': '''[Match] | |
136 | Name=engreen | |
137 | ||
138 | [Network] | |
139 | DHCP=ipv6 | |
140 | LinkLocalAddressing=ipv6 | |
141 | ConfigureWithoutCarrier=yes | |
142 | ||
143 | [DHCP] | |
144 | RouteMetric=100 | |
145 | UseMTU=true | |
146 | '''}) | |
147 | self.assert_nm(None, '''[keyfile] | |
148 | # devices managed by networkd | |
149 | unmanaged-devices+=interface-name:en1,interface-name:enblue,interface-name:engreen,''') | |
150 | self.assert_nm_udev(None) | |
151 | ||
107 | 152 | |
108 | 153 | class TestNetworkManager(TestBase): |
109 | 154 | |
220 | 265 | method=ignore |
221 | 266 | ''' % uuid}) |
222 | 267 | self.assert_nm_udev(None) |
268 | ||
269 | def test_vlan_sriov(self): | |
270 | # we need to make sure renderer: sriov vlans are not saved as part of | |
271 | # the NM/networkd config | |
272 | self.generate('''network: | |
273 | version: 2 | |
274 | renderer: NetworkManager | |
275 | ethernets: | |
276 | en1: {} | |
277 | vlans: | |
278 | enblue: | |
279 | id: 1 | |
280 | link: en1 | |
281 | addresses: [1.2.3.4/24] | |
282 | renderer: sriov | |
283 | engreen: {id: 2, link: en1, dhcp6: true}''') | |
284 | ||
285 | self.assert_networkd({}) | |
286 | self.assert_nm({'en1': '''[connection] | |
287 | id=netplan-en1 | |
288 | type=ethernet | |
289 | interface-name=en1 | |
290 | ||
291 | [ethernet] | |
292 | wake-on-lan=0 | |
293 | ||
294 | [ipv4] | |
295 | method=link-local | |
296 | ||
297 | [ipv6] | |
298 | method=ignore | |
299 | ''', | |
300 | 'engreen': '''[connection] | |
301 | id=netplan-engreen | |
302 | type=vlan | |
303 | interface-name=engreen | |
304 | ||
305 | [vlan] | |
306 | id=2 | |
307 | parent=en1 | |
308 | ||
309 | [ipv4] | |
310 | method=link-local | |
311 | ||
312 | [ipv6] | |
313 | method=auto | |
314 | '''}) | |
315 | self.assert_nm_udev(None) |
30 | 30 | wl0: |
31 | 31 | access-points: |
32 | 32 | "Joe's Home": |
33 | password: "s3kr1t" | |
33 | password: "s0s3kr1t" | |
34 | bssid: 00:11:22:33:44:55 | |
35 | band: 2.4GHz | |
36 | channel: 11 | |
34 | 37 | workplace: |
35 | password: "c0mpany" | |
38 | password: "c0mpany1" | |
39 | bssid: de:ad:be:ef:ca:fe | |
40 | band: 5GHz | |
41 | channel: 100 | |
36 | 42 | peer2peer: |
37 | 43 | mode: adhoc |
44 | channel-no-band: | |
45 | channel: 7 | |
46 | band-no-channel: | |
47 | band: 2.4G | |
48 | band-no-channel2: | |
49 | band: 5G | |
38 | 50 | dhcp4: yes''') |
39 | 51 | |
40 | 52 | self.assert_networkd({'wl0.network': ND_WIFI_DHCP4 % 'wl0'}) |
46 | 58 | # generates wpa config and enables wpasupplicant unit |
47 | 59 | with open(os.path.join(self.workdir.name, 'run/netplan/wpa-wl0.conf')) as f: |
48 | 60 | new_config = f.read() |
61 | ||
62 | network = 'ssid="{}"\n freq_list='.format('band-no-channel2') | |
63 | freqs_5GHz = [5610, 5310, 5620, 5320, 5630, 5640, 5340, 5035, 5040, 5045, 5055, 5060, 5660, 5680, 5670, 5080, 5690, | |
64 | 5700, 5710, 5720, 5825, 5745, 5755, 5805, 5765, 5160, 5775, 5170, 5480, 5180, 5795, 5190, 5500, 5200, | |
65 | 5510, 5210, 5520, 5220, 5530, 5230, 5540, 5240, 5550, 5250, 5560, 5260, 5570, 5270, 5580, 5280, 5590, | |
66 | 5290, 5600, 5300, 5865, 5845, 5785] | |
67 | freqs = new_config.split(network) | |
68 | freqs = freqs[1].split('\n')[0] | |
69 | self.assertEqual(len(freqs.split(' ')), len(freqs_5GHz)) | |
70 | for freq in freqs_5GHz: | |
71 | self.assertRegexpMatches(new_config, '{}[ 0-9]*{}[ 0-9]*\n'.format(network, freq)) | |
72 | ||
73 | network = 'ssid="{}"\n freq_list='.format('band-no-channel') | |
74 | freqs_24GHz = [2412, 2417, 2422, 2427, 2432, 2442, 2447, 2437, 2452, 2457, 2462, 2467, 2472, 2484] | |
75 | freqs = new_config.split(network) | |
76 | freqs = freqs[1].split('\n')[0] | |
77 | self.assertEqual(len(freqs.split(' ')), len(freqs_24GHz)) | |
78 | for freq in freqs_24GHz: | |
79 | self.assertRegexpMatches(new_config, '{}[ 0-9]*{}[ 0-9]*\n'.format(network, freq)) | |
80 | ||
81 | self.assertIn(''' | |
82 | network={ | |
83 | ssid="channel-no-band" | |
84 | key_mgmt=NONE | |
85 | } | |
86 | ''', new_config) | |
49 | 87 | self.assertIn(''' |
50 | 88 | network={ |
51 | 89 | ssid="peer2peer" |
56 | 94 | self.assertIn(''' |
57 | 95 | network={ |
58 | 96 | ssid="workplace" |
97 | bssid=de:ad:be:ef:ca:fe | |
98 | freq_list=5500 | |
59 | 99 | key_mgmt=WPA-PSK |
60 | psk="c0mpany" | |
100 | psk="c0mpany1" | |
61 | 101 | } |
62 | 102 | ''', new_config) |
63 | 103 | self.assertIn(''' |
64 | 104 | network={ |
65 | 105 | ssid="Joe's Home" |
106 | bssid=00:11:22:33:44:55 | |
107 | freq_list=2462 | |
66 | 108 | key_mgmt=WPA-PSK |
67 | psk="s3kr1t" | |
109 | psk="s0s3kr1t" | |
68 | 110 | } |
69 | 111 | ''', new_config) |
70 | 112 | self.assertEqual(stat.S_IMODE(os.fstat(f.fileno()).st_mode), 0o600) |
113 | self.assertTrue(os.path.isfile(os.path.join( | |
114 | self.workdir.name, 'run/systemd/system/netplan-wpa-wl0.service'))) | |
71 | 115 | self.assertTrue(os.path.islink(os.path.join( |
72 | self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa@wl0.service'))) | |
116 | self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa-wl0.service'))) | |
73 | 117 | |
74 | 118 | def test_wifi_route(self): |
75 | 119 | self.generate('''network: |
78 | 122 | wl0: |
79 | 123 | access-points: |
80 | 124 | workplace: |
81 | password: "c0mpany" | |
125 | password: "c0mpany1" | |
82 | 126 | dhcp4: yes |
83 | 127 | routes: |
84 | 128 | - to: 10.10.10.0/24 |
114 | 158 | driver: foo |
115 | 159 | access-points: |
116 | 160 | workplace: |
117 | password: "c0mpany" | |
161 | password: "c0mpany1" | |
118 | 162 | dhcp4: yes''', expect_fail=True) |
119 | 163 | self.assertIn('networkd backend does not support wifi with match:', err) |
120 | 164 | |
125 | 169 | wl0: |
126 | 170 | access-points: |
127 | 171 | workplace: |
128 | password: "c0mpany" | |
172 | password: "c0mpany1" | |
129 | 173 | mode: ap |
130 | 174 | dhcp4: yes''', expect_fail=True) |
131 | 175 | self.assertIn('networkd does not support wifi in access point mode', err) |
132 | 176 | |
177 | def test_wifi_wowlan(self): | |
178 | self.generate('''network: | |
179 | version: 2 | |
180 | wifis: | |
181 | wl0: | |
182 | wakeonwlan: | |
183 | - any | |
184 | - disconnect | |
185 | - magic_pkt | |
186 | - gtk_rekey_failure | |
187 | - eap_identity_req | |
188 | - four_way_handshake | |
189 | - rfkill_release | |
190 | access-points: | |
191 | homenet: {mode: infrastructure}''') | |
192 | ||
193 | self.assert_networkd({'wl0.network': '''[Match] | |
194 | Name=wl0 | |
195 | ||
196 | [Network] | |
197 | LinkLocalAddressing=ipv6 | |
198 | '''}) | |
199 | self.assert_nm(None, '''[keyfile] | |
200 | # devices managed by networkd | |
201 | unmanaged-devices+=interface-name:wl0,''') | |
202 | self.assert_nm_udev(None) | |
203 | ||
204 | # generates wpa config and enables wpasupplicant unit | |
205 | with open(os.path.join(self.workdir.name, 'run/netplan/wpa-wl0.conf')) as f: | |
206 | new_config = f.read() | |
207 | self.assertIn(''' | |
208 | wowlan_triggers=any disconnect magic_pkt gtk_rekey_failure eap_identity_req four_way_handshake rfkill_release | |
209 | network={ | |
210 | ssid="homenet" | |
211 | key_mgmt=NONE | |
212 | } | |
213 | ''', new_config) | |
214 | self.assertEqual(stat.S_IMODE(os.fstat(f.fileno()).st_mode), 0o600) | |
215 | self.assertTrue(os.path.isfile(os.path.join( | |
216 | self.workdir.name, 'run/systemd/system/netplan-wpa-wl0.service'))) | |
217 | self.assertTrue(os.path.islink(os.path.join( | |
218 | self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa-wl0.service'))) | |
219 | ||
220 | def test_wifi_wowlan_default(self): | |
221 | self.generate('''network: | |
222 | version: 2 | |
223 | wifis: | |
224 | wl0: | |
225 | wakeonwlan: [default] | |
226 | access-points: | |
227 | homenet: {mode: infrastructure}''') | |
228 | ||
229 | self.assert_networkd({'wl0.network': '''[Match] | |
230 | Name=wl0 | |
231 | ||
232 | [Network] | |
233 | LinkLocalAddressing=ipv6 | |
234 | '''}) | |
235 | self.assert_nm(None, '''[keyfile] | |
236 | # devices managed by networkd | |
237 | unmanaged-devices+=interface-name:wl0,''') | |
238 | self.assert_nm_udev(None) | |
239 | ||
240 | # generates wpa config and enables wpasupplicant unit | |
241 | with open(os.path.join(self.workdir.name, 'run/netplan/wpa-wl0.conf')) as f: | |
242 | new_config = f.read() | |
243 | self.assertIn(''' | |
244 | network={ | |
245 | ssid="homenet" | |
246 | key_mgmt=NONE | |
247 | } | |
248 | ''', new_config) | |
249 | self.assertEqual(stat.S_IMODE(os.fstat(f.fileno()).st_mode), 0o600) | |
250 | self.assertTrue(os.path.isfile(os.path.join( | |
251 | self.workdir.name, 'run/systemd/system/netplan-wpa-wl0.service'))) | |
252 | self.assertTrue(os.path.islink(os.path.join( | |
253 | self.workdir.name, 'run/systemd/system/systemd-networkd.service.wants/netplan-wpa-wl0.service'))) | |
254 | ||
133 | 255 | |
134 | 256 | class TestNetworkManager(TestBase): |
135 | 257 | |
141 | 263 | wl0: |
142 | 264 | access-points: |
143 | 265 | "Joe's Home": |
144 | password: "s3kr1t" | |
266 | password: "s0s3kr1t" | |
267 | bssid: 00:11:22:33:44:55 | |
268 | band: 2.4GHz | |
269 | channel: 11 | |
145 | 270 | workplace: |
146 | password: "c0mpany" | |
271 | password: "c0mpany1" | |
272 | bssid: de:ad:be:ef:ca:fe | |
273 | band: 5GHz | |
274 | channel: 100 | |
275 | channel-no-band: | |
276 | channel: 22 | |
277 | band-no-channel: | |
278 | band: 5GHz | |
147 | 279 | dhcp4: yes''') |
148 | 280 | |
149 | 281 | self.assert_nm({'wl0-Joe%27s%20Home': '''[connection] |
163 | 295 | [wifi] |
164 | 296 | ssid=Joe's Home |
165 | 297 | mode=infrastructure |
298 | bssid=00:11:22:33:44:55 | |
299 | band=bg | |
300 | channel=11 | |
166 | 301 | |
167 | 302 | [wifi-security] |
168 | 303 | key-mgmt=wpa-psk |
169 | psk=s3kr1t | |
304 | psk=s0s3kr1t | |
170 | 305 | ''', |
171 | 306 | 'wl0-workplace': '''[connection] |
172 | 307 | id=netplan-wl0-workplace |
185 | 320 | [wifi] |
186 | 321 | ssid=workplace |
187 | 322 | mode=infrastructure |
323 | bssid=de:ad:be:ef:ca:fe | |
324 | band=a | |
325 | channel=100 | |
188 | 326 | |
189 | 327 | [wifi-security] |
190 | 328 | key-mgmt=wpa-psk |
191 | psk=c0mpany | |
329 | psk=c0mpany1 | |
330 | ''', | |
331 | 'wl0-channel-no-band': '''[connection] | |
332 | id=netplan-wl0-channel-no-band | |
333 | type=wifi | |
334 | interface-name=wl0 | |
335 | ||
336 | [ethernet] | |
337 | wake-on-lan=0 | |
338 | ||
339 | [ipv4] | |
340 | method=auto | |
341 | ||
342 | [ipv6] | |
343 | method=ignore | |
344 | ||
345 | [wifi] | |
346 | ssid=channel-no-band | |
347 | mode=infrastructure | |
348 | ''', | |
349 | 'wl0-band-no-channel': '''[connection] | |
350 | id=netplan-wl0-band-no-channel | |
351 | type=wifi | |
352 | interface-name=wl0 | |
353 | ||
354 | [ethernet] | |
355 | wake-on-lan=0 | |
356 | ||
357 | [ipv4] | |
358 | method=auto | |
359 | ||
360 | [ipv6] | |
361 | method=ignore | |
362 | ||
363 | [wifi] | |
364 | ssid=band-no-channel | |
365 | mode=infrastructure | |
366 | band=a | |
192 | 367 | '''}) |
193 | 368 | self.assert_networkd({}) |
194 | 369 | self.assert_nm_udev(None) |
262 | 437 | access-points: |
263 | 438 | homenet: |
264 | 439 | mode: ap |
265 | password: s3cret''') | |
440 | password: s0s3cret''') | |
266 | 441 | |
267 | 442 | self.assert_nm({'wl0-homenet': '''[connection] |
268 | 443 | id=netplan-wl0-homenet |
284 | 459 | |
285 | 460 | [wifi-security] |
286 | 461 | key-mgmt=wpa-psk |
287 | psk=s3cret | |
462 | psk=s0s3cret | |
288 | 463 | '''}) |
289 | 464 | self.assert_networkd({}) |
290 | 465 | self.assert_nm_udev(None) |
317 | 492 | ssid=homenet |
318 | 493 | mode=adhoc |
319 | 494 | '''}) |
495 | ||
496 | def test_wifi_wowlan(self): | |
497 | self.generate('''network: | |
498 | version: 2 | |
499 | renderer: NetworkManager | |
500 | wifis: | |
501 | wl0: | |
502 | wakeonwlan: [any, tcp, four_way_handshake, magic_pkt] | |
503 | access-points: | |
504 | homenet: {mode: infrastructure}''') | |
505 | ||
506 | self.assert_nm({'wl0-homenet': '''[connection] | |
507 | id=netplan-wl0-homenet | |
508 | type=wifi | |
509 | interface-name=wl0 | |
510 | ||
511 | [ethernet] | |
512 | wake-on-lan=0 | |
513 | ||
514 | [802-11-wireless] | |
515 | wake-on-wlan=330 | |
516 | ||
517 | [ipv4] | |
518 | method=link-local | |
519 | ||
520 | [ipv6] | |
521 | method=ignore | |
522 | ||
523 | [wifi] | |
524 | ssid=homenet | |
525 | mode=infrastructure | |
526 | '''}) | |
527 | ||
528 | def test_wifi_wowlan_default(self): | |
529 | self.generate('''network: | |
530 | version: 2 | |
531 | renderer: NetworkManager | |
532 | wifis: | |
533 | wl0: | |
534 | wakeonwlan: [default] | |
535 | access-points: | |
536 | homenet: {mode: infrastructure}''') | |
537 | ||
538 | self.assert_nm({'wl0-homenet': '''[connection] | |
539 | id=netplan-wl0-homenet | |
540 | type=wifi | |
541 | interface-name=wl0 | |
542 | ||
543 | [ethernet] | |
544 | wake-on-lan=0 | |
545 | ||
546 | [ipv4] | |
547 | method=link-local | |
548 | ||
549 | [ipv6] | |
550 | method=ignore | |
551 | ||
552 | [wifi] | |
553 | ssid=homenet | |
554 | mode=infrastructure | |
555 | '''}) | |
556 | ||
557 | ||
558 | class TestConfigErrors(TestBase): | |
559 | ||
560 | def test_wifi_invalid_wowlan(self): | |
561 | err = self.generate('''network: | |
562 | version: 2 | |
563 | wifis: | |
564 | wl0: | |
565 | wakeonwlan: [bogus] | |
566 | access-points: | |
567 | homenet: {mode: infrastructure}''', expect_fail=True) | |
568 | self.assertIn("Error in network definition: invalid value for wakeonwlan: 'bogus'", err) | |
569 | ||
570 | def test_wifi_wowlan_unsupported(self): | |
571 | err = self.generate('''network: | |
572 | version: 2 | |
573 | wifis: | |
574 | wl0: | |
575 | wakeonwlan: [tcp] | |
576 | access-points: | |
577 | homenet: {mode: infrastructure}''', expect_fail=True) | |
578 | self.assertIn("ERROR: unsupported wowlan_triggers mask: 0x100", err) | |
579 | ||
580 | def test_wifi_wowlan_exclusive(self): | |
581 | err = self.generate('''network: | |
582 | version: 2 | |
583 | wifis: | |
584 | wl0: | |
585 | wakeonwlan: [default, magic_pkt] | |
586 | access-points: | |
587 | homenet: {mode: infrastructure}''', expect_fail=True) | |
588 | self.assertIn("Error in network definition: 'default' is an exclusive flag for wakeonwlan", err) |
27 | 27 | import tempfile |
28 | 28 | import unittest |
29 | 29 | import shutil |
30 | import gi | |
31 | ||
32 | # make sure we point to libnetplan properly. | |
33 | os.environ.update({'LD_LIBRARY_PATH': '.:{}'.format(os.environ.get('LD_LIBRARY_PATH'))}) | |
30 | 34 | |
31 | 35 | test_backends = "networkd NetworkManager" if "NETPLAN_TEST_BACKENDS" not in os.environ else os.environ["NETPLAN_TEST_BACKENDS"] |
32 | 36 | |
361 | 365 | stderr=subprocess.PIPE, universal_newlines=True) |
362 | 366 | self.fail('timed out waiting for networkd to settle down:\n%s\n%s\n%s' % (st, st_e, st_e2)) |
363 | 367 | |
364 | if subprocess.call(['nm-online', '--quiet', '--timeout=120', '--wait-for-startup']) != 0: | |
368 | if subprocess.call(['nm-online', '--quiet', '--timeout=240', '--wait-for-startup']) != 0: | |
365 | 369 | self.fail('timed out waiting for NetworkManager to settle down') |
370 | ||
371 | def nm_online_full(self, iface, timeout=60): | |
372 | '''Wait for NetworkManager connection to be completed (incl. IP4 & DHCP)''' | |
373 | ||
374 | gi.require_version('NM', '1.0') | |
375 | from gi.repository import NM | |
376 | for t in range(timeout): | |
377 | c = NM.Client.new(None) | |
378 | con = c.get_device_by_iface(iface).get_active_connection() | |
379 | if not con: | |
380 | self.fail('no active connection for %s by NM' % iface) | |
381 | flags = NM.utils_enum_to_str(NM.ActivationStateFlags, con.get_state_flags()) | |
382 | if "ip4-ready" in flags: | |
383 | break | |
384 | time.sleep(1) | |
385 | else: | |
386 | self.fail('timed out waiting for %s to get ready by NM' % iface) | |
366 | 387 | |
367 | 388 | def nm_wait_connected(self, iface, timeout): |
368 | 389 | for t in range(timeout): |
92 | 92 | search: ["fakesuffix"] |
93 | 93 | ''' % {'r': self.backend, 'ec': self.dev_e_client, 'e2c': self.dev_e2_client}) |
94 | 94 | self.generate_and_settle() |
95 | if self.backend == 'NetworkManager': | |
96 | self.nm_online_full(self.dev_e_client) | |
95 | 97 | self.assert_iface_up(self.dev_e_client, |
96 | 98 | ['inet 172.16.42.99/18', |
97 | 99 | 'inet6 1234:ffff::42/64', |
0 | #!/usr/bin/python3 | |
1 | # | |
2 | # Copyright (C) 2020 Canonical, Ltd. | |
3 | # Author: Łukasz 'sil2100' Zemczak <lukasz.zemczak@canonical.com> | |
4 | # | |
5 | # This program is free software; you can redistribute it and/or modify | |
6 | # it under the terms of the GNU General Public License as published by | |
7 | # the Free Software Foundation; version 3. | |
8 | # | |
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. | |
13 | # | |
14 | # You should have received a copy of the GNU General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | ||
17 | import os | |
18 | import unittest | |
19 | import tempfile | |
20 | ||
21 | from subprocess import CalledProcessError | |
22 | from collections import defaultdict | |
23 | from unittest.mock import patch, mock_open, call | |
24 | ||
25 | import netplan.cli.sriov as sriov | |
26 | ||
27 | from netplan.configmanager import ConfigManager, ConfigurationError | |
28 | ||
29 | ||
30 | class MockSRIOVOpen(): | |
31 | def __init__(self): | |
32 | # now this is a VERY ugly hack to make mock_open() better | |
33 | self.read_queue = [] | |
34 | self.write_queue = [] | |
35 | ||
36 | def sriov_read(): | |
37 | action = self.read_queue.pop(0) | |
38 | if isinstance(action, str): | |
39 | return action | |
40 | else: | |
41 | raise action | |
42 | ||
43 | def sriov_write(data): | |
44 | if not self.write_queue: | |
45 | return | |
46 | action = self.write_queue.pop(0) | |
47 | if isinstance(action, Exception): | |
48 | raise action | |
49 | ||
50 | self.open = mock_open() | |
51 | self.open.return_value.read.side_effect = sriov_read | |
52 | self.open.return_value.write.side_effect = sriov_write | |
53 | ||
54 | ||
55 | def mock_set_counts(interfaces, config_manager, vf_counts, active_vfs, active_pfs): | |
56 | counts = {'enp1': 2, 'enp2': 1} | |
57 | vfs = {'enp1s16f1': None, 'enp1s16f2': None, 'customvf1': None} | |
58 | pfs = {'enp1': 'enp1', 'enpx': 'enp2'} | |
59 | vf_counts.update(counts) | |
60 | active_vfs.update(vfs) | |
61 | active_pfs.update(pfs) | |
62 | ||
63 | ||
64 | class TestSRIOV(unittest.TestCase): | |
65 | def setUp(self): | |
66 | self.workdir = tempfile.TemporaryDirectory() | |
67 | os.makedirs(os.path.join(self.workdir.name, 'etc/netplan')) | |
68 | self.configmanager = ConfigManager(prefix=self.workdir.name, extra_files={}) | |
69 | ||
70 | def _prepare_sysfs_dir_structure(self): | |
71 | # prepare a directory hierarchy for testing the matching | |
72 | # this might look really scary, but that's how sysfs presents devices | |
73 | # such as these | |
74 | os.makedirs(os.path.join(self.workdir.name, 'sys/class/net')) | |
75 | ||
76 | # first the VF | |
77 | vf_iface_path = os.path.join(self.workdir.name, 'sys/devices/pci0000:00/0000:00:1f.6/net/enp2s16f1') | |
78 | vf_dev_path = os.path.join(self.workdir.name, 'sys/devices/pci0000:00/0000:00:1f.6') | |
79 | os.makedirs(vf_iface_path) | |
80 | with open(os.path.join(vf_dev_path, 'vendor'), 'w') as f: | |
81 | f.write('0x001f\n') | |
82 | with open(os.path.join(vf_dev_path, 'device'), 'w') as f: | |
83 | f.write('0xb33f\n') | |
84 | os.symlink('../../devices/pci0000:00/0000:00:1f.6/net/enp2s16f1', | |
85 | os.path.join(self.workdir.name, 'sys/class/net/enp2s16f1')) | |
86 | os.symlink('../../../0000:00:1f.6', os.path.join(self.workdir.name, 'sys/class/net/enp2s16f1/device')) | |
87 | ||
88 | # now the PF | |
89 | os.path.join(self.workdir.name, 'sys/class/net/enp2') | |
90 | pf_iface_path = os.path.join(self.workdir.name, 'sys/devices/pci0000:00/0000:00:1f.0/net/enp2') | |
91 | pf_dev_path = os.path.join(self.workdir.name, 'sys/devices/pci0000:00/0000:00:1f.0') | |
92 | os.makedirs(pf_iface_path) | |
93 | with open(os.path.join(pf_dev_path, 'vendor'), 'w') as f: | |
94 | f.write('0x001f\n') | |
95 | with open(os.path.join(pf_dev_path, 'device'), 'w') as f: | |
96 | f.write('0x1337\n') | |
97 | os.symlink('../../devices/pci0000:00/0000:00:1f.0/net/enp2', | |
98 | os.path.join(self.workdir.name, 'sys/class/net/enp2')) | |
99 | os.symlink('../../../0000:00:1f.0', os.path.join(self.workdir.name, 'sys/class/net/enp2/device')) | |
100 | # the PF additionally has device links to all the VFs defined for it | |
101 | os.symlink('../../../0000:00:1f.4', os.path.join(pf_dev_path, 'virtfn1')) | |
102 | os.symlink('../../../0000:00:1f.5', os.path.join(pf_dev_path, 'virtfn2')) | |
103 | os.symlink('../../../0000:00:1f.6', os.path.join(pf_dev_path, 'virtfn3')) | |
104 | os.symlink('../../../0000:00:1f.7', os.path.join(pf_dev_path, 'virtfn4')) | |
105 | ||
106 | @patch('netplan.cli.utils.get_interface_driver_name') | |
107 | @patch('netplan.cli.utils.get_interface_macaddress') | |
108 | def test_get_vf_count_and_functions(self, gim, gidn): | |
109 | # we mock-out get_interface_driver_name and get_interface_macaddress | |
110 | # to return useful values for the test | |
111 | gim.side_effect = lambda x: '00:01:02:03:04:05' if x == 'enp3' else '00:00:00:00:00:00' | |
112 | gidn.side_effect = lambda x: 'foo' if x == 'enp2' else 'bar' | |
113 | with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd: | |
114 | print('''network: | |
115 | version: 2 | |
116 | renderer: networkd | |
117 | ethernets: | |
118 | renderer: networkd | |
119 | enp1: | |
120 | mtu: 9000 | |
121 | enp2: | |
122 | match: | |
123 | driver: foo | |
124 | enp3: | |
125 | match: | |
126 | macaddress: 00:01:02:03:04:05 | |
127 | enpx: | |
128 | match: | |
129 | name: enp[4-5] | |
130 | enp0: | |
131 | mtu: 9000 | |
132 | enp8: | |
133 | virtual-function-count: 7 | |
134 | enp9: {} | |
135 | wlp6s0: {} | |
136 | enp1s16f1: | |
137 | link: enp1 | |
138 | macaddress: 01:02:03:04:05:00 | |
139 | enp1s16f2: | |
140 | link: enp1 | |
141 | macaddress: 01:02:03:04:05:01 | |
142 | enp2s16f1: | |
143 | link: enp2 | |
144 | enp2s16f2: {link: enp2} | |
145 | enp3s16f1: | |
146 | link: enp3 | |
147 | enpxs16f1: | |
148 | match: | |
149 | name: enp[4-5]s16f1 | |
150 | link: enpx | |
151 | enp9s16f1: | |
152 | link: enp9 | |
153 | ''', file=fd) | |
154 | self.configmanager.parse() | |
155 | interfaces = ['enp1', 'enp2', 'enp3', 'enp5', 'enp0', 'enp8'] | |
156 | vf_counts = defaultdict(int) | |
157 | vfs = {} | |
158 | pfs = {} | |
159 | ||
160 | # call the function under test | |
161 | sriov.get_vf_count_and_functions(interfaces, self.configmanager, | |
162 | vf_counts, vfs, pfs) | |
163 | # check if the right vf counts have been recorded in vf_counts | |
164 | self.assertDictEqual( | |
165 | vf_counts, | |
166 | {'enp1': 2, 'enp2': 2, 'enp3': 1, 'enp5': 1, 'enp8': 7}) | |
167 | # also check if the vfs and pfs dictionaries got properly set | |
168 | self.assertDictEqual( | |
169 | vfs, | |
170 | {'enp1s16f1': None, 'enp1s16f2': None, 'enp2s16f1': None, | |
171 | 'enp2s16f2': None, 'enp3s16f1': None, 'enpxs16f1': None}) | |
172 | self.assertDictEqual( | |
173 | pfs, | |
174 | {'enp1': 'enp1', 'enp2': 'enp2', 'enp3': 'enp3', | |
175 | 'enpx': 'enp5', 'enp8': 'enp8'}) | |
176 | ||
177 | @patch('netplan.cli.utils.get_interface_driver_name') | |
178 | @patch('netplan.cli.utils.get_interface_macaddress') | |
179 | def test_get_vf_count_and_functions_many_match(self, gim, gidn): | |
180 | # we mock-out get_interface_driver_name and get_interface_macaddress | |
181 | # to return useful values for the test | |
182 | gim.side_effect = lambda x: '00:01:02:03:04:05' if x == 'enp3' else '00:00:00:00:00:00' | |
183 | gidn.side_effect = lambda x: 'foo' if x == 'enp2' else 'bar' | |
184 | with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd: | |
185 | print('''network: | |
186 | version: 2 | |
187 | renderer: networkd | |
188 | ethernets: | |
189 | renderer: networkd | |
190 | enpx: | |
191 | match: | |
192 | name: enp* | |
193 | mtu: 9000 | |
194 | enpxs16f1: | |
195 | link: enpx | |
196 | ''', file=fd) | |
197 | self.configmanager.parse() | |
198 | interfaces = ['enp1', 'wlp6s0', 'enp2', 'enp3'] | |
199 | vf_counts = defaultdict(int) | |
200 | vfs = {} | |
201 | pfs = {} | |
202 | ||
203 | # call the function under test | |
204 | with self.assertRaises(ConfigurationError) as e: | |
205 | sriov.get_vf_count_and_functions(interfaces, self.configmanager, | |
206 | vf_counts, vfs, pfs) | |
207 | ||
208 | self.assertIn('matched more than one interface for a PF device: enpx', | |
209 | str(e.exception)) | |
210 | ||
211 | @patch('netplan.cli.utils.get_interface_driver_name') | |
212 | @patch('netplan.cli.utils.get_interface_macaddress') | |
213 | def test_get_vf_count_and_functions_not_enough_explicit(self, gim, gidn): | |
214 | # we mock-out get_interface_driver_name and get_interface_macaddress | |
215 | # to return useful values for the test | |
216 | gim.side_effect = lambda x: '00:01:02:03:04:05' if x == 'enp3' else '00:00:00:00:00:00' | |
217 | gidn.side_effect = lambda x: 'foo' if x == 'enp2' else 'bar' | |
218 | with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd: | |
219 | print('''network: | |
220 | version: 2 | |
221 | renderer: networkd | |
222 | ethernets: | |
223 | renderer: networkd | |
224 | enp1: | |
225 | virtual-function-count: 2 | |
226 | mtu: 9000 | |
227 | enp1s16f1: | |
228 | link: enp1 | |
229 | enp1s16f2: | |
230 | link: enp1 | |
231 | enp1s16f3: | |
232 | link: enp1 | |
233 | ''', file=fd) | |
234 | self.configmanager.parse() | |
235 | interfaces = ['enp1', 'wlp6s0'] | |
236 | vf_counts = defaultdict(int) | |
237 | vfs = {} | |
238 | pfs = {} | |
239 | ||
240 | # call the function under test | |
241 | with self.assertRaises(ConfigurationError) as e: | |
242 | sriov.get_vf_count_and_functions(interfaces, self.configmanager, | |
243 | vf_counts, vfs, pfs) | |
244 | ||
245 | self.assertIn('more VFs allocated than the explicit size declared: 3 > 2', | |
246 | str(e.exception)) | |
247 | ||
248 | def test_set_numvfs_for_pf(self): | |
249 | sriov_open = MockSRIOVOpen() | |
250 | sriov_open.read_queue = ['8\n'] | |
251 | ||
252 | with patch('builtins.open', sriov_open.open): | |
253 | ret = sriov.set_numvfs_for_pf('enp1', 2) | |
254 | ||
255 | self.assertTrue(ret) | |
256 | self.assertListEqual(sriov_open.open.call_args_list, | |
257 | [call('/sys/class/net/enp1/device/sriov_totalvfs'), | |
258 | call('/sys/class/net/enp1/device/sriov_numvfs', 'w')]) | |
259 | handle = sriov_open.open() | |
260 | handle.write.assert_called_once_with('2') | |
261 | ||
262 | def test_set_numvfs_for_pf_failsafe(self): | |
263 | sriov_open = MockSRIOVOpen() | |
264 | sriov_open.read_queue = ['8\n'] | |
265 | sriov_open.write_queue = [IOError(16, 'Error'), None, None] | |
266 | ||
267 | with patch('builtins.open', sriov_open.open): | |
268 | ret = sriov.set_numvfs_for_pf('enp1', 2) | |
269 | ||
270 | self.assertTrue(ret) | |
271 | handle = sriov_open.open() | |
272 | self.assertEqual(handle.write.call_count, 3) | |
273 | ||
274 | def test_set_numvfs_for_pf_over_max(self): | |
275 | sriov_open = MockSRIOVOpen() | |
276 | sriov_open.read_queue = ['8\n'] | |
277 | ||
278 | with patch('builtins.open', sriov_open.open): | |
279 | with self.assertRaises(ConfigurationError) as e: | |
280 | sriov.set_numvfs_for_pf('enp1', 9) | |
281 | ||
282 | self.assertIn('cannot allocate more VFs for PF enp1 than supported', | |
283 | str(e.exception)) | |
284 | ||
285 | def test_set_numvfs_for_pf_over_theoretical_max(self): | |
286 | sriov_open = MockSRIOVOpen() | |
287 | sriov_open.read_queue = ['1337\n'] | |
288 | ||
289 | with patch('builtins.open', sriov_open.open): | |
290 | with self.assertRaises(ConfigurationError) as e: | |
291 | sriov.set_numvfs_for_pf('enp1', 345) | |
292 | ||
293 | self.assertIn('cannot allocate more VFs for PF enp1 than the SR-IOV maximum', | |
294 | str(e.exception)) | |
295 | ||
296 | def test_set_numvfs_for_pf_read_failed(self): | |
297 | sriov_open = MockSRIOVOpen() | |
298 | cases = ( | |
299 | [IOError], | |
300 | ['not a number\n'], | |
301 | ) | |
302 | ||
303 | with patch('builtins.open', sriov_open.open): | |
304 | for case in cases: | |
305 | sriov_open.read_queue = case | |
306 | with self.assertRaises(RuntimeError): | |
307 | sriov.set_numvfs_for_pf('enp1', 3) | |
308 | ||
309 | def test_set_numvfs_for_pf_write_failed(self): | |
310 | sriov_open = MockSRIOVOpen() | |
311 | sriov_open.read_queue = ['8\n'] | |
312 | sriov_open.write_queue = [IOError(16, 'Error'), IOError(16, 'Error')] | |
313 | ||
314 | with patch('builtins.open', sriov_open.open): | |
315 | with self.assertRaises(RuntimeError) as e: | |
316 | sriov.set_numvfs_for_pf('enp1', 2) | |
317 | ||
318 | self.assertIn('failed setting sriov_numvfs to 2 for enp1', | |
319 | str(e.exception)) | |
320 | ||
321 | def test_perform_hardware_specific_quirks(self): | |
322 | # for now we have no custom quirks defined, so we just | |
323 | # check if the function succeeds | |
324 | sriov_open = MockSRIOVOpen() | |
325 | sriov_open.read_queue = ['0x001f\n', '0x1337\n'] | |
326 | ||
327 | with patch('builtins.open', sriov_open.open): | |
328 | sriov.perform_hardware_specific_quirks('enp1') | |
329 | ||
330 | # it's good enough if it did all the matching | |
331 | self.assertListEqual(sriov_open.open.call_args_list, | |
332 | [call('/sys/class/net/enp1/device/vendor'), | |
333 | call('/sys/class/net/enp1/device/device'), ]) | |
334 | ||
335 | def test_perform_hardware_specific_quirks_failed(self): | |
336 | sriov_open = MockSRIOVOpen() | |
337 | cases = ( | |
338 | [IOError], | |
339 | ['0x001f\n', IOError], | |
340 | ) | |
341 | ||
342 | with patch('builtins.open', sriov_open.open): | |
343 | for case in cases: | |
344 | sriov_open.read_queue = case | |
345 | with self.assertRaises(RuntimeError) as e: | |
346 | sriov.perform_hardware_specific_quirks('enp1') | |
347 | ||
348 | self.assertIn('could not determine vendor and device ID of enp1', | |
349 | str(e.exception)) | |
350 | ||
351 | @patch('subprocess.check_call') | |
352 | def test_apply_vlan_filter_for_vf(self, check_call): | |
353 | self._prepare_sysfs_dir_structure() | |
354 | ||
355 | sriov.apply_vlan_filter_for_vf('enp2', 'enp2s16f1', 'vlan10', 10, prefix=self.workdir.name) | |
356 | ||
357 | self.assertEqual(check_call.call_count, 1) | |
358 | self.assertListEqual(check_call.call_args[0][0], | |
359 | ['ip', 'link', 'set', 'dev', 'enp2', | |
360 | 'vf', '3', 'vlan', '10']) | |
361 | ||
362 | @patch('subprocess.check_call') | |
363 | def test_apply_vlan_filter_for_vf_failed_no_index(self, check_call): | |
364 | self._prepare_sysfs_dir_structure() | |
365 | # we remove the PF -> VF link, simulating a system error | |
366 | os.unlink(os.path.join(self.workdir.name, 'sys/class/net/enp2/device/virtfn3')) | |
367 | ||
368 | with self.assertRaises(RuntimeError) as e: | |
369 | sriov.apply_vlan_filter_for_vf('enp2', 'enp2s16f1', 'vlan10', 10, prefix=self.workdir.name) | |
370 | ||
371 | self.assertIn('could not determine the VF index for enp2s16f1 while configuring vlan vlan10', | |
372 | str(e.exception)) | |
373 | self.assertEqual(check_call.call_count, 0) | |
374 | ||
375 | @patch('subprocess.check_call') | |
376 | def test_apply_vlan_filter_for_vf_failed_ip_link_set(self, check_call): | |
377 | self._prepare_sysfs_dir_structure() | |
378 | check_call.side_effect = CalledProcessError(-1, None) | |
379 | ||
380 | with self.assertRaises(RuntimeError) as e: | |
381 | sriov.apply_vlan_filter_for_vf('enp2', 'enp2s16f1', 'vlan10', 10, prefix=self.workdir.name) | |
382 | ||
383 | self.assertIn('failed setting SR-IOV VLAN filter for vlan vlan10', | |
384 | str(e.exception)) | |
385 | ||
386 | @patch('netifaces.interfaces') | |
387 | @patch('netplan.cli.sriov.get_vf_count_and_functions') | |
388 | @patch('netplan.cli.sriov.set_numvfs_for_pf') | |
389 | @patch('netplan.cli.sriov.perform_hardware_specific_quirks') | |
390 | @patch('netplan.cli.sriov.apply_vlan_filter_for_vf') | |
391 | @patch('netplan.cli.utils.get_interface_driver_name') | |
392 | @patch('netplan.cli.utils.get_interface_macaddress') | |
393 | def test_apply_sriov_config(self, gim, gidn, apply_vlan, quirks, | |
394 | set_numvfs, get_counts, netifs): | |
395 | # set up the environment | |
396 | with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd: | |
397 | print('''network: | |
398 | version: 2 | |
399 | renderer: networkd | |
400 | ethernets: | |
401 | enp1: | |
402 | mtu: 9000 | |
403 | enpx: | |
404 | match: | |
405 | name: enp[2-3] | |
406 | enp1s16f1: | |
407 | link: enp1 | |
408 | macaddress: 01:02:03:04:05:00 | |
409 | enp1s16f2: | |
410 | link: enp1 | |
411 | customvf1: | |
412 | match: | |
413 | name: enp[2-3]s16f[1-4] | |
414 | link: enpx | |
415 | vlans: | |
416 | vf1.15: | |
417 | renderer: sriov | |
418 | id: 15 | |
419 | link: customvf1 | |
420 | vf1.16: | |
421 | renderer: sriov | |
422 | id: 16 | |
423 | link: foobar | |
424 | ''', file=fd) | |
425 | self.configmanager.parse() | |
426 | interfaces = ['enp1', 'enp2', 'enp5', 'wlp6s0'] | |
427 | # set up all the mock objects | |
428 | netifs.return_value = ['enp1', 'enp2', 'enp5', 'wlp6s0', | |
429 | 'enp1s16f1', 'enp1s16f2', 'enp2s16f1'] | |
430 | get_counts.side_effect = mock_set_counts | |
431 | set_numvfs.side_effect = lambda pf, _: False if pf == 'enp2' else True | |
432 | gidn.return_value = 'foodriver' | |
433 | gim.return_value = '00:01:02:03:04:05' | |
434 | ||
435 | # call method under test | |
436 | sriov.apply_sriov_config(interfaces, self.configmanager) | |
437 | ||
438 | # check if the config got applied as expected | |
439 | # we had 2 PFs, one having two VFs and the other only one | |
440 | self.assertEqual(set_numvfs.call_count, 2) | |
441 | self.assertListEqual(set_numvfs.call_args_list, | |
442 | [call('enp1', 2), | |
443 | call('enp2', 1)]) | |
444 | # one of the pfs already had sufficient VFs allocated, so only enp1 | |
445 | # changed the vf count and only that one should trigger quirks | |
446 | quirks.assert_called_once_with('enp1') | |
447 | # only one had a hardware vlan | |
448 | apply_vlan.assert_called_once_with('enp2', 'enp2s16f1', 'vf1.15', 15) | |
449 | ||
450 | @patch('netifaces.interfaces') | |
451 | @patch('netplan.cli.sriov.get_vf_count_and_functions') | |
452 | @patch('netplan.cli.sriov.set_numvfs_for_pf') | |
453 | @patch('netplan.cli.sriov.perform_hardware_specific_quirks') | |
454 | @patch('netplan.cli.sriov.apply_vlan_filter_for_vf') | |
455 | @patch('netplan.cli.utils.get_interface_driver_name') | |
456 | @patch('netplan.cli.utils.get_interface_macaddress') | |
457 | def test_apply_sriov_config_invalid_vlan(self, gim, gidn, apply_vlan, quirks, | |
458 | set_numvfs, get_counts, netifs): | |
459 | # set up the environment | |
460 | with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd: | |
461 | print('''network: | |
462 | version: 2 | |
463 | renderer: networkd | |
464 | ethernets: | |
465 | enp1: | |
466 | mtu: 9000 | |
467 | enpx: | |
468 | match: | |
469 | name: enp[2-3] | |
470 | enp1s16f1: | |
471 | link: enp1 | |
472 | macaddress: 01:02:03:04:05:00 | |
473 | enp1s16f2: | |
474 | link: enp1 | |
475 | customvf1: | |
476 | match: | |
477 | name: enp[2-3]s16f[1-4] | |
478 | link: enpx | |
479 | vlans: | |
480 | vf1.15: | |
481 | renderer: sriov | |
482 | link: customvf1 | |
483 | ''', file=fd) | |
484 | self.configmanager.parse() | |
485 | interfaces = ['enp1', 'enp2', 'enp5', 'wlp6s0'] | |
486 | # set up all the mock objects | |
487 | netifs.return_value = ['enp1', 'enp2', 'enp5', 'wlp6s0', | |
488 | 'enp1s16f1', 'enp1s16f2', 'enp2s16f1'] | |
489 | get_counts.side_effect = mock_set_counts | |
490 | set_numvfs.side_effect = lambda pf, _: False if pf == 'enp2' else True | |
491 | gidn.return_value = 'foodriver' | |
492 | gim.return_value = '00:01:02:03:04:05' | |
493 | ||
494 | # call method under test | |
495 | with self.assertRaises(ConfigurationError) as e: | |
496 | sriov.apply_sriov_config(interfaces, self.configmanager) | |
497 | ||
498 | self.assertIn('no id property defined for SR-IOV vlan vf1.15', | |
499 | str(e.exception)) | |
500 | self.assertEqual(apply_vlan.call_count, 0) | |
501 | ||
502 | @patch('netifaces.interfaces') | |
503 | @patch('netplan.cli.sriov.get_vf_count_and_functions') | |
504 | @patch('netplan.cli.sriov.set_numvfs_for_pf') | |
505 | @patch('netplan.cli.sriov.perform_hardware_specific_quirks') | |
506 | @patch('netplan.cli.sriov.apply_vlan_filter_for_vf') | |
507 | @patch('netplan.cli.utils.get_interface_driver_name') | |
508 | @patch('netplan.cli.utils.get_interface_macaddress') | |
509 | def test_apply_sriov_config_too_many_vlans(self, gim, gidn, apply_vlan, quirks, | |
510 | set_numvfs, get_counts, netifs): | |
511 | # set up the environment | |
512 | with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd: | |
513 | print('''network: | |
514 | version: 2 | |
515 | renderer: networkd | |
516 | ethernets: | |
517 | enp1: | |
518 | mtu: 9000 | |
519 | enpx: | |
520 | match: | |
521 | name: enp[2-3] | |
522 | enp1s16f1: | |
523 | link: enp1 | |
524 | macaddress: 01:02:03:04:05:00 | |
525 | enp1s16f2: | |
526 | link: enp1 | |
527 | customvf1: | |
528 | match: | |
529 | name: enp[2-3]s16f[1-4] | |
530 | link: enpx | |
531 | vlans: | |
532 | vf1.15: | |
533 | renderer: sriov | |
534 | id: 15 | |
535 | link: customvf1 | |
536 | vf1.16: | |
537 | renderer: sriov | |
538 | id: 16 | |
539 | link: customvf1 | |
540 | ''', file=fd) | |
541 | self.configmanager.parse() | |
542 | interfaces = ['enp1', 'enp2', 'enp5', 'wlp6s0'] | |
543 | # set up all the mock objects | |
544 | netifs.return_value = ['enp1', 'enp2', 'enp5', 'wlp6s0', | |
545 | 'enp1s16f1', 'enp1s16f2', 'enp2s16f1'] | |
546 | get_counts.side_effect = mock_set_counts | |
547 | set_numvfs.side_effect = lambda pf, _: False if pf == 'enp2' else True | |
548 | gidn.return_value = 'foodriver' | |
549 | gim.return_value = '00:01:02:03:04:05' | |
550 | ||
551 | # call method under test | |
552 | with self.assertRaises(ConfigurationError) as e: | |
553 | sriov.apply_sriov_config(interfaces, self.configmanager) | |
554 | ||
555 | self.assertIn('interface enp2s16f1 for netplan device customvf1 (vf1.16) already has an SR-IOV vlan defined', | |
556 | str(e.exception)) | |
557 | self.assertEqual(apply_vlan.call_count, 1) | |
558 | ||
559 | @patch('netifaces.interfaces') | |
560 | @patch('netplan.cli.sriov.get_vf_count_and_functions') | |
561 | @patch('netplan.cli.sriov.set_numvfs_for_pf') | |
562 | @patch('netplan.cli.sriov.perform_hardware_specific_quirks') | |
563 | @patch('netplan.cli.sriov.apply_vlan_filter_for_vf') | |
564 | @patch('netplan.cli.utils.get_interface_driver_name') | |
565 | @patch('netplan.cli.utils.get_interface_macaddress') | |
566 | def test_apply_sriov_config_many_match(self, gim, gidn, apply_vlan, quirks, | |
567 | set_numvfs, get_counts, netifs): | |
568 | # set up the environment | |
569 | with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd: | |
570 | print('''network: | |
571 | version: 2 | |
572 | renderer: networkd | |
573 | ethernets: | |
574 | enp1: | |
575 | mtu: 9000 | |
576 | enpx: | |
577 | match: | |
578 | name: enp[2-3] | |
579 | enp1s16f1: | |
580 | link: enp1 | |
581 | macaddress: 01:02:03:04:05:00 | |
582 | enp1s16f2: | |
583 | link: enp1 | |
584 | customvf1: | |
585 | match: | |
586 | name: enp*s16f[1-4] | |
587 | link: enpx | |
588 | ''', file=fd) | |
589 | self.configmanager.parse() | |
590 | interfaces = ['enp1', 'enp2', 'enp5', 'wlp6s0'] | |
591 | # set up all the mock objects | |
592 | netifs.return_value = ['enp1', 'enp2', 'enp5', 'wlp6s0', | |
593 | 'enp1s16f1', 'enp1s16f2', 'enp2s16f1'] | |
594 | get_counts.side_effect = mock_set_counts | |
595 | set_numvfs.side_effect = lambda pf, _: False if pf == 'enp2' else True | |
596 | gidn.return_value = 'foodriver' | |
597 | gim.return_value = '00:01:02:03:04:05' | |
598 | ||
599 | # call method under test | |
600 | with self.assertRaises(ConfigurationError) as e: | |
601 | sriov.apply_sriov_config(interfaces, self.configmanager) | |
602 | ||
603 | self.assertIn('matched more than one interface for a VF device: customvf1', | |
604 | str(e.exception)) |