diff --git a/nixos/doc/manual/release-notes/rl-1903.xml b/nixos/doc/manual/release-notes/rl-1903.xml
index 6c45301b91e..eca280afdf1 100644
--- a/nixos/doc/manual/release-notes/rl-1903.xml
+++ b/nixos/doc/manual/release-notes/rl-1903.xml
@@ -476,6 +476,14 @@
+
+
+ The ndppd module
+ now supports all config options provided by the current
+ upstream version as service options. Additionally the ndppd package doesn't contain
+ the systemd unit configuration from upstream anymore, the unit is completely configured by the NixOS module now.
+
+
diff --git a/nixos/modules/services/networking/ndppd.nix b/nixos/modules/services/networking/ndppd.nix
index 1d6c48dd8d3..ba17f1ba825 100644
--- a/nixos/modules/services/networking/ndppd.nix
+++ b/nixos/modules/services/networking/ndppd.nix
@@ -5,43 +5,163 @@ with lib;
let
cfg = config.services.ndppd;
- configFile = pkgs.runCommand "ndppd.conf" {} ''
- substitute ${pkgs.ndppd}/etc/ndppd.conf $out \
- --replace eth0 ${cfg.interface} \
- --replace 1111:: ${cfg.network}
- '';
-in {
- options = {
- services.ndppd = {
- enable = mkEnableOption "daemon that proxies NDP (Neighbor Discovery Protocol) messages between interfaces";
+ render = s: f: concatStringsSep "\n" (mapAttrsToList f s);
+ prefer = a: b: if a != null then a else b;
+
+ ndppdConf = prefer cfg.configFile (pkgs.writeText "ndppd.conf" ''
+ route-ttl ${toString cfg.routeTTL}
+ ${render cfg.proxies (proxyInterfaceName: proxy: ''
+ proxy ${prefer proxy.interface proxyInterfaceName} {
+ router ${boolToString proxy.router}
+ timeout ${toString proxy.timeout}
+ ttl ${toString proxy.ttl}
+ ${render proxy.rules (ruleNetworkName: rule: ''
+ rule ${prefer rule.network ruleNetworkName} {
+ ${rule.method}${if rule.method == "iface" then " ${rule.interface}" else ""}
+ }'')}
+ }'')}
+ '');
+
+ proxy = types.submodule {
+ options = {
interface = mkOption {
- type = types.string;
- default = "eth0";
- example = "ens3";
- description = "Interface which is on link-level with router.";
- };
- network = mkOption {
- type = types.string;
- default = "1111::";
- example = "2001:DB8::/32";
- description = "Network that we proxy.";
- };
- configFile = mkOption {
- type = types.nullOr types.path;
+ type = types.nullOr types.str;
+ description = ''
+ Listen for any Neighbor Solicitation messages on this interface,
+ and respond to them according to a set of rules.
+ Defaults to the name of the attrset.
+ '';
default = null;
- description = "Path to configuration file.";
};
+ router = mkOption {
+ type = types.bool;
+ description = ''
+ Turns on or off the router flag for Neighbor Advertisement Messages.
+ '';
+ default = true;
+ };
+ timeout = mkOption {
+ type = types.int;
+ description = ''
+ Controls how long to wait for a Neighbor Advertisment Message before
+ invalidating the entry, in milliseconds.
+ '';
+ default = 500;
+ };
+ ttl = mkOption {
+ type = types.int;
+ description = ''
+ Controls how long a valid or invalid entry remains in the cache, in
+ milliseconds.
+ '';
+ default = 30000;
+ };
+ rules = mkOption {
+ type = types.attrsOf rule;
+ description = ''
+ This is a rule that the target address is to match against. If no netmask
+ is provided, /128 is assumed. You may have several rule sections, and the
+ addresses may or may not overlap.
+ '';
+ default = {};
+ };
+ };
+ };
+
+ rule = types.submodule {
+ options = {
+ network = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ This is the target address is to match against. If no netmask
+ is provided, /128 is assumed. The addresses of serveral rules
+ may or may not overlap.
+ Defaults to the name of the attrset.
+ '';
+ default = null;
+ };
+ method = mkOption {
+ type = types.enum [ "static" "iface" "auto" ];
+ description = ''
+ static: Immediately answer any Neighbor Solicitation Messages
+ (if they match the IP rule).
+ iface: Forward the Neighbor Solicitation Message through the specified
+ interface and only respond if a matching Neighbor Advertisement
+ Message is received.
+ auto: Same as iface, but instead of manually specifying the outgoing
+ interface, check for a matching route in /proc/net/ipv6_route.
+ '';
+ default = "auto";
+ };
+ interface = mkOption {
+ type = types.nullOr types.str;
+ description = "Interface to use when method is iface.";
+ default = null;
+ };
+ };
+ };
+
+in {
+ options.services.ndppd = {
+ enable = mkEnableOption "daemon that proxies NDP (Neighbor Discovery Protocol) messages between interfaces";
+ interface = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ Interface which is on link-level with router.
+ (Legacy option, use services.ndppd.proxies.<interface>.rules.<network> instead)
+ '';
+ default = null;
+ example = "eth0";
+ };
+ network = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ Network that we proxy.
+ (Legacy option, use services.ndppd.proxies.<interface>.rules.<network> instead)
+ '';
+ default = null;
+ example = "1111::/64";
+ };
+ configFile = mkOption {
+ type = types.nullOr types.path;
+ description = "Path to configuration file.";
+ default = null;
+ };
+ routeTTL = mkOption {
+ type = types.int;
+ description = ''
+ This tells 'ndppd' how often to reload the route file /proc/net/ipv6_route,
+ in milliseconds.
+ '';
+ default = 30000;
+ };
+ proxies = mkOption {
+ type = types.attrsOf proxy;
+ description = ''
+ This sets up a listener, that will listen for any Neighbor Solicitation
+ messages, and respond to them according to a set of rules.
+ '';
+ default = {};
+ example = { "eth0".rules."1111::/64" = {}; };
};
};
config = mkIf cfg.enable {
- systemd.packages = [ pkgs.ndppd ];
- environment.etc."ndppd.conf".source = if (cfg.configFile != null) then cfg.configFile else configFile;
+ warnings = mkIf (cfg.interface != null && cfg.network != null) [ ''
+ The options services.ndppd.interface and services.ndppd.network will probably be removed soon,
+ please use services.ndppd.proxies..rules. instead.
+ '' ];
+
+ services.ndppd.proxies = mkIf (cfg.interface != null && cfg.network != null) {
+ "${cfg.interface}".rules."${cfg.network}" = {};
+ };
+
systemd.services.ndppd = {
- serviceConfig.RuntimeDirectory = [ "ndppd" ];
+ description = "NDP Proxy Daemon";
+ documentation = [ "man:ndppd(1)" "man:ndppd.conf(5)" ];
+ after = [ "network-pre.target" ];
wantedBy = [ "multi-user.target" ];
+ serviceConfig.ExecStart = "${pkgs.ndppd}/bin/ndppd -c ${ndppdConf}";
};
};
-
- meta.maintainers = with maintainers; [ gnidorah ];
}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 0c8284eb08d..96b34755d6f 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -142,6 +142,7 @@ in
nat.firewall = handleTest ./nat.nix { withFirewall = true; };
nat.firewall-conntrack = handleTest ./nat.nix { withFirewall = true; withConntrackHelpers = true; };
nat.standalone = handleTest ./nat.nix { withFirewall = false; };
+ ndppd = handleTest ./ndppd.nix {};
neo4j = handleTest ./neo4j.nix {};
netdata = handleTest ./netdata.nix {};
networking.networkd = handleTest ./networking.nix { networkd = true; };
diff --git a/nixos/tests/ndppd.nix b/nixos/tests/ndppd.nix
new file mode 100644
index 00000000000..9f24eb6d9d4
--- /dev/null
+++ b/nixos/tests/ndppd.nix
@@ -0,0 +1,61 @@
+import ./make-test.nix ({ pkgs, lib, ...} : {
+ name = "ndppd";
+ meta = with pkgs.stdenv.lib.maintainers; {
+ maintainers = [ fpletz ];
+ };
+
+ nodes = {
+ upstream = { pkgs, ... }: {
+ environment.systemPackages = [ pkgs.tcpdump ];
+ networking.useDHCP = false;
+ networking.interfaces = {
+ eth1 = {
+ ipv6.addresses = [
+ { address = "fd23::1"; prefixLength = 112; }
+ ];
+ ipv6.routes = [
+ { address = "fd42::";
+ prefixLength = 112;
+ }
+ ];
+ };
+ };
+ };
+ server = { pkgs, ... }: {
+ boot.kernel.sysctl = {
+ "net.ipv6.conf.all.forwarding" = "1";
+ "net.ipv6.conf.default.forwarding" = "1";
+ };
+ environment.systemPackages = [ pkgs.tcpdump ];
+ networking.useDHCP = false;
+ networking.interfaces = {
+ eth1 = {
+ ipv6.addresses = [
+ { address = "fd23::2"; prefixLength = 112; }
+ ];
+ };
+ };
+ services.ndppd = {
+ enable = true;
+ interface = "eth1";
+ network = "fd42::/112";
+ };
+ containers.client = {
+ autoStart = true;
+ privateNetwork = true;
+ hostAddress = "192.168.255.1";
+ localAddress = "192.168.255.2";
+ hostAddress6 = "fd42::1";
+ localAddress6 = "fd42::2";
+ config = {};
+ };
+ };
+ };
+
+ testScript = ''
+ startAll;
+ $server->waitForUnit("multi-user.target");
+ $upstream->waitForUnit("multi-user.target");
+ $upstream->waitUntilSucceeds("ping -c5 fd42::2");
+ '';
+})
diff --git a/pkgs/applications/networking/ndppd/default.nix b/pkgs/applications/networking/ndppd/default.nix
index a5eb9021048..6e6315ced7d 100644
--- a/pkgs/applications/networking/ndppd/default.nix
+++ b/pkgs/applications/networking/ndppd/default.nix
@@ -1,11 +1,6 @@
-{ stdenv, fetchFromGitHub, fetchurl, gzip, ... }:
+{ stdenv, fetchFromGitHub, fetchurl, gzip }:
-let
- serviceFile = fetchurl {
- url = "https://raw.githubusercontent.com/DanielAdolfsson/ndppd/f37e8eb33dc68b3385ecba9b36a5efd92755580f/ndppd.service";
- sha256 = "1zf54pzjfj9j9gr48075njqrgad4myd3dqmhvzxmjy4gjy9ixmyh";
- };
-in stdenv.mkDerivation rec {
+stdenv.mkDerivation rec {
name = "ndppd-${version}";
version = "0.2.5";
@@ -27,11 +22,6 @@ in stdenv.mkDerivation rec {
postInstall = ''
mkdir -p $out/etc
cp ndppd.conf-dist $out/etc/ndppd.conf
-
- mkdir -p $out/lib/systemd/system
- # service file needed for our module is not in release yet
- substitute ${serviceFile} $out/lib/systemd/system/ndppd.service \
- --replace /usr/sbin/ndppd $out/sbin/ndppd
'';
meta = {