at 23.11-pre 6.2 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.ndppd; 7 8 render = s: f: concatStringsSep "\n" (mapAttrsToList f s); 9 prefer = a: b: if a != null then a else b; 10 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}"} 21 }'')} 22 }'')} 23 ''); 24 25 proxy = types.submodule { 26 options = { 27 interface = mkOption { 28 type = types.nullOr types.str; 29 description = lib.mdDoc '' 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. 33 ''; 34 default = null; 35 }; 36 router = mkOption { 37 type = types.bool; 38 description = lib.mdDoc '' 39 Turns on or off the router flag for Neighbor Advertisement Messages. 40 ''; 41 default = true; 42 }; 43 timeout = mkOption { 44 type = types.int; 45 description = lib.mdDoc '' 46 Controls how long to wait for a Neighbor Advertisement Message before 47 invalidating the entry, in milliseconds. 48 ''; 49 default = 500; 50 }; 51 ttl = mkOption { 52 type = types.int; 53 description = lib.mdDoc '' 54 Controls how long a valid or invalid entry remains in the cache, in 55 milliseconds. 56 ''; 57 default = 30000; 58 }; 59 rules = mkOption { 60 type = types.attrsOf rule; 61 description = lib.mdDoc '' 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. 65 ''; 66 default = {}; 67 }; 68 }; 69 }; 70 71 rule = types.submodule { 72 options = { 73 network = mkOption { 74 type = types.nullOr types.str; 75 description = lib.mdDoc '' 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. 80 ''; 81 default = null; 82 }; 83 method = mkOption { 84 type = types.enum [ "static" "iface" "auto" ]; 85 description = lib.mdDoc '' 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 90 Message is received. 91 auto: Same as iface, but instead of manually specifying the outgoing 92 interface, check for a matching route in /proc/net/ipv6_route. 93 ''; 94 default = "auto"; 95 }; 96 interface = mkOption { 97 type = types.nullOr types.str; 98 description = lib.mdDoc "Interface to use when method is iface."; 99 default = null; 100 }; 101 }; 102 }; 103 104in { 105 options.services.ndppd = { 106 enable = mkEnableOption (lib.mdDoc "daemon that proxies NDP (Neighbor Discovery Protocol) messages between interfaces"); 107 interface = mkOption { 108 type = types.nullOr types.str; 109 description = lib.mdDoc '' 110 Interface which is on link-level with router. 111 (Legacy option, use services.ndppd.proxies.\<interface\>.rules.\<network\> instead) 112 ''; 113 default = null; 114 example = "eth0"; 115 }; 116 network = mkOption { 117 type = types.nullOr types.str; 118 description = lib.mdDoc '' 119 Network that we proxy. 120 (Legacy option, use services.ndppd.proxies.\<interface\>.rules.\<network\> instead) 121 ''; 122 default = null; 123 example = "1111::/64"; 124 }; 125 configFile = mkOption { 126 type = types.nullOr types.path; 127 description = lib.mdDoc "Path to configuration file."; 128 default = null; 129 }; 130 routeTTL = mkOption { 131 type = types.int; 132 description = lib.mdDoc '' 133 This tells 'ndppd' how often to reload the route file /proc/net/ipv6_route, 134 in milliseconds. 135 ''; 136 default = 30000; 137 }; 138 proxies = mkOption { 139 type = types.attrsOf proxy; 140 description = lib.mdDoc '' 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. 143 ''; 144 default = {}; 145 example = literalExpression '' 146 { 147 eth0.rules."1111::/64" = {}; 148 } 149 ''; 150 }; 151 }; 152 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. 157 '' ]; 158 159 services.ndppd.proxies = mkIf (cfg.interface != null && cfg.network != null) { 160 ${cfg.interface}.rules.${cfg.network} = {}; 161 }; 162 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" ]; 168 serviceConfig = { 169 ExecStart = "${pkgs.ndppd}/bin/ndppd -c ${ndppdConf}"; 170 171 # Sandboxing 172 CapabilityBoundingSet = "CAP_NET_RAW CAP_NET_ADMIN"; 173 ProtectSystem = "strict"; 174 ProtectHome = true; 175 PrivateTmp = true; 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; 186 }; 187 }; 188 }; 189}