at 23.05-pre 6.0 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 cfg4 = config.services.dhcpd4; 8 cfg6 = config.services.dhcpd6; 9 10 writeConfig = postfix: cfg: pkgs.writeText "dhcpd.conf" 11 '' 12 default-lease-time 600; 13 max-lease-time 7200; 14 ${optionalString (!cfg.authoritative) "not "}authoritative; 15 ddns-update-style interim; 16 log-facility local1; # see dhcpd.nix 17 18 ${cfg.extraConfig} 19 20 ${lib.concatMapStrings 21 (machine: '' 22 host ${machine.hostName} { 23 hardware ethernet ${machine.ethernetAddress}; 24 fixed-address${ 25 optionalString (postfix == "6") postfix 26 } ${machine.ipAddress}; 27 } 28 '') 29 cfg.machines 30 } 31 ''; 32 33 dhcpdService = postfix: cfg: 34 let 35 configFile = 36 if cfg.configFile != null 37 then cfg.configFile 38 else writeConfig postfix cfg; 39 leaseFile = "/var/lib/dhcpd${postfix}/dhcpd.leases"; 40 args = [ 41 "@${pkgs.dhcp}/sbin/dhcpd" "dhcpd${postfix}" "-${postfix}" 42 "-pf" "/run/dhcpd${postfix}/dhcpd.pid" 43 "-cf" configFile 44 "-lf" leaseFile 45 ] ++ cfg.extraFlags 46 ++ cfg.interfaces; 47 in 48 optionalAttrs cfg.enable { 49 "dhcpd${postfix}" = { 50 description = "DHCPv${postfix} server"; 51 wantedBy = [ "multi-user.target" ]; 52 after = [ "network.target" ]; 53 54 preStart = "touch ${leaseFile}"; 55 serviceConfig = { 56 ExecStart = concatMapStringsSep " " escapeShellArg args; 57 Type = "forking"; 58 Restart = "always"; 59 DynamicUser = true; 60 User = "dhcpd"; 61 Group = "dhcpd"; 62 AmbientCapabilities = [ 63 "CAP_NET_RAW" # to send ICMP messages 64 "CAP_NET_BIND_SERVICE" # to bind on DHCP port (67) 65 ]; 66 StateDirectory = "dhcpd${postfix}"; 67 RuntimeDirectory = "dhcpd${postfix}"; 68 PIDFile = "/run/dhcpd${postfix}/dhcpd.pid"; 69 }; 70 }; 71 }; 72 73 machineOpts = { ... }: { 74 75 options = { 76 77 hostName = mkOption { 78 type = types.str; 79 example = "foo"; 80 description = lib.mdDoc '' 81 Hostname which is assigned statically to the machine. 82 ''; 83 }; 84 85 ethernetAddress = mkOption { 86 type = types.str; 87 example = "00:16:76:9a:32:1d"; 88 description = lib.mdDoc '' 89 MAC address of the machine. 90 ''; 91 }; 92 93 ipAddress = mkOption { 94 type = types.str; 95 example = "192.168.1.10"; 96 description = lib.mdDoc '' 97 IP address of the machine. 98 ''; 99 }; 100 101 }; 102 }; 103 104 dhcpConfig = postfix: { 105 106 enable = mkOption { 107 type = types.bool; 108 default = false; 109 description = lib.mdDoc '' 110 Whether to enable the DHCPv${postfix} server. 111 ''; 112 }; 113 114 extraConfig = mkOption { 115 type = types.lines; 116 default = ""; 117 example = '' 118 option subnet-mask 255.255.255.0; 119 option broadcast-address 192.168.1.255; 120 option routers 192.168.1.5; 121 option domain-name-servers 130.161.158.4, 130.161.33.17, 130.161.180.1; 122 option domain-name "example.org"; 123 subnet 192.168.1.0 netmask 255.255.255.0 { 124 range 192.168.1.100 192.168.1.200; 125 } 126 ''; 127 description = lib.mdDoc '' 128 Extra text to be appended to the DHCP server configuration 129 file. Currently, you almost certainly need to specify something 130 there, such as the options specifying the subnet mask, DNS servers, 131 etc. 132 ''; 133 }; 134 135 extraFlags = mkOption { 136 type = types.listOf types.str; 137 default = []; 138 description = lib.mdDoc '' 139 Additional command line flags to be passed to the dhcpd daemon. 140 ''; 141 }; 142 143 configFile = mkOption { 144 type = types.nullOr types.path; 145 default = null; 146 description = lib.mdDoc '' 147 The path of the DHCP server configuration file. If no file 148 is specified, a file is generated using the other options. 149 ''; 150 }; 151 152 interfaces = mkOption { 153 type = types.listOf types.str; 154 default = ["eth0"]; 155 description = lib.mdDoc '' 156 The interfaces on which the DHCP server should listen. 157 ''; 158 }; 159 160 machines = mkOption { 161 type = with types; listOf (submodule machineOpts); 162 default = []; 163 example = [ 164 { hostName = "foo"; 165 ethernetAddress = "00:16:76:9a:32:1d"; 166 ipAddress = "192.168.1.10"; 167 } 168 { hostName = "bar"; 169 ethernetAddress = "00:19:d1:1d:c4:9a"; 170 ipAddress = "192.168.1.11"; 171 } 172 ]; 173 description = lib.mdDoc '' 174 A list mapping Ethernet addresses to IPv${postfix} addresses for the 175 DHCP server. 176 ''; 177 }; 178 179 authoritative = mkOption { 180 type = types.bool; 181 default = true; 182 description = lib.mdDoc '' 183 Whether the DHCP server shall send DHCPNAK messages to misconfigured 184 clients. If this is not done, clients may be unable to get a correct 185 IP address after changing subnets until their old lease has expired. 186 ''; 187 }; 188 189 }; 190 191in 192 193{ 194 195 imports = [ 196 (mkRenamedOptionModule [ "services" "dhcpd" ] [ "services" "dhcpd4" ]) 197 ] ++ flip map [ "4" "6" ] (postfix: 198 mkRemovedOptionModule [ "services" "dhcpd${postfix}" "stateDir" ] '' 199 The DHCP server state directory is now managed with the systemd's DynamicUser mechanism. 200 This means the directory is named after the service (dhcpd${postfix}), created under 201 /var/lib/private/ and symlinked to /var/lib/. 202 '' 203 ); 204 205 ###### interface 206 207 options = { 208 209 services.dhcpd4 = dhcpConfig "4"; 210 services.dhcpd6 = dhcpConfig "6"; 211 212 }; 213 214 215 ###### implementation 216 217 config = mkIf (cfg4.enable || cfg6.enable) { 218 219 systemd.services = dhcpdService "4" cfg4 // dhcpdService "6" cfg6; 220 221 }; 222 223}