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