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