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