1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.avahi;
7
8 yesNo = yes: if yes then "yes" else "no";
9
10 avahiDaemonConf = with cfg; pkgs.writeText "avahi-daemon.conf" ''
11 [server]
12 ${# Users can set `networking.hostName' to the empty string, when getting
13 # a host name from DHCP. In that case, let Avahi take whatever the
14 # current host name is; setting `host-name' to the empty string in
15 # `avahi-daemon.conf' would be invalid.
16 optionalString (hostName != "") "host-name=${hostName}"}
17 browse-domains=${concatStringsSep ", " browseDomains}
18 use-ipv4=${yesNo ipv4}
19 use-ipv6=${yesNo ipv6}
20 ${optionalString (allowInterfaces!=null) "allow-interfaces=${concatStringsSep "," allowInterfaces}"}
21 ${optionalString (denyInterfaces!=null) "deny-interfaces=${concatStringsSep "," denyInterfaces}"}
22 ${optionalString (domainName!=null) "domain-name=${domainName}"}
23 allow-point-to-point=${yesNo allowPointToPoint}
24 ${optionalString (cacheEntriesMax!=null) "cache-entries-max=${toString cacheEntriesMax}"}
25
26 [wide-area]
27 enable-wide-area=${yesNo wideArea}
28
29 [publish]
30 disable-publishing=${yesNo (!publish.enable)}
31 disable-user-service-publishing=${yesNo (!publish.userServices)}
32 publish-addresses=${yesNo (publish.userServices || publish.addresses)}
33 publish-hinfo=${yesNo publish.hinfo}
34 publish-workstation=${yesNo publish.workstation}
35 publish-domain=${yesNo publish.domain}
36
37 [reflector]
38 enable-reflector=${yesNo reflector}
39 ${extraConfig}
40 '';
41in
42{
43 imports = [
44 (lib.mkRenamedOptionModule [ "services" "avahi" "interfaces" ] [ "services" "avahi" "allowInterfaces" ])
45 ];
46
47 options.services.avahi = {
48 enable = mkOption {
49 type = types.bool;
50 default = false;
51 description = lib.mdDoc ''
52 Whether to run the Avahi daemon, which allows Avahi clients
53 to use Avahi's service discovery facilities and also allows
54 the local machine to advertise its presence and services
55 (through the mDNS responder implemented by `avahi-daemon`).
56 '';
57 };
58
59 hostName = mkOption {
60 type = types.str;
61 default = config.networking.hostName;
62 defaultText = literalExpression "config.networking.hostName";
63 description = lib.mdDoc ''
64 Host name advertised on the LAN. If not set, avahi will use the value
65 of {option}`config.networking.hostName`.
66 '';
67 };
68
69 domainName = mkOption {
70 type = types.str;
71 default = "local";
72 description = lib.mdDoc ''
73 Domain name for all advertisements.
74 '';
75 };
76
77 browseDomains = mkOption {
78 type = types.listOf types.str;
79 default = [ ];
80 example = [ "0pointer.de" "zeroconf.org" ];
81 description = lib.mdDoc ''
82 List of non-local DNS domains to be browsed.
83 '';
84 };
85
86 ipv4 = mkOption {
87 type = types.bool;
88 default = true;
89 description = lib.mdDoc "Whether to use IPv4.";
90 };
91
92 ipv6 = mkOption {
93 type = types.bool;
94 default = config.networking.enableIPv6;
95 defaultText = literalExpression "config.networking.enableIPv6";
96 description = lib.mdDoc "Whether to use IPv6.";
97 };
98
99 allowInterfaces = mkOption {
100 type = types.nullOr (types.listOf types.str);
101 default = null;
102 description = lib.mdDoc ''
103 List of network interfaces that should be used by the {command}`avahi-daemon`.
104 Other interfaces will be ignored. If `null`, all local interfaces
105 except loopback and point-to-point will be used.
106 '';
107 };
108
109 denyInterfaces = mkOption {
110 type = types.nullOr (types.listOf types.str);
111 default = null;
112 description = lib.mdDoc ''
113 List of network interfaces that should be ignored by the
114 {command}`avahi-daemon`. Other unspecified interfaces will be used,
115 unless {option}`allowInterfaces` is set. This option takes precedence
116 over {option}`allowInterfaces`.
117 '';
118 };
119
120 openFirewall = mkOption {
121 type = types.bool;
122 default = true;
123 description = lib.mdDoc ''
124 Whether to open the firewall for UDP port 5353.
125 Disabling this setting also disables discovering of network devices.
126 '';
127 };
128
129 allowPointToPoint = mkOption {
130 type = types.bool;
131 default = false;
132 description = lib.mdDoc ''
133 Whether to use POINTTOPOINT interfaces. Might make mDNS unreliable due to usually large
134 latencies with such links and opens a potential security hole by allowing mDNS access from Internet
135 connections.
136 '';
137 };
138
139 wideArea = mkOption {
140 type = types.bool;
141 default = true;
142 description = lib.mdDoc "Whether to enable wide-area service discovery.";
143 };
144
145 reflector = mkOption {
146 type = types.bool;
147 default = false;
148 description = lib.mdDoc "Reflect incoming mDNS requests to all allowed network interfaces.";
149 };
150
151 extraServiceFiles = mkOption {
152 type = with types; attrsOf (either str path);
153 default = { };
154 example = literalExpression ''
155 {
156 ssh = "''${pkgs.avahi}/etc/avahi/services/ssh.service";
157 smb = '''
158 <?xml version="1.0" standalone='no'?><!--*-nxml-*-->
159 <!DOCTYPE service-group SYSTEM "avahi-service.dtd">
160 <service-group>
161 <name replace-wildcards="yes">%h</name>
162 <service>
163 <type>_smb._tcp</type>
164 <port>445</port>
165 </service>
166 </service-group>
167 ''';
168 }
169 '';
170 description = lib.mdDoc ''
171 Specify custom service definitions which are placed in the avahi service directory.
172 See the {manpage}`avahi.service(5)` manpage for detailed information.
173 '';
174 };
175
176 publish = {
177 enable = mkOption {
178 type = types.bool;
179 default = false;
180 description = lib.mdDoc "Whether to allow publishing in general.";
181 };
182
183 userServices = mkOption {
184 type = types.bool;
185 default = false;
186 description = lib.mdDoc "Whether to publish user services. Will set `addresses=true`.";
187 };
188
189 addresses = mkOption {
190 type = types.bool;
191 default = false;
192 description = lib.mdDoc "Whether to register mDNS address records for all local IP addresses.";
193 };
194
195 hinfo = mkOption {
196 type = types.bool;
197 default = false;
198 description = lib.mdDoc ''
199 Whether to register a mDNS HINFO record which contains information about the
200 local operating system and CPU.
201 '';
202 };
203
204 workstation = mkOption {
205 type = types.bool;
206 default = false;
207 description = lib.mdDoc ''
208 Whether to register a service of type "_workstation._tcp" on the local LAN.
209 '';
210 };
211
212 domain = mkOption {
213 type = types.bool;
214 default = false;
215 description = lib.mdDoc "Whether to announce the locally used domain name for browsing by other hosts.";
216 };
217 };
218
219 nssmdns = mkOption {
220 type = types.bool;
221 default = false;
222 description = lib.mdDoc ''
223 Whether to enable the mDNS NSS (Name Service Switch) plug-in.
224 Enabling it allows applications to resolve names in the `.local`
225 domain by transparently querying the Avahi daemon.
226 '';
227 };
228
229 cacheEntriesMax = mkOption {
230 type = types.nullOr types.int;
231 default = null;
232 description = lib.mdDoc ''
233 Number of resource records to be cached per interface. Use 0 to
234 disable caching. Avahi daemon defaults to 4096 if not set.
235 '';
236 };
237
238 extraConfig = mkOption {
239 type = types.lines;
240 default = "";
241 description = lib.mdDoc ''
242 Extra config to append to avahi-daemon.conf.
243 '';
244 };
245 };
246
247 config = mkIf cfg.enable {
248 users.users.avahi = {
249 description = "avahi-daemon privilege separation user";
250 home = "/var/empty";
251 group = "avahi";
252 isSystemUser = true;
253 };
254
255 users.groups.avahi = { };
256
257 system.nssModules = optional cfg.nssmdns pkgs.nssmdns;
258 system.nssDatabases.hosts = optionals cfg.nssmdns (mkMerge [
259 (mkBefore [ "mdns_minimal [NOTFOUND=return]" ]) # before resolve
260 (mkAfter [ "mdns" ]) # after dns
261 ]);
262
263 environment.systemPackages = [ pkgs.avahi ];
264
265 environment.etc = (mapAttrs'
266 (n: v: nameValuePair
267 "avahi/services/${n}.service"
268 { ${if types.path.check v then "source" else "text"} = v; }
269 )
270 cfg.extraServiceFiles);
271
272 systemd.sockets.avahi-daemon = {
273 description = "Avahi mDNS/DNS-SD Stack Activation Socket";
274 listenStreams = [ "/run/avahi-daemon/socket" ];
275 wantedBy = [ "sockets.target" ];
276 };
277
278 systemd.tmpfiles.rules = [ "d /run/avahi-daemon - avahi avahi -" ];
279
280 systemd.services.avahi-daemon = {
281 description = "Avahi mDNS/DNS-SD Stack";
282 wantedBy = [ "multi-user.target" ];
283 requires = [ "avahi-daemon.socket" ];
284
285 # Make NSS modules visible so that `avahi_nss_support ()' can
286 # return a sensible value.
287 environment.LD_LIBRARY_PATH = config.system.nssModules.path;
288
289 path = [ pkgs.coreutils pkgs.avahi ];
290
291 serviceConfig = {
292 NotifyAccess = "main";
293 BusName = "org.freedesktop.Avahi";
294 Type = "dbus";
295 ExecStart = "${pkgs.avahi}/sbin/avahi-daemon --syslog -f ${avahiDaemonConf}";
296 ConfigurationDirectory = "avahi/services";
297 };
298 };
299
300 services.dbus.enable = true;
301 services.dbus.packages = [ pkgs.avahi ];
302
303 networking.firewall.allowedUDPPorts = mkIf cfg.openFirewall [ 5353 ];
304 };
305}