1 { config, lib, pkgs, ... }:
3 cfg = config.services.avahi;
5 yesNo = yes: if yes then "yes" else "no";
7 avahiDaemonConf = with cfg; pkgs.writeText "avahi-daemon.conf" ''
9 ${# Users can set `networking.hostName' to the empty string, when getting
10 # a host name from DHCP. In that case, let Avahi take whatever the
11 # current host name is; setting `host-name' to the empty string in
12 # `avahi-daemon.conf' would be invalid.
13 lib.optionalString (hostName != "") "host-name=${hostName}"}
14 browse-domains=${lib.concatStringsSep ", " browseDomains}
15 use-ipv4=${yesNo ipv4}
16 use-ipv6=${yesNo ipv6}
17 ${lib.optionalString (allowInterfaces!=null) "allow-interfaces=${lib.concatStringsSep "," allowInterfaces}"}
18 ${lib.optionalString (denyInterfaces!=null) "deny-interfaces=${lib.concatStringsSep "," denyInterfaces}"}
19 ${lib.optionalString (domainName!=null) "domain-name=${domainName}"}
20 allow-point-to-point=${yesNo allowPointToPoint}
21 ${lib.optionalString (cacheEntriesMax!=null) "cache-entries-max=${toString cacheEntriesMax}"}
24 enable-wide-area=${yesNo wideArea}
27 disable-publishing=${yesNo (!publish.enable)}
28 disable-user-service-publishing=${yesNo (!publish.userServices)}
29 publish-addresses=${yesNo (publish.userServices || publish.addresses)}
30 publish-hinfo=${yesNo publish.hinfo}
31 publish-workstation=${yesNo publish.workstation}
32 publish-domain=${yesNo publish.domain}
35 enable-reflector=${yesNo reflector}
41 (lib.mkRenamedOptionModule [ "services" "avahi" "interfaces" ] [ "services" "avahi" "allowInterfaces" ])
42 (lib.mkRenamedOptionModule [ "services" "avahi" "nssmdns" ] [ "services" "avahi" "nssmdns4" ])
45 options.services.avahi = {
46 enable = lib.mkOption {
47 type = lib.types.bool;
50 Whether to run the Avahi daemon, which allows Avahi clients
51 to use Avahi's service discovery facilities and also allows
52 the local machine to advertise its presence and services
53 (through the mDNS responder implemented by `avahi-daemon`).
57 package = lib.mkPackageOption pkgs "avahi" { };
59 hostName = lib.mkOption {
61 default = config.networking.hostName;
62 defaultText = lib.literalExpression "config.networking.hostName";
64 Host name advertised on the LAN. If not set, avahi will use the value
65 of {option}`config.networking.hostName`.
69 domainName = lib.mkOption {
73 Domain name for all advertisements.
77 browseDomains = lib.mkOption {
78 type = lib.types.listOf lib.types.str;
80 example = [ "0pointer.de" "zeroconf.org" ];
82 List of non-local DNS domains to be browsed.
87 type = lib.types.bool;
89 description = "Whether to use IPv4.";
93 type = lib.types.bool;
95 description = "Whether to use IPv6.";
98 allowInterfaces = lib.mkOption {
99 type = lib.types.nullOr (lib.types.listOf lib.types.str);
102 List of network interfaces that should be used by the {command}`avahi-daemon`.
103 Other interfaces will be ignored. If `null`, all local interfaces
104 except loopback and point-to-point will be used.
108 denyInterfaces = lib.mkOption {
109 type = lib.types.nullOr (lib.types.listOf lib.types.str);
112 List of network interfaces that should be ignored by the
113 {command}`avahi-daemon`. Other unspecified interfaces will be used,
114 unless {option}`allowInterfaces` is set. This option takes precedence
115 over {option}`allowInterfaces`.
119 openFirewall = lib.mkOption {
120 type = lib.types.bool;
123 Whether to open the firewall for UDP port 5353.
124 Disabling this setting also disables discovering of network devices.
128 allowPointToPoint = lib.mkOption {
129 type = lib.types.bool;
132 Whether to use POINTTOPOINT interfaces. Might make mDNS unreliable due to usually large
133 latencies with such links and opens a potential security hole by allowing mDNS access from Internet
138 wideArea = lib.mkOption {
139 type = lib.types.bool;
141 description = "Whether to enable wide-area service discovery.";
144 reflector = lib.mkOption {
145 type = lib.types.bool;
147 description = "Reflect incoming mDNS requests to all allowed network interfaces.";
150 extraServiceFiles = lib.mkOption {
151 type = with lib.types; attrsOf (either str path);
153 example = lib.literalExpression ''
155 ssh = "''${pkgs.avahi}/etc/avahi/services/ssh.service";
157 <?xml version="1.0" standalone='no'?><!--*-nxml-*-->
158 <!DOCTYPE service-group SYSTEM "avahi-service.dtd">
160 <name replace-wildcards="yes">%h</name>
162 <type>_smb._tcp</type>
170 Specify custom service definitions which are placed in the avahi service directory.
171 See the {manpage}`avahi.service(5)` manpage for detailed information.
176 enable = lib.mkOption {
177 type = lib.types.bool;
179 description = "Whether to allow publishing in general.";
182 userServices = lib.mkOption {
183 type = lib.types.bool;
185 description = "Whether to publish user services. Will set `addresses=true`.";
188 addresses = lib.mkOption {
189 type = lib.types.bool;
191 description = "Whether to register mDNS address records for all local IP addresses.";
194 hinfo = lib.mkOption {
195 type = lib.types.bool;
198 Whether to register a mDNS HINFO record which contains information about the
199 local operating system and CPU.
203 workstation = lib.mkOption {
204 type = lib.types.bool;
207 Whether to register a service of type "_workstation._tcp" on the local LAN.
211 domain = lib.mkOption {
212 type = lib.types.bool;
214 description = "Whether to announce the locally used domain name for browsing by other hosts.";
218 nssmdns4 = lib.mkOption {
219 type = lib.types.bool;
222 Whether to enable the mDNS NSS (Name Service Switch) plug-in for IPv4.
223 Enabling it allows applications to resolve names in the `.local`
224 domain by transparently querying the Avahi daemon.
228 nssmdns6 = lib.mkOption {
229 type = lib.types.bool;
232 Whether to enable the mDNS NSS (Name Service Switch) plug-in for IPv6.
233 Enabling it allows applications to resolve names in the `.local`
234 domain by transparently querying the Avahi daemon.
237 Due to the fact that most mDNS responders only register local IPv4 addresses,
238 most user want to leave this option disabled to avoid long timeouts when applications first resolve the none existing IPv6 address.
243 cacheEntriesMax = lib.mkOption {
244 type = lib.types.nullOr lib.types.int;
247 Number of resource records to be cached per interface. Use 0 to
248 disable caching. Avahi daemon defaults to 4096 if not set.
252 extraConfig = lib.mkOption {
253 type = lib.types.lines;
256 Extra config to append to avahi-daemon.conf.
261 config = lib.mkIf cfg.enable {
262 users.users.avahi = {
263 description = "avahi-daemon privilege separation user";
269 users.groups.avahi = { };
271 system.nssModules = lib.optional (cfg.nssmdns4 || cfg.nssmdns6) pkgs.nssmdns;
272 system.nssDatabases.hosts = let
273 mdns = if (cfg.nssmdns4 && cfg.nssmdns6) then
275 else if (!cfg.nssmdns4 && cfg.nssmdns6) then
277 else if (cfg.nssmdns4 && !cfg.nssmdns6) then
281 in lib.optionals (cfg.nssmdns4 || cfg.nssmdns6) (lib.mkMerge [
282 (lib.mkBefore [ "${mdns}_minimal [NOTFOUND=return]" ]) # before resolve
283 (lib.mkAfter [ "${mdns}" ]) # after dns
286 environment.systemPackages = [ cfg.package ];
288 environment.etc = (lib.mapAttrs'
289 (n: v: lib.nameValuePair
290 "avahi/services/${n}.service"
291 { ${if lib.types.path.check v then "source" else "text"} = v; }
293 cfg.extraServiceFiles);
295 systemd.sockets.avahi-daemon = {
296 description = "Avahi mDNS/DNS-SD Stack Activation Socket";
297 listenStreams = [ "/run/avahi-daemon/socket" ];
298 wantedBy = [ "sockets.target" ];
301 systemd.tmpfiles.rules = [ "d /run/avahi-daemon - avahi avahi -" ];
303 systemd.services.avahi-daemon = {
304 description = "Avahi mDNS/DNS-SD Stack";
305 wantedBy = [ "multi-user.target" ];
306 requires = [ "avahi-daemon.socket" ];
308 # Make NSS modules visible so that `avahi_nss_support ()' can
309 # return a sensible value.
310 environment.LD_LIBRARY_PATH = config.system.nssModules.path;
312 path = [ pkgs.coreutils cfg.package ];
315 NotifyAccess = "main";
316 BusName = "org.freedesktop.Avahi";
318 ExecStart = "${cfg.package}/sbin/avahi-daemon --syslog -f ${avahiDaemonConf}";
319 ConfigurationDirectory = "avahi/services";
322 CapabilityBoundingSet = [
323 # https://github.com/avahi/avahi/blob/v0.9-rc1/avahi-daemon/caps.c#L38
328 DevicePolicy = "closed";
329 LockPersonality = true;
330 MemoryDenyWriteExecute = true;
331 NoNewPrivileges = true;
332 PrivateDevices = true;
334 PrivateUsers = false;
337 ProtectControlGroups = true;
339 ProtectHostname = true;
340 ProtectKernelLogs = true;
341 ProtectKernelModules = true;
342 ProtectKernelTunables = true;
343 ProtectProc = "invisible";
344 ProtectSystem = "strict";
345 RestrictAddressFamilies = [
351 RestrictNamespaces = true;
352 RestrictRealtime = true;
353 RestrictSUIDSGID = true;
354 SystemCallArchitectures = "native";
358 "@chown setgroups setresuid"
364 services.dbus.enable = true;
365 services.dbus.packages = [ cfg.package ];
367 networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall [ 5353 ];