diff options
author | Gregor Kleen <gkleen@yggdrasil.li> | 2021-05-15 20:27:55 +0200 |
---|---|---|
committer | Gregor Kleen <gkleen@yggdrasil.li> | 2021-05-15 20:27:55 +0200 |
commit | 26e71c7a3496218cf86381ef3e2d167a2a3f07fe (patch) | |
tree | 0ce8fbeafb0f4fee3a1448856adbbe7a68153a37 /modules/networkd.nix | |
parent | 76daf3ac0aa3399d7fcfbadc35c14ed2d0bbe952 (diff) | |
download | nixos-26e71c7a3496218cf86381ef3e2d167a2a3f07fe.tar nixos-26e71c7a3496218cf86381ef3e2d167a2a3f07fe.tar.gz nixos-26e71c7a3496218cf86381ef3e2d167a2a3f07fe.tar.bz2 nixos-26e71c7a3496218cf86381ef3e2d167a2a3f07fe.tar.xz nixos-26e71c7a3496218cf86381ef3e2d167a2a3f07fe.zip |
networkd: multiple gateway route sections
Diffstat (limited to 'modules/networkd.nix')
-rw-r--r-- | modules/networkd.nix | 297 |
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 | |||
3 | with utils; | ||
4 | with lib; | ||
5 | |||
6 | let | ||
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 | |||
26 | in | ||
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 | } | ||