at 23.11-pre 20 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 interfaceRoutes = i: 16 i.ipv4.routes 17 ++ optionals cfg.enableIPv6 i.ipv6.routes; 18 19 dhcpStr = useDHCP: if useDHCP == true || useDHCP == null then "yes" else "no"; 20 21 slaves = 22 concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds)) 23 ++ concatLists (map (bridge: bridge.interfaces) (attrValues cfg.bridges)) 24 ++ map (sit: sit.dev) (attrValues cfg.sits) 25 ++ map (gre: gre.dev) (attrValues cfg.greTunnels) 26 ++ map (vlan: vlan.interface) (attrValues cfg.vlans) 27 # add dependency to physical or independently created vswitch member interface 28 # TODO: warn the user that any address configured on those interfaces will be useless 29 ++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (attrValues cfg.vswitches); 30 31 domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain); 32 genericNetwork = override: 33 let gateway = optional (cfg.defaultGateway != null && (cfg.defaultGateway.address or "") != "") cfg.defaultGateway.address 34 ++ optional (cfg.defaultGateway6 != null && (cfg.defaultGateway6.address or "") != "") cfg.defaultGateway6.address; 35 makeGateway = gateway: { 36 routeConfig = { 37 Gateway = gateway; 38 GatewayOnLink = false; 39 }; 40 }; 41 in optionalAttrs (gateway != [ ]) { 42 routes = override (map makeGateway gateway); 43 } // optionalAttrs (domains != [ ]) { 44 domains = override domains; 45 }; 46 47 genericDhcpNetworks = initrd: mkIf cfg.useDHCP { 48 networks."99-ethernet-default-dhcp" = { 49 # We want to match physical ethernet interfaces as commonly 50 # found on laptops, desktops and servers, to provide an 51 # "out-of-the-box" setup that works for common cases. This 52 # heuristic isn't perfect (it could match interfaces with 53 # custom names that _happen_ to start with en or eth), but 54 # should be good enough to make the common case easy and can 55 # be overridden on a case-by-case basis using 56 # higher-priority networks or by disabling useDHCP. 57 58 # Type=ether matches veth interfaces as well, and this is 59 # more likely to result in interfaces being configured to 60 # use DHCP when they shouldn't. 61 62 # When wait-online.anyInterface is enabled, RequiredForOnline really 63 # means "sufficient for online", so we can enable it. 64 # Otherwise, don't block the network coming online because of default networks. 65 matchConfig.Name = ["en*" "eth*"]; 66 DHCP = "yes"; 67 linkConfig.RequiredForOnline = 68 lib.mkDefault (if initrd 69 then config.boot.initrd.systemd.network.wait-online.anyInterface 70 else config.systemd.network.wait-online.anyInterface); 71 networkConfig.IPv6PrivacyExtensions = "kernel"; 72 }; 73 networks."99-wireless-client-dhcp" = { 74 # Like above, but this is much more likely to be correct. 75 matchConfig.WLANInterfaceType = "station"; 76 DHCP = "yes"; 77 linkConfig.RequiredForOnline = 78 lib.mkDefault config.systemd.network.wait-online.anyInterface; 79 networkConfig.IPv6PrivacyExtensions = "kernel"; 80 # We also set the route metric to one more than the default 81 # of 1024, so that Ethernet is preferred if both are 82 # available. 83 dhcpV4Config.RouteMetric = 1025; 84 ipv6AcceptRAConfig.RouteMetric = 1025; 85 }; 86 }; 87 88 89 interfaceNetworks = mkMerge (forEach interfaces (i: { 90 netdevs = mkIf i.virtual ({ 91 "40-${i.name}" = { 92 netdevConfig = { 93 Name = i.name; 94 Kind = i.virtualType; 95 }; 96 "${i.virtualType}Config" = optionalAttrs (i.virtualOwner != null) { 97 User = i.virtualOwner; 98 }; 99 }; 100 }); 101 networks."40-${i.name}" = mkMerge [ (genericNetwork id) { 102 name = mkDefault i.name; 103 DHCP = mkForce (dhcpStr 104 (if i.useDHCP != null then i.useDHCP else false)); 105 address = forEach (interfaceIps i) 106 (ip: "${ip.address}/${toString ip.prefixLength}"); 107 routes = forEach (interfaceRoutes i) 108 (route: { 109 # Most of these route options have not been tested. 110 # Please fix or report any mistakes you may find. 111 routeConfig = 112 optionalAttrs (route.address != null && route.prefixLength != null) { 113 Destination = "${route.address}/${toString route.prefixLength}"; 114 } // 115 optionalAttrs (route.options ? fastopen_no_cookie) { 116 FastOpenNoCookie = route.options.fastopen_no_cookie; 117 } // 118 optionalAttrs (route.via != null) { 119 Gateway = route.via; 120 } // 121 optionalAttrs (route.type != null) { 122 Type = route.type; 123 } // 124 optionalAttrs (route.options ? onlink) { 125 GatewayOnLink = true; 126 } // 127 optionalAttrs (route.options ? initrwnd) { 128 InitialAdvertisedReceiveWindow = route.options.initrwnd; 129 } // 130 optionalAttrs (route.options ? initcwnd) { 131 InitialCongestionWindow = route.options.initcwnd; 132 } // 133 optionalAttrs (route.options ? pref) { 134 IPv6Preference = route.options.pref; 135 } // 136 optionalAttrs (route.options ? mtu) { 137 MTUBytes = route.options.mtu; 138 } // 139 optionalAttrs (route.options ? metric) { 140 Metric = route.options.metric; 141 } // 142 optionalAttrs (route.options ? src) { 143 PreferredSource = route.options.src; 144 } // 145 optionalAttrs (route.options ? protocol) { 146 Protocol = route.options.protocol; 147 } // 148 optionalAttrs (route.options ? quickack) { 149 QuickAck = route.options.quickack; 150 } // 151 optionalAttrs (route.options ? scope) { 152 Scope = route.options.scope; 153 } // 154 optionalAttrs (route.options ? from) { 155 Source = route.options.from; 156 } // 157 optionalAttrs (route.options ? table) { 158 Table = route.options.table; 159 } // 160 optionalAttrs (route.options ? advmss) { 161 TCPAdvertisedMaximumSegmentSize = route.options.advmss; 162 } // 163 optionalAttrs (route.options ? ttl-propagate) { 164 TTLPropagate = route.options.ttl-propagate == "enabled"; 165 }; 166 }); 167 networkConfig.IPv6PrivacyExtensions = "kernel"; 168 linkConfig = optionalAttrs (i.macAddress != null) { 169 MACAddress = i.macAddress; 170 } // optionalAttrs (i.mtu != null) { 171 MTUBytes = toString i.mtu; 172 }; 173 }]; 174 })); 175 176in 177 178{ 179 config = mkMerge [ 180 181 (mkIf config.boot.initrd.network.enable { 182 # Note this is if initrd.network.enable, not if 183 # initrd.systemd.network.enable. By setting the latter and not the 184 # former, the user retains full control over the configuration. 185 boot.initrd.systemd.network = mkMerge [(genericDhcpNetworks true) interfaceNetworks]; 186 }) 187 188 (mkIf cfg.useNetworkd { 189 190 assertions = [ { 191 assertion = cfg.defaultGatewayWindowSize == null; 192 message = "networking.defaultGatewayWindowSize is not supported by networkd."; 193 } { 194 assertion = cfg.defaultGateway == null || cfg.defaultGateway.interface == null; 195 message = "networking.defaultGateway.interface is not supported by networkd."; 196 } { 197 assertion = cfg.defaultGateway6 == null || cfg.defaultGateway6.interface == null; 198 message = "networking.defaultGateway6.interface is not supported by networkd."; 199 } ] ++ flip mapAttrsToList cfg.bridges (n: { rstp, ... }: { 200 assertion = !rstp; 201 message = "networking.bridges.${n}.rstp is not supported by networkd."; 202 }) ++ flip mapAttrsToList cfg.fooOverUDP (n: { local, ... }: { 203 assertion = local == null; 204 message = "networking.fooOverUDP.${n}.local is not supported by networkd."; 205 }); 206 207 networking.dhcpcd.enable = mkDefault false; 208 209 systemd.network = 210 mkMerge [ { 211 enable = true; 212 } 213 (genericDhcpNetworks false) 214 interfaceNetworks 215 (mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: { 216 netdevs."40-${name}" = { 217 netdevConfig = { 218 Name = name; 219 Kind = "bridge"; 220 }; 221 }; 222 networks = listToAttrs (forEach bridge.interfaces (bi: 223 nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) { 224 DHCP = mkOverride 0 (dhcpStr false); 225 networkConfig.Bridge = name; 226 } ]))); 227 }))) 228 (mkMerge (flip mapAttrsToList cfg.bonds (name: bond: { 229 netdevs."40-${name}" = { 230 netdevConfig = { 231 Name = name; 232 Kind = "bond"; 233 }; 234 bondConfig = let 235 # manual mapping as of 2017-02-03 236 # man 5 systemd.netdev [BOND] 237 # to https://www.kernel.org/doc/Documentation/networking/bonding.txt 238 # driver options. 239 driverOptionMapping = let 240 trans = f: optName: { valTransform = f; optNames = [optName]; }; 241 simp = trans id; 242 ms = trans (v: v + "ms"); 243 in { 244 Mode = simp "mode"; 245 TransmitHashPolicy = simp "xmit_hash_policy"; 246 LACPTransmitRate = simp "lacp_rate"; 247 MIIMonitorSec = ms "miimon"; 248 UpDelaySec = ms "updelay"; 249 DownDelaySec = ms "downdelay"; 250 LearnPacketIntervalSec = simp "lp_interval"; 251 AdSelect = simp "ad_select"; 252 FailOverMACPolicy = simp "fail_over_mac"; 253 ARPValidate = simp "arp_validate"; 254 # apparently in ms for this value?! Upstream bug? 255 ARPIntervalSec = simp "arp_interval"; 256 ARPIPTargets = simp "arp_ip_target"; 257 ARPAllTargets = simp "arp_all_targets"; 258 PrimaryReselectPolicy = simp "primary_reselect"; 259 ResendIGMP = simp "resend_igmp"; 260 PacketsPerSlave = simp "packets_per_slave"; 261 GratuitousARP = { valTransform = id; 262 optNames = [ "num_grat_arp" "num_unsol_na" ]; }; 263 AllSlavesActive = simp "all_slaves_active"; 264 MinLinks = simp "min_links"; 265 }; 266 267 do = bond.driverOptions; 268 assertNoUnknownOption = let 269 knownOptions = flatten (mapAttrsToList (_: kOpts: kOpts.optNames) 270 driverOptionMapping); 271 # options that apparently don’t exist in the networkd config 272 unknownOptions = [ "primary" ]; 273 assertTrace = bool: msg: if bool then true else builtins.trace msg false; 274 in assert all (driverOpt: assertTrace 275 (elem driverOpt (knownOptions ++ unknownOptions)) 276 "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.") 277 (mapAttrsToList (k: _: k) do); ""; 278 # get those driverOptions that have been set 279 filterSystemdOptions = filterAttrs (sysDOpt: kOpts: 280 any (kOpt: do ? ${kOpt}) kOpts.optNames); 281 # build final set of systemd options to bond values 282 buildOptionSet = mapAttrs (_: kOpts: with kOpts; 283 # we simply take the first set kernel bond option 284 # (one option has multiple names, which is silly) 285 head (map (optN: valTransform (do.${optN})) 286 # only map those that exist 287 (filter (o: do ? ${o}) optNames))); 288 in seq assertNoUnknownOption 289 (buildOptionSet (filterSystemdOptions driverOptionMapping)); 290 291 }; 292 293 networks = listToAttrs (forEach bond.interfaces (bi: 294 nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) { 295 DHCP = mkOverride 0 (dhcpStr false); 296 networkConfig.Bond = name; 297 } ]))); 298 }))) 299 (mkMerge (flip mapAttrsToList cfg.macvlans (name: macvlan: { 300 netdevs."40-${name}" = { 301 netdevConfig = { 302 Name = name; 303 Kind = "macvlan"; 304 }; 305 macvlanConfig = optionalAttrs (macvlan.mode != null) { Mode = macvlan.mode; }; 306 }; 307 networks."40-${macvlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) { 308 macvlan = [ name ]; 309 } ]); 310 }))) 311 (mkMerge (flip mapAttrsToList cfg.fooOverUDP (name: fou: { 312 netdevs."40-${name}" = { 313 netdevConfig = { 314 Name = name; 315 Kind = "fou"; 316 }; 317 # unfortunately networkd cannot encode dependencies of netdevs on addresses/routes, 318 # so we cannot specify Local=, Peer=, PeerPort=. this looks like a missing feature 319 # in networkd. 320 fooOverUDPConfig = { 321 Port = fou.port; 322 Encapsulation = if fou.protocol != null then "FooOverUDP" else "GenericUDPEncapsulation"; 323 } // (optionalAttrs (fou.protocol != null) { 324 Protocol = fou.protocol; 325 }); 326 }; 327 }))) 328 (mkMerge (flip mapAttrsToList cfg.sits (name: sit: { 329 netdevs."40-${name}" = { 330 netdevConfig = { 331 Name = name; 332 Kind = "sit"; 333 }; 334 tunnelConfig = 335 (optionalAttrs (sit.remote != null) { 336 Remote = sit.remote; 337 }) // (optionalAttrs (sit.local != null) { 338 Local = sit.local; 339 }) // (optionalAttrs (sit.ttl != null) { 340 TTL = sit.ttl; 341 }) // (optionalAttrs (sit.encapsulation != null) ( 342 { 343 FooOverUDP = true; 344 Encapsulation = 345 if sit.encapsulation.type == "fou" 346 then "FooOverUDP" 347 else "GenericUDPEncapsulation"; 348 FOUDestinationPort = sit.encapsulation.port; 349 } // (optionalAttrs (sit.encapsulation.sourcePort != null) { 350 FOUSourcePort = sit.encapsulation.sourcePort; 351 }))); 352 }; 353 networks = mkIf (sit.dev != null) { 354 "40-${sit.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) { 355 tunnel = [ name ]; 356 } ]); 357 }; 358 }))) 359 (mkMerge (flip mapAttrsToList cfg.greTunnels (name: gre: { 360 netdevs."40-${name}" = { 361 netdevConfig = { 362 Name = name; 363 Kind = gre.type; 364 }; 365 tunnelConfig = 366 (optionalAttrs (gre.remote != null) { 367 Remote = gre.remote; 368 }) // (optionalAttrs (gre.local != null) { 369 Local = gre.local; 370 }) // (optionalAttrs (gre.ttl != null) { 371 TTL = gre.ttl; 372 }); 373 }; 374 networks = mkIf (gre.dev != null) { 375 "40-${gre.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) { 376 tunnel = [ name ]; 377 } ]); 378 }; 379 }))) 380 (mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: { 381 netdevs."40-${name}" = { 382 netdevConfig = { 383 Name = name; 384 Kind = "vlan"; 385 }; 386 vlanConfig.Id = vlan.id; 387 }; 388 networks."40-${vlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) { 389 vlan = [ name ]; 390 } ]); 391 }))) 392 ]; 393 394 # We need to prefill the slaved devices with networking options 395 # This forces the network interface creator to initialize slaves. 396 networking.interfaces = listToAttrs (map (i: nameValuePair i { }) slaves); 397 398 systemd.services = let 399 # We must escape interfaces due to the systemd interpretation 400 subsystemDevice = interface: 401 "sys-subsystem-net-devices-${escapeSystemdPath interface}.device"; 402 # support for creating openvswitch switches 403 createVswitchDevice = n: v: nameValuePair "${n}-netdev" 404 (let 405 deps = map subsystemDevice (attrNames (filterAttrs (_: config: config.type != "internal") v.interfaces)); 406 ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules; 407 in 408 { description = "Open vSwitch Interface ${n}"; 409 wantedBy = [ "network.target" (subsystemDevice n) ]; 410 # and create bridge before systemd-networkd starts because it might create internal interfaces 411 before = [ "systemd-networkd.service" ]; 412 # shutdown the bridge when network is shutdown 413 partOf = [ "network.target" ]; 414 # requires ovs-vswitchd to be alive at all times 415 bindsTo = [ "ovs-vswitchd.service" ]; 416 # start switch after physical interfaces and vswitch daemon 417 after = [ "network-pre.target" "ovs-vswitchd.service" ] ++ deps; 418 wants = deps; # if one or more interface fails, the switch should continue to run 419 serviceConfig.Type = "oneshot"; 420 serviceConfig.RemainAfterExit = true; 421 path = [ pkgs.iproute2 config.virtualisation.vswitch.package ]; 422 preStart = '' 423 echo "Resetting Open vSwitch ${n}..." 424 ovs-vsctl --if-exists del-br ${n} -- add-br ${n} \ 425 -- set bridge ${n} protocols=${concatStringsSep "," v.supportedOpenFlowVersions} 426 ''; 427 script = '' 428 echo "Configuring Open vSwitch ${n}..." 429 ovs-vsctl ${concatStrings (mapAttrsToList (name: config: " -- add-port ${n} ${name}" + optionalString (config.vlan != null) " tag=${toString config.vlan}") v.interfaces)} \ 430 ${concatStrings (mapAttrsToList (name: config: optionalString (config.type != null) " -- set interface ${name} type=${config.type}") v.interfaces)} \ 431 ${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \ 432 ${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)} 433 434 435 echo "Adding OpenFlow rules for Open vSwitch ${n}..." 436 ovs-ofctl --protocols=${v.openFlowVersion} add-flows ${n} ${ofRules} 437 ''; 438 postStop = '' 439 echo "Cleaning Open vSwitch ${n}" 440 echo "Shutting down internal ${n} interface" 441 ip link set ${n} down || true 442 echo "Deleting flows for ${n}" 443 ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true 444 echo "Deleting Open vSwitch ${n}" 445 ovs-vsctl --if-exists del-br ${n} || true 446 ''; 447 }); 448 in mapAttrs' createVswitchDevice cfg.vswitches 449 // { 450 "network-local-commands" = { 451 after = [ "systemd-networkd.service" ]; 452 bindsTo = [ "systemd-networkd.service" ]; 453 }; 454 }; 455 }) 456 457 ]; 458}