at 16.09-beta 15 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 40in 41 42{ 43 44 config = mkIf (!cfg.useNetworkd) { 45 46 systemd.services = 47 let 48 49 networkLocalCommands = { 50 after = [ "network-setup.service" ]; 51 bindsTo = [ "network-setup.service" ]; 52 }; 53 54 networkSetup = 55 { description = "Networking Setup"; 56 57 after = [ "network-interfaces.target" "network-pre.target" ]; 58 before = [ "network.target" ]; 59 wantedBy = [ "network.target" ]; 60 61 unitConfig.ConditionCapability = "CAP_NET_ADMIN"; 62 63 path = [ pkgs.iproute ]; 64 65 serviceConfig.Type = "oneshot"; 66 serviceConfig.RemainAfterExit = true; 67 68 script = 69 '' 70 # Set the static DNS configuration, if given. 71 ${pkgs.openresolv}/sbin/resolvconf -m 1 -a static <<EOF 72 ${optionalString (cfg.nameservers != [] && cfg.domain != null) '' 73 domain ${cfg.domain} 74 ''} 75 ${optionalString (cfg.search != []) ("search " + concatStringsSep " " cfg.search)} 76 ${flip concatMapStrings cfg.nameservers (ns: '' 77 nameserver ${ns} 78 '')} 79 EOF 80 81 # Set the default gateway. 82 ${optionalString (cfg.defaultGateway != null && cfg.defaultGateway != "") '' 83 # FIXME: get rid of "|| true" (necessary to make it idempotent). 84 ip route add default via "${cfg.defaultGateway}" ${ 85 optionalString (cfg.defaultGatewayWindowSize != null) 86 "window ${toString cfg.defaultGatewayWindowSize}"} || true 87 ''} 88 ${optionalString (cfg.defaultGateway6 != null && cfg.defaultGateway6 != "") '' 89 # FIXME: get rid of "|| true" (necessary to make it idempotent). 90 ip -6 route add ::/0 via "${cfg.defaultGateway6}" ${ 91 optionalString (cfg.defaultGatewayWindowSize != null) 92 "window ${toString cfg.defaultGatewayWindowSize}"} || true 93 ''} 94 ''; 95 }; 96 97 # For each interface <foo>, create a job ‘network-addresses-<foo>.service" 98 # that performs static address configuration. It has a "wants" 99 # dependency on ‘<foo>.service’, which is supposed to create 100 # the interface and need not exist (i.e. for hardware 101 # interfaces). It has a binds-to dependency on the actual 102 # network device, so it only gets started after the interface 103 # has appeared, and it's stopped when the interface 104 # disappears. 105 configureAddrs = i: 106 let 107 ips = interfaceIps i; 108 in 109 nameValuePair "network-addresses-${i.name}" 110 { description = "Address configuration of ${i.name}"; 111 wantedBy = [ "network-interfaces.target" ]; 112 before = [ "network-interfaces.target" ]; 113 bindsTo = [ (subsystemDevice i.name) ]; 114 after = [ (subsystemDevice i.name) "network-pre.target" ]; 115 serviceConfig.Type = "oneshot"; 116 serviceConfig.RemainAfterExit = true; 117 path = [ pkgs.iproute ]; 118 script = 119 '' 120 echo "bringing up interface..." 121 ip link set "${i.name}" up 122 123 restart_network_interfaces=false 124 '' + flip concatMapStrings (ips) (ip: 125 let 126 address = "${ip.address}/${toString ip.prefixLength}"; 127 in 128 '' 129 echo "checking ip ${address}..." 130 if out=$(ip addr add "${address}" dev "${i.name}" 2>&1); then 131 echo "added ip ${address}..." 132 restart_network_setup=true 133 elif ! echo "$out" | grep "File exists" >/dev/null 2>&1; then 134 echo "failed to add ${address}" 135 exit 1 136 fi 137 '') 138 + optionalString (ips != [ ]) 139 '' 140 if [ "$restart_network_setup" = "true" ]; then 141 # Ensure that the default gateway remains set. 142 # (Flushing this interface may have removed it.) 143 ${config.systemd.package}/bin/systemctl try-restart --no-block network-setup.service 144 fi 145 ${config.systemd.package}/bin/systemctl start ip-up.target 146 ''; 147 preStop = flip concatMapStrings (ips) (ip: 148 let 149 address = "${ip.address}/${toString ip.prefixLength}"; 150 in 151 '' 152 echo -n "deleting ${address}..." 153 ip addr del "${address}" dev "${i.name}" >/dev/null 2>&1 || echo -n " Failed" 154 echo "" 155 ''); 156 }; 157 158 createTunDevice = i: nameValuePair "${i.name}-netdev" 159 { description = "Virtual Network Interface ${i.name}"; 160 requires = [ "dev-net-tun.device" ]; 161 after = [ "dev-net-tun.device" "network-pre.target" ]; 162 wantedBy = [ "network.target" (subsystemDevice i.name) ]; 163 before = [ "network-interfaces.target" (subsystemDevice i.name) ]; 164 path = [ pkgs.iproute ]; 165 serviceConfig = { 166 Type = "oneshot"; 167 RemainAfterExit = true; 168 }; 169 script = '' 170 ip tuntap add dev "${i.name}" \ 171 ${optionalString (i.virtualType != null) "mode ${i.virtualType}"} \ 172 user "${i.virtualOwner}" 173 ''; 174 postStop = '' 175 ip link del ${i.name} 176 ''; 177 }; 178 179 createBridgeDevice = n: v: nameValuePair "${n}-netdev" 180 (let 181 deps = map subsystemDevice v.interfaces; 182 in 183 { description = "Bridge Interface ${n}"; 184 wantedBy = [ "network.target" (subsystemDevice n) ]; 185 bindsTo = deps ++ optional v.rstp "mstpd.service"; 186 partOf = optional v.rstp "mstpd.service"; 187 after = [ "network-pre.target" "mstpd.service" ] ++ deps 188 ++ concatMap (i: [ "network-addresses-${i}.service" "network-link-${i}.service" ]) v.interfaces; 189 before = [ "network-interfaces.target" (subsystemDevice n) ]; 190 serviceConfig.Type = "oneshot"; 191 serviceConfig.RemainAfterExit = true; 192 path = [ pkgs.iproute ]; 193 script = '' 194 # Remove Dead Interfaces 195 echo "Removing old bridge ${n}..." 196 ip link show "${n}" >/dev/null 2>&1 && ip link del "${n}" 197 198 echo "Adding bridge ${n}..." 199 ip link add name "${n}" type bridge 200 201 # Enslave child interfaces 202 ${flip concatMapStrings v.interfaces (i: '' 203 ip link set "${i}" master "${n}" 204 ip link set "${i}" up 205 '')} 206 207 # Enable stp on the interface 208 ${optionalString v.rstp '' 209 echo 2 >/sys/class/net/${n}/bridge/stp_state 210 ''} 211 212 ip link set "${n}" up 213 ''; 214 postStop = '' 215 ip link set "${n}" down || true 216 ip link del "${n}" || true 217 ''; 218 }); 219 220 createVswitchDevice = n: v: nameValuePair "${n}-netdev" 221 (let 222 deps = map subsystemDevice v.interfaces; 223 ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules; 224 in 225 { description = "Open vSwitch Interface ${n}"; 226 wantedBy = [ "network.target" "vswitchd.service" ] ++ deps; 227 bindsTo = [ "vswitchd.service" (subsystemDevice n) ] ++ deps; 228 partOf = [ "vswitchd.service" ]; 229 after = [ "network-pre.target" "vswitchd.service" ] ++ deps; 230 before = [ "network-interfaces.target" ]; 231 serviceConfig.Type = "oneshot"; 232 serviceConfig.RemainAfterExit = true; 233 path = [ pkgs.iproute config.virtualisation.vswitch.package ]; 234 script = '' 235 echo "Removing old Open vSwitch ${n}..." 236 ovs-vsctl --if-exists del-br ${n} 237 238 echo "Adding Open vSwitch ${n}..." 239 ovs-vsctl -- add-br ${n} ${concatMapStrings (i: " -- add-port ${n} ${i}") v.interfaces} \ 240 ${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \ 241 ${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)} 242 243 echo "Adding OpenFlow rules for Open vSwitch ${n}..." 244 ovs-ofctl add-flows ${n} ${ofRules} 245 ''; 246 postStop = '' 247 ip link set ${n} down || true 248 ovs-ofctl del-flows ${n} || true 249 ovs-vsctl --if-exists del-br ${n} 250 ''; 251 }); 252 253 createBondDevice = n: v: nameValuePair "${n}-netdev" 254 (let 255 deps = map subsystemDevice v.interfaces; 256 in 257 { description = "Bond Interface ${n}"; 258 wantedBy = [ "network.target" (subsystemDevice n) ]; 259 bindsTo = deps; 260 after = [ "network-pre.target" ] ++ deps 261 ++ concatMap (i: [ "network-addresses-${i}.service" "network-link-${i}.service" ]) v.interfaces; 262 before = [ "network-interfaces.target" (subsystemDevice n) ]; 263 serviceConfig.Type = "oneshot"; 264 serviceConfig.RemainAfterExit = true; 265 path = [ pkgs.iproute pkgs.gawk ]; 266 script = '' 267 echo "Destroying old bond ${n}..." 268 ${destroyBond n} 269 270 echo "Creating new bond ${n}..." 271 ip link add name "${n}" type bond \ 272 ${optionalString (v.mode != null) "mode ${toString v.mode}"} \ 273 ${optionalString (v.miimon != null) "miimon ${toString v.miimon}"} \ 274 ${optionalString (v.xmit_hash_policy != null) "xmit_hash_policy ${toString v.xmit_hash_policy}"} \ 275 ${optionalString (v.lacp_rate != null) "lacp_rate ${toString v.lacp_rate}"} 276 277 # !!! There must be a better way to wait for the interface 278 while [ ! -d "/sys/class/net/${n}" ]; do sleep 0.1; done; 279 280 # Bring up the bond and enslave the specified interfaces 281 ip link set "${n}" up 282 ${flip concatMapStrings v.interfaces (i: '' 283 ip link set "${i}" down 284 ip link set "${i}" master "${n}" 285 '')} 286 ''; 287 postStop = destroyBond n; 288 }); 289 290 createMacvlanDevice = n: v: nameValuePair "${n}-netdev" 291 (let 292 deps = [ (subsystemDevice v.interface) ]; 293 in 294 { description = "Vlan Interface ${n}"; 295 wantedBy = [ "network.target" (subsystemDevice n) ]; 296 bindsTo = deps; 297 after = [ "network-pre.target" ] ++ deps; 298 before = [ "network-interfaces.target" (subsystemDevice n) ]; 299 serviceConfig.Type = "oneshot"; 300 serviceConfig.RemainAfterExit = true; 301 path = [ pkgs.iproute ]; 302 script = '' 303 # Remove Dead Interfaces 304 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}" 305 ip link add link "${v.interface}" name "${n}" type macvlan \ 306 ${optionalString (v.mode != null) "mode ${v.mode}"} 307 ip link set "${n}" up 308 ''; 309 postStop = '' 310 ip link delete "${n}" 311 ''; 312 }); 313 314 createSitDevice = n: v: nameValuePair "${n}-netdev" 315 (let 316 deps = optional (v.dev != null) (subsystemDevice v.dev); 317 in 318 { description = "6-to-4 Tunnel Interface ${n}"; 319 wantedBy = [ "network.target" (subsystemDevice n) ]; 320 bindsTo = deps; 321 after = [ "network-pre.target" ] ++ deps; 322 before = [ "network-interfaces.target" (subsystemDevice n) ]; 323 serviceConfig.Type = "oneshot"; 324 serviceConfig.RemainAfterExit = true; 325 path = [ pkgs.iproute ]; 326 script = '' 327 # Remove Dead Interfaces 328 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}" 329 ip link add name "${n}" type sit \ 330 ${optionalString (v.remote != null) "remote \"${v.remote}\""} \ 331 ${optionalString (v.local != null) "local \"${v.local}\""} \ 332 ${optionalString (v.ttl != null) "ttl ${toString v.ttl}"} \ 333 ${optionalString (v.dev != null) "dev \"${v.dev}\""} 334 ip link set "${n}" up 335 ''; 336 postStop = '' 337 ip link delete "${n}" 338 ''; 339 }); 340 341 createVlanDevice = n: v: nameValuePair "${n}-netdev" 342 (let 343 deps = [ (subsystemDevice v.interface) ]; 344 in 345 { description = "Vlan Interface ${n}"; 346 wantedBy = [ "network.target" (subsystemDevice n) ]; 347 bindsTo = deps; 348 after = [ "network-pre.target" ] ++ deps; 349 before = [ "network-interfaces.target" (subsystemDevice n) ]; 350 serviceConfig.Type = "oneshot"; 351 serviceConfig.RemainAfterExit = true; 352 path = [ pkgs.iproute ]; 353 script = '' 354 # Remove Dead Interfaces 355 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}" 356 ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}" 357 ip link set "${n}" up 358 ''; 359 postStop = '' 360 ip link delete "${n}" 361 ''; 362 }); 363 364 in listToAttrs ( 365 map configureAddrs interfaces ++ 366 map createTunDevice (filter (i: i.virtual) interfaces)) 367 // mapAttrs' createBridgeDevice cfg.bridges 368 // mapAttrs' createVswitchDevice cfg.vswitches 369 // mapAttrs' createBondDevice cfg.bonds 370 // mapAttrs' createMacvlanDevice cfg.macvlans 371 // mapAttrs' createSitDevice cfg.sits 372 // mapAttrs' createVlanDevice cfg.vlans 373 // { 374 "network-setup" = networkSetup; 375 "network-local-commands" = networkLocalCommands; 376 }; 377 378 services.udev.extraRules = 379 '' 380 KERNEL=="tun", TAG+="systemd" 381 ''; 382 383 }; 384 385}