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