1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 dhcpcd = if !config.boot.isContainer then pkgs.dhcpcd else pkgs.dhcpcd.override { udev = null; };
8
9 cfg = config.networking.dhcpcd;
10
11 interfaces = attrValues config.networking.interfaces;
12
13 enableDHCP = config.networking.dhcpcd.enable &&
14 (config.networking.useDHCP || any (i: i.useDHCP == true) interfaces);
15
16 # Don't start dhcpcd on explicitly configured interfaces or on
17 # interfaces that are part of a bridge, bond or sit device.
18 ignoredInterfaces =
19 map (i: i.name) (filter (i: if i.useDHCP != null then !i.useDHCP else i.ip4 != [ ] || i.ipAddress != null) interfaces)
20 ++ mapAttrsToList (i: _: i) config.networking.sits
21 ++ concatLists (attrValues (mapAttrs (n: v: v.interfaces) config.networking.bridges))
22 ++ concatLists (attrValues (mapAttrs (n: v: v.interfaces) config.networking.vswitches))
23 ++ concatLists (attrValues (mapAttrs (n: v: v.interfaces) config.networking.bonds))
24 ++ config.networking.dhcpcd.denyInterfaces;
25
26 arrayAppendOrNull = a1: a2: if a1 == null && a2 == null then null
27 else if a1 == null then a2 else if a2 == null then a1
28 else a1 ++ a2;
29
30 # If dhcp is disabled but explicit interfaces are enabled,
31 # we need to provide dhcp just for those interfaces.
32 allowInterfaces = arrayAppendOrNull cfg.allowInterfaces
33 (if !config.networking.useDHCP && enableDHCP then
34 map (i: i.name) (filter (i: i.useDHCP == true) interfaces) else null);
35
36 # Config file adapted from the one that ships with dhcpcd.
37 dhcpcdConf = pkgs.writeText "dhcpcd.conf"
38 ''
39 # Inform the DHCP server of our hostname for DDNS.
40 hostname
41
42 # A list of options to request from the DHCP server.
43 option domain_name_servers, domain_name, domain_search, host_name
44 option classless_static_routes, ntp_servers, interface_mtu
45
46 # A ServerID is required by RFC2131.
47 # Commented out because of many non-compliant DHCP servers in the wild :(
48 #require dhcp_server_identifier
49
50 # A hook script is provided to lookup the hostname if not set by
51 # the DHCP server, but it should not be run by default.
52 nohook lookup-hostname
53
54 # Ignore peth* devices; on Xen, they're renamed physical
55 # Ethernet cards used for bridging. Likewise for vif* and tap*
56 # (Xen) and virbr* and vnet* (libvirt).
57 denyinterfaces ${toString ignoredInterfaces} lo peth* vif* tap* tun* virbr* vnet* vboxnet* sit*
58
59 # Use the list of allowed interfaces if specified
60 ${optionalString (allowInterfaces != null) "allowinterfaces ${toString allowInterfaces}"}
61
62 ${cfg.extraConfig}
63 '';
64
65 exitHook = pkgs.writeText "dhcpcd.exit-hook"
66 ''
67 if [ "$reason" = BOUND -o "$reason" = REBOOT ]; then
68 # Restart ntpd. We need to restart it to make sure that it
69 # will actually do something: if ntpd cannot resolve the
70 # server hostnames in its config file, then it will never do
71 # anything ever again ("couldn't resolve ..., giving up on
72 # it"), so we silently lose time synchronisation. This also
73 # applies to openntpd.
74 ${config.systemd.package}/bin/systemctl try-reload-or-restart ntpd.service openntpd.service || true
75 fi
76
77 ${cfg.runHook}
78 '';
79
80in
81
82{
83
84 ###### interface
85
86 options = {
87
88 networking.dhcpcd.enable = mkOption {
89 type = types.bool;
90 default = true;
91 description = ''
92 Whether to enable dhcpcd for device configuration. This is mainly to
93 explicitly disable dhcpcd (for example when using networkd).
94 '';
95 };
96
97 networking.dhcpcd.persistent = mkOption {
98 type = types.bool;
99 default = false;
100 description = ''
101 Whenever to leave interfaces configured on dhcpcd daemon
102 shutdown. Set to true if you have your root or store mounted
103 over the network or this machine accepts SSH connections
104 through DHCP interfaces and clients should be notified when
105 it shuts down.
106 '';
107 };
108
109 networking.dhcpcd.denyInterfaces = mkOption {
110 type = types.listOf types.str;
111 default = [];
112 description = ''
113 Disable the DHCP client for any interface whose name matches
114 any of the shell glob patterns in this list. The purpose of
115 this option is to blacklist virtual interfaces such as those
116 created by Xen, libvirt, LXC, etc.
117 '';
118 };
119
120 networking.dhcpcd.allowInterfaces = mkOption {
121 type = types.nullOr (types.listOf types.str);
122 default = null;
123 description = ''
124 Enable the DHCP client for any interface whose name matches
125 any of the shell glob patterns in this list. Any interface not
126 explicitly matched by this pattern will be denied. This pattern only
127 applies when non-null.
128 '';
129 };
130
131 networking.dhcpcd.extraConfig = mkOption {
132 type = types.lines;
133 default = "";
134 description = ''
135 Literal string to append to the config file generated for dhcpcd.
136 '';
137 };
138
139 networking.dhcpcd.runHook = mkOption {
140 type = types.lines;
141 default = "";
142 example = "if [[ $reason =~ BOUND ]]; then echo $interface: Routers are $new_routers - were $old_routers; fi";
143 description = ''
144 Shell code that will be run after all other hooks. See
145 `man dhcpcd-run-hooks` for details on what is possible.
146 '';
147 };
148
149 };
150
151
152 ###### implementation
153
154 config = mkIf enableDHCP {
155
156 systemd.services.dhcpcd =
157 { description = "DHCP Client";
158
159 wantedBy = [ "network-online.target" ];
160 after = [ "network.target" ];
161 wants = [ "network.target" ];
162
163 # Stopping dhcpcd during a reconfiguration is undesirable
164 # because it brings down the network interfaces configured by
165 # dhcpcd. So do a "systemctl restart" instead.
166 stopIfChanged = false;
167
168 path = [ dhcpcd pkgs.nettools pkgs.openresolv ];
169
170 unitConfig.ConditionCapability = "CAP_NET_ADMIN";
171
172 serviceConfig =
173 { Type = "forking";
174 PIDFile = "/run/dhcpcd.pid";
175 ExecStart = "@${dhcpcd}/sbin/dhcpcd dhcpcd -w --quiet ${optionalString cfg.persistent "--persistent"} --config ${dhcpcdConf}";
176 ExecReload = "${dhcpcd}/sbin/dhcpcd --rebind";
177 Restart = "always";
178 };
179 };
180
181 environment.systemPackages = [ dhcpcd ];
182
183 environment.etc =
184 [ { source = exitHook;
185 target = "dhcpcd.exit-hook";
186 }
187 ];
188
189 powerManagement.resumeCommands = mkIf config.systemd.services.dhcpcd.enable
190 ''
191 # Tell dhcpcd to rebind its interfaces if it's running.
192 ${config.systemd.package}/bin/systemctl reload dhcpcd.service
193 '';
194
195 };
196
197}