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