1{ config, lib, utils, ... }:
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 "both" else "none";
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
23in
24
25{
26
27 config = mkIf cfg.useNetworkd {
28
29 assertions = [ {
30 assertion = cfg.defaultGatewayWindowSize == null;
31 message = "networking.defaultGatewayWindowSize is not supported by networkd.";
32 } {
33 assertion = cfg.vswitches == {};
34 message = "networking.vswichtes are not supported by networkd.";
35 } {
36 assertion = cfg.defaultGateway == null || cfg.defaultGateway.interface == null;
37 message = "networking.defaultGateway.interface is not supported by networkd.";
38 } {
39 assertion = cfg.defaultGateway6 == null || cfg.defaultGateway6.interface == null;
40 message = "networking.defaultGateway6.interface is not supported by networkd.";
41 } ] ++ flip mapAttrsToList cfg.bridges (n: { rstp, ... }: {
42 assertion = !rstp;
43 message = "networking.bridges.${n}.rstp is not supported by networkd.";
44 });
45
46 networking.dhcpcd.enable = mkDefault false;
47
48 systemd.services.network-local-commands = {
49 after = [ "systemd-networkd.service" ];
50 bindsTo = [ "systemd-networkd.service" ];
51 };
52
53 systemd.network =
54 let
55 domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain);
56 genericNetwork = override:
57 let gateway = optional (cfg.defaultGateway != null) cfg.defaultGateway.address
58 ++ optional (cfg.defaultGateway6 != null) cfg.defaultGateway6.address;
59 in {
60 DHCP = override (dhcpStr cfg.useDHCP);
61 } // optionalAttrs (gateway != [ ]) {
62 gateway = override gateway;
63 } // optionalAttrs (domains != [ ]) {
64 domains = override domains;
65 };
66 in mkMerge [ {
67 enable = true;
68 networks."99-main" = genericNetwork mkDefault;
69 }
70 (mkMerge (flip map interfaces (i: {
71 netdevs = mkIf i.virtual ({
72 "40-${i.name}" = {
73 netdevConfig = {
74 Name = i.name;
75 Kind = i.virtualType;
76 };
77 "${i.virtualType}Config" = optionalAttrs (i.virtualOwner != null) {
78 User = i.virtualOwner;
79 };
80 };
81 });
82 networks."40-${i.name}" = mkMerge [ (genericNetwork mkDefault) {
83 name = mkDefault i.name;
84 DHCP = mkForce (dhcpStr
85 (if i.useDHCP != null then i.useDHCP else cfg.useDHCP && interfaceIps i == [ ]));
86 address = flip map (interfaceIps i)
87 (ip: "${ip.address}/${toString ip.prefixLength}");
88 networkConfig.IPv6PrivacyExtensions = "kernel";
89 } ];
90 })))
91 (mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: {
92 netdevs."40-${name}" = {
93 netdevConfig = {
94 Name = name;
95 Kind = "bridge";
96 };
97 };
98 networks = listToAttrs (flip map bridge.interfaces (bi:
99 nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
100 DHCP = mkOverride 0 (dhcpStr false);
101 networkConfig.Bridge = name;
102 } ])));
103 })))
104 (mkMerge (flip mapAttrsToList cfg.bonds (name: bond: {
105 netdevs."40-${name}" = {
106 netdevConfig = {
107 Name = name;
108 Kind = "bond";
109 };
110 bondConfig = let
111 # manual mapping as of 2017-02-03
112 # man 5 systemd.netdev [BOND]
113 # to https://www.kernel.org/doc/Documentation/networking/bonding.txt
114 # driver options.
115 driverOptionMapping = let
116 trans = f: optName: { valTransform = f; optNames = [optName]; };
117 simp = trans id;
118 ms = trans (v: v + "ms");
119 in {
120 Mode = simp "mode";
121 TransmitHashPolicy = simp "xmit_hash_policy";
122 LACPTransmitRate = simp "lacp_rate";
123 MIIMonitorSec = ms "miimon";
124 UpDelaySec = ms "updelay";
125 DownDelaySec = ms "downdelay";
126 LearnPacketIntervalSec = simp "lp_interval";
127 AdSelect = simp "ad_select";
128 FailOverMACPolicy = simp "fail_over_mac";
129 ARPValidate = simp "arp_validate";
130 # apparently in ms for this value?! Upstream bug?
131 ARPIntervalSec = simp "arp_interval";
132 ARPIPTargets = simp "arp_ip_target";
133 ARPAllTargets = simp "arp_all_targets";
134 PrimaryReselectPolicy = simp "primary_reselect";
135 ResendIGMP = simp "resend_igmp";
136 PacketsPerSlave = simp "packets_per_slave";
137 GratuitousARP = { valTransform = id;
138 optNames = [ "num_grat_arp" "num_unsol_na" ]; };
139 AllSlavesActive = simp "all_slaves_active";
140 MinLinks = simp "min_links";
141 };
142
143 do = bond.driverOptions;
144 assertNoUnknownOption = let
145 knownOptions = flatten (mapAttrsToList (_: kOpts: kOpts.optNames)
146 driverOptionMapping);
147 # options that apparently don’t exist in the networkd config
148 unknownOptions = [ "primary" ];
149 assertTrace = bool: msg: if bool then true else builtins.trace msg false;
150 in assert all (driverOpt: assertTrace
151 (elem driverOpt (knownOptions ++ unknownOptions))
152 "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.")
153 (mapAttrsToList (k: _: k) do); "";
154 # get those driverOptions that have been set
155 filterSystemdOptions = filterAttrs (sysDOpt: kOpts:
156 any (kOpt: do ? "${kOpt}") kOpts.optNames);
157 # build final set of systemd options to bond values
158 buildOptionSet = mapAttrs (_: kOpts: with kOpts;
159 # we simply take the first set kernel bond option
160 # (one option has multiple names, which is silly)
161 head (map (optN: valTransform (do."${optN}"))
162 # only map those that exist
163 (filter (o: do ? "${o}") optNames)));
164 in seq assertNoUnknownOption
165 (buildOptionSet (filterSystemdOptions driverOptionMapping));
166
167 };
168
169 networks = listToAttrs (flip map bond.interfaces (bi:
170 nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
171 DHCP = mkOverride 0 (dhcpStr false);
172 networkConfig.Bond = name;
173 } ])));
174 })))
175 (mkMerge (flip mapAttrsToList cfg.macvlans (name: macvlan: {
176 netdevs."40-${name}" = {
177 netdevConfig = {
178 Name = name;
179 Kind = "macvlan";
180 };
181 macvlanConfig = optionalAttrs (macvlan.mode != null) { Mode = macvlan.mode; };
182 };
183 networks."40-${macvlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
184 macvlan = [ name ];
185 } ]);
186 })))
187 (mkMerge (flip mapAttrsToList cfg.sits (name: sit: {
188 netdevs."40-${name}" = {
189 netdevConfig = {
190 Name = name;
191 Kind = "sit";
192 };
193 tunnelConfig =
194 (optionalAttrs (sit.remote != null) {
195 Remote = sit.remote;
196 }) // (optionalAttrs (sit.local != null) {
197 Local = sit.local;
198 }) // (optionalAttrs (sit.ttl != null) {
199 TTL = sit.ttl;
200 });
201 };
202 networks = mkIf (sit.dev != null) {
203 "40-${sit.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
204 tunnel = [ name ];
205 } ]);
206 };
207 })))
208 (mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: {
209 netdevs."40-${name}" = {
210 netdevConfig = {
211 Name = name;
212 Kind = "vlan";
213 };
214 vlanConfig.Id = vlan.id;
215 };
216 networks."40-${vlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
217 vlan = [ name ];
218 } ]);
219 })))
220 ];
221
222 # We need to prefill the slaved devices with networking options
223 # This forces the network interface creator to initialize slaves.
224 networking.interfaces = listToAttrs (map (i: nameValuePair i { }) slaves);
225
226 };
227
228}