summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregor Kleen <gkleen@yggdrasil.li>2021-05-15 20:27:55 +0200
committerGregor Kleen <gkleen@yggdrasil.li>2021-05-15 20:27:55 +0200
commit26e71c7a3496218cf86381ef3e2d167a2a3f07fe (patch)
tree0ce8fbeafb0f4fee3a1448856adbbe7a68153a37
parent76daf3ac0aa3399d7fcfbadc35c14ed2d0bbe952 (diff)
downloadnixos-26e71c7a3496218cf86381ef3e2d167a2a3f07fe.tar
nixos-26e71c7a3496218cf86381ef3e2d167a2a3f07fe.tar.gz
nixos-26e71c7a3496218cf86381ef3e2d167a2a3f07fe.tar.bz2
nixos-26e71c7a3496218cf86381ef3e2d167a2a3f07fe.tar.xz
nixos-26e71c7a3496218cf86381ef3e2d167a2a3f07fe.zip
networkd: multiple gateway route sections
-rw-r--r--modules/networkd.nix297
1 files changed, 297 insertions, 0 deletions
diff --git a/modules/networkd.nix b/modules/networkd.nix
new file mode 100644
index 00000000..d0a48f98
--- /dev/null
+++ b/modules/networkd.nix
@@ -0,0 +1,297 @@
1{ config, lib, utils, pkgs, ... }:
2
3with utils;
4with lib;
5
6let
7
8 cfg = config.networking;
9 interfaces = attrValues cfg.interfaces;
10
11 interfaceIps = i:
12 i.ipv4.addresses
13 ++ optionals cfg.enableIPv6 i.ipv6.addresses;
14
15 dhcpStr = useDHCP: if useDHCP == true || useDHCP == null then "yes" else "no";
16
17 slaves =
18 concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds))
19 ++ concatLists (map (bridge: bridge.interfaces) (attrValues cfg.bridges))
20 ++ map (sit: sit.dev) (attrValues cfg.sits)
21 ++ map (vlan: vlan.interface) (attrValues cfg.vlans)
22 # add dependency to physical or independently created vswitch member interface
23 # TODO: warn the user that any address configured on those interfaces will be useless
24 ++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (attrValues cfg.vswitches);
25
26in
27
28{
29 disabledModules = [ "tasks/network-interfaces-systemd.nix" ];
30
31 config = mkIf cfg.useNetworkd {
32
33 assertions = [ {
34 assertion = cfg.defaultGatewayWindowSize == null;
35 message = "networking.defaultGatewayWindowSize is not supported by networkd.";
36 } {
37 assertion = cfg.vswitches == {};
38 message = "networking.vswitches are not supported by networkd.";
39 } {
40 assertion = cfg.defaultGateway == null || cfg.defaultGateway.interface == null;
41 message = "networking.defaultGateway.interface is not supported by networkd.";
42 } {
43 assertion = cfg.defaultGateway6 == null || cfg.defaultGateway6.interface == null;
44 message = "networking.defaultGateway6.interface is not supported by networkd.";
45 } {
46 assertion = cfg.useDHCP == false;
47 message = ''
48 networking.useDHCP is not supported by networkd.
49 Please use per interface configuration and set the global option to false.
50 '';
51 } ] ++ flip mapAttrsToList cfg.bridges (n: { rstp, ... }: {
52 assertion = !rstp;
53 message = "networking.bridges.${n}.rstp is not supported by networkd.";
54 });
55
56 networking.dhcpcd.enable = mkDefault false;
57
58 systemd.network =
59 let
60 domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain);
61 genericNetwork = override:
62 let gateways = optional (cfg.defaultGateway != null && (cfg.defaultGateway.address or "") != "") cfg.defaultGateway.address
63 ++ optional (cfg.defaultGateway6 != null && (cfg.defaultGateway6.address or "") != "") cfg.defaultGateway6.address;
64 in optionalAttrs (gateways != [ ]) {
65 routes = override (map (gateway: {
66 routeConfig = {
67 Gateway = gateway;
68 GatewayOnLink = false;
69 };
70 }) gateways);
71 } // optionalAttrs (domains != [ ]) {
72 domains = override domains;
73 };
74 in mkMerge [ {
75 enable = true;
76 }
77 (mkMerge (forEach interfaces (i: {
78 netdevs = mkIf i.virtual ({
79 "40-${i.name}" = {
80 netdevConfig = {
81 Name = i.name;
82 Kind = i.virtualType;
83 };
84 "${i.virtualType}Config" = optionalAttrs (i.virtualOwner != null) {
85 User = i.virtualOwner;
86 };
87 };
88 });
89 networks."40-${i.name}" = mkMerge [ (genericNetwork mkDefault) {
90 name = mkDefault i.name;
91 DHCP = mkForce (dhcpStr
92 (if i.useDHCP != null then i.useDHCP else false));
93 address = forEach (interfaceIps i)
94 (ip: "${ip.address}/${toString ip.prefixLength}");
95 networkConfig.IPv6PrivacyExtensions = "kernel";
96 linkConfig = optionalAttrs (i.macAddress != null) {
97 MACAddress = i.macAddress;
98 } // optionalAttrs (i.mtu != null) {
99 MTUBytes = toString i.mtu;
100 };
101 }];
102 })))
103 (mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: {
104 netdevs."40-${name}" = {
105 netdevConfig = {
106 Name = name;
107 Kind = "bridge";
108 };
109 };
110 networks = listToAttrs (forEach bridge.interfaces (bi:
111 nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
112 DHCP = mkOverride 0 (dhcpStr false);
113 networkConfig.Bridge = name;
114 } ])));
115 })))
116 (mkMerge (flip mapAttrsToList cfg.bonds (name: bond: {
117 netdevs."40-${name}" = {
118 netdevConfig = {
119 Name = name;
120 Kind = "bond";
121 };
122 bondConfig = let
123 # manual mapping as of 2017-02-03
124 # man 5 systemd.netdev [BOND]
125 # to https://www.kernel.org/doc/Documentation/networking/bonding.txt
126 # driver options.
127 driverOptionMapping = let
128 trans = f: optName: { valTransform = f; optNames = [optName]; };
129 simp = trans id;
130 ms = trans (v: v + "ms");
131 in {
132 Mode = simp "mode";
133 TransmitHashPolicy = simp "xmit_hash_policy";
134 LACPTransmitRate = simp "lacp_rate";
135 MIIMonitorSec = ms "miimon";
136 UpDelaySec = ms "updelay";
137 DownDelaySec = ms "downdelay";
138 LearnPacketIntervalSec = simp "lp_interval";
139 AdSelect = simp "ad_select";
140 FailOverMACPolicy = simp "fail_over_mac";
141 ARPValidate = simp "arp_validate";
142 # apparently in ms for this value?! Upstream bug?
143 ARPIntervalSec = simp "arp_interval";
144 ARPIPTargets = simp "arp_ip_target";
145 ARPAllTargets = simp "arp_all_targets";
146 PrimaryReselectPolicy = simp "primary_reselect";
147 ResendIGMP = simp "resend_igmp";
148 PacketsPerSlave = simp "packets_per_slave";
149 GratuitousARP = { valTransform = id;
150 optNames = [ "num_grat_arp" "num_unsol_na" ]; };
151 AllSlavesActive = simp "all_slaves_active";
152 MinLinks = simp "min_links";
153 };
154
155 do = bond.driverOptions;
156 assertNoUnknownOption = let
157 knownOptions = flatten (mapAttrsToList (_: kOpts: kOpts.optNames)
158 driverOptionMapping);
159 # options that apparently don’t exist in the networkd config
160 unknownOptions = [ "primary" ];
161 assertTrace = bool: msg: if bool then true else builtins.trace msg false;
162 in assert all (driverOpt: assertTrace
163 (elem driverOpt (knownOptions ++ unknownOptions))
164 "The bond.driverOption `${driverOpt}` cannot be mapped to the list of known networkd bond options. Please add it to the mapping above the assert or to `unknownOptions` should it not exist in networkd.")
165 (mapAttrsToList (k: _: k) do); "";
166 # get those driverOptions that have been set
167 filterSystemdOptions = filterAttrs (sysDOpt: kOpts:
168 any (kOpt: do ? ${kOpt}) kOpts.optNames);
169 # build final set of systemd options to bond values
170 buildOptionSet = mapAttrs (_: kOpts: with kOpts;
171 # we simply take the first set kernel bond option
172 # (one option has multiple names, which is silly)
173 head (map (optN: valTransform (do.${optN}))
174 # only map those that exist
175 (filter (o: do ? ${o}) optNames)));
176 in seq assertNoUnknownOption
177 (buildOptionSet (filterSystemdOptions driverOptionMapping));
178
179 };
180
181 networks = listToAttrs (forEach bond.interfaces (bi:
182 nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
183 DHCP = mkOverride 0 (dhcpStr false);
184 networkConfig.Bond = name;
185 } ])));
186 })))
187 (mkMerge (flip mapAttrsToList cfg.macvlans (name: macvlan: {
188 netdevs."40-${name}" = {
189 netdevConfig = {
190 Name = name;
191 Kind = "macvlan";
192 };
193 macvlanConfig = optionalAttrs (macvlan.mode != null) { Mode = macvlan.mode; };
194 };
195 networks."40-${macvlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
196 macvlan = [ name ];
197 } ]);
198 })))
199 (mkMerge (flip mapAttrsToList cfg.sits (name: sit: {
200 netdevs."40-${name}" = {
201 netdevConfig = {
202 Name = name;
203 Kind = "sit";
204 };
205 tunnelConfig =
206 (optionalAttrs (sit.remote != null) {
207 Remote = sit.remote;
208 }) // (optionalAttrs (sit.local != null) {
209 Local = sit.local;
210 }) // (optionalAttrs (sit.ttl != null) {
211 TTL = sit.ttl;
212 });
213 };
214 networks = mkIf (sit.dev != null) {
215 "40-${sit.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
216 tunnel = [ name ];
217 } ]);
218 };
219 })))
220 (mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: {
221 netdevs."40-${name}" = {
222 netdevConfig = {
223 Name = name;
224 Kind = "vlan";
225 };
226 vlanConfig.Id = vlan.id;
227 };
228 networks."40-${vlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
229 vlan = [ name ];
230 } ]);
231 })))
232 ];
233
234 # We need to prefill the slaved devices with networking options
235 # This forces the network interface creator to initialize slaves.
236 networking.interfaces = listToAttrs (map (i: nameValuePair i { }) slaves);
237
238 systemd.services = let
239 # We must escape interfaces due to the systemd interpretation
240 subsystemDevice = interface:
241 "sys-subsystem-net-devices-${escapeSystemdPath interface}.device";
242 # support for creating openvswitch switches
243 createVswitchDevice = n: v: nameValuePair "${n}-netdev"
244 (let
245 deps = map subsystemDevice (attrNames (filterAttrs (_: config: config.type != "internal") v.interfaces));
246 ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules;
247 in
248 { description = "Open vSwitch Interface ${n}";
249 wantedBy = [ "network.target" (subsystemDevice n) ];
250 # and create bridge before systemd-networkd starts because it might create internal interfaces
251 before = [ "systemd-networkd.service" ];
252 # shutdown the bridge when network is shutdown
253 partOf = [ "network.target" ];
254 # requires ovs-vswitchd to be alive at all times
255 bindsTo = [ "ovs-vswitchd.service" ];
256 # start switch after physical interfaces and vswitch daemon
257 after = [ "network-pre.target" "ovs-vswitchd.service" ] ++ deps;
258 wants = deps; # if one or more interface fails, the switch should continue to run
259 serviceConfig.Type = "oneshot";
260 serviceConfig.RemainAfterExit = true;
261 path = [ pkgs.iproute2 config.virtualisation.vswitch.package ];
262 preStart = ''
263 echo "Resetting Open vSwitch ${n}..."
264 ovs-vsctl --if-exists del-br ${n} -- add-br ${n} \
265 -- set bridge ${n} protocols=${concatStringsSep "," v.supportedOpenFlowVersions}
266 '';
267 script = ''
268 echo "Configuring Open vSwitch ${n}..."
269 ovs-vsctl ${concatStrings (mapAttrsToList (name: config: " -- add-port ${n} ${name}" + optionalString (config.vlan != null) " tag=${toString config.vlan}") v.interfaces)} \
270 ${concatStrings (mapAttrsToList (name: config: optionalString (config.type != null) " -- set interface ${name} type=${config.type}") v.interfaces)} \
271 ${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \
272 ${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)}
273
274
275 echo "Adding OpenFlow rules for Open vSwitch ${n}..."
276 ovs-ofctl --protocols=${v.openFlowVersion} add-flows ${n} ${ofRules}
277 '';
278 postStop = ''
279 echo "Cleaning Open vSwitch ${n}"
280 echo "Shuting down internal ${n} interface"
281 ip link set ${n} down || true
282 echo "Deleting flows for ${n}"
283 ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true
284 echo "Deleting Open vSwitch ${n}"
285 ovs-vsctl --if-exists del-br ${n} || true
286 '';
287 });
288 in mapAttrs' createVswitchDevice cfg.vswitches
289 // {
290 "network-local-commands" = {
291 after = [ "systemd-networkd.service" ];
292 bindsTo = [ "systemd-networkd.service" ];
293 };
294 };
295 };
296
297}