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 touch ${cfg.stateDir}/dhcpd.leases
40 '';
41
42 serviceConfig =
43 let
44 configFile = if cfg.configFile != null then cfg.configFile else writeConfig cfg;
45 args = [ "@${pkgs.dhcp}/sbin/dhcpd" "dhcpd${postfix}" "-${postfix}"
46 "-pf" "/run/dhcpd${postfix}/dhcpd.pid"
47 "-cf" "${configFile}"
48 "-lf" "${cfg.stateDir}/dhcpd.leases"
49 "-user" "dhcpd" "-group" "nogroup"
50 ] ++ cfg.extraFlags
51 ++ cfg.interfaces;
52
53 in {
54 ExecStart = concatMapStringsSep " " escapeShellArg args;
55 Type = "forking";
56 Restart = "always";
57 RuntimeDirectory = [ "dhcpd${postfix}" ];
58 PIDFile = "/run/dhcpd${postfix}/dhcpd.pid";
59 };
60 };
61 };
62
63 machineOpts = { ... }: {
64
65 options = {
66
67 hostName = mkOption {
68 type = types.str;
69 example = "foo";
70 description = ''
71 Hostname which is assigned statically to the machine.
72 '';
73 };
74
75 ethernetAddress = mkOption {
76 type = types.str;
77 example = "00:16:76:9a:32:1d";
78 description = ''
79 MAC address of the machine.
80 '';
81 };
82
83 ipAddress = mkOption {
84 type = types.str;
85 example = "192.168.1.10";
86 description = ''
87 IP address of the machine.
88 '';
89 };
90
91 };
92 };
93
94 dhcpConfig = postfix: {
95
96 enable = mkOption {
97 type = types.bool;
98 default = false;
99 description = ''
100 Whether to enable the DHCPv${postfix} server.
101 '';
102 };
103
104 stateDir = mkOption {
105 type = types.path;
106 # We use /var/lib/dhcp for DHCPv4 to save backwards compatibility.
107 default = "/var/lib/dhcp${if postfix == "4" then "" else postfix}";
108 description = ''
109 State directory for the DHCP server.
110 '';
111 };
112
113 extraConfig = mkOption {
114 type = types.lines;
115 default = "";
116 example = ''
117 option subnet-mask 255.255.255.0;
118 option broadcast-address 192.168.1.255;
119 option routers 192.168.1.5;
120 option domain-name-servers 130.161.158.4, 130.161.33.17, 130.161.180.1;
121 option domain-name "example.org";
122 subnet 192.168.1.0 netmask 255.255.255.0 {
123 range 192.168.1.100 192.168.1.200;
124 }
125 '';
126 description = ''
127 Extra text to be appended to the DHCP server configuration
128 file. Currently, you almost certainly need to specify something
129 there, such as the options specifying the subnet mask, DNS servers,
130 etc.
131 '';
132 };
133
134 extraFlags = mkOption {
135 type = types.listOf types.str;
136 default = [];
137 description = ''
138 Additional command line flags to be passed to the dhcpd daemon.
139 '';
140 };
141
142 configFile = mkOption {
143 type = types.nullOr types.path;
144 default = null;
145 description = ''
146 The path of the DHCP server configuration file. If no file
147 is specified, a file is generated using the other options.
148 '';
149 };
150
151 interfaces = mkOption {
152 type = types.listOf types.str;
153 default = ["eth0"];
154 description = ''
155 The interfaces on which the DHCP server should listen.
156 '';
157 };
158
159 machines = mkOption {
160 type = with types; listOf (submodule machineOpts);
161 default = [];
162 example = [
163 { hostName = "foo";
164 ethernetAddress = "00:16:76:9a:32:1d";
165 ipAddress = "192.168.1.10";
166 }
167 { hostName = "bar";
168 ethernetAddress = "00:19:d1:1d:c4:9a";
169 ipAddress = "192.168.1.11";
170 }
171 ];
172 description = ''
173 A list mapping Ethernet addresses to IPv${postfix} addresses for the
174 DHCP server.
175 '';
176 };
177
178 };
179
180in
181
182{
183
184 ###### interface
185
186 options = {
187
188 services.dhcpd4 = dhcpConfig "4";
189 services.dhcpd6 = dhcpConfig "6";
190
191 };
192
193
194 ###### implementation
195
196 config = mkIf (cfg4.enable || cfg6.enable) {
197
198 users = {
199 extraUsers.dhcpd = {
200 uid = config.ids.uids.dhcpd;
201 description = "DHCP daemon user";
202 };
203 };
204
205 systemd.services = dhcpdService "4" cfg4 // dhcpdService "6" cfg6;
206
207 };
208
209}