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