at 25.11-pre 12 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 cfg = config.services.avahi; 9 10 yesNo = yes: if yes then "yes" else "no"; 11 12 avahiDaemonConf = 13 with cfg; 14 pkgs.writeText "avahi-daemon.conf" '' 15 [server] 16 ${ 17 # Users can set `networking.hostName' to the empty string, when getting 18 # a host name from DHCP. In that case, let Avahi take whatever the 19 # current host name is; setting `host-name' to the empty string in 20 # `avahi-daemon.conf' would be invalid. 21 lib.optionalString (hostName != "") "host-name=${hostName}" 22 } 23 browse-domains=${lib.concatStringsSep ", " browseDomains} 24 use-ipv4=${yesNo ipv4} 25 use-ipv6=${yesNo ipv6} 26 ${lib.optionalString ( 27 allowInterfaces != null 28 ) "allow-interfaces=${lib.concatStringsSep "," allowInterfaces}"} 29 ${lib.optionalString ( 30 denyInterfaces != null 31 ) "deny-interfaces=${lib.concatStringsSep "," denyInterfaces}"} 32 ${lib.optionalString (domainName != null) "domain-name=${domainName}"} 33 allow-point-to-point=${yesNo allowPointToPoint} 34 ${lib.optionalString (cacheEntriesMax != null) "cache-entries-max=${toString cacheEntriesMax}"} 35 36 [wide-area] 37 enable-wide-area=${yesNo wideArea} 38 39 [publish] 40 disable-publishing=${yesNo (!publish.enable)} 41 disable-user-service-publishing=${yesNo (!publish.userServices)} 42 publish-addresses=${yesNo (publish.userServices || publish.addresses)} 43 publish-hinfo=${yesNo publish.hinfo} 44 publish-workstation=${yesNo publish.workstation} 45 publish-domain=${yesNo publish.domain} 46 47 [reflector] 48 enable-reflector=${yesNo reflector} 49 ${extraConfig} 50 ''; 51in 52{ 53 imports = [ 54 (lib.mkRenamedOptionModule 55 [ "services" "avahi" "interfaces" ] 56 [ "services" "avahi" "allowInterfaces" ] 57 ) 58 (lib.mkRenamedOptionModule [ "services" "avahi" "nssmdns" ] [ "services" "avahi" "nssmdns4" ]) 59 ]; 60 61 options.services.avahi = { 62 enable = lib.mkOption { 63 type = lib.types.bool; 64 default = false; 65 description = '' 66 Whether to run the Avahi daemon, which allows Avahi clients 67 to use Avahi's service discovery facilities and also allows 68 the local machine to advertise its presence and services 69 (through the mDNS responder implemented by `avahi-daemon`). 70 ''; 71 }; 72 73 package = lib.mkPackageOption pkgs "avahi" { }; 74 75 hostName = lib.mkOption { 76 type = lib.types.str; 77 default = config.networking.hostName; 78 defaultText = lib.literalExpression "config.networking.hostName"; 79 description = '' 80 Host name advertised on the LAN. If not set, avahi will use the value 81 of {option}`config.networking.hostName`. 82 ''; 83 }; 84 85 domainName = lib.mkOption { 86 type = lib.types.str; 87 default = "local"; 88 description = '' 89 Domain name for all advertisements. 90 ''; 91 }; 92 93 browseDomains = lib.mkOption { 94 type = lib.types.listOf lib.types.str; 95 default = [ ]; 96 example = [ 97 "0pointer.de" 98 "zeroconf.org" 99 ]; 100 description = '' 101 List of non-local DNS domains to be browsed. 102 ''; 103 }; 104 105 ipv4 = lib.mkOption { 106 type = lib.types.bool; 107 default = true; 108 description = "Whether to use IPv4."; 109 }; 110 111 ipv6 = lib.mkOption { 112 type = lib.types.bool; 113 default = config.networking.enableIPv6; 114 defaultText = lib.literalExpression "config.networking.enableIPv6"; 115 description = "Whether to use IPv6."; 116 }; 117 118 allowInterfaces = lib.mkOption { 119 type = lib.types.nullOr (lib.types.listOf lib.types.str); 120 default = null; 121 description = '' 122 List of network interfaces that should be used by the {command}`avahi-daemon`. 123 Other interfaces will be ignored. If `null`, all local interfaces 124 except loopback and point-to-point will be used. 125 ''; 126 }; 127 128 denyInterfaces = lib.mkOption { 129 type = lib.types.nullOr (lib.types.listOf lib.types.str); 130 default = null; 131 description = '' 132 List of network interfaces that should be ignored by the 133 {command}`avahi-daemon`. Other unspecified interfaces will be used, 134 unless {option}`allowInterfaces` is set. This option takes precedence 135 over {option}`allowInterfaces`. 136 ''; 137 }; 138 139 openFirewall = lib.mkOption { 140 type = lib.types.bool; 141 default = true; 142 description = '' 143 Whether to open the firewall for UDP port 5353. 144 Disabling this setting also disables discovering of network devices. 145 ''; 146 }; 147 148 allowPointToPoint = lib.mkOption { 149 type = lib.types.bool; 150 default = false; 151 description = '' 152 Whether to use POINTTOPOINT interfaces. Might make mDNS unreliable due to usually large 153 latencies with such links and opens a potential security hole by allowing mDNS access from Internet 154 connections. 155 ''; 156 }; 157 158 wideArea = lib.mkOption { 159 type = lib.types.bool; 160 default = true; 161 description = "Whether to enable wide-area service discovery."; 162 }; 163 164 reflector = lib.mkOption { 165 type = lib.types.bool; 166 default = false; 167 description = "Reflect incoming mDNS requests to all allowed network interfaces."; 168 }; 169 170 extraServiceFiles = lib.mkOption { 171 type = with lib.types; attrsOf (either str path); 172 default = { }; 173 example = lib.literalExpression '' 174 { 175 ssh = "''${pkgs.avahi}/etc/avahi/services/ssh.service"; 176 smb = ''' 177 <?xml version="1.0" standalone='no'?><!--*-nxml-*--> 178 <!DOCTYPE service-group SYSTEM "avahi-service.dtd"> 179 <service-group> 180 <name replace-wildcards="yes">%h</name> 181 <service> 182 <type>_smb._tcp</type> 183 <port>445</port> 184 </service> 185 </service-group> 186 '''; 187 } 188 ''; 189 description = '' 190 Specify custom service definitions which are placed in the avahi service directory. 191 See the {manpage}`avahi.service(5)` manpage for detailed information. 192 ''; 193 }; 194 195 publish = { 196 enable = lib.mkOption { 197 type = lib.types.bool; 198 default = false; 199 description = "Whether to allow publishing in general."; 200 }; 201 202 userServices = lib.mkOption { 203 type = lib.types.bool; 204 default = false; 205 description = "Whether to publish user services. Will set `addresses=true`."; 206 }; 207 208 addresses = lib.mkOption { 209 type = lib.types.bool; 210 default = false; 211 description = "Whether to register mDNS address records for all local IP addresses."; 212 }; 213 214 hinfo = lib.mkOption { 215 type = lib.types.bool; 216 default = false; 217 description = '' 218 Whether to register a mDNS HINFO record which contains information about the 219 local operating system and CPU. 220 ''; 221 }; 222 223 workstation = lib.mkOption { 224 type = lib.types.bool; 225 default = false; 226 description = '' 227 Whether to register a service of type "_workstation._tcp" on the local LAN. 228 ''; 229 }; 230 231 domain = lib.mkOption { 232 type = lib.types.bool; 233 default = false; 234 description = "Whether to announce the locally used domain name for browsing by other hosts."; 235 }; 236 }; 237 238 nssmdns4 = lib.mkOption { 239 type = lib.types.bool; 240 default = false; 241 description = '' 242 Whether to enable the mDNS NSS (Name Service Switch) plug-in for IPv4. 243 Enabling it allows applications to resolve names in the `.local` 244 domain by transparently querying the Avahi daemon. 245 ''; 246 }; 247 248 nssmdns6 = lib.mkOption { 249 type = lib.types.bool; 250 default = false; 251 description = '' 252 Whether to enable the mDNS NSS (Name Service Switch) plug-in for IPv6. 253 Enabling it allows applications to resolve names in the `.local` 254 domain by transparently querying the Avahi daemon. 255 256 ::: {.note} 257 Due to the fact that most mDNS responders only register local IPv4 addresses, 258 most user want to leave this option disabled to avoid long timeouts when applications first resolve the none existing IPv6 address. 259 ::: 260 ''; 261 }; 262 263 cacheEntriesMax = lib.mkOption { 264 type = lib.types.nullOr lib.types.int; 265 default = null; 266 description = '' 267 Number of resource records to be cached per interface. Use 0 to 268 disable caching. Avahi daemon defaults to 4096 if not set. 269 ''; 270 }; 271 272 extraConfig = lib.mkOption { 273 type = lib.types.lines; 274 default = ""; 275 description = '' 276 Extra config to append to avahi-daemon.conf. 277 ''; 278 }; 279 }; 280 281 config = lib.mkIf cfg.enable { 282 users.users.avahi = { 283 description = "avahi-daemon privilege separation user"; 284 home = "/var/empty"; 285 group = "avahi"; 286 isSystemUser = true; 287 }; 288 289 users.groups.avahi = { }; 290 291 system.nssModules = lib.optional (cfg.nssmdns4 || cfg.nssmdns6) pkgs.nssmdns; 292 system.nssDatabases.hosts = 293 let 294 mdns = 295 if (cfg.nssmdns4 && cfg.nssmdns6) then 296 "mdns" 297 else if (!cfg.nssmdns4 && cfg.nssmdns6) then 298 "mdns6" 299 else if (cfg.nssmdns4 && !cfg.nssmdns6) then 300 "mdns4" 301 else 302 ""; 303 in 304 lib.optionals (cfg.nssmdns4 || cfg.nssmdns6) ( 305 lib.mkMerge [ 306 (lib.mkBefore [ "${mdns}_minimal [NOTFOUND=return]" ]) # before resolve 307 (lib.mkAfter [ "${mdns}" ]) # after dns 308 ] 309 ); 310 311 environment.systemPackages = [ cfg.package ]; 312 313 environment.etc = ( 314 lib.mapAttrs' ( 315 n: v: 316 lib.nameValuePair "avahi/services/${n}.service" { 317 ${if lib.types.path.check v then "source" else "text"} = v; 318 } 319 ) cfg.extraServiceFiles 320 ); 321 322 systemd.sockets.avahi-daemon = { 323 description = "Avahi mDNS/DNS-SD Stack Activation Socket"; 324 listenStreams = [ "/run/avahi-daemon/socket" ]; 325 wantedBy = [ "sockets.target" ]; 326 }; 327 328 systemd.tmpfiles.rules = [ "d /run/avahi-daemon - avahi avahi -" ]; 329 330 systemd.services.avahi-daemon = { 331 description = "Avahi mDNS/DNS-SD Stack"; 332 wantedBy = [ "multi-user.target" ]; 333 requires = [ "avahi-daemon.socket" ]; 334 documentation = [ 335 "man:avahi-daemon(8)" 336 "man:avahi-daemon.conf(5)" 337 "man:avahi.hosts(5)" 338 "man:avahi.service(5)" 339 ]; 340 341 # Make NSS modules visible so that `avahi_nss_support ()' can 342 # return a sensible value. 343 environment.LD_LIBRARY_PATH = config.system.nssModules.path; 344 345 path = [ 346 pkgs.coreutils 347 cfg.package 348 ]; 349 350 serviceConfig = { 351 NotifyAccess = "main"; 352 BusName = "org.freedesktop.Avahi"; 353 Type = "dbus"; 354 ExecStart = "${cfg.package}/sbin/avahi-daemon --syslog -f ${avahiDaemonConf}"; 355 ConfigurationDirectory = "avahi/services"; 356 357 # Hardening 358 CapabilityBoundingSet = [ 359 # https://github.com/avahi/avahi/blob/v0.9-rc1/avahi-daemon/caps.c#L38 360 "CAP_SYS_CHROOT" 361 "CAP_SETUID" 362 "CAP_SETGID" 363 ]; 364 DevicePolicy = "closed"; 365 LockPersonality = true; 366 MemoryDenyWriteExecute = true; 367 NoNewPrivileges = true; 368 PrivateDevices = true; 369 PrivateTmp = true; 370 PrivateUsers = false; 371 ProcSubset = "pid"; 372 ProtectClock = true; 373 ProtectControlGroups = true; 374 ProtectHome = true; 375 ProtectHostname = true; 376 ProtectKernelLogs = true; 377 ProtectKernelModules = true; 378 ProtectKernelTunables = true; 379 ProtectProc = "invisible"; 380 ProtectSystem = "strict"; 381 RestrictAddressFamilies = [ 382 "AF_INET" 383 "AF_INET6" 384 "AF_NETLINK" 385 "AF_UNIX" 386 ]; 387 RestrictNamespaces = true; 388 RestrictRealtime = true; 389 RestrictSUIDSGID = true; 390 SystemCallArchitectures = "native"; 391 SystemCallFilter = [ 392 "@system-service" 393 "~@privileged" 394 "@chown setgroups setresuid" 395 ]; 396 UMask = "0077"; 397 }; 398 }; 399 400 services.dbus.enable = true; 401 services.dbus.packages = [ cfg.package ]; 402 403 networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall [ 5353 ]; 404 }; 405}