1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg4 = config.services.dhcpd4;
8 cfg6 = config.services.dhcpd6;
9
10 writeConfig = cfg: pkgs.writeText "dhcpd.conf"
11 ''
12 default-lease-time 600;
13 max-lease-time 7200;
14 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 ${machine.ipAddress};
25 }
26 '')
27 cfg.machines
28 }
29 '';
30
31 dhcpdService = postfix: cfg: optionalAttrs cfg.enable {
32 "dhcpd${postfix}" = {
33 description = "DHCPv${postfix} server";
34 wantedBy = [ "multi-user.target" ];
35 after = [ "network.target" ];
36
37 preStart = ''
38 mkdir -m 755 -p ${cfg.stateDir}
39 chown dhcpd:nogroup ${cfg.stateDir}
40 touch ${cfg.stateDir}/dhcpd.leases
41 '';
42
43 serviceConfig =
44 let
45 configFile = if cfg.configFile != null then cfg.configFile else writeConfig cfg;
46 args = [ "@${pkgs.dhcp}/sbin/dhcpd" "dhcpd${postfix}" "-${postfix}"
47 "-pf" "/run/dhcpd${postfix}/dhcpd.pid"
48 "-cf" "${configFile}"
49 "-lf" "${cfg.stateDir}/dhcpd.leases"
50 "-user" "dhcpd" "-group" "nogroup"
51 ] ++ cfg.extraFlags
52 ++ cfg.interfaces;
53
54 in {
55 ExecStart = concatMapStringsSep " " escapeShellArg args;
56 Type = "forking";
57 Restart = "always";
58 RuntimeDirectory = [ "dhcpd${postfix}" ];
59 PIDFile = "/run/dhcpd${postfix}/dhcpd.pid";
60 };
61 };
62 };
63
64 machineOpts = { ... }: {
65
66 options = {
67
68 hostName = mkOption {
69 type = types.str;
70 example = "foo";
71 description = ''
72 Hostname which is assigned statically to the machine.
73 '';
74 };
75
76 ethernetAddress = mkOption {
77 type = types.str;
78 example = "00:16:76:9a:32:1d";
79 description = ''
80 MAC address of the machine.
81 '';
82 };
83
84 ipAddress = mkOption {
85 type = types.str;
86 example = "192.168.1.10";
87 description = ''
88 IP address of the machine.
89 '';
90 };
91
92 };
93 };
94
95 dhcpConfig = postfix: {
96
97 enable = mkOption {
98 type = types.bool;
99 default = false;
100 description = ''
101 Whether to enable the DHCPv${postfix} server.
102 '';
103 };
104
105 stateDir = mkOption {
106 type = types.path;
107 # We use /var/lib/dhcp for DHCPv4 to save backwards compatibility.
108 default = "/var/lib/dhcp${if postfix == "4" then "" else postfix}";
109 description = ''
110 State directory for the DHCP 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 = ''
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 = ''
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 = ''
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 = ''
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 = ''
174 A list mapping Ethernet addresses to IPv${postfix} addresses for the
175 DHCP server.
176 '';
177 };
178
179 };
180
181in
182
183{
184
185 ###### interface
186
187 options = {
188
189 services.dhcpd4 = dhcpConfig "4";
190 services.dhcpd6 = dhcpConfig "6";
191
192 };
193
194
195 ###### implementation
196
197 config = mkIf (cfg4.enable || cfg6.enable) {
198
199 users = {
200 users.dhcpd = {
201 uid = config.ids.uids.dhcpd;
202 description = "DHCP daemon user";
203 };
204 };
205
206 systemd.services = dhcpdService "4" cfg4 // dhcpdService "6" cfg6;
207
208 };
209
210}