1{ config, lib, pkgs, ... }:
2
3with pkgs;
4with lib;
5
6let
7 cfg = config.networking.networkmanager;
8
9 # /var/lib/misc is for dnsmasq.leases.
10 stateDirs = "/var/lib/NetworkManager /var/lib/dhclient /var/lib/misc";
11
12 dns =
13 if cfg.useDnsmasq then "dnsmasq"
14 else if config.services.resolved.enable then "systemd-resolved"
15 else if config.services.unbound.enable then "unbound"
16 else "default";
17
18 configFile = writeText "NetworkManager.conf" ''
19 [main]
20 plugins=keyfile
21 dhcp=${cfg.dhcp}
22 dns=${dns}
23
24 [keyfile]
25 ${optionalString (cfg.unmanaged != [])
26 ''unmanaged-devices=${lib.concatStringsSep ";" cfg.unmanaged}''}
27
28 [logging]
29 level=${cfg.logLevel}
30
31 [connection]
32 ipv6.ip6-privacy=2
33 ethernet.cloned-mac-address=${cfg.ethernet.macAddress}
34 wifi.cloned-mac-address=${cfg.wifi.macAddress}
35 ${optionalString (cfg.wifi.powersave != null)
36 ''wifi.powersave=${if cfg.wifi.powersave then "3" else "2"}''}
37
38 [device]
39 wifi.scan-rand-mac-address=${if cfg.wifi.scanRandMacAddress then "yes" else "no"}
40 '';
41
42 /*
43 [network-manager]
44 Identity=unix-group:networkmanager
45 Action=org.freedesktop.NetworkManager.*
46 ResultAny=yes
47 ResultInactive=no
48 ResultActive=yes
49
50 [modem-manager]
51 Identity=unix-group:networkmanager
52 Action=org.freedesktop.ModemManager*
53 ResultAny=yes
54 ResultInactive=no
55 ResultActive=yes
56 */
57 polkitConf = ''
58 polkit.addRule(function(action, subject) {
59 if (
60 subject.isInGroup("networkmanager")
61 && (action.id.indexOf("org.freedesktop.NetworkManager.") == 0
62 || action.id.indexOf("org.freedesktop.ModemManager") == 0
63 ))
64 { return polkit.Result.YES; }
65 });
66 '';
67
68 ns = xs: writeText "nameservers" (
69 concatStrings (map (s: "nameserver ${s}\n") xs)
70 );
71
72 overrideNameserversScript = writeScript "02overridedns" ''
73 #!/bin/sh
74 tmp=`${coreutils}/bin/mktemp`
75 ${gnused}/bin/sed '/nameserver /d' /etc/resolv.conf > $tmp
76 ${gnugrep}/bin/grep 'nameserver ' /etc/resolv.conf | \
77 ${gnugrep}/bin/grep -vf ${ns (cfg.appendNameservers ++ cfg.insertNameservers)} > $tmp.ns
78 ${optionalString (cfg.appendNameservers != []) "${coreutils}/bin/cat $tmp $tmp.ns ${ns cfg.appendNameservers} > /etc/resolv.conf"}
79 ${optionalString (cfg.insertNameservers != []) "${coreutils}/bin/cat $tmp ${ns cfg.insertNameservers} $tmp.ns > /etc/resolv.conf"}
80 ${coreutils}/bin/rm -f $tmp $tmp.ns
81 '';
82
83 dispatcherTypesSubdirMap = {
84 "basic" = "";
85 "pre-up" = "pre-up.d/";
86 "pre-down" = "pre-down.d/";
87 };
88
89 macAddressOpt = mkOption {
90 type = types.either types.str (types.enum ["permanent" "preserve" "random" "stable"]);
91 default = "preserve";
92 example = "00:11:22:33:44:55";
93 description = ''
94 "XX:XX:XX:XX:XX:XX": MAC address of the interface.
95 <literal>permanent</literal>: use the permanent MAC address of the device.
96 <literal>preserve</literal>: don’t change the MAC address of the device upon activation.
97 <literal>random</literal>: generate a randomized value upon each connect.
98 <literal>stable</literal>: generate a stable, hashed MAC address.
99 '';
100 };
101
102in {
103
104 ###### interface
105
106 options = {
107
108 networking.networkmanager = {
109
110 enable = mkOption {
111 type = types.bool;
112 default = false;
113 description = ''
114 Whether to use NetworkManager to obtain an IP address and other
115 configuration for all network interfaces that are not manually
116 configured. If enabled, a group <literal>networkmanager</literal>
117 will be created. Add all users that should have permission
118 to change network settings to this group.
119 '';
120 };
121
122 unmanaged = mkOption {
123 type = types.listOf types.string;
124 default = [];
125 description = ''
126 List of interfaces that will not be managed by NetworkManager.
127 Interface name can be specified here, but if you need more fidelity
128 see "Device List Format" in NetworkManager.conf man page.
129 '';
130 };
131
132 # Ugly hack for using the correct gnome3 packageSet
133 basePackages = mkOption {
134 type = types.attrsOf types.package;
135 default = { inherit networkmanager modemmanager wpa_supplicant
136 networkmanager-openvpn networkmanager-vpnc
137 networkmanager-openconnect networkmanager-fortisslvpn
138 networkmanager-pptp networkmanager-l2tp
139 networkmanager-iodine; };
140 internal = true;
141 };
142
143 packages = mkOption {
144 type = types.listOf types.path;
145 default = [ ];
146 description = ''
147 Extra packages that provide NetworkManager plugins.
148 '';
149 apply = list: (attrValues cfg.basePackages) ++ list;
150 };
151
152 dhcp = mkOption {
153 type = types.enum [ "dhclient" "dhcpcd" "internal" ];
154 default = "dhclient";
155 description = ''
156 Which program (or internal library) should be used for DHCP.
157 '';
158 };
159
160 logLevel = mkOption {
161 type = types.enum [ "OFF" "ERR" "WARN" "INFO" "DEBUG" "TRACE" ];
162 default = "WARN";
163 description = ''
164 Set the default logging verbosity level.
165 '';
166 };
167
168 appendNameservers = mkOption {
169 type = types.listOf types.str;
170 default = [];
171 description = ''
172 A list of name servers that should be appended
173 to the ones configured in NetworkManager or received by DHCP.
174 '';
175 };
176
177 insertNameservers = mkOption {
178 type = types.listOf types.str;
179 default = [];
180 description = ''
181 A list of name servers that should be inserted before
182 the ones configured in NetworkManager or received by DHCP.
183 '';
184 };
185
186 ethernet.macAddress = macAddressOpt;
187
188 wifi = {
189 macAddress = macAddressOpt;
190
191 powersave = mkOption {
192 type = types.nullOr types.bool;
193 default = null;
194 description = ''
195 Whether to enable Wi-Fi power saving.
196 '';
197 };
198
199 scanRandMacAddress = mkOption {
200 type = types.bool;
201 default = true;
202 description = ''
203 Whether to enable MAC address randomization of a Wi-Fi device
204 during scanning.
205 '';
206 };
207 };
208
209 useDnsmasq = mkOption {
210 type = types.bool;
211 default = false;
212 description = ''
213 Enable NetworkManager's dnsmasq integration. NetworkManager will run
214 dnsmasq as a local caching nameserver, using a "split DNS"
215 configuration if you are connected to a VPN, and then update
216 resolv.conf to point to the local nameserver.
217 '';
218 };
219
220 dispatcherScripts = mkOption {
221 type = types.listOf (types.submodule {
222 options = {
223 source = mkOption {
224 type = types.path;
225 description = ''
226 A script.
227 '';
228 };
229
230 type = mkOption {
231 type = types.enum (attrNames dispatcherTypesSubdirMap);
232 default = "basic";
233 description = ''
234 Dispatcher hook type. Only basic hooks are currently available.
235 '';
236 };
237 };
238 });
239 default = [];
240 description = ''
241 A list of scripts which will be executed in response to network events.
242 '';
243 };
244
245 enableStrongSwan = mkOption {
246 type = types.bool;
247 default = false;
248 description = ''
249 Enable the StrongSwan plugin.
250 </para><para>
251 If you enable this option the
252 <literal>networkmanager_strongswan</literal> plugin will be added to
253 the <option>networking.networkmanager.packages</option> option
254 so you don't need to to that yourself.
255 '';
256 };
257 };
258 };
259
260
261 ###### implementation
262
263 config = mkIf cfg.enable {
264
265 assertions = [{
266 assertion = config.networking.wireless.enable == false;
267 message = "You can not use networking.networkmanager with networking.wireless";
268 }];
269
270 boot.kernelModules = [ "ppp_mppe" ]; # Needed for most (all?) PPTP VPN connections.
271
272 environment.etc = with cfg.basePackages; [
273 { source = configFile;
274 target = "NetworkManager/NetworkManager.conf";
275 }
276 { source = "${networkmanager-openvpn}/etc/NetworkManager/VPN/nm-openvpn-service.name";
277 target = "NetworkManager/VPN/nm-openvpn-service.name";
278 }
279 { source = "${networkmanager-vpnc}/etc/NetworkManager/VPN/nm-vpnc-service.name";
280 target = "NetworkManager/VPN/nm-vpnc-service.name";
281 }
282 { source = "${networkmanager-openconnect}/etc/NetworkManager/VPN/nm-openconnect-service.name";
283 target = "NetworkManager/VPN/nm-openconnect-service.name";
284 }
285 { source = "${networkmanager-fortisslvpn}/etc/NetworkManager/VPN/nm-fortisslvpn-service.name";
286 target = "NetworkManager/VPN/nm-fortisslvpn-service.name";
287 }
288 { source = "${networkmanager-pptp}/etc/NetworkManager/VPN/nm-pptp-service.name";
289 target = "NetworkManager/VPN/nm-pptp-service.name";
290 }
291 { source = "${networkmanager-l2tp}/etc/NetworkManager/VPN/nm-l2tp-service.name";
292 target = "NetworkManager/VPN/nm-l2tp-service.name";
293 }
294 { source = "${networkmanager_strongswan}/etc/NetworkManager/VPN/nm-strongswan-service.name";
295 target = "NetworkManager/VPN/nm-strongswan-service.name";
296 }
297 { source = "${networkmanager-iodine}/etc/NetworkManager/VPN/nm-iodine-service.name";
298 target = "NetworkManager/VPN/nm-iodine-service.name";
299 }
300 ] ++ optional (cfg.appendNameservers == [] || cfg.insertNameservers == [])
301 { source = overrideNameserversScript;
302 target = "NetworkManager/dispatcher.d/02overridedns";
303 }
304 ++ lib.imap1 (i: s: {
305 inherit (s) source;
306 target = "NetworkManager/dispatcher.d/${dispatcherTypesSubdirMap.${s.type}}03userscript${lib.fixedWidthNumber 4 i}";
307 }) cfg.dispatcherScripts;
308
309 environment.systemPackages = cfg.packages;
310
311 users.extraGroups = [{
312 name = "networkmanager";
313 gid = config.ids.gids.networkmanager;
314 }
315 {
316 name = "nm-openvpn";
317 gid = config.ids.gids.nm-openvpn;
318 }];
319 users.extraUsers = [{
320 name = "nm-openvpn";
321 uid = config.ids.uids.nm-openvpn;
322 extraGroups = [ "networkmanager" ];
323 }
324 {
325 name = "nm-iodine";
326 isSystemUser = true;
327 group = "networkmanager";
328 }];
329
330 systemd.packages = cfg.packages;
331
332 systemd.services."network-manager" = {
333 wantedBy = [ "network.target" ];
334 restartTriggers = [ configFile ];
335
336 preStart = ''
337 mkdir -m 700 -p /etc/NetworkManager/system-connections
338 mkdir -m 700 -p /etc/ipsec.d
339 mkdir -m 755 -p ${stateDirs}
340 '';
341 };
342
343 # Turn off NixOS' network management
344 networking = {
345 useDHCP = false;
346 # use mkDefault to trigger the assertion about the conflict above
347 wireless.enable = lib.mkDefault false;
348 };
349
350 security.polkit.extraConfig = polkitConf;
351
352 networking.networkmanager.packages =
353 mkIf cfg.enableStrongSwan [ pkgs.networkmanager_strongswan ];
354
355 services.dbus.packages =
356 optional cfg.enableStrongSwan pkgs.strongswanNM ++ cfg.packages;
357
358 services.udev.packages = cfg.packages;
359 };
360}