at 17.09-beta 19 kB view raw
1{ config, lib, pkgs, utils, ... }: 2 3with utils; 4with lib; 5 6let 7 8 cfg = config.networking; 9 interfaces = attrValues cfg.interfaces; 10 hasVirtuals = any (i: i.virtual) interfaces; 11 12 # We must escape interfaces due to the systemd interpretation 13 subsystemDevice = interface: 14 "sys-subsystem-net-devices-${escapeSystemdPath interface}.device"; 15 16 interfaceIps = i: 17 i.ip4 ++ optionals cfg.enableIPv6 i.ip6 18 ++ optional (i.ipAddress != null) { 19 address = i.ipAddress; 20 prefixLength = i.prefixLength; 21 } ++ optional (cfg.enableIPv6 && i.ipv6Address != null) { 22 address = i.ipv6Address; 23 prefixLength = i.ipv6PrefixLength; 24 }; 25 26 destroyBond = i: '' 27 while true; do 28 UPDATED=1 29 SLAVES=$(ip link | grep 'master ${i}' | awk -F: '{print $2}') 30 for I in $SLAVES; do 31 UPDATED=0 32 ip link set "$I" nomaster 33 done 34 [ "$UPDATED" -eq "1" ] && break 35 done 36 ip link set "${i}" down 2>/dev/null || true 37 ip link del "${i}" 2>/dev/null || true 38 ''; 39 40 # warn that these attributes are deprecated (2017-2-2) 41 # Should be removed in the release after next 42 bondDeprecation = rec { 43 deprecated = [ "lacp_rate" "miimon" "mode" "xmit_hash_policy" ]; 44 filterDeprecated = bond: (filterAttrs (attrName: attr: 45 elem attrName deprecated && attr != null) bond); 46 }; 47 48 bondWarnings = 49 let oneBondWarnings = bondName: bond: 50 mapAttrsToList (bondText bondName) (bondDeprecation.filterDeprecated bond); 51 bondText = bondName: optName: _: 52 "${bondName}.${optName} is deprecated, use ${bondName}.driverOptions"; 53 in { 54 warnings = flatten (mapAttrsToList oneBondWarnings cfg.bonds); 55 }; 56 57 normalConfig = { 58 59 systemd.services = 60 let 61 62 deviceDependency = dev: 63 # Use systemd service if we manage device creation, else 64 # trust udev when not in a container 65 if (hasAttr dev (filterAttrs (k: v: v.virtual) cfg.interfaces)) || 66 (hasAttr dev cfg.bridges) || 67 (hasAttr dev cfg.bonds) || 68 (hasAttr dev cfg.macvlans) || 69 (hasAttr dev cfg.sits) || 70 (hasAttr dev cfg.vlans) || 71 (hasAttr dev cfg.vswitches) || 72 (hasAttr dev cfg.wlanInterfaces) 73 then [ "${dev}-netdev.service" ] 74 else optional (dev != null && dev != "lo" && !config.boot.isContainer) (subsystemDevice dev); 75 76 networkLocalCommands = { 77 after = [ "network-setup.service" ]; 78 bindsTo = [ "network-setup.service" ]; 79 }; 80 81 networkSetup = 82 { description = "Networking Setup"; 83 84 after = [ "network-pre.target" "systemd-udevd.service" "systemd-sysctl.service" ]; 85 before = [ "network.target" "shutdown.target" ]; 86 wants = [ "network.target" ]; 87 conflicts = [ "shutdown.target" ]; 88 wantedBy = [ "multi-user.target" ]; 89 90 unitConfig.ConditionCapability = "CAP_NET_ADMIN"; 91 92 path = [ pkgs.iproute ]; 93 94 serviceConfig = { 95 Type = "oneshot"; 96 RemainAfterExit = true; 97 }; 98 99 unitConfig.DefaultDependencies = false; 100 101 script = 102 '' 103 # Set the static DNS configuration, if given. 104 ${pkgs.openresolv}/sbin/resolvconf -m 1 -a static <<EOF 105 ${optionalString (cfg.nameservers != [] && cfg.domain != null) '' 106 domain ${cfg.domain} 107 ''} 108 ${optionalString (cfg.search != []) ("search " + concatStringsSep " " cfg.search)} 109 ${flip concatMapStrings cfg.nameservers (ns: '' 110 nameserver ${ns} 111 '')} 112 EOF 113 114 # Set the default gateway. 115 ${optionalString (cfg.defaultGateway != null && cfg.defaultGateway.address != "") '' 116 # FIXME: get rid of "|| true" (necessary to make it idempotent). 117 ip route add default ${optionalString (cfg.defaultGateway.metric != null) 118 "metric ${toString cfg.defaultGateway.metric}" 119 } via "${cfg.defaultGateway.address}" ${ 120 optionalString (cfg.defaultGatewayWindowSize != null) 121 "window ${toString cfg.defaultGatewayWindowSize}"} ${ 122 optionalString (cfg.defaultGateway.interface != null) 123 "dev ${cfg.defaultGateway.interface}"} proto static || true 124 ''} 125 ${optionalString (cfg.defaultGateway6 != null && cfg.defaultGateway6.address != "") '' 126 # FIXME: get rid of "|| true" (necessary to make it idempotent). 127 ip -6 route add ::/0 ${optionalString (cfg.defaultGateway6.metric != null) 128 "metric ${toString cfg.defaultGateway6.metric}" 129 } via "${cfg.defaultGateway6.address}" ${ 130 optionalString (cfg.defaultGatewayWindowSize != null) 131 "window ${toString cfg.defaultGatewayWindowSize}"} ${ 132 optionalString (cfg.defaultGateway6.interface != null) 133 "dev ${cfg.defaultGateway6.interface}"} proto static || true 134 ''} 135 ''; 136 }; 137 138 # For each interface <foo>, create a job ‘network-addresses-<foo>.service" 139 # that performs static address configuration. It has a "wants" 140 # dependency on ‘<foo>.service’, which is supposed to create 141 # the interface and need not exist (i.e. for hardware 142 # interfaces). It has a binds-to dependency on the actual 143 # network device, so it only gets started after the interface 144 # has appeared, and it's stopped when the interface 145 # disappears. 146 configureAddrs = i: 147 let 148 ips = interfaceIps i; 149 in 150 nameValuePair "network-addresses-${i.name}" 151 { description = "Address configuration of ${i.name}"; 152 wantedBy = [ "network-setup.service" ]; 153 # propagate stop and reload from network-setup 154 partOf = [ "network-setup.service" ]; 155 # order before network-setup because the routes that are configured 156 # there may need ip addresses configured 157 before = [ "network-setup.service" ]; 158 bindsTo = deviceDependency i.name; 159 after = [ "network-pre.target" ] ++ (deviceDependency i.name); 160 serviceConfig.Type = "oneshot"; 161 serviceConfig.RemainAfterExit = true; 162 # Restart rather than stop+start this unit to prevent the 163 # network from dying during switch-to-configuration. 164 stopIfChanged = false; 165 path = [ pkgs.iproute ]; 166 script = 167 '' 168 # FIXME: shouldn't this be done in network-link? 169 echo "bringing up interface..." 170 ip link set "${i.name}" up 171 172 state="/run/nixos/network/addresses/${i.name}" 173 174 mkdir -p $(dirname "$state") 175 176 '' + flip concatMapStrings (ips) (ip: 177 let 178 address = "${ip.address}/${toString ip.prefixLength}"; 179 in 180 '' 181 echo "${address}" >> $state 182 if out=$(ip addr add "${address}" dev "${i.name}" 2>&1); then 183 echo "added ip ${address}" 184 elif ! echo "$out" | grep "File exists" >/dev/null 2>&1; then 185 echo "failed to add ${address}" 186 exit 1 187 fi 188 ''); 189 preStop = '' 190 state="/run/nixos/network/addresses/${i.name}" 191 while read address; do 192 echo -n "deleting $address..." 193 ip addr del "$address" dev "${i.name}" >/dev/null 2>&1 || echo -n " Failed" 194 echo "" 195 done < "$state" 196 rm -f "$state" 197 ''; 198 }; 199 200 createTunDevice = i: nameValuePair "${i.name}-netdev" 201 { description = "Virtual Network Interface ${i.name}"; 202 bindsTo = [ "dev-net-tun.device" ]; 203 after = [ "dev-net-tun.device" "network-pre.target" ]; 204 wantedBy = [ "network-setup.service" (subsystemDevice i.name) ]; 205 partOf = [ "network-setup.service" ]; 206 before = [ "network-setup.service" (subsystemDevice i.name) ]; 207 path = [ pkgs.iproute ]; 208 serviceConfig = { 209 Type = "oneshot"; 210 RemainAfterExit = true; 211 }; 212 script = '' 213 ip tuntap add dev "${i.name}" \ 214 ${optionalString (i.virtualType != null) "mode ${i.virtualType}"} \ 215 user "${i.virtualOwner}" 216 ''; 217 postStop = '' 218 ip link del ${i.name} || true 219 ''; 220 }; 221 222 createBridgeDevice = n: v: nameValuePair "${n}-netdev" 223 (let 224 deps = concatLists (map deviceDependency v.interfaces); 225 in 226 { description = "Bridge Interface ${n}"; 227 wantedBy = [ "network-setup.service" (subsystemDevice n) ]; 228 bindsTo = deps ++ optional v.rstp "mstpd.service"; 229 partOf = [ "network-setup.service" ] ++ optional v.rstp "mstpd.service"; 230 after = [ "network-pre.target" ] ++ deps ++ optional v.rstp "mstpd.service" 231 ++ concatMap (i: [ "network-addresses-${i}.service" "network-link-${i}.service" ]) v.interfaces; 232 before = [ "network-setup.service" (subsystemDevice n) ]; 233 serviceConfig.Type = "oneshot"; 234 serviceConfig.RemainAfterExit = true; 235 path = [ pkgs.iproute ]; 236 script = '' 237 # Remove Dead Interfaces 238 echo "Removing old bridge ${n}..." 239 ip link show "${n}" >/dev/null 2>&1 && ip link del "${n}" 240 241 echo "Adding bridge ${n}..." 242 ip link add name "${n}" type bridge 243 244 # Enslave child interfaces 245 ${flip concatMapStrings v.interfaces (i: '' 246 ip link set "${i}" master "${n}" 247 ip link set "${i}" up 248 '')} 249 # Save list of enslaved interfaces 250 echo "${flip concatMapStrings v.interfaces (i: '' 251 ${i} 252 '')}" > /run/${n}.interfaces 253 254 # Enable stp on the interface 255 ${optionalString v.rstp '' 256 echo 2 >/sys/class/net/${n}/bridge/stp_state 257 ''} 258 259 ip link set "${n}" up 260 ''; 261 postStop = '' 262 ip link set "${n}" down || true 263 ip link del "${n}" || true 264 rm -f /run/${n}.interfaces 265 ''; 266 reload = '' 267 # Un-enslave child interfaces (old list of interfaces) 268 for interface in `cat /run/${n}.interfaces`; do 269 ip link set "$interface" nomaster up 270 done 271 272 # Enslave child interfaces (new list of interfaces) 273 ${flip concatMapStrings v.interfaces (i: '' 274 ip link set "${i}" master "${n}" 275 ip link set "${i}" up 276 '')} 277 # Save list of enslaved interfaces 278 echo "${flip concatMapStrings v.interfaces (i: '' 279 ${i} 280 '')}" > /run/${n}.interfaces 281 282 # (Un-)set stp on the bridge 283 echo ${if v.rstp then "2" else "0"} > /sys/class/net/${n}/bridge/stp_state 284 ''; 285 reloadIfChanged = true; 286 }); 287 288 createVswitchDevice = n: v: nameValuePair "${n}-netdev" 289 (let 290 deps = concatLists (map deviceDependency v.interfaces); 291 ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules; 292 in 293 { description = "Open vSwitch Interface ${n}"; 294 wantedBy = [ "network-setup.service" "vswitchd.service" ] ++ deps; 295 bindsTo = [ "vswitchd.service" (subsystemDevice n) ] ++ deps; 296 partOf = [ "network-setup.service" "vswitchd.service" ]; 297 after = [ "network-pre.target" "vswitchd.service" ] ++ deps; 298 before = [ "network-setup.service" ]; 299 serviceConfig.Type = "oneshot"; 300 serviceConfig.RemainAfterExit = true; 301 path = [ pkgs.iproute config.virtualisation.vswitch.package ]; 302 script = '' 303 echo "Removing old Open vSwitch ${n}..." 304 ovs-vsctl --if-exists del-br ${n} 305 306 echo "Adding Open vSwitch ${n}..." 307 ovs-vsctl -- add-br ${n} ${concatMapStrings (i: " -- add-port ${n} ${i}") v.interfaces} \ 308 ${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \ 309 ${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)} 310 311 echo "Adding OpenFlow rules for Open vSwitch ${n}..." 312 ovs-ofctl add-flows ${n} ${ofRules} 313 ''; 314 postStop = '' 315 ip link set ${n} down || true 316 ovs-ofctl del-flows ${n} || true 317 ovs-vsctl --if-exists del-br ${n} 318 ''; 319 }); 320 321 createBondDevice = n: v: nameValuePair "${n}-netdev" 322 (let 323 deps = concatLists (map deviceDependency v.interfaces); 324 in 325 { description = "Bond Interface ${n}"; 326 wantedBy = [ "network-setup.service" (subsystemDevice n) ]; 327 bindsTo = deps; 328 partOf = [ "network-setup.service" ]; 329 after = [ "network-pre.target" ] ++ deps 330 ++ concatMap (i: [ "network-addresses-${i}.service" "network-link-${i}.service" ]) v.interfaces; 331 before = [ "network-setup.service" (subsystemDevice n) ]; 332 serviceConfig.Type = "oneshot"; 333 serviceConfig.RemainAfterExit = true; 334 path = [ pkgs.iproute pkgs.gawk ]; 335 script = '' 336 echo "Destroying old bond ${n}..." 337 ${destroyBond n} 338 339 echo "Creating new bond ${n}..." 340 ip link add name "${n}" type bond \ 341 ${let opts = (mapAttrs (const toString) 342 (bondDeprecation.filterDeprecated v)) 343 // v.driverOptions; 344 in concatStringsSep "\n" 345 (mapAttrsToList (set: val: " ${set} ${val} \\") opts)} 346 347 # !!! There must be a better way to wait for the interface 348 while [ ! -d "/sys/class/net/${n}" ]; do sleep 0.1; done; 349 350 # Bring up the bond and enslave the specified interfaces 351 ip link set "${n}" up 352 ${flip concatMapStrings v.interfaces (i: '' 353 ip link set "${i}" down 354 ip link set "${i}" master "${n}" 355 '')} 356 ''; 357 postStop = destroyBond n; 358 }); 359 360 createMacvlanDevice = n: v: nameValuePair "${n}-netdev" 361 (let 362 deps = deviceDependency v.interface; 363 in 364 { description = "Vlan Interface ${n}"; 365 wantedBy = [ "network-setup.service" (subsystemDevice n) ]; 366 bindsTo = deps; 367 partOf = [ "network-setup.service" ]; 368 after = [ "network-pre.target" ] ++ deps; 369 before = [ "network-setup.service" (subsystemDevice n) ]; 370 serviceConfig.Type = "oneshot"; 371 serviceConfig.RemainAfterExit = true; 372 path = [ pkgs.iproute ]; 373 script = '' 374 # Remove Dead Interfaces 375 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}" 376 ip link add link "${v.interface}" name "${n}" type macvlan \ 377 ${optionalString (v.mode != null) "mode ${v.mode}"} 378 ip link set "${n}" up 379 ''; 380 postStop = '' 381 ip link delete "${n}" || true 382 ''; 383 }); 384 385 createSitDevice = n: v: nameValuePair "${n}-netdev" 386 (let 387 deps = deviceDependency v.dev; 388 in 389 { description = "6-to-4 Tunnel Interface ${n}"; 390 wantedBy = [ "network-setup.service" (subsystemDevice n) ]; 391 bindsTo = deps; 392 partOf = [ "network-setup.service" ]; 393 after = [ "network-pre.target" ] ++ deps; 394 before = [ "network-setup.service" (subsystemDevice n) ]; 395 serviceConfig.Type = "oneshot"; 396 serviceConfig.RemainAfterExit = true; 397 path = [ pkgs.iproute ]; 398 script = '' 399 # Remove Dead Interfaces 400 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}" 401 ip link add name "${n}" type sit \ 402 ${optionalString (v.remote != null) "remote \"${v.remote}\""} \ 403 ${optionalString (v.local != null) "local \"${v.local}\""} \ 404 ${optionalString (v.ttl != null) "ttl ${toString v.ttl}"} \ 405 ${optionalString (v.dev != null) "dev \"${v.dev}\""} 406 ip link set "${n}" up 407 ''; 408 postStop = '' 409 ip link delete "${n}" || true 410 ''; 411 }); 412 413 createVlanDevice = n: v: nameValuePair "${n}-netdev" 414 (let 415 deps = deviceDependency v.interface; 416 in 417 { description = "Vlan Interface ${n}"; 418 wantedBy = [ "network-setup.service" (subsystemDevice n) ]; 419 bindsTo = deps; 420 partOf = [ "network-setup.service" ]; 421 after = [ "network-pre.target" ] ++ deps; 422 before = [ "network-setup.service" (subsystemDevice n) ]; 423 serviceConfig.Type = "oneshot"; 424 serviceConfig.RemainAfterExit = true; 425 path = [ pkgs.iproute ]; 426 script = '' 427 # Remove Dead Interfaces 428 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}" 429 ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}" 430 ip link set "${n}" up 431 ''; 432 postStop = '' 433 ip link delete "${n}" || true 434 ''; 435 }); 436 437 in listToAttrs ( 438 map configureAddrs interfaces ++ 439 map createTunDevice (filter (i: i.virtual) interfaces)) 440 // mapAttrs' createBridgeDevice cfg.bridges 441 // mapAttrs' createVswitchDevice cfg.vswitches 442 // mapAttrs' createBondDevice cfg.bonds 443 // mapAttrs' createMacvlanDevice cfg.macvlans 444 // mapAttrs' createSitDevice cfg.sits 445 // mapAttrs' createVlanDevice cfg.vlans 446 // { 447 "network-setup" = networkSetup; 448 "network-local-commands" = networkLocalCommands; 449 }; 450 451 services.udev.extraRules = 452 '' 453 KERNEL=="tun", TAG+="systemd" 454 ''; 455 456 457 }; 458 459in 460 461{ 462 config = mkMerge [ 463 bondWarnings 464 (mkIf (!cfg.useNetworkd) normalConfig) 465 ]; 466}