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