FreeBSD mDNSResponder — porting plan

A port of Apple's mDNSResponder + libdns_sd to FreeBSD, providing native Bonjour / zeroconf service discovery: .local hostname resolution, automatic printer / file-share discovery on the LAN, and the same dns-sd(1) + libdns_sd API surface that Apple-derived applications already speak. Companion to launchd, configd, kmodloader, asl, notifyd.

Status: planning v0 — deferred

1. Goal & non-goals

1.1 Goal

Provide a working mdnsd daemon and libdns_sd client library on FreeBSD so that:

1.2 Non-goals (this iteration)

2. Repository

Monorepo. Source under mdns/ in freebsd-launchd:

freebsd-launchd/
├── src/                          launchd
├── configd/                      Apple configd
├── kmodloader/                   clean-room kmodloader
├── asl/                          Apple syslog
├── notifyd/                      Apple Libnotify
├── mdns/                         Apple mDNSResponder (this plan)
│   ├── scripts/import-source.sh
│   ├── Makefile
│   ├── compat/
│   └── src/                      forked Apple mDNSResponder-878.70.2
│       ├── mDNSCore/             platform-agnostic core (kept as-is)
│       ├── mDNSPosix/            BSD/Linux/FreeBSD platform layer (used as-is)
│       ├── mDNSShared/           shared utilities, libdns_sd source
│       ├── Clients/              dns-sd CLI + sample apps
│       ├── DSO/                  DNS Stateful Operations (RFC 8490)
│       └── ServiceRegistration/  mDNS service-registration daemon
└── make-mdnsresponder.sh         STANDALONE — builds + installs

3. Architecture

+-------------------------+ | /etc/mdnsd.conf | | (interface filters, | | logging level, etc.) | +-----------+-------------+ | (vnode source: live reload) v +---------------------+ +-------+--------+ +----------------------+ | UDP/5353 multicast |-->| mdnsd |<-->| /var/run/mDNSResponder| | (224.0.0.251) | | (mDNSCore + | | (Unix socket; client | | READ source per | | mDNSPosix + | | IPC; libdispatch | | iface that's up | | libdispatch) | | READ source) | +---------------------+ +-------+--------+ +----------------------+ | +--------+--------+ | record cache | | (in-memory | | authoritative | | + cached) | +-----------------+ Client side: app links libdns_sd.so -- DNSServiceRegister(...), etc. Library opens /var/run/mDNSResponder, sends framed messages, receives callbacks via the socket fd registered with the app's runloop / dispatch queue.

3.1 Why mDNSPosix already works

Apple has invested in non-Darwin builds because mDNSResponder is also shipped as a daemon on Linux distributions and Solaris (now defunct), and because various Apple-internal teams build for embedded/Wind-River targets that aren't macOS. The mDNSPosix/ directory contains:

This already builds on FreeBSD as of recent tags. Verification: the net/mDNSResponder port in the FreeBSD ports tree uses exactly this layer.

3.2 libdispatch upgrade (optional)

The default PosixDaemon.c uses select(2). For consistency with the rest of our daemon stack (launchd, configd, asl, notifyd, kmodloader all use libdispatch), we replace the select loop with DISPATCH_SOURCE_TYPE_READ sources for: each multicast UDP socket, the client IPC socket, signals. ~200 LOC change in PosixDaemon.c; non-blocking optional polish — the select-based daemon works fine.

3.3 Event sources (libdispatch, post-Phase-3)

Source typeWatchesReaction
DISPATCH_SOURCE_TYPE_READUDP/5353 multicast socket per active ifaceparse mDNS message; update record cache; respond to queries
DISPATCH_SOURCE_TYPE_READ/var/run/mDNSResponder (Unix socket listener)accept new client connections
DISPATCH_SOURCE_TYPE_READeach connected client fdparse client IPC framed messages: register / browse / resolve / cancel
DISPATCH_SOURCE_TYPE_VNODE/etc/mdnsd.confreload config; reset interface filters
DISPATCH_SOURCE_TYPE_SIGNALSIGTERM, SIGHUP, SIGUSR1SIGTERM: clean shutdown. SIGHUP: reload conf. SIGUSR1: dump cache.
configd subscription (DO)network-state events from configdiface up: open multicast socket; iface down: tear down. Replaces configd-internal polling that mDNSResponder.proj's macOSX layer does.

4. Install paths

ArtifactPathWhy
mdnsd binary/usr/sbin/mdnsdSystem daemon, admin-callable. Same path FreeBSD's net/mDNSResponder port uses.
dns-sd CLI/usr/bin/dns-sdService-registration / browsing tool. Apple's name; user-callable.
libdns_sd.so/System/Library/Libraries/libdns_sd.soClient library. Apps link against this.
Header/System/Library/Headers/dns_sd.hPublic API. Apps #include <dns_sd.h>.
nsswitch module/usr/local/lib/nss_mdns.so.1nsswitch.conf integration: hosts: files mdns dns resolves .local via mDNS.
Daemon socket/var/run/mDNSResponderConventional Apple path for client IPC.
launchd plist/System/Library/LaunchDaemons/org.freebsd.mdnsd.plistProject-shipped daemon.
Config/etc/mdnsd.confOptional; interface filters and logging.

5. Locked architectural decisions

DecisionChoice
Source baselineApple mDNSResponder-878.70.2. Apache 2.0. Latest tag.
Platform layerUse mDNSPosix/ as-is. Drop mDNSMacOSX/ and mDNSWindows/.
Mach IPCNone — mDNSResponder doesn't use Mach in non-Darwin builds. Zero <mach/> includes in the imported tree once mDNSMacOSX is dropped.
Client IPC mechanismExisting libdns_sd.c Unix-socket protocol. Already there; framed messages over /var/run/mDNSResponder.
Event loopPhase 1: stock select(2) loop in PosixDaemon.c. Phase 3: upgrade to libdispatch sources for consistency.
Coexistence with AvahiNone. Pick one per system. Apple-shaped installs default to this port.
nsswitch integrationShip nss_mdns.so + edit /etc/nsswitch.conf overlay so .local hostname lookups resolve via mDNS.
License (top-level)BSD-2-Clause. Apple's mDNS source retains Apache 2.0 per-file (mostly; a few BSD-licensed sub-files preserved as-is).

6. File-by-file plan (mdns/src/)

Imported source: Apple mDNSResponder-878.70.2. 261 files, ~154k LOC. Zero Mach-tied files (the Mach-using code is all under mDNSMacOSX/, which we drop wholesale).

6.1 Deleted on import

6.2 Retained — Phase 2 fate

Directory / fileApple LOCAction
mDNSCore/~50kKeep as-is. Platform-agnostic mDNS protocol implementation. No edits expected.
mDNSPosix/~8kKeep mostly as-is. Phase 3 upgrades the daemon's event loop from select to libdispatch.
mDNSShared/dnssd_clientstub.c + dnssd_ipc.{c,h}~5kKeep. The libdns_sd client library + wire protocol.
mDNSShared/dnssd_clientshim.c~700Keep. Embedded-build alternative to the daemon (for static linking; we don't use, but trivial to keep).
mDNSShared/dnsextd*~10kDrop — dnsextd is the wide-area Bonjour helper; we don't ship BTMM.
Clients/dns-sd.c~3kKeep. The dns-sd(1) CLI.
Clients/SimpleChat/, SampleCode/, etc.Keep selectively as documentation; don't ship binaries.
DSO/~3kKeep. DNS Stateful Operations (RFC 8490) for long-running queries; modern mDNS uses this.
ServiceRegistration/~5kKeep. Daemon for centralized service registration.

Total post-Phase-2: roughly 80-85k LOC kept, ~70k LOC dropped (mostly the Darwin and Windows platform layers). Net: a clean cross-platform Bonjour stack.

7. FreeBSD-only wins (vs Apple's Darwin-tied build)

FeatureApple's Darwin buildThis port (FreeBSD-only)
Network state ingestionconfigd's network-state plugin via Mach notificationsconfigd subscription via DO (per our configd port). Or stock mDNSPosix iface poll.
Power management hooksIOKit power-management for sleep/wakeSkip in Phase 1. Phase 3+: subscribe to notifyd's org.freebsd.power.sleep-requested / ...wake; pause/resume mDNS state appropriately.
Sandboxingsandbox(7) profile (mdnsd.sb)Drop. Capsicum-based sandboxing later.
Wide-Area BonjourIntegration with iCloud + Back to My Mac infrastructureDropped (non-goal).
Build-system gatesiOS / sim / catalyst #ifDelete. One target.

8. Use cases for gershwin

8.1 .local hostname resolution

Each gershwin machine on the LAN registers <hostname>.local via mDNS. Other machines (gershwin, macOS, Linux running Avahi, Windows Bonjour) resolve it without DNS server config. ssh joe.local, open http://joe.local:8080 Just Work.

8.2 Service discovery for the desktop

ConcernBonjour service typeEffect
Printers_ipp._tcp, _ipps._tcp, _pdl-datastream._tcpWorkspace's print dialog populates the printer list automatically. No manual IP / queue-name entry.
File shares (SMB)_smb._tcpWorkspace's File Viewer "Network" sidebar shows other machines automatically.
SSH targets_ssh._tcpTerminal apps populate "known hosts" with discoverable SSH servers.
HTTP services_http._tcp"Network" browser shows web servers running on the LAN.
iTunes-style media sharing_daap._tcpMusic apps auto-discover libraries on the network. (Useful if gershwin grows a Music app.)
Sleep / Wake announcementsintegration with notifyd's power eventsWhen a machine wakes, re-announce all registered services. When it sleeps, withdraw.

8.3 Apps that "just work"

Any app that calls DNSServiceRegister(), DNSServiceBrowse(), DNSServiceResolve(), etc., links unchanged. Specific examples:

9. launchd integration

9.1 The plist

<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
    <key>Label</key>             <string>org.freebsd.mdnsd</string>
    <key>ProgramArguments</key>  <array><string>/usr/sbin/mdnsd</string></array>
    <key>RunAtLoad</key>         <true/>
    <key>KeepAlive</key>         <true/>
    <key>Sockets</key>           <dict>
        <key>Listeners</key>     <dict>
            <key>SockPathName</key> <string>/var/run/mDNSResponder</string>
            <key>SockType</key>     <string>stream</string>
            <key>SockPathMode</key> <integer>438</integer> <!-- 0666 -->
        </dict>
    </dict>
</dict>
</plist>

9.2 Boot ordering

mDNSResponder needs at least loopback up. configd's network-state events are nice-to-have for hot-iface integration but not blocking. Order: starts in parallel with the other system daemons; comes online faster on systems with no NICs (loopback only) or slower as it waits for first iface advertisement.

10. Licensing

Apple's mDNSResponder is mostly Apache 2.0 (per the LICENSE file in the repo). Some sub-files use BSD-derived licenses (BSD-3-Clause, BSD-2-Clause, MIT) preserved per-file. Different from configd / asl / notifyd which are APSL.

SourceLicenseHow we handle it
Apple mDNSResponder-878.70.2 (majority Apache 2.0)Apache 2.0 (most files); BSD-2/3-Clause / MIT (some)Per-file headers preserved verbatim. Edits inherit per-file license.
This repo's new code (FreeBSD shims, integration glue)BSD-2-ClauseSPDX header on each new file.
libdispatch (linked, not in tree)Apache 2.0 + Runtime ExceptionListed in NOTICE.

11. Phased delivery

Phase 0 — repo scaffold + import

Phase 1 — build + run mdnsd via mDNSPosix

Phase 2 — nsswitch + dns-sd CLI

Phase 3 — libdispatch event loop (polish)

Phase 4 — gershwin integration

Phase 5+ — optional

12. Open questions

Q1. Avahi coexistence. A user might have net/avahi installed from FreeBSD pkg. Both daemons bind UDP/5353; one wins, the other fails to start. Decision: ship a build-time conflict in our pkg (if/when we package this) marking it as conflicting with avahi-daemon. Document in README.
Q2. Proxy mDNS for reliability. A laptop sleeping disappears from .local. Apple's macOS resolution: AirPort base stations / AppleTVs run a "Sleep Proxy Service" (_sleep-proxy._udp) that re-advertises sleeping clients. We don't have AppleTVs, but a user running gershwin-server-machine could optionally run our mdnsd in sleep-proxy mode. Phase 5+ feature; flag not enabled by default.
Q3. Default hostname registration. mdnsd announces $(hostname).local at startup. If the host's hostname is "Amnesiac" (the FreeBSD kernel default), every system on a livecd LAN announces itself as Amnesiac.local — conflict storm. Decision: configd should set a unique hostname (UUID-prefixed, or based on the primary MAC) before mdnsd starts. Coordinate via launchd ordering.
Q4. Wide-area integration. Apple's wide-area Bonjour (BTMM) requires a DNS-SD-over-TLS uplink to a controlled-by-Apple resolver. We drop wide-area entirely. If users want Wide-Area Bonjour later (hosting their own DNS server with the right SRV records), they configure the daemon with custom upstream. Out of scope for this iteration.

13. References