1 { config, lib, pkgs, ... }:
6 cfg = config.services.ndppd;
8 render = s: f: concatStringsSep "\n" (mapAttrsToList f s);
9 prefer = a: b: if a != null then a else b;
11 ndppdConf = prefer cfg.configFile (pkgs.writeText "ndppd.conf" ''
12 route-ttl ${toString cfg.routeTTL}
13 ${render cfg.proxies (proxyInterfaceName: proxy: ''
14 proxy ${prefer proxy.interface proxyInterfaceName} {
15 router ${boolToString proxy.router}
16 timeout ${toString proxy.timeout}
17 ttl ${toString proxy.ttl}
18 ${render proxy.rules (ruleNetworkName: rule: ''
19 rule ${prefer rule.network ruleNetworkName} {
20 ${rule.method}${optionalString (rule.method == "iface") " ${rule.interface}"}
25 proxy = types.submodule {
27 interface = mkOption {
28 type = types.nullOr types.str;
30 Listen for any Neighbor Solicitation messages on this interface,
31 and respond to them according to a set of rules.
32 Defaults to the name of the attrset.
39 Turns on or off the router flag for Neighbor Advertisement Messages.
46 Controls how long to wait for a Neighbor Advertisement Message before
47 invalidating the entry, in milliseconds.
54 Controls how long a valid or invalid entry remains in the cache, in
60 type = types.attrsOf rule;
62 This is a rule that the target address is to match against. If no netmask
63 is provided, /128 is assumed. You may have several rule sections, and the
64 addresses may or may not overlap.
71 rule = types.submodule {
74 type = types.nullOr types.str;
76 This is the target address is to match against. If no netmask
77 is provided, /128 is assumed. The addresses of several rules
78 may or may not overlap.
79 Defaults to the name of the attrset.
84 type = types.enum [ "static" "iface" "auto" ];
86 static: Immediately answer any Neighbor Solicitation Messages
87 (if they match the IP rule).
88 iface: Forward the Neighbor Solicitation Message through the specified
89 interface and only respond if a matching Neighbor Advertisement
91 auto: Same as iface, but instead of manually specifying the outgoing
92 interface, check for a matching route in /proc/net/ipv6_route.
96 interface = mkOption {
97 type = types.nullOr types.str;
98 description = "Interface to use when method is iface.";
105 options.services.ndppd = {
106 enable = mkEnableOption "daemon that proxies NDP (Neighbor Discovery Protocol) messages between interfaces";
107 interface = mkOption {
108 type = types.nullOr types.str;
110 Interface which is on link-level with router.
111 (Legacy option, use services.ndppd.proxies.\<interface\>.rules.\<network\> instead)
117 type = types.nullOr types.str;
119 Network that we proxy.
120 (Legacy option, use services.ndppd.proxies.\<interface\>.rules.\<network\> instead)
123 example = "1111::/64";
125 configFile = mkOption {
126 type = types.nullOr types.path;
127 description = "Path to configuration file.";
130 routeTTL = mkOption {
133 This tells 'ndppd' how often to reload the route file /proc/net/ipv6_route,
139 type = types.attrsOf proxy;
141 This sets up a listener, that will listen for any Neighbor Solicitation
142 messages, and respond to them according to a set of rules.
145 example = literalExpression ''
147 eth0.rules."1111::/64" = {};
153 config = mkIf cfg.enable {
154 warnings = mkIf (cfg.interface != null && cfg.network != null) [ ''
155 The options services.ndppd.interface and services.ndppd.network will probably be removed soon,
156 please use services.ndppd.proxies.<interface>.rules.<network> instead.
159 services.ndppd.proxies = mkIf (cfg.interface != null && cfg.network != null) {
160 ${cfg.interface}.rules.${cfg.network} = {};
163 systemd.services.ndppd = {
164 description = "NDP Proxy Daemon";
165 documentation = [ "man:ndppd(1)" "man:ndppd.conf(5)" ];
166 after = [ "network-pre.target" ];
167 wantedBy = [ "multi-user.target" ];
169 ExecStart = "${pkgs.ndppd}/bin/ndppd -c ${ndppdConf}";
172 CapabilityBoundingSet = "CAP_NET_RAW CAP_NET_ADMIN";
173 ProtectSystem = "strict";
176 PrivateDevices = true;
177 ProtectKernelTunables = true;
178 ProtectKernelModules = true;
179 ProtectControlGroups = true;
180 RestrictAddressFamilies = "AF_INET6 AF_PACKET AF_NETLINK";
181 RestrictNamespaces = true;
182 LockPersonality = true;
183 MemoryDenyWriteExecute = true;
184 RestrictRealtime = true;
185 RestrictSUIDSGID = true;