summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorWilliam A. Kennington III <william@wkennington.com>2014-11-29 19:59:31 -0800
committerWilliam A. Kennington III <william@wkennington.com>2014-11-29 19:59:31 -0800
commitbcfe7b2200af1eff797dfc9868e424c9d906ea23 (patch)
tree90f1646cdbb98bb59c16dfcffab69fa5dc4b6058 /nixos
parent20487919b2d349895798af6579b1097528f17ad2 (diff)
parenta403379b8858e0921b6a3e61aaca9147e256c5b9 (diff)
downloadnixlib-bcfe7b2200af1eff797dfc9868e424c9d906ea23.tar
nixlib-bcfe7b2200af1eff797dfc9868e424c9d906ea23.tar.gz
nixlib-bcfe7b2200af1eff797dfc9868e424c9d906ea23.tar.bz2
nixlib-bcfe7b2200af1eff797dfc9868e424c9d906ea23.tar.lz
nixlib-bcfe7b2200af1eff797dfc9868e424c9d906ea23.tar.xz
nixlib-bcfe7b2200af1eff797dfc9868e424c9d906ea23.tar.zst
nixlib-bcfe7b2200af1eff797dfc9868e424c9d906ea23.zip
Merge pull request #5043 from wkennington/master.networkd
nixos/networking: Revamp networking configuration and add an experimental networkd option.
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/config/networking.nix29
-rwxr-xr-xnixos/modules/module-list.nix2
-rw-r--r--nixos/modules/services/networking/chrony.nix4
-rw-r--r--nixos/modules/services/networking/dhcpcd.nix20
-rw-r--r--nixos/modules/services/networking/dnsmasq.nix2
-rw-r--r--nixos/modules/services/networking/gogoclient.nix3
-rw-r--r--nixos/modules/services/networking/networkmanager.nix1
-rw-r--r--nixos/modules/services/networking/ntpd.nix3
-rw-r--r--nixos/modules/services/networking/openntpd.nix3
-rw-r--r--nixos/modules/system/boot/stage-2-init.sh2
-rw-r--r--nixos/modules/system/boot/systemd-unit-options.nix537
-rw-r--r--nixos/modules/system/boot/systemd.nix254
-rw-r--r--nixos/modules/tasks/filesystems/nfs.nix6
-rw-r--r--nixos/modules/tasks/network-interfaces-scripted.nix340
-rw-r--r--nixos/modules/tasks/network-interfaces-systemd.nix174
-rw-r--r--nixos/modules/tasks/network-interfaces.nix457
-rw-r--r--nixos/release-combined.nix8
-rw-r--r--nixos/release.nix16
-rw-r--r--nixos/tests/networking.nix365
19 files changed, 1851 insertions, 375 deletions
diff --git a/nixos/modules/config/networking.nix b/nixos/modules/config/networking.nix
index 136a5bda7459..773d0b1f1a7d 100644
--- a/nixos/modules/config/networking.nix
+++ b/nixos/modules/config/networking.nix
@@ -84,13 +84,40 @@ in
               dnsmasq_conf=/etc/dnsmasq-conf.conf
               dnsmasq_resolv=/etc/dnsmasq-resolv.conf
             '';
-      };
+
+      } // (optionalAttrs config.services.resolved.enable (
+        if dnsmasqResolve then {
+          "dnsmasq-resolv.conf".source = "/run/systemd/resolve/resolv.conf";
+        } else {
+          "resolv.conf".source = "/run/systemd/resolve/resolv.conf";
+        }
+      ));
 
     # The ‘ip-up’ target is started when we have IP connectivity.  So
     # services that depend on IP connectivity (like ntpd) should be
     # pulled in by this target.
     systemd.targets.ip-up.description = "Services Requiring IP Connectivity";
 
+    # This is needed when /etc/resolv.conf is being overriden by networkd
+    # and other configurations. If the file is destroyed by an environment
+    # activation then it must be rebuilt so that applications which interface
+    # with /etc/resolv.conf directly don't break.
+    system.activationScripts.resolvconf = stringAfter [ "etc" "tmpfs" "var" ]
+      ''
+        # Systemd resolved controls its own resolv.conf
+        rm -f /run/resolvconf/interfaces/systemd
+        ${optionalString config.services.resolved.enable ''
+          rm -rf /run/resolvconf/interfaces
+          mkdir -p /run/resolvconf/interfaces
+          ln -s /run/systemd/resolve/resolv.conf /run/resolvconf/interfaces/systemd
+        ''}
+
+        # Make sure resolv.conf is up to date if not managed by systemd
+        ${optionalString (!config.services.resolved.enable) ''
+          ${pkgs.openresolv}/bin/resolvconf -u
+        ''}
+      '';
+
   };
 
 }
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 8cdc9d2dd4c1..ecf68136f97b 100755
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -388,6 +388,8 @@
   ./tasks/kbd.nix
   ./tasks/lvm.nix
   ./tasks/network-interfaces.nix
+  ./tasks/network-interfaces-systemd.nix
+  ./tasks/network-interfaces-scripted.nix
   ./tasks/scsi-link-power-management.nix
   ./tasks/swraid.nix
   ./tasks/trackpoint.nix
diff --git a/nixos/modules/services/networking/chrony.nix b/nixos/modules/services/networking/chrony.nix
index 58b30269ca7a..fe062b30e4b7 100644
--- a/nixos/modules/services/networking/chrony.nix
+++ b/nixos/modules/services/networking/chrony.nix
@@ -100,8 +100,8 @@ in
     jobs.chronyd =
       { description = "chrony daemon";
 
-        wantedBy = [ "ip-up.target" ];
-        partOf = [ "ip-up.target" ];
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
 
         path = [ chrony ];
 
diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix
index 15dbf80a987e..1ad8cbae15cf 100644
--- a/nixos/modules/services/networking/dhcpcd.nix
+++ b/nixos/modules/services/networking/dhcpcd.nix
@@ -8,15 +8,29 @@ let
 
   cfg = config.networking.dhcpcd;
 
+  interfaces = attrValues config.networking.interfaces;
+
+  enableDHCP = config.networking.useDHCP || any (i: i.useDHCP == true) interfaces;
+
   # Don't start dhcpcd on explicitly configured interfaces or on
   # interfaces that are part of a bridge, bond or sit device.
   ignoredInterfaces =
-    map (i: i.name) (filter (i: i.ip4 != [ ] || i.ipAddress != null) (attrValues config.networking.interfaces))
+    map (i: i.name) (filter (i: if i.useDHCP != null then !i.useDHCP else i.ip4 != [ ] || i.ipAddress != null) interfaces)
     ++ mapAttrsToList (i: _: i) config.networking.sits
     ++ concatLists (attrValues (mapAttrs (n: v: v.interfaces) config.networking.bridges))
     ++ concatLists (attrValues (mapAttrs (n: v: v.interfaces) config.networking.bonds))
     ++ config.networking.dhcpcd.denyInterfaces;
 
+  arrayAppendOrNull = a1: a2: if a1 == null && a2 == null then null
+    else if a1 == null then a2 else if a2 == null then a1
+      else a1 ++ a2;
+
+  # If dhcp is disabled but explicit interfaces are enabled,
+  # we need to provide dhcp just for those interfaces.
+  allowInterfaces = arrayAppendOrNull cfg.allowInterfaces
+    (if !config.networking.useDHCP && enableDHCP then
+      map (i: i.name) (filter (i: i.useDHCP == true) interfaces) else null);
+
   # Config file adapted from the one that ships with dhcpcd.
   dhcpcdConf = pkgs.writeText "dhcpcd.conf"
     ''
@@ -41,7 +55,7 @@ let
       denyinterfaces ${toString ignoredInterfaces} lo peth* vif* tap* tun* virbr* vnet* vboxnet* sit*
 
       # Use the list of allowed interfaces if specified
-      ${optionalString (cfg.allowInterfaces != null) "allowinterfaces ${toString cfg.allowInterfaces}"}
+      ${optionalString (allowInterfaces != null) "allowinterfaces ${toString allowInterfaces}"}
 
       ${cfg.extraConfig}
     '';
@@ -132,7 +146,7 @@ in
 
   ###### implementation
 
-  config = mkIf config.networking.useDHCP {
+  config = mkIf enableDHCP {
 
     systemd.services.dhcpcd =
       { description = "DHCP Client";
diff --git a/nixos/modules/services/networking/dnsmasq.nix b/nixos/modules/services/networking/dnsmasq.nix
index 5c68dd89fb12..fbb211911f1c 100644
--- a/nixos/modules/services/networking/dnsmasq.nix
+++ b/nixos/modules/services/networking/dnsmasq.nix
@@ -82,7 +82,7 @@ in
 
     systemd.services.dnsmasq = {
         description = "dnsmasq daemon";
-        after = [ "network.target" ];
+        after = [ "network.target" "systemd-resolved.conf" ];
         wantedBy = [ "multi-user.target" ];
         path = [ dnsmasq ];
         preStart = ''
diff --git a/nixos/modules/services/networking/gogoclient.nix b/nixos/modules/services/networking/gogoclient.nix
index 416007941976..9d16f0efb435 100644
--- a/nixos/modules/services/networking/gogoclient.nix
+++ b/nixos/modules/services/networking/gogoclient.nix
@@ -76,8 +76,7 @@ in
         exec ${pkgs.gogoclient}/bin/gogoc -y -f /var/lib/gogoc/gogoc.conf
       '';
     } // optionalAttrs cfg.autorun {
-      wantedBy = [ "ip-up.target" ];
-      partOf = [ "ip-up.target" ];
+      wantedBy = [ "multi-user.target" ];
     };
 
   };
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index 39e83e7b4272..37bc1df2361b 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -52,6 +52,7 @@ let
     #!/bin/sh
     if test "$2" = "up"; then
       ${config.systemd.package}/bin/systemctl start ip-up.target
+      ${config.systemd.package}/bin/systemctl start network-online.target
     fi
   '';
 
diff --git a/nixos/modules/services/networking/ntpd.nix b/nixos/modules/services/networking/ntpd.nix
index 527882aad288..8f4bf26d411d 100644
--- a/nixos/modules/services/networking/ntpd.nix
+++ b/nixos/modules/services/networking/ntpd.nix
@@ -77,8 +77,7 @@ in
     jobs.ntpd =
       { description = "NTP Daemon";
 
-        wantedBy = [ "ip-up.target" ];
-        partOf = [ "ip-up.target" ];
+        wantedBy = [ "multi-user.target" ];
 
         path = [ ntp ];
 
diff --git a/nixos/modules/services/networking/openntpd.nix b/nixos/modules/services/networking/openntpd.nix
index bd8a7a04a2af..2f9031481d1d 100644
--- a/nixos/modules/services/networking/openntpd.nix
+++ b/nixos/modules/services/networking/openntpd.nix
@@ -41,8 +41,7 @@ in
 
     systemd.services.openntpd = {
       description = "OpenNTP Server";
-      wantedBy = [ "ip-up.target" ];
-      partOf = [ "ip-up.target" ];
+      wantedBy = [ "multi-user.target" ];
       serviceConfig.ExecStart = "${package}/sbin/ntpd -d -f ${cfgFile}";
     };
   };
diff --git a/nixos/modules/system/boot/stage-2-init.sh b/nixos/modules/system/boot/stage-2-init.sh
index 6fff776f8581..3762bda94a5c 100644
--- a/nixos/modules/system/boot/stage-2-init.sh
+++ b/nixos/modules/system/boot/stage-2-init.sh
@@ -141,8 +141,6 @@ fi
 # Use /etc/resolv.conf supplied by systemd-nspawn, if applicable.
 if [ -n "@useHostResolvConf@" -a -e /etc/resolv.conf ]; then
     cat /etc/resolv.conf | resolvconf -m 1000 -a host
-else
-    touch /etc/resolv.conf
 fi
 
 
diff --git a/nixos/modules/system/boot/systemd-unit-options.nix b/nixos/modules/system/boot/systemd-unit-options.nix
index 07f3cb9e952c..20851c626d75 100644
--- a/nixos/modules/system/boot/systemd-unit-options.nix
+++ b/nixos/modules/system/boot/systemd-unit-options.nix
@@ -4,15 +4,184 @@ with lib;
 
 let
 
-  checkService = v:
-    let assertValueOneOf = name: values: attr:
-          let val = attr.${name};
-          in optional (attr ? ${name} && !elem val values) "Systemd service field `${name}' cannot have value `${val}'.";
-        checkType = assertValueOneOf "Type" ["simple" "forking" "oneshot" "dbus" "notify" "idle"];
-        checkRestart = assertValueOneOf "Restart" ["no" "on-success" "on-failure" "on-abort" "always"];
-        errors = concatMap (c: c v) [checkType checkRestart];
-    in if errors == [] then true
-       else builtins.trace (concatStringsSep "\n" errors) false;
+  boolValues = [true false "yes" "no"];
+
+  assertValueOneOf = name: values: group: attr:
+    optional (attr ? ${name} && !elem attr.${name} values)
+      "Systemd ${group} field `${name}' cannot have value `${attr.${name}}'.";
+
+  assertHasField = name: group: attr:
+    optional (!(attr ? ${name}))
+      "Systemd ${group} field `${name}' must exist.";
+
+  assertOnlyFields = fields: group: attr:
+    let badFields = filter (name: ! elem name fields) (attrNames attr); in
+    optional (badFields != [ ])
+      "Systemd ${group} has extra fields [${concatStringsSep " " badFields}].";
+
+  assertRange = name: min: max: group: attr:
+    optional (attr ? ${name} && !(min <= attr.${name} && max >= attr.${name}))
+      "Systemd ${group} field `${name}' is outside the range [${toString min},${toString max}]";
+
+  digits = map toString (range 0 9);
+
+  isByteFormat = s:
+    let
+      l = reverseList (stringToCharacters s);
+      suffix = head l;
+      nums = tail l;
+    in elem suffix (["K" "M" "G" "T"] ++ digits)
+      && all (num: elem num digits) nums;
+
+  assertByteFormat = name: group: attr:
+    optional (attr ? ${name} && ! isByteFormat attr.${name})
+      "Systemd ${group} field `${name}' must be in byte format [0-9]+[KMGT].";
+
+  hexChars = stringToCharacters "0123456789abcdefABCDEF";
+
+  isMacAddress = s: stringLength s == 17
+    && flip all (splitString ":" s) (bytes:
+      all (byte: elem byte hexChars) (stringToCharacters bytes)
+    );
+
+  assertMacAddress = name: group: attr:
+    optional (attr ? ${name} && ! isMacAddress attr.${name})
+      "Systemd ${group} field `${name}' must be a valid mac address.";
+
+  checkUnitConfig = group: checks: v:
+    let errors = concatMap (c: c group v) checks; in
+    if errors == [] then true
+      else builtins.trace (concatStringsSep "\n" errors) false;
+
+  checkService = checkUnitConfig "Service" [
+    (assertValueOneOf "Type" [
+      "simple" "forking" "oneshot" "dbus" "notify" "idle"
+    ])
+    (assertValueOneOf "Restart" [
+      "no" "on-success" "on-failure" "on-abort" "always"
+    ])
+  ];
+
+  checkLink = checkUnitConfig "Link" [
+    (assertOnlyFields [
+      "Description" "Alias" "MACAddressPolicy" "MACAddress" "NamePolicy" "Name"
+      "MTUBytes" "BitsPerSecond" "Duplex" "WakeOnLan"
+    ])
+    (assertValueOneOf "MACAddressPolicy" ["persistent" "random"])
+    (assertMacAddress "MACAddress")
+    (assertValueOneOf "NamePolicy" [
+      "kernel" "database" "onboard" "slot" "path" "mac"
+    ])
+    (assertByteFormat "MTUBytes")
+    (assertByteFormat "BitsPerSecond")
+    (assertValueOneOf "Duplex" ["half" "full"])
+    (assertValueOneOf "WakeOnLan" ["phy" "magic" "off"])
+  ];
+
+  checkNetdev = checkUnitConfig "Netdev" [
+    (assertOnlyFields [
+      "Description" "Name" "Kind" "MTUBytes" "MACAddress"
+    ])
+    (assertHasField "Name")
+    (assertHasField "Kind")
+    (assertValueOneOf "Kind" [
+      "bridge" "bond" "vlan" "macvlan" "vxlan" "ipip"
+      "gre" "sit" "vti" "veth" "tun" "tap" "dummy"
+    ])
+    (assertByteFormat "MTUBytes")
+    (assertMacAddress "MACAddress")
+  ];
+
+  checkVlan = checkUnitConfig "VLAN" [
+    (assertOnlyFields ["Id"])
+    (assertRange "Id" 0 4094)
+  ];
+
+  checkMacvlan = checkUnitConfig "MACVLAN" [
+    (assertOnlyFields ["Mode"])
+    (assertValueOneOf "Mode" ["private" "vepa" "bridge" "passthru"])
+  ];
+
+  checkVxlan = checkUnitConfig "VXLAN" [
+    (assertOnlyFields ["Id" "Group" "TOS" "TTL" "MacLearning"])
+    (assertRange "TTL" 0 255)
+    (assertValueOneOf "MacLearning" boolValues)
+  ];
+
+  checkTunnel = checkUnitConfig "Tunnel" [
+    (assertOnlyFields ["Local" "Remote" "TOS" "TTL" "DiscoverPathMTU"])
+    (assertRange "TTL" 0 255)
+    (assertValueOneOf "DiscoverPathMTU" boolValues)
+  ];
+
+  checkPeer = checkUnitConfig "Peer" [
+    (assertOnlyFields ["Name" "MACAddress"])
+    (assertMacAddress "MACAddress")
+  ];
+
+  tunTapChecks = [
+    (assertOnlyFields ["OneQueue" "MultiQueue" "PacketInfo" "User" "Group"])
+    (assertValueOneOf "OneQueue" boolValues)
+    (assertValueOneOf "MultiQueue" boolValues)
+    (assertValueOneOf "PacketInfo" boolValues)
+  ];
+
+  checkTun = checkUnitConfig "Tun" tunTapChecks;
+
+  checkTap = checkUnitConfig "Tap" tunTapChecks;
+
+  checkBond = checkUnitConfig "Bond" [
+    (assertOnlyFields [
+      "Mode" "TransmitHashPolicy" "LACPTransmitRate" "MIIMonitorSec"
+      "UpDelaySec" "DownDelaySec"
+    ])
+    (assertValueOneOf "Mode" [
+      "balance-rr" "active-backup" "balance-xor"
+      "broadcast" "802.3ad" "balance-tlb" "balance-alb"
+    ])
+    (assertValueOneOf "TransmitHashPolicy" [
+      "layer2" "layer3+4" "layer2+3" "encap2+3" "802.3ad" "encap3+4"
+    ])
+    (assertValueOneOf "LACPTransmitRate" ["slow" "fast"])
+  ];
+
+  checkNetwork = checkUnitConfig "Network" [
+    (assertOnlyFields [
+      "Description" "DHCP" "DHCPServer" "IPv4LL" "IPv4LLRoute"
+      "LLMNR" "Domains" "Bridge" "Bond"
+    ])
+    (assertValueOneOf "DHCP" ["both" "none" "v4" "v6"])
+    (assertValueOneOf "DHCPServer" boolValues)
+    (assertValueOneOf "IPv4LL" boolValues)
+    (assertValueOneOf "IPv4LLRoute" boolValues)
+    (assertValueOneOf "LLMNR" boolValues)
+  ];
+
+  checkAddress = checkUnitConfig "Address" [
+    (assertOnlyFields ["Address" "Peer" "Broadcast" "Label"])
+    (assertHasField "Address")
+  ];
+
+  checkRoute = checkUnitConfig "Route" [
+    (assertOnlyFields ["Gateway" "Destination" "Metric"])
+    (assertHasField "Gateway")
+  ];
+
+  checkDhcp = checkUnitConfig "DHCP" [
+    (assertOnlyFields [
+      "UseDNS" "UseMTU" "SendHostname" "UseHostname" "UseDomains" "UseRoutes"
+      "CriticalConnections" "VendorClassIdentifier" "RequestBroadcast"
+      "RouteMetric"
+    ])
+    (assertValueOneOf "UseDNS" boolValues)
+    (assertValueOneOf "UseMTU" boolValues)
+    (assertValueOneOf "SendHostname" boolValues)
+    (assertValueOneOf "UseHostname" boolValues)
+    (assertValueOneOf "UseDomains" boolValues)
+    (assertValueOneOf "UseRoutes" boolValues)
+    (assertValueOneOf "CriticalConnections" boolValues)
+    (assertValueOneOf "RequestBroadcast" boolValues)
+  ];
 
   unitOption = mkOptionType {
     name = "systemd option";
@@ -140,6 +309,15 @@ in rec {
       '';
     };
 
+    requisite = mkOption {
+      default = [];
+      type = types.listOf types.str;
+      description = ''
+        Similar to requires. However if the units listed are not started,
+        they will not be started and the transaction will fail.
+      '';
+    };
+
     unitConfig = mkOption {
       default = {};
       example = { RequiresMountsFor = "/data"; };
@@ -441,4 +619,345 @@ in rec {
 
   targetOptions = commonUnitOptions;
 
+  commonNetworkOptions = {
+
+    enable = mkOption {
+      default = true;
+      type = types.bool;
+      description = ''
+        If set to false, this unit will be a symlink to
+        /dev/null.
+      '';
+    };
+
+    matchConfig = mkOption {
+      default = {};
+      example = { Name = "eth0"; };
+      type = types.attrsOf unitOption;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Match]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.link</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for details.
+      '';
+    };
+
+  };
+
+  linkOptions = commonNetworkOptions // {
+
+    linkConfig = mkOption {
+      default = {};
+      example = { MACAddress = "00:ff:ee:aa:cc:dd"; };
+      type = types.addCheck (types.attrsOf unitOption) checkLink;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Link]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.link</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+  };
+
+  netdevOptions = commonNetworkOptions // {
+
+    netdevConfig = mkOption {
+      default = {};
+      example = { Name = "mybridge"; Kind = "bridge"; };
+      type = types.addCheck (types.attrsOf unitOption) checkNetdev;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Netdev]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    vlanConfig = mkOption {
+      default = {};
+      example = { Id = "4"; };
+      type = types.addCheck (types.attrsOf unitOption) checkVlan;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[VLAN]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    macvlanConfig = mkOption {
+      default = {};
+      example = { Mode = "private"; };
+      type = types.addCheck (types.attrsOf unitOption) checkMacvlan;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[MACVLAN]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    vxlanConfig = mkOption {
+      default = {};
+      example = { Id = "4"; };
+      type = types.addCheck (types.attrsOf unitOption) checkVxlan;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[VXLAN]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    tunnelConfig = mkOption {
+      default = {};
+      example = { Remote = "192.168.1.1"; };
+      type = types.addCheck (types.attrsOf unitOption) checkTunnel;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Tunnel]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    peerConfig = mkOption {
+      default = {};
+      example = { Name = "veth2"; };
+      type = types.addCheck (types.attrsOf unitOption) checkPeer;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Peer]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    tunConfig = mkOption {
+      default = {};
+      example = { User = "openvpn"; };
+      type = types.addCheck (types.attrsOf unitOption) checkTun;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Tun]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    tapConfig = mkOption {
+      default = {};
+      example = { User = "openvpn"; };
+      type = types.addCheck (types.attrsOf unitOption) checkTap;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Tap]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    bondConfig = mkOption {
+      default = {};
+      example = { Mode = "802.3ad"; };
+      type = types.addCheck (types.attrsOf unitOption) checkBond;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Bond]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+  };
+
+  addressOptions = {
+
+    addressConfig = mkOption {
+      default = {};
+      example = { Address = "192.168.0.100/24"; };
+      type = types.addCheck (types.attrsOf unitOption) checkAddress;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Address]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+  };
+
+  routeOptions = {
+
+    routeConfig = mkOption {
+      default = {};
+      example = { Gateway = "192.168.0.1"; };
+      type = types.addCheck (types.attrsOf unitOption) checkRoute;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Route]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+  };
+
+  networkOptions = commonNetworkOptions // {
+
+    networkConfig = mkOption {
+      default = {};
+      example = { Description = "My Network"; };
+      type = types.addCheck (types.attrsOf unitOption) checkNetwork;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Network]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    dhcpConfig = mkOption {
+      default = {};
+      example = { UseDNS = true; UseRoutes = true; };
+      type = types.addCheck (types.attrsOf unitOption) checkDhcp;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[DHCP]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    name = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The name of the network interface to match against.
+      '';
+    };
+
+    DHCP = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        Whether to enable DHCP on the interfaces matched.
+      '';
+    };
+
+    domains = mkOption {
+      type = types.nullOr (types.listOf types.str);
+      default = null;
+      description = ''
+        A list of domains to pass to the network config.
+      '';
+    };
+
+    address = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of addresses to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    gateway = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of gateways to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    dns = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of dns servers to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    ntp = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of ntp servers to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    vlan = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of vlan interfaces to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    macvlan = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of macvlan interfaces to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    vxlan = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of vxlan interfaces to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    tunnel = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of tunnel interfaces to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    addresses = mkOption {
+      default = [ ];
+      type = types.listOf types.optionSet;
+      options = [ addressOptions ];
+      description = ''
+        A list of address sections to be added to the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+    routes = mkOption {
+      default = [ ];
+      type = types.listOf types.optionSet;
+      options = [ routeOptions ];
+      description = ''
+        A list of route sections to be added to the unit.  See
+        <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
+  };
+
 }
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 8a86149a9e12..89029a098e96 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -96,6 +96,12 @@ let
       "systemd-modules-load.service"
       "kmod-static-nodes.service"
 
+      # Networking
+      "systemd-networkd.service"
+      "systemd-networkd-wait-online.service"
+      "systemd-resolved.service"
+      "systemd-timesyncd.service"
+
       # Filesystems.
       "systemd-fsck@.service"
       "systemd-fsck-root.service"
@@ -212,6 +218,8 @@ let
           { PartOf = toString config.partOf; }
         // optionalAttrs (config.conflicts != [])
           { Conflicts = toString config.conflicts; }
+        // optionalAttrs (config.requisite != [])
+          { Requisite = toString config.requisite; }
         // optionalAttrs (config.restartTriggers != [])
           { X-Restart-Triggers = toString config.restartTriggers; }
         // optionalAttrs (config.description != "") {
@@ -292,6 +300,19 @@ let
     };
   };
 
+  networkConfig = { name, config, ... }: {
+    config = {
+      matchConfig = optionalAttrs (config.name != null) {
+        Name = config.name;
+      };
+      networkConfig = optionalAttrs (config.DHCP != null) {
+        DHCP = config.DHCP;
+      } // optionalAttrs (config.domains != null) {
+        Domains = concatStringsSep " " config.domains;
+      };
+    };
+  };
+
   toOption = x:
     if x == true then "true"
     else if x == false then "false"
@@ -384,6 +405,103 @@ let
         '';
     };
 
+  commonMatchText = def: ''
+      [Match]
+      ${attrsToSection def.matchConfig}
+    '';
+
+  linkToUnit = name: def:
+    { inherit (def) enable;
+      text = commonMatchText def +
+        ''
+          [Link]
+          ${attrsToSection def.linkConfig}
+        '';
+    };
+
+  netdevToUnit = name: def:
+    { inherit (def) enable;
+      text = commonMatchText def +
+        ''
+          [NetDev]
+          ${attrsToSection def.netdevConfig}
+
+          ${optionalString (def.vlanConfig != { }) ''
+            [VLAN]
+            ${attrsToSection def.vlanConfig}
+
+          ''}
+          ${optionalString (def.macvlanConfig != { }) ''
+            [MACVLAN]
+            ${attrsToSection def.macvlanConfig}
+
+          ''}
+          ${optionalString (def.vxlanConfig != { }) ''
+            [VXLAN]
+            ${attrsToSection def.vxlanConfig}
+
+          ''}
+          ${optionalString (def.tunnelConfig != { }) ''
+            [Tunnel]
+            ${attrsToSection def.tunnelConfig}
+
+          ''}
+          ${optionalString (def.peerConfig != { }) ''
+            [Peer]
+            ${attrsToSection def.peerConfig}
+
+          ''}
+          ${optionalString (def.tunConfig != { }) ''
+            [Tun]
+            ${attrsToSection def.tunConfig}
+
+          ''}
+          ${optionalString (def.tapConfig != { }) ''
+            [Tap]
+            ${attrsToSection def.tapConfig}
+
+          ''}
+          ${optionalString (def.bondConfig != { }) ''
+            [Bond]
+            ${attrsToSection def.bondConfig}
+
+          ''}
+        '';
+    };
+
+  networkToUnit = name: def:
+    { inherit (def) enable;
+      text = commonMatchText def +
+        ''
+          [Network]
+          ${attrsToSection def.networkConfig}
+          ${concatStringsSep "\n" (map (s: "Address=${s}") def.address)}
+          ${concatStringsSep "\n" (map (s: "Gateway=${s}") def.gateway)}
+          ${concatStringsSep "\n" (map (s: "DNS=${s}") def.dns)}
+          ${concatStringsSep "\n" (map (s: "NTP=${s}") def.ntp)}
+          ${concatStringsSep "\n" (map (s: "VLAN=${s}") def.vlan)}
+          ${concatStringsSep "\n" (map (s: "MACVLAN=${s}") def.macvlan)}
+          ${concatStringsSep "\n" (map (s: "VXLAN=${s}") def.vxlan)}
+          ${concatStringsSep "\n" (map (s: "Tunnel=${s}") def.tunnel)}
+
+          ${optionalString (def.dhcpConfig != { }) ''
+            [DHCP]
+            ${attrsToSection def.dhcpConfig}
+
+          ''}
+          ${flip concatMapStrings def.addresses (x: ''
+            [Address]
+            ${attrsToSection x.addressConfig}
+
+          '')}
+          ${flip concatMapStrings def.routes (x: ''
+            [Route]
+            ${attrsToSection x.routeConfig}
+
+          '')}
+        '';
+    };
+
   generateUnits = type: units: upstreamUnits: upstreamWants:
     pkgs.runCommand "${type}-units" { preferLocalBuild = true; } ''
       mkdir -p $out
@@ -468,8 +586,9 @@ let
         mkdir -p $out/getty.target.wants/
         ln -s ../autovt@tty1.service $out/getty.target.wants/
 
-        ln -s ../local-fs.target ../remote-fs.target ../network.target ../nss-lookup.target \
-              ../nss-user-lookup.target ../swap.target $out/multi-user.target.wants/
+        ln -s ../local-fs.target ../remote-fs.target ../network.target \
+        ../nss-lookup.target ../nss-user-lookup.target ../swap.target \
+        $out/multi-user.target.wants/
       ''}
     ''; # */
 
@@ -562,6 +681,47 @@ in
       '';
     };
 
+    systemd.network.enable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether to enable networkd or not.
+      '';
+    };
+
+    systemd.network.links = mkOption {
+      default = {};
+      type = types.attrsOf types.optionSet;
+      options = [ linkOptions ];
+      description = "Definiton of systemd network links.";
+    };
+
+    systemd.network.netdevs = mkOption {
+      default = {};
+      type = types.attrsOf types.optionSet;
+      options = [ netdevOptions ];
+      description = "Definiton of systemd network devices.";
+    };
+
+    systemd.network.networks = mkOption {
+      default = {};
+      type = types.attrsOf types.optionSet;
+      options = [ networkOptions networkConfig ];
+      description = "Definiton of systemd networks.";
+    };
+
+    systemd.network.units = mkOption {
+      description = "Definition of networkd units.";
+      default = {};
+      type = types.attrsOf types.optionSet;
+      options = { name, config, ... }:
+        { options = concreteUnitOptions;
+          config = {
+            unit = mkDefault (makeUnit name config);
+          };
+        };
+    };
+
     systemd.defaultUnit = mkOption {
       default = "multi-user.target";
       type = types.str;
@@ -645,6 +805,22 @@ in
       '';
     };
 
+    services.resolved.enable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Enables the systemd dns resolver daemon.
+      '';
+    };
+
+    services.timesyncd.enable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Enables the systemd ntp client daemon.
+      '';
+    };
+
     systemd.tmpfiles.rules = mkOption {
       type = types.listOf types.str;
       default = [];
@@ -701,7 +877,7 @@ in
 
   ###### implementation
 
-  config = {
+  config = mkMerge [ {
 
     warnings = concatLists (mapAttrsToList (name: service:
       optional (service.serviceConfig.Type or "" == "oneshot" && service.serviceConfig.Restart or "no" != "no")
@@ -714,6 +890,9 @@ in
     environment.etc."systemd/system".source =
       generateUnits "system" cfg.units upstreamSystemUnits upstreamSystemWants;
 
+    environment.etc."systemd/network".source =
+      generateUnits "network" cfg.network.units [] [];
+
     environment.etc."systemd/user".source =
       generateUnits "user" cfg.user.units upstreamUserUnits [];
 
@@ -766,6 +945,8 @@ in
         unitConfig.X-StopOnReconfiguration = true;
       };
 
+    systemd.targets.network-online.after = [ "ip-up.target" ];
+
     systemd.units =
       mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.targets
       // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
@@ -779,6 +960,11 @@ in
                    (v: let n = escapeSystemdPath v.where;
                        in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts);
 
+    systemd.network.units =
+      mapAttrs' (n: v: nameValuePair "${n}.link" (linkToUnit n v)) cfg.network.links
+      // mapAttrs' (n: v: nameValuePair "${n}.netdev" (netdevToUnit n v)) cfg.network.netdevs
+      // mapAttrs' (n: v: nameValuePair "${n}.network" (networkToUnit n v)) cfg.network.networks;
+
     systemd.user.units =
       mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.user.services
       // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.user.sockets;
@@ -833,5 +1019,65 @@ in
     systemd.services.systemd-remount-fs.restartIfChanged = false;
     systemd.services.systemd-journal-flush.restartIfChanged = false;
 
-  };
+  }
+  (mkIf config.systemd.network.enable {
+    users.extraUsers.systemd-network.uid = config.ids.uids.systemd-network;
+    users.extraGroups.systemd-network.gid = config.ids.gids.systemd-network;
+
+    systemd.services.systemd-networkd = {
+      wantedBy = [ "multi-user.target" ];
+      restartTriggers = [ config.environment.etc."systemd/network".source ];
+    };
+
+    systemd.services.systemd-networkd-wait-online = {
+      before = [ "network-online.target" "ip-up.target" ];
+      wantedBy = [ "network-online.target" "ip-up.target" ];
+    };
+
+    systemd.services."systemd-network-wait-online@" = {
+      description = "Wait for Network Interface %I to be Configured";
+      conflicts = [ "shutdown.target" ];
+      requisite = [ "systemd-networkd.service" ];
+      after = [ "systemd-networkd.service" ];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStart = "${config.systemd.package}/lib/systemd/systemd-networkd-wait-online -i %I";
+      };
+    };
+
+    services.resolved.enable = mkDefault true;
+    services.timesyncd.enable = mkDefault config.services.ntp.enable;
+  })
+  (mkIf config.services.resolved.enable {
+    users.extraUsers.systemd-resolve.uid = config.ids.uids.systemd-resolve;
+    users.extraGroups.systemd-resolve.gid = config.ids.gids.systemd-resolve;
+
+    systemd.services.systemd-resolved = {
+      wantedBy = [ "multi-user.target" ];
+      restartTriggers = [ config.environment.etc."systemd/resolved.conf".source ];
+    };
+
+    environment.etc."systemd/resolved.conf".text = ''
+      [Resolve]
+      DNS=${concatStringsSep " " config.networking.nameservers}
+    '';
+  })
+  (mkIf config.services.timesyncd.enable {
+    users.extraUsers.systemd-timesync.uid = config.ids.uids.systemd-timesync;
+    users.extraGroups.systemd-timesync.gid = config.ids.gids.systemd-timesync;
+
+    systemd.services.systemd-timesyncd = {
+      wantedBy = [ "sysinit.target" ];
+      restartTriggers = [ config.environment.etc."systemd/timesyncd.conf".source ];
+    };
+
+    environment.etc."systemd/timesyncd.conf".text = ''
+      [Time]
+      NTP=${concatStringsSep " " config.services.ntp.servers}
+    '';
+
+    systemd.services.ntpd.enable = false;
+  })
+  ];
 }
diff --git a/nixos/modules/tasks/filesystems/nfs.nix b/nixos/modules/tasks/filesystems/nfs.nix
index 16752ce7e1b5..f7f2d9658069 100644
--- a/nixos/modules/tasks/filesystems/nfs.nix
+++ b/nixos/modules/tasks/filesystems/nfs.nix
@@ -73,8 +73,7 @@ in
 
         path = [ pkgs.nfsUtils pkgs.sysvtools pkgs.utillinux ];
 
-        wantedBy = [ "network-online.target" "multi-user.target" ];
-        before = [ "network-online.target" ];
+        wantedBy = [ "multi-user.target" ];
         requires = [ "basic.target" "rpcbind.service" ];
         after = [ "basic.target" "rpcbind.service" "network.target" ];
 
@@ -100,8 +99,7 @@ in
 
         path = [ pkgs.sysvtools pkgs.utillinux ];
 
-        wantedBy = [ "network-online.target" "multi-user.target" ];
-        before = [ "network-online.target" ];
+        wantedBy = [ "multi-user.target" ];
         requires = [ "rpcbind.service" ];
         after = [ "rpcbind.service" ];
 
diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix
new file mode 100644
index 000000000000..30fcb3a80104
--- /dev/null
+++ b/nixos/modules/tasks/network-interfaces-scripted.nix
@@ -0,0 +1,340 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+with utils;
+
+let
+
+  cfg = config.networking;
+  interfaces = attrValues cfg.interfaces;
+  hasVirtuals = any (i: i.virtual) interfaces;
+
+  # We must escape interfaces due to the systemd interpretation
+  subsystemDevice = interface:
+    "sys-subsystem-net-devices-${escapeSystemdPath interface}.device";
+
+  interfaceIps = i:
+    i.ip4 ++ optionals cfg.enableIPv6 i.ip6
+    ++ optional (i.ipAddress != null) {
+      address = i.ipAddress;
+      prefixLength = i.prefixLength;
+    } ++ optional (cfg.enableIPv6 && i.ipv6Address != null) {
+      address = i.ipv6Address;
+      prefixLength = i.ipv6PrefixLength;
+    };
+
+  destroyBond = i: ''
+    while true; do
+      UPDATED=1
+      SLAVES=$(ip link | grep 'master ${i}' | awk -F: '{print $2}')
+      for I in $SLAVES; do
+        UPDATED=0
+        ip link set "$I" nomaster
+      done
+      [ "$UPDATED" -eq "1" ] && break
+    done
+    ip link set "${i}" down || true
+    ip link del "${i}" || true
+  '';
+
+in
+
+{
+
+  config = mkIf (!cfg.useNetworkd) {
+
+    systemd.targets."network-interfaces" =
+      { description = "All Network Interfaces";
+        wantedBy = [ "network.target" ];
+        unitConfig.X-StopOnReconfiguration = true;
+      };
+
+    systemd.services =
+      let
+
+        networkLocalCommands = {
+          after = [ "network-setup.service" ];
+          bindsTo = [ "network-setup.service" ];
+        };
+
+        networkSetup =
+          { description = "Networking Setup";
+
+            after = [ "network-interfaces.target" ];
+            before = [ "network.target" ];
+            wantedBy = [ "network.target" ];
+
+            unitConfig.ConditionCapability = "CAP_NET_ADMIN";
+
+            path = [ pkgs.iproute ];
+
+            serviceConfig.Type = "oneshot";
+            serviceConfig.RemainAfterExit = true;
+
+            script =
+              (optionalString (!config.services.resolved.enable) ''
+                # Set the static DNS configuration, if given.
+                ${pkgs.openresolv}/sbin/resolvconf -m 1 -a static <<EOF
+                ${optionalString (cfg.nameservers != [] && cfg.domain != null) ''
+                  domain ${cfg.domain}
+                ''}
+                ${optionalString (cfg.search != []) ("search " + concatStringsSep " " cfg.search)}
+                ${flip concatMapStrings cfg.nameservers (ns: ''
+                  nameserver ${ns}
+                '')}
+                EOF
+              '') + ''
+                # Set the default gateway.
+                ${optionalString (cfg.defaultGateway != null) ''
+                  # FIXME: get rid of "|| true" (necessary to make it idempotent).
+                  ip route add default via "${cfg.defaultGateway}" ${
+                    optionalString (cfg.defaultGatewayWindowSize != null)
+                      "window ${cfg.defaultGatewayWindowSize}"} || true
+                ''}
+              '';
+          };
+
+        # For each interface <foo>, create a job ‘network-addresses-<foo>.service"
+        # that performs static address configuration.  It has a "wants"
+        # dependency on ‘<foo>.service’, which is supposed to create
+        # the interface and need not exist (i.e. for hardware
+        # interfaces).  It has a binds-to dependency on the actual
+        # network device, so it only gets started after the interface
+        # has appeared, and it's stopped when the interface
+        # disappears.
+        configureAddrs = i:
+          let
+            ips = interfaceIps i;
+          in
+          nameValuePair "network-addresses-${i.name}"
+          { description = "Addresss configuration of ${i.name}";
+            wantedBy = [ "network-interfaces.target" ];
+            before = [ "network-interfaces.target" ];
+            bindsTo = [ (subsystemDevice i.name) ];
+            after = [ (subsystemDevice i.name) ];
+            serviceConfig.Type = "oneshot";
+            serviceConfig.RemainAfterExit = true;
+            path = [ pkgs.iproute ];
+            script =
+              ''
+                echo "bringing up interface..."
+                ip link set "${i.name}" up
+
+                restart_network_interfaces=false
+              '' + flip concatMapStrings (ips) (ip:
+                let
+                  address = "${ip.address}/${toString ip.prefixLength}";
+                in
+                ''
+                  echo "checking ip ${address}..."
+                  if out=$(ip addr add "${address}" dev "${i.name}" 2>&1); then
+                    echo "added ip ${address}..."
+                    restart_network_setup=true
+                  elif ! echo "$out" | grep "File exists" >/dev/null 2>&1; then
+                    echo "failed to add ${address}"
+                    exit 1
+                  fi
+                '')
+              + optionalString (ips != [ ])
+                ''
+                  if [ "$restart_network_setup" = "true" ]; then
+                    # Ensure that the default gateway remains set.
+                    # (Flushing this interface may have removed it.)
+                    ${config.systemd.package}/bin/systemctl try-restart --no-block network-setup.service
+                  fi
+                  ${config.systemd.package}/bin/systemctl start ip-up.target
+                '';
+            preStop =
+              ''
+                echo "releasing configured ip's..."
+              '' + flip concatMapStrings (ips) (ip:
+                let
+                  address = "${ip.address}/${toString ip.prefixLength}";
+                in
+                ''
+                  echo -n "Deleting ${address}..."
+                  ip addr del "${address}" dev "${i.name}" >/dev/null 2>&1 || echo -n " Failed"
+                  echo ""
+                '');
+          };
+
+        createTunDevice = i: nameValuePair "${i.name}-netdev"
+          { description = "Virtual Network Interface ${i.name}";
+            requires = [ "dev-net-tun.device" ];
+            after = [ "dev-net-tun.device" ];
+            wantedBy = [ "network.target" (subsystemDevice i.name) ];
+            path = [ pkgs.iproute ];
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
+            };
+            script = ''
+              ip tuntap add dev "${i.name}" \
+              ${optionalString (i.virtualType != null) "mode ${i.virtualType}"} \
+              user "${i.virtualOwner}"
+            '';
+            postStop = ''
+              ip link del ${i.name}
+            '';
+          };
+
+        createBridgeDevice = n: v: nameValuePair "${n}-netdev"
+          (let
+            deps = map subsystemDevice v.interfaces;
+          in
+          { description = "Bridge Interface ${n}";
+            wantedBy = [ "network.target" (subsystemDevice n) ];
+            bindsTo = deps;
+            after = deps;
+            serviceConfig.Type = "oneshot";
+            serviceConfig.RemainAfterExit = true;
+            path = [ pkgs.iproute ];
+            script = ''
+              # Remove Dead Interfaces
+              echo "Removing old bridge ${n}..."
+              ip link show "${n}" >/dev/null 2>&1 && ip link del "${n}"
+
+              echo "Adding bridge ${n}..."
+              ip link add name "${n}" type bridge
+
+              # Enslave child interfaces
+              ${flip concatMapStrings v.interfaces (i: ''
+                ip link set "${i}" master "${n}"
+                ip link set "${i}" up
+              '')}
+
+              ip link set "${n}" up
+            '';
+            postStop = ''
+              ip link set "${n}" down || true
+              ip link del "${n}" || true
+            '';
+          });
+
+        createBondDevice = n: v: nameValuePair "${n}-netdev"
+          (let
+            deps = map subsystemDevice v.interfaces;
+          in
+          { description = "Bond Interface ${n}";
+            wantedBy = [ "network.target" (subsystemDevice n) ];
+            bindsTo = deps;
+            after = deps;
+            before = [ "${n}-cfg.service" ];
+            serviceConfig.Type = "oneshot";
+            serviceConfig.RemainAfterExit = true;
+            path = [ pkgs.iproute pkgs.gawk ];
+            script = ''
+              echo "Destroying old bond ${n}..."
+              ${destroyBond n}
+
+              echo "Creating new bond ${n}..."
+              ip link add name "${n}" type bond \
+                ${optionalString (v.mode != null) "mode ${toString v.mode}"} \
+                ${optionalString (v.miimon != null) "miimon ${toString v.miimon}"} \
+                ${optionalString (v.xmit_hash_policy != null) "xmit_hash_policy ${toString v.xmit_hash_policy}"} \
+                ${optionalString (v.lacp_rate != null) "lacp_rate ${toString v.lacp_rate}"}
+
+              # !!! There must be a better way to wait for the interface
+              while [ ! -d "/sys/class/net/${n}" ]; do sleep 0.1; done;
+
+              # Bring up the bond and enslave the specified interfaces
+              ip link set "${n}" up
+              ${flip concatMapStrings v.interfaces (i: ''
+                ip link set "${i}" master "${n}"
+              '')}
+            '';
+            postStop = destroyBond n;
+          });
+
+        createMacvlanDevice = n: v: nameValuePair "${n}-netdev"
+          (let
+            deps = [ (subsystemDevice v.interface) ];
+          in
+          { description = "Vlan Interface ${n}";
+            wantedBy = [ "network.target" (subsystemDevice n) ];
+            bindsTo = deps;
+            after = deps;
+            serviceConfig.Type = "oneshot";
+            serviceConfig.RemainAfterExit = true;
+            path = [ pkgs.iproute ];
+            script = ''
+              # Remove Dead Interfaces
+              ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+              ip link add link "${v.interface}" name "${n}" type macvlan \
+                ${optionalString (v.mode != null) "mode ${v.mode}"}
+              ip link set "${n}" up
+            '';
+            postStop = ''
+              ip link delete "${n}"
+            '';
+          });
+
+        createSitDevice = n: v: nameValuePair "${n}-netdev"
+          (let
+            deps = optional (v.dev != null) (subsystemDevice v.dev);
+          in
+          { description = "6-to-4 Tunnel Interface ${n}";
+            wantedBy = [ "network.target" (subsystemDevice n) ];
+            bindsTo = deps;
+            after = deps;
+            serviceConfig.Type = "oneshot";
+            serviceConfig.RemainAfterExit = true;
+            path = [ pkgs.iproute ];
+            script = ''
+              # Remove Dead Interfaces
+              ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+              ip link add name "${n}" type sit \
+                ${optionalString (v.remote != null) "remote \"${v.remote}\""} \
+                ${optionalString (v.local != null) "local \"${v.local}\""} \
+                ${optionalString (v.ttl != null) "ttl ${toString v.ttl}"} \
+                ${optionalString (v.dev != null) "dev \"${v.dev}\""}
+              ip link set "${n}" up
+            '';
+            postStop = ''
+              ip link delete "${n}"
+            '';
+          });
+
+        createVlanDevice = n: v: nameValuePair "${n}-netdev"
+          (let
+            deps = [ (subsystemDevice v.interface) ];
+          in
+          { description = "Vlan Interface ${n}";
+            wantedBy = [ "network.target" (subsystemDevice n) ];
+            bindsTo = deps;
+            after = deps;
+            serviceConfig.Type = "oneshot";
+            serviceConfig.RemainAfterExit = true;
+            path = [ pkgs.iproute ];
+            script = ''
+              # Remove Dead Interfaces
+              ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+              ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}"
+              ip link set "${n}" up
+            '';
+            postStop = ''
+              ip link delete "${n}"
+            '';
+          });
+
+      in listToAttrs (
+           map configureAddrs interfaces ++
+           map createTunDevice (filter (i: i.virtual) interfaces))
+         // mapAttrs' createBridgeDevice cfg.bridges
+         // mapAttrs' createBondDevice cfg.bonds
+         // mapAttrs' createMacvlanDevice cfg.macvlans
+         // mapAttrs' createSitDevice cfg.sits
+         // mapAttrs' createVlanDevice cfg.vlans
+         // {
+           "network-setup" = networkSetup;
+           "network-local-commands" = networkLocalCommands;
+         };
+
+    services.udev.extraRules =
+      ''
+        KERNEL=="tun", TAG+="systemd"
+      '';
+
+  };
+
+}
diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix
new file mode 100644
index 000000000000..334b24b5ad38
--- /dev/null
+++ b/nixos/modules/tasks/network-interfaces-systemd.nix
@@ -0,0 +1,174 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+with utils;
+
+let
+
+  cfg = config.networking;
+  interfaces = attrValues cfg.interfaces;
+
+  interfaceIps = i:
+    i.ip4 ++ optionals cfg.enableIPv6 i.ip6
+    ++ optional (i.ipAddress != null) {
+      address = i.ipAddress;
+      prefixLength = i.prefixLength;
+    } ++ optional (cfg.enableIPv6 && i.ipv6Address != null) {
+      address = i.ipv6Address;
+      prefixLength = i.ipv6PrefixLength;
+    };
+
+  dhcpStr = useDHCP: if useDHCP then "both" else "none";
+
+  slaves =
+    concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds))
+    ++ concatLists (map (bridge: bridge.interfaces) (attrValues cfg.bridges))
+    ++ map (sit: sit.dev) (attrValues cfg.sits)
+    ++ map (vlan: vlan.interface) (attrValues cfg.vlans);
+
+in
+
+{
+
+  config = mkIf cfg.useNetworkd {
+
+    assertions = [ {
+      assertion = cfg.defaultGatewayWindowSize == null;
+      message = "networking.defaultGatewayWindowSize is not supported by networkd.";
+    } ];
+
+    systemd.services.dhcpcd.enable = mkDefault false;
+
+    systemd.services.network-local-commands = {
+      after = [ "systemd-networkd.service" ];
+      bindsTo = [ "systemd-networkd.service" ];
+    };
+
+    systemd.network =
+      let
+        domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain);
+        genericNetwork = override: {
+          DHCP = override (dhcpStr cfg.useDHCP);
+        } // optionalAttrs (cfg.defaultGateway != null) {
+          gateway = override [ cfg.defaultGateway ];
+        } // optionalAttrs (domains != [ ]) {
+          domains = override domains;
+        };
+      in mkMerge [ {
+        enable = true;
+        networks."99-main" = genericNetwork mkDefault;
+      }
+      (mkMerge (flip map interfaces (i: {
+        netdevs = mkIf i.virtual (
+          let
+            devType = if i.virtualType != null then i.virtualType
+              else (if hasPrefix "tun" i.name then "tun" else "tap");
+          in {
+            "40-${i.name}" = {
+              netdevConfig = {
+                Name = i.name;
+                Kind = devType;
+              };
+              "${devType}Config" = optionalAttrs (i.virtualOwner != null) {
+                User = i.virtualOwner;
+              };
+            };
+          });
+        networks."40-${i.name}" = mkMerge [ (genericNetwork mkDefault) {
+          name = mkDefault i.name;
+          DHCP = mkForce (dhcpStr
+            (if i.useDHCP != null then i.useDHCP else interfaceIps i == [ ]));
+          address = flip map (interfaceIps i)
+            (ip: "${ip.address}/${toString ip.prefixLength}");
+        } ];
+      })))
+      (mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: {
+        netdevs."40-${name}" = {
+          netdevConfig = {
+            Name = name;
+            Kind = "bridge";
+          };
+        };
+        networks = listToAttrs (flip map bridge.interfaces (bi:
+          nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
+            DHCP = mkOverride 0 (dhcpStr false);
+            networkConfig.Bridge = name;
+          } ])));
+      })))
+      (mkMerge (flip mapAttrsToList cfg.bonds (name: bond: {
+        netdevs."40-${name}" = {
+          netdevConfig = {
+            Name = name;
+            Kind = "bond";
+          };
+          bondConfig =
+            (optionalAttrs (bond.lacp_rate != null) {
+              LACPTransmitRate = bond.lacp_rate;
+            }) // (optionalAttrs (bond.miimon != null) {
+              MIIMonitorSec = bond.miimon;
+            }) // (optionalAttrs (bond.mode != null) {
+              Mode = bond.mode;
+            }) // (optionalAttrs (bond.xmit_hash_policy != null) {
+              TransmitHashPolicy = bond.xmit_hash_policy;
+            });
+        };
+        networks = listToAttrs (flip map bond.interfaces (bi:
+          nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
+            DHCP = mkOverride 0 (dhcpStr false);
+            networkConfig.Bond = name;
+          } ])));
+      })))
+      (mkMerge (flip mapAttrsToList cfg.macvlans (name: macvlan: {
+        netdevs."40-${name}" = {
+          netdevConfig = {
+            Name = name;
+            Kind = "macvlan";
+          };
+          macvlanConfig.Mode = macvlan.mode;
+        };
+        networks."40-${macvlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
+          macvlan = [ name ];
+        } ]);
+      })))
+      (mkMerge (flip mapAttrsToList cfg.sits (name: sit: {
+        netdevs."40-${name}" = {
+          netdevConfig = {
+            Name = name;
+            Kind = "sit";
+          };
+          tunnelConfig =
+            (optionalAttrs (sit.remote != null) {
+              Remote = sit.remote;
+            }) // (optionalAttrs (sit.local != null) {
+              Local = sit.local;
+            }) // (optionalAttrs (sit.ttl != null) {
+              TTL = sit.ttl;
+            });
+        };
+        networks = mkIf (sit.dev != null) {
+          "40-${sit.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
+            tunnel = [ name ];
+          } ]);
+        };
+      })))
+      (mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: {
+        netdevs."40-${name}" = {
+          netdevConfig = {
+            Name = name;
+            Kind = "vlan";
+          };
+          vlanConfig.Id = vlan.id;
+        };
+        networks."40-${vlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
+          vlan = [ name ];
+        } ]);
+      })))
+    ];
+
+    # We need to prefill the slaved devices with networking options
+    # This forces the network interface creator to initialize slaves.
+    networking.interfaces = listToAttrs (map (i: nameValuePair i { }) slaves);
+
+  };
+
+}
diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
index 9579eaa77d06..0ee2c9d2d00d 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -45,6 +45,16 @@ let
         description = "Name of the interface.";
       };
 
+      useDHCP = mkOption {
+        type = types.nullOr types.bool;
+        default = null;
+        description = ''
+          Whether this interface should be configured with dhcp.
+          Null implies the old behavior which depends on whether ip addresses
+          are specified or not.
+        '';
+      };
+
       ip4 = mkOption {
         default = [ ];
         example = [
@@ -203,6 +213,7 @@ in
 
     networking.hostName = mkOption {
       default = "nixos";
+      type = types.str;
       description = ''
         The name of the machine.  Leave it empty if you want to obtain
         it from a DHCP server (if using DHCP).
@@ -225,14 +236,16 @@ in
 
     networking.enableIPv6 = mkOption {
       default = true;
+      type = types.bool;
       description = ''
         Whether to enable support for IPv6.
       '';
     };
 
     networking.defaultGateway = mkOption {
-      default = "";
+      default = null;
       example = "131.211.84.1";
+      type = types.nullOr types.str;
       description = ''
         The default gateway.  It can be left empty if it is auto-detected through DHCP.
       '';
@@ -266,8 +279,9 @@ in
     };
 
     networking.domain = mkOption {
-      default = "";
+      default = null;
       example = "home";
+      type = types.nullOr types.str;
       description = ''
         The domain.  It can be left empty if it is auto-detected through DHCP.
       '';
@@ -414,6 +428,37 @@ in
       };
     };
 
+    networking.macvlans = mkOption {
+      type = types.attrsOf types.optionSet;
+      default = { };
+      example = {
+        wan = {
+          interface = "enp2s0";
+          mode = "vepa";
+        };
+      };
+      description = ''
+        This option allows you to define macvlan interfaces which should
+        be automatically created.
+      '';
+      options = {
+
+        interface = mkOption {
+          example = "enp4s0";
+          type = types.string;
+          description = "The interface the macvlan will transmit packets through.";
+        };
+
+        mode = mkOption {
+          default = null;
+          type = types.nullOr types.str;
+          example = "vepa";
+          description = "The mode of the macvlan device.";
+        };
+
+      };
+    };
+
     networking.sits = mkOption {
       type = types.attrsOf types.optionSet;
       default = { };
@@ -523,6 +568,16 @@ in
       '';
     };
 
+    networking.useNetworkd = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether we should use networkd as the network configuration backend or
+        the legacy script based system. Note that this option is experimental,
+        enable at your own risk.
+      '';
+    };
+
   };
 
 
@@ -552,345 +607,18 @@ in
       # from being created.
       optionalString hasBonds "options bonding max_bonds=0";
 
-    environment.systemPackages =
-      [ pkgs.host
-        pkgs.iproute
-        pkgs.iputils
-        pkgs.nettools
-        pkgs.wirelesstools
-        pkgs.iw
-        pkgs.rfkill
-        pkgs.openresolv
-      ]
-      ++ optional (cfg.bridges != {}) pkgs.bridge_utils
-      ++ optional hasVirtuals pkgs.tunctl
-      ++ optional cfg.enableIPv6 pkgs.ndisc6;
+    boot.kernel.sysctl = {
+      "net.net.ipv4.conf.all.promote_secondaries" = true;
+      "net.ipv6.conf.all.disable_ipv6" = mkDefault (!cfg.enableIPv6);
+      "net.ipv6.conf.default.disable_ipv6" = mkDefault (!cfg.enableIPv6);
+      "net.ipv4.conf.all_forwarding" = mkDefault (any (i: i.proxyARP) interfaces);
+      "net.ipv6.conf.all.forwarding" = mkDefault (any (i: i.proxyARP) interfaces);
+    } // listToAttrs (concatLists (flip map (filter (i: i.proxyARP) interfaces)
+        (i: flip map [ "4" "6" ] (v: nameValuePair "net.ipv${v}.conf.${i.name}.proxy_arp" true))
+      ));
 
     security.setuidPrograms = [ "ping" "ping6" ];
 
-    systemd.targets."network-interfaces" =
-      { description = "All Network Interfaces";
-        wantedBy = [ "network.target" ];
-        unitConfig.X-StopOnReconfiguration = true;
-      };
-
-    systemd.services =
-      let
-
-        networkSetup =
-          { description = "Networking Setup";
-
-            after = [ "network-interfaces.target" ];
-            before = [ "network.target" ];
-            wantedBy = [ "network.target" ];
-
-            unitConfig.ConditionCapability = "CAP_NET_ADMIN";
-
-            path = [ pkgs.iproute ];
-
-            serviceConfig.Type = "oneshot";
-            serviceConfig.RemainAfterExit = true;
-
-            script =
-              ''
-                # Set the static DNS configuration, if given.
-                ${pkgs.openresolv}/sbin/resolvconf -m 1 -a static <<EOF
-                ${optionalString (cfg.nameservers != [] && cfg.domain != "") ''
-                  domain ${cfg.domain}
-                ''}
-                ${optionalString (cfg.search != []) ("search " + concatStringsSep " " cfg.search)}
-                ${flip concatMapStrings cfg.nameservers (ns: ''
-                  nameserver ${ns}
-                '')}
-                EOF
-
-                # Disable or enable IPv6.
-                ${optionalString (!config.boot.isContainer) ''
-                  if [ -e /proc/sys/net/ipv6/conf/all/disable_ipv6 ]; then
-                    echo ${if cfg.enableIPv6 then "0" else "1"} > /proc/sys/net/ipv6/conf/all/disable_ipv6
-                  fi
-                ''}
-
-                # Set the default gateway.
-                ${optionalString (cfg.defaultGateway != "") ''
-                  # FIXME: get rid of "|| true" (necessary to make it idempotent).
-                  ip route add default via "${cfg.defaultGateway}" ${
-                    optionalString (cfg.defaultGatewayWindowSize != null)
-                      "window ${cfg.defaultGatewayWindowSize}"} || true
-                ''}
-
-                # Turn on forwarding if any interface has enabled proxy_arp.
-                ${optionalString (any (i: i.proxyARP) interfaces) ''
-                  echo 1 > /proc/sys/net/ipv4/ip_forward
-                ''}
-
-                # Run any user-specified commands.
-                ${cfg.localCommands}
-              '';
-          };
-
-        # For each interface <foo>, create a job ‘<foo>-cfg.service"
-        # that performs static configuration.  It has a "wants"
-        # dependency on ‘<foo>.service’, which is supposed to create
-        # the interface and need not exist (i.e. for hardware
-        # interfaces).  It has a binds-to dependency on the actual
-        # network device, so it only gets started after the interface
-        # has appeared, and it's stopped when the interface
-        # disappears.
-        configureInterface = i:
-          let
-            ips = i.ip4 ++ optionals cfg.enableIPv6 i.ip6
-              ++ optional (i.ipAddress != null) {
-                address = i.ipAddress;
-                prefixLength = i.prefixLength;
-              } ++ optional (cfg.enableIPv6 && i.ipv6Address != null) {
-                address = i.ipv6Address;
-                prefixLength = i.ipv6PrefixLength;
-              };
-          in
-          nameValuePair "${i.name}-cfg"
-          { description = "Configuration of ${i.name}";
-            wantedBy = [ "network-interfaces.target" ];
-            bindsTo = [ (subsystemDevice i.name) ];
-            after = [ (subsystemDevice i.name) ];
-            serviceConfig.Type = "oneshot";
-            serviceConfig.RemainAfterExit = true;
-            path = [ pkgs.iproute pkgs.gawk ];
-            script =
-              ''
-                echo "bringing up interface..."
-                ip link set "${i.name}" up
-              ''
-              + optionalString (i.macAddress != null)
-                ''
-                  echo "setting MAC address to ${i.macAddress}..."
-                  ip link set "${i.name}" address "${i.macAddress}"
-                ''
-              + optionalString (i.mtu != null)
-                ''
-                  echo "setting MTU to ${toString i.mtu}..."
-                  ip link set "${i.name}" mtu "${toString i.mtu}"
-                ''
-
-              # Ip Setup
-              +
-                ''
-                  curIps=$(ip -o a show dev "${i.name}" | awk '{print $4}')
-                  # Only do an add if it's necessary.  This is
-                  # useful when the Nix store is accessed via this
-                  # interface (e.g. in a QEMU VM test).
-                ''
-              + flip concatMapStrings (ips) (ip:
-                let
-                  address = "${ip.address}/${toString ip.prefixLength}";
-                in
-                ''
-                  echo "checking ip ${address}..."
-                  if ! echo "$curIps" | grep "${address}" >/dev/null 2>&1; then
-                    if out=$(ip addr add "${address}" dev "${i.name}" 2>&1); then
-                      echo "added ip ${address}..."
-                      restart_network_setup=true
-                    elif ! echo "$out" | grep "File exists" >/dev/null 2>&1; then
-                      echo "failed to add ${address}"
-                      exit 1
-                    fi
-                  fi
-                '')
-              + optionalString (ips != [ ])
-                ''
-                  if [ restart_network_setup = true ]; then
-                    # Ensure that the default gateway remains set.
-                    # (Flushing this interface may have removed it.)
-                    ${config.systemd.package}/bin/systemctl try-restart --no-block network-setup.service
-                  fi
-                  ${config.systemd.package}/bin/systemctl start ip-up.target
-                ''
-              + optionalString i.proxyARP
-                ''
-                  echo 1 > /proc/sys/net/ipv4/conf/${i.name}/proxy_arp
-                ''
-              + optionalString (i.proxyARP && cfg.enableIPv6)
-                ''
-                  echo 1 > /proc/sys/net/ipv6/conf/${i.name}/proxy_ndp
-                '';
-            preStop =
-              ''
-                echo "releasing configured ip's..."
-              ''
-              + flip concatMapStrings (ips) (ip:
-                let
-                  address = "${ip.address}/${toString ip.prefixLength}";
-                in
-                ''
-                  echo -n "Deleting ${address}..."
-                  ip addr del "${address}" dev "${i.name}" >/dev/null 2>&1 || echo -n " Failed"
-                  echo ""
-                '');
-          };
-
-        createTunDevice = i: nameValuePair "${i.name}-netdev"
-          { description = "Virtual Network Interface ${i.name}";
-            requires = [ "dev-net-tun.device" ];
-            after = [ "dev-net-tun.device" ];
-            wantedBy = [ "network.target" (subsystemDevice i.name) ];
-            path = [ pkgs.iproute ];
-            serviceConfig = {
-              Type = "oneshot";
-              RemainAfterExit = true;
-            };
-            script = ''
-              ip tuntap add dev "${i.name}" \
-              ${optionalString (i.virtualType != null) "mode ${i.virtualType}"} \
-              user "${i.virtualOwner}"
-            '';
-            postStop = ''
-              ip link del ${i.name}
-            '';
-          };
-
-        createBridgeDevice = n: v: nameValuePair "${n}-netdev"
-          (let
-            deps = map subsystemDevice v.interfaces;
-          in
-          { description = "Bridge Interface ${n}";
-            wantedBy = [ "network.target" (subsystemDevice n) ];
-            bindsTo = deps;
-            after = deps;
-            serviceConfig.Type = "oneshot";
-            serviceConfig.RemainAfterExit = true;
-            path = [ pkgs.bridge_utils pkgs.iproute ];
-            script =
-              ''
-                # Remove Dead Interfaces
-                ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
-
-                brctl addbr "${n}"
-
-                # Set bridge's hello time to 0 to avoid startup delays.
-                brctl setfd "${n}" 0
-
-                ${flip concatMapStrings v.interfaces (i: ''
-                  brctl addif "${n}" "${i}"
-                  ip link set "${i}" up
-                  ip addr flush dev "${i}"
-
-                  echo "bringing up network device ${n}..."
-                  ip link set "${n}" up
-                '')}
-
-                # !!! Should delete (brctl delif) any interfaces that
-                # no longer belong to the bridge.
-              '';
-            postStop =
-              ''
-                ip link set "${n}" down
-                brctl delbr "${n}"
-              '';
-          });
-
-        createBondDevice = n: v: nameValuePair "${n}-netdev"
-          (let
-            deps = map subsystemDevice v.interfaces;
-          in
-          { description = "Bond Interface ${n}";
-            wantedBy = [ "network.target" (subsystemDevice n) ];
-            bindsTo = deps;
-            after = deps;
-            before = [ "${n}-cfg.service" ];
-            serviceConfig.Type = "oneshot";
-            serviceConfig.RemainAfterExit = true;
-            path = [ pkgs.ifenslave pkgs.iproute ];
-            script = ''
-              ip link add name "${n}" type bond
-
-              # !!! There must be a better way to wait for the interface
-              while [ ! -d /sys/class/net/${n} ]; do sleep 0.1; done;
-
-              # Ensure the link is down so that we can set options
-              ip link set "${n}" down
-
-              # Set the miimon and mode options
-              ${optionalString (v.miimon != null)
-                "echo \"${toString v.miimon}\" >/sys/class/net/${n}/bonding/miimon"}
-              ${optionalString (v.mode != null)
-                "echo \"${v.mode}\" >/sys/class/net/${n}/bonding/mode"}
-              ${optionalString (v.lacp_rate != null)
-                "echo \"${v.lacp_rate}\" >/sys/class/net/${n}/bonding/lacp_rate"}
-              ${optionalString (v.xmit_hash_policy != null)
-                "echo \"${v.xmit_hash_policy}\" >/sys/class/net/${n}/bonding/xmit_hash_policy"}
-
-              # Bring up the bond and enslave the specified interfaces
-              ip link set "${n}" up
-              ${flip concatMapStrings v.interfaces (i: ''
-                ifenslave "${n}" "${i}"
-              '')}
-            '';
-            postStop = ''
-              ${flip concatMapStrings v.interfaces (i: ''
-                ifenslave -d "${n}" "${i}" >/dev/null 2>&1 || true
-              '')}
-              ip link set "${n}" down >/dev/null 2>&1 || true
-              ip link del "${n}" >/dev/null 2>&1 || true
-            '';
-          });
-
-        createSitDevice = n: v: nameValuePair "${n}-netdev"
-          (let
-            deps = optional (v.dev != null) (subsystemDevice v.dev);
-          in
-          { description = "6-to-4 Tunnel Interface ${n}";
-            wantedBy = [ "network.target" (subsystemDevice n) ];
-            bindsTo = deps;
-            after = deps;
-            serviceConfig.Type = "oneshot";
-            serviceConfig.RemainAfterExit = true;
-            path = [ pkgs.iproute ];
-            script = ''
-              # Remove Dead Interfaces
-              ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
-              ip link add name "${n}" type sit \
-                ${optionalString (v.remote != null) "remote \"${v.remote}\""} \
-                ${optionalString (v.local != null) "local \"${v.local}\""} \
-                ${optionalString (v.ttl != null) "ttl ${toString v.ttl}"} \
-                ${optionalString (v.dev != null) "dev \"${v.dev}\""}
-              ip link set "${n}" up
-            '';
-            postStop = ''
-              ip link delete "${n}"
-            '';
-          });
-
-        createVlanDevice = n: v: nameValuePair "${n}-netdev"
-          (let
-            deps = [ (subsystemDevice v.interface) ];
-          in
-          { description = "Vlan Interface ${n}";
-            wantedBy = [ "network.target" (subsystemDevice n) ];
-            bindsTo = deps;
-            after = deps;
-            serviceConfig.Type = "oneshot";
-            serviceConfig.RemainAfterExit = true;
-            path = [ pkgs.iproute ];
-            script = ''
-              # Remove Dead Interfaces
-              ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
-              ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}"
-              ip link set "${n}" up
-            '';
-            postStop = ''
-              ip link delete "${n}"
-            '';
-          });
-
-      in listToAttrs (
-           map configureInterface interfaces ++
-           map createTunDevice (filter (i: i.virtual) interfaces))
-         // mapAttrs' createBridgeDevice cfg.bridges
-         // mapAttrs' createBondDevice cfg.bonds
-         // mapAttrs' createSitDevice cfg.sits
-         // mapAttrs' createVlanDevice cfg.vlans
-         // { "network-setup" = networkSetup; };
-
     # Set the host and domain names in the activation script.  Don't
     # clear it if it's not configured in the NixOS configuration,
     # since it may have been set by dhcpcd in the meantime.
@@ -899,7 +627,7 @@ in
         hostname "${cfg.hostName}"
       '';
     system.activationScripts.domain =
-      optionalString (cfg.domain != "") ''
+      optionalString (cfg.domain != null) ''
         domainname "${cfg.domain}"
       '';
 
@@ -918,11 +646,54 @@ in
         }
       ];
 
-    services.udev.extraRules =
-      ''
-        KERNEL=="tun", TAG+="systemd"
-      '';
+    environment.systemPackages =
+      [ pkgs.host
+        pkgs.iproute
+        pkgs.iputils
+        pkgs.nettools
+        pkgs.wirelesstools
+        pkgs.iw
+        pkgs.rfkill
+        pkgs.openresolv
+      ];
 
+    systemd.services = {
+      network-local-commands = {
+        description = "Extra networking commands.";
+        before = [ "network.target" ];
+        wantedBy = [ "network.target" ];
+        unitConfig.ConditionCapability = "CAP_NET_ADMIN";
+        path = [ pkgs.iproute ];
+        serviceConfig.Type = "oneshot";
+        serviceConfig.RemainAfterExit = true;
+        script = ''
+          # Run any user-specified commands.
+          ${cfg.localCommands}
+        '';
+      };
+    } // (listToAttrs (flip map interfaces (i:
+      nameValuePair "network-link-${i.name}"
+      { description = "Link configuration of ${i.name}";
+        wantedBy = [ "network-interfaces.target" ];
+        before = [ "network-interfaces.target" ];
+        bindsTo = [ (subsystemDevice i.name) ];
+        after = [ (subsystemDevice i.name) ];
+        path = [ pkgs.iproute ];
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+        };
+        script =
+          ''
+            echo "Configuring link..."
+          '' + optionalString (i.macAddress != null) ''
+            echo "setting MAC address to ${i.macAddress}..."
+            ip link set "${i.name}" address "${i.macAddress}"
+          '' + optionalString (i.mtu != null) ''
+            echo "setting MTU to ${toString i.mtu}..."
+            ip link set "${i.name}" mtu "${toString i.mtu}"
+          '';
+      })));
   };
 
 }
diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix
index 5173c33cab71..e850c1f6cddb 100644
--- a/nixos/release-combined.nix
+++ b/nixos/release-combined.nix
@@ -66,6 +66,14 @@ in rec {
         (all nixos.tests.misc)
         (all nixos.tests.nat.firewall)
         (all nixos.tests.nat.standalone)
+        (all nixos.tests.networking.scripted.static)
+        (all nixos.tests.networking.scripted.dhcpSimple)
+        (all nixos.tests.networking.scripted.dhcpOneIf)
+        (all nixos.tests.networking.scripted.bond)
+        (all nixos.tests.networking.scripted.bridge)
+        (all nixos.tests.networking.scripted.macvlan)
+        (all nixos.tests.networking.scripted.sit)
+        (all nixos.tests.networking.scripted.vlan)
         (all nixos.tests.nfs3)
         (all nixos.tests.openssh)
         (all nixos.tests.printing)
diff --git a/nixos/release.nix b/nixos/release.nix
index 7e2bfdb850b4..f3fe109b58ee 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -269,6 +269,22 @@ in rec {
   tests.mysqlReplication = callTest tests/mysql-replication.nix {};
   tests.nat.firewall = callTest tests/nat.nix { withFirewall = true; };
   tests.nat.standalone = callTest tests/nat.nix { withFirewall = false; };
+  tests.networking.networkd.static = callTest tests/networking.nix { networkd = true; test = "static"; };
+  tests.networking.networkd.dhcpSimple = callTest tests/networking.nix { networkd = true; test = "dhcpSimple"; };
+  tests.networking.networkd.dhcpOneIf = callTest tests/networking.nix { networkd = true; test = "dhcpOneIf"; };
+  tests.networking.networkd.bond = callTest tests/networking.nix { networkd = true; test = "bond"; };
+  tests.networking.networkd.bridge = callTest tests/networking.nix { networkd = true; test = "bridge"; };
+  tests.networking.networkd.macvlan = callTest tests/networking.nix { networkd = true; test = "macvlan"; };
+  tests.networking.networkd.sit = callTest tests/networking.nix { networkd = true; test = "sit"; };
+  tests.networking.networkd.vlan = callTest tests/networking.nix { networkd = true; test = "vlan"; };
+  tests.networking.scripted.static = callTest tests/networking.nix { networkd = false; test = "static"; };
+  tests.networking.scripted.dhcpSimple = callTest tests/networking.nix { networkd = false; test = "dhcpSimple"; };
+  tests.networking.scripted.dhcpOneIf = callTest tests/networking.nix { networkd = false; test = "dhcpOneIf"; };
+  tests.networking.scripted.bond = callTest tests/networking.nix { networkd = false; test = "bond"; };
+  tests.networking.scripted.bridge = callTest tests/networking.nix { networkd = false; test = "bridge"; };
+  tests.networking.scripted.macvlan = callTest tests/networking.nix { networkd = false; test = "macvlan"; };
+  tests.networking.scripted.sit = callTest tests/networking.nix { networkd = false; test = "sit"; };
+  tests.networking.scripted.vlan = callTest tests/networking.nix { networkd = false; test = "vlan"; };
   tests.nfs3 = callTest tests/nfs.nix { version = 3; };
   tests.nsd = callTest tests/nsd.nix {};
   tests.openssh = callTest tests/openssh.nix {};
diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix
new file mode 100644
index 000000000000..b5e09cc3950b
--- /dev/null
+++ b/nixos/tests/networking.nix
@@ -0,0 +1,365 @@
+import ./make-test.nix ({ networkd, test, ... }:
+  let
+    router = { config, pkgs, ... }:
+      with pkgs.lib;
+      let
+        vlanIfs = range 1 (length config.virtualisation.vlans);
+      in {
+        virtualisation.vlans = [ 1 2 3 ];
+        networking = {
+          useDHCP = false;
+          useNetworkd = networkd;
+          firewall.allowPing = true;
+          interfaces = mkOverride 0 (listToAttrs (flip map vlanIfs (n:
+            nameValuePair "eth${toString n}" {
+              ipAddress = "192.168.${toString n}.1";
+              prefixLength = 24;
+            })));
+        };
+        services.dhcpd = {
+          enable = true;
+          interfaces = map (n: "eth${toString n}") vlanIfs;
+          extraConfig = ''
+            option subnet-mask 255.255.255.0;
+          '' + flip concatMapStrings vlanIfs (n: ''
+            subnet 192.168.${toString n}.0 netmask 255.255.255.0 {
+              option broadcast-address 192.168.${toString n}.255;
+              option routers 192.168.${toString n}.1;
+              range 192.168.${toString n}.2 192.168.${toString n}.254;
+            }
+          '');
+        };
+      };
+    testCases = {
+      static = {
+        name = "Static";
+        nodes.router = router;
+        nodes.client = { config, pkgs, ... }: with pkgs.lib; {
+          virtualisation.vlans = [ 1 2 ];
+          networking = {
+            useNetworkd = networkd;
+            firewall.allowPing = true;
+            useDHCP = false;
+            defaultGateway = "192.168.1.1";
+            interfaces.eth1.ip4 = mkOverride 0 [
+              { address = "192.168.1.2"; prefixLength = 24; }
+              { address = "192.168.1.3"; prefixLength = 32; }
+              { address = "192.168.1.10"; prefixLength = 32; }
+            ];
+            interfaces.eth2.ip4 = mkOverride 0 [
+              { address = "192.168.2.2"; prefixLength = 24; }
+            ];
+          };
+        };
+        testScript = { nodes, ... }:
+          ''
+            startAll;
+
+            $client->waitForUnit("network.target");
+            $router->waitForUnit("network.target");
+
+            # Make sure dhcpcd is not started
+            $client->fail("systemctl status dhcpcd.service");
+
+            # Test vlan 1
+            $client->succeed("ping -c 1 192.168.1.1");
+            $client->succeed("ping -c 1 192.168.1.2");
+            $client->succeed("ping -c 1 192.168.1.3");
+            $client->succeed("ping -c 1 192.168.1.10");
+
+            $router->succeed("ping -c 1 192.168.1.1");
+            $router->succeed("ping -c 1 192.168.1.2");
+            $router->succeed("ping -c 1 192.168.1.3");
+            $router->succeed("ping -c 1 192.168.1.10");
+
+            # Test vlan 2
+            $client->succeed("ping -c 1 192.168.2.1");
+            $client->succeed("ping -c 1 192.168.2.2");
+
+            $router->succeed("ping -c 1 192.168.2.1");
+            $router->succeed("ping -c 1 192.168.2.2");
+
+            # Test default gateway
+            $router->succeed("ping -c 1 192.168.3.1");
+            $client->succeed("ping -c 1 192.168.3.1");
+          '';
+      };
+      dhcpSimple = {
+        name = "SimpleDHCP";
+        nodes.router = router;
+        nodes.client = { config, pkgs, ... }: with pkgs.lib; {
+          virtualisation.vlans = [ 1 2 ];
+          networking = {
+            useNetworkd = networkd;
+            firewall.allowPing = true;
+            useDHCP = true;
+            interfaces.eth1.ip4 = mkOverride 0 [ ];
+            interfaces.eth2.ip4 = mkOverride 0 [ ];
+          };
+        };
+        testScript = { nodes, ... }:
+          ''
+            startAll;
+
+            $client->waitForUnit("network.target");
+            $router->waitForUnit("network.target");
+            $client->waitForUnit("dhcpcd.service");
+
+            # Wait until we have an ip address on each interface
+            $client->succeed("while ! ip addr show dev eth1 | grep '192.168.1'; do true; done");
+            $client->succeed("while ! ip addr show dev eth2 | grep '192.168.2'; do true; done");
+
+            # Test vlan 1
+            $client->succeed("ping -c 1 192.168.1.1");
+            $client->succeed("ping -c 1 192.168.1.2");
+
+            $router->succeed("ping -c 1 192.168.1.1");
+            $router->succeed("ping -c 1 192.168.1.2");
+
+            # Test vlan 2
+            $client->succeed("ping -c 1 192.168.2.1");
+            $client->succeed("ping -c 1 192.168.2.2");
+
+            $router->succeed("ping -c 1 192.168.2.1");
+            $router->succeed("ping -c 1 192.168.2.2");
+          '';
+      };
+      dhcpOneIf = {
+        name = "OneInterfaceDHCP";
+        nodes.router = router;
+        nodes.client = { config, pkgs, ... }: with pkgs.lib; {
+          virtualisation.vlans = [ 1 2 ];
+          networking = {
+            useNetworkd = networkd;
+            firewall.allowPing = true;
+            useDHCP = false;
+            interfaces.eth1 = {
+              ip4 = mkOverride 0 [ ];
+              useDHCP = true;
+            };
+            interfaces.eth2.ip4 = mkOverride 0 [ ];
+          };
+        };
+        testScript = { nodes, ... }:
+          ''
+            startAll;
+
+            $client->waitForUnit("network.target");
+            $router->waitForUnit("network.target");
+            $client->waitForUnit("dhcpcd.service");
+
+            # Wait until we have an ip address on each interface
+            $client->succeed("while ! ip addr show dev eth1 | grep '192.168.1'; do true; done");
+
+            # Test vlan 1
+            $client->succeed("ping -c 1 192.168.1.1");
+            $client->succeed("ping -c 1 192.168.1.2");
+
+            $router->succeed("ping -c 1 192.168.1.1");
+            $router->succeed("ping -c 1 192.168.1.2");
+
+            # Test vlan 2
+            $client->succeed("ping -c 1 192.168.2.1");
+            $client->fail("ping -c 1 192.168.2.2");
+
+            $router->succeed("ping -c 1 192.168.2.1");
+            $router->fail("ping -c 1 192.168.2.2");
+          '';
+      };
+      bond = let
+        node = address: { config, pkgs, ... }: with pkgs.lib; {
+          virtualisation.vlans = [ 1 2 ];
+          networking = {
+            useNetworkd = networkd;
+            firewall.allowPing = true;
+            useDHCP = false;
+            bonds.bond = {
+              mode = "balance-rr";
+              interfaces = [ "eth1" "eth2" ];
+            };
+            interfaces.bond.ip4 = mkOverride 0
+              [ { inherit address; prefixLength = 30; } ];
+          };
+        };
+      in {
+        name = "Bond";
+        nodes.client1 = node "192.168.1.1";
+        nodes.client2 = node "192.168.1.2";
+        testScript = { nodes, ... }:
+          ''
+            startAll;
+
+            $client1->waitForUnit("network.target");
+            $client2->waitForUnit("network.target");
+
+            # Test bonding
+            $client1->succeed("ping -c 2 192.168.1.1");
+            $client1->succeed("ping -c 2 192.168.1.2");
+
+            $client2->succeed("ping -c 2 192.168.1.1");
+            $client2->succeed("ping -c 2 192.168.1.2");
+          '';
+      };
+      bridge = let
+        node = { address, vlan }: { config, pkgs, ... }: with pkgs.lib; {
+          virtualisation.vlans = [ vlan ];
+          networking = {
+            useNetworkd = networkd;
+            firewall.allowPing = true;
+            useDHCP = false;
+            interfaces.eth1.ip4 = mkOverride 0
+              [ { inherit address; prefixLength = 24; } ];
+          };
+        };
+      in {
+        name = "Bridge";
+        nodes.client1 = node { address = "192.168.1.2"; vlan = 1; };
+        nodes.client2 = node { address = "192.168.1.3"; vlan = 2; };
+        nodes.router = { config, pkgs, ... }: with pkgs.lib; {
+          virtualisation.vlans = [ 1 2 ];
+          networking = {
+            useNetworkd = networkd;
+            firewall.allowPing = true;
+            useDHCP = false;
+            bridges.bridge.interfaces = [ "eth1" "eth2" ];
+            interfaces.eth1.ip4 = mkOverride 0 [ ];
+            interfaces.eth2.ip4 = mkOverride 0 [ ];
+            interfaces.bridge.ip4 = mkOverride 0
+              [ { address = "192.168.1.1"; prefixLength = 24; } ];
+          };
+        };
+        testScript = { nodes, ... }:
+          ''
+            startAll;
+
+            $client1->waitForUnit("network.target");
+            $client2->waitForUnit("network.target");
+            $router->waitForUnit("network.target");
+
+            # Test bridging
+            $client1->succeed("ping -c 1 192.168.1.1");
+            $client1->succeed("ping -c 1 192.168.1.2");
+            $client1->succeed("ping -c 1 192.168.1.3");
+
+            $client2->succeed("ping -c 1 192.168.1.1");
+            $client2->succeed("ping -c 1 192.168.1.2");
+            $client2->succeed("ping -c 1 192.168.1.3");
+
+            $router->succeed("ping -c 1 192.168.1.1");
+            $router->succeed("ping -c 1 192.168.1.2");
+            $router->succeed("ping -c 1 192.168.1.3");
+          '';
+      };
+      macvlan = {
+        name = "MACVLAN";
+        nodes.router = router;
+        nodes.client = { config, pkgs, ... }: with pkgs.lib; {
+          virtualisation.vlans = [ 1 ];
+          networking = {
+            useNetworkd = networkd;
+            firewall.allowPing = true;
+            useDHCP = true;
+            macvlans.macvlan.interface = "eth1";
+            interfaces.eth1.ip4 = mkOverride 0 [ ];
+          };
+        };
+        testScript = { nodes, ... }:
+          ''
+            startAll;
+
+            $client->waitForUnit("network.target");
+            $router->waitForUnit("network.target");
+            $client->waitForUnit("dhcpcd.service");
+
+            # Wait until we have an ip address on each interface
+            $client->succeed("while ! ip addr show dev eth1 | grep '192.168.1'; do true; done");
+            $client->succeed("while ! ip addr show dev macvlan | grep '192.168.1'; do true; done");
+
+            # Test macvlan
+            $client->succeed("ping -c 1 192.168.1.1");
+            $client->succeed("ping -c 1 192.168.1.2");
+            $client->succeed("ping -c 1 192.168.1.3");
+
+            $router->succeed("ping -c 1 192.168.1.1");
+            $router->succeed("ping -c 1 192.168.1.2");
+            $router->succeed("ping -c 1 192.168.1.3");
+          '';
+      };
+      sit = let
+        node = { address4, remote, address6 }: { config, pkgs, ... }: with pkgs.lib; {
+          virtualisation.vlans = [ 1 ];
+          networking = {
+            useNetworkd = networkd;
+            firewall.enable = false;
+            useDHCP = false;
+            sits.sit = {
+              inherit remote;
+              local = address4;
+              dev = "eth1";
+            };
+            interfaces.eth1.ip4 = mkOverride 0
+              [ { address = address4; prefixLength = 24; } ];
+            interfaces.sit.ip6 = mkOverride 0
+              [ { address = address6; prefixLength = 64; } ];
+          };
+        };
+      in {
+        name = "Sit";
+        nodes.client1 = node { address4 = "192.168.1.1"; remote = "192.168.1.2"; address6 = "fc00::1"; };
+        nodes.client2 = node { address4 = "192.168.1.2"; remote = "192.168.1.1"; address6 = "fc00::2"; };
+        testScript = { nodes, ... }:
+          ''
+            startAll;
+
+            $client1->waitForUnit("network.target");
+            $client2->waitForUnit("network.target");
+
+            $client1->succeed("ip addr >&2");
+            $client2->succeed("ip addr >&2");
+
+            # Test ipv6
+            $client1->succeed("ping6 -c 1 fc00::1");
+            $client1->succeed("ping6 -c 1 fc00::2");
+
+            $client2->succeed("ping6 -c 1 fc00::1");
+            $client2->succeed("ping6 -c 1 fc00::2");
+          '';
+      };
+      vlan = let
+        node = address: { config, pkgs, ... }: with pkgs.lib; {
+          #virtualisation.vlans = [ 1 ];
+          networking = {
+            useNetworkd = networkd;
+            firewall.allowPing = true;
+            useDHCP = false;
+            vlans.vlan = {
+              id = 1;
+              interface = "eth0";
+            };
+            interfaces.eth0.ip4 = mkOverride 0 [ ];
+            interfaces.eth1.ip4 = mkOverride 0 [ ];
+            interfaces.vlan.ip4 = mkOverride 0
+              [ { inherit address; prefixLength = 24; } ];
+          };
+        };
+      in {
+        name = "vlan";
+        nodes.client1 = node "192.168.1.1";
+        nodes.client2 = node "192.168.1.2";
+        testScript = { nodes, ... }:
+          ''
+            startAll;
+
+            $client1->waitForUnit("network.target");
+            $client2->waitForUnit("network.target");
+
+            # Test vlan is setup
+            $client1->succeed("ip addr show dev vlan >&2");
+            $client2->succeed("ip addr show dev vlan >&2");
+          '';
+      };
+    };
+    case = testCases.${test};
+  in case // {
+    name = "${case.name}-Networking-${if networkd then "Networkd" else "Scripted"}";
+  })