Merge pull request #55122 from elseym/ndppd-module

ndppd module: refactor and fix
This commit is contained in:
Maximilian Bosch 2019-02-04 21:51:00 +01:00 committed by GitHub
commit 5a3a543078
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 220 additions and 40 deletions

View File

@ -476,6 +476,14 @@
</para>
</note>
</listitem>
<listitem>
<para>
The <link xlink:href="https://github.com/DanielAdolfsson/ndppd"><literal>ndppd</literal></link> module
now supports <link linkend="opt-services.ndppd.enable">all config options</link> provided by the current
upstream version as service options. Additionally the <literal>ndppd</literal> package doesn't contain
the systemd unit configuration from upstream anymore, the unit is completely configured by the NixOS module now.
</para>
</listitem>
</itemizedlist>
</section>
</section>

View File

@ -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.&lt;interface&gt;.rules.&lt;network&gt; instead)
'';
default = null;
example = "eth0";
};
network = mkOption {
type = types.nullOr types.str;
description = ''
Network that we proxy.
(Legacy option, use services.ndppd.proxies.&lt;interface&gt;.rules.&lt;network&gt; 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.<interface>.rules.<network> 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 ];
}

View File

@ -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; };

61
nixos/tests/ndppd.nix Normal file
View File

@ -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");
'';
})

View File

@ -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 = {