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