Skip to content

Replace DNS-SD dependency with built-in mDNS responder, and Add multi-platform CI build verification#523

Open
kgbook wants to merge 10 commits into
FDH2:masterfrom
kgbook:master
Open

Replace DNS-SD dependency with built-in mDNS responder, and Add multi-platform CI build verification#523
kgbook wants to merge 10 commits into
FDH2:masterfrom
kgbook:master

Conversation

@kgbook

@kgbook kgbook commented May 16, 2026

Copy link
Copy Markdown

Summary

  1. Replace the DNS-SD/Avahi dependency with a built-in mDNS responder.

The previous implementation depended on avahi-daemon for DNS-SD/mDNS service discovery and advertisement. While this works well on typical Linux desktop/server environments, avahi-daemon is relatively heavy for embedded targets and introduces extra runtime dependencies, daemon management, and deployment complexity.

This is especially problematic for cross-platform support, particularly on Android, where running and managing an external Avahi daemon is not practical and may conflict with the platform’s permission and service model.

This PR rewrites the mDNS responder internally, removing the dependency on Avahi/DNS-SD while keeping the required service discovery and advertisement behavior.

  1. Adds multi-platform CI build verification for:
  • Linux (ubuntu-latest): cmake, GStreamer, OpenSSL, libplist, DBus
  • macOS (macos-latest): Homebrew dependencies
  • Windows (windows-latest): MSYS2 + MinGW toolchain

Triggered on push and PR to master/main branches.

Platform Key Dependencies
Linux cmake, g++, make, libssl-dev, libplist-dev, gstreamer1.0-dev, libdbus-1-dev
macOS cmake, openssl@3, libplist, glib, gstreamer, gst-plugins-base, gst-plugins-good
Windows mingw-w64-x86_64-cmake, mingw-w64-x86_64-gcc, openssl, libplist, gstreamer

Motivation

  • Reduce external runtime dependencies
  • Avoid requiring avahi-daemon
  • Improve portability across Linux, Android, and embedded platforms
  • Simplify deployment and integration
  • Make mDNS behavior easier to control inside the application
  • Triggered CI workflow on push and PR to master/main branches.

Changes

  • Removed dependency on DNS-SD/Avahi
  • Added a built-in mDNS responder implementation
  • Reimplemented service announcement and response handling
  • Kept compatibility with existing mDNS/DNS-SD behavior
  • Verified the new implementation works correctly in testing

Validation

The rewritten mDNS responder has been tested and verified successfully, including service discovery and response behavior.

kgbook and others added 5 commits May 16, 2026 09:41
- Linux (ubuntu-latest): CMake + GStreamer + OpenSSL + libplist + DBus
- macOS (macos-latest): Homebrew dependencies
- Windows (windows-latest): MSYS2 + MinGW toolchain

Triggered on push and PR to master/main branches
GitHub Actions ubuntu-latest requires sudo for package installation
@kgbook kgbook changed the title Replace DNS-SD dependency with built-in mDNS responder Replace DNS-SD dependency with built-in mDNS responder, and Add multi-platform CI build verification May 16, 2026
@fduncanh

fduncanh commented May 21, 2026

Copy link
Copy Markdown
Collaborator

replacing avahi is a major change. can you describe where the mdnsd.c code came from? Did you adapt it from somewhere? it would have needed a lot of testing if written from scratch.

I could not find this code in Juho V's "shareplay" code, but the header says:

Copyright (C) 2011-2012 Juho Vähä-Herttua

@kgbook

kgbook commented May 22, 2026

Copy link
Copy Markdown
Author

My mistake, I originally implemented this in dnssd.c, then split it into mdnsd.c later. I forgot to update the copyright information after copying. I have fixed it to use kgbook's copyright.

Tested OK on Debian 13, and Apple M1 iMac.

@kgbook

kgbook commented May 22, 2026

Copy link
Copy Markdown
Author

I also wrote a short article about this mDNS discovery work here:

airplay mdns discovery

The article includes UML diagrams and packet capture analysis, which provide more background on the implementation and debugging process.

It is written in Chinese, but Google Translate should be enough to read it. I hope it can provide some useful background and help.

@fduncanh

Copy link
Copy Markdown
Collaborator

Can you move your PR to the new uxplay mdnsd branch?

thanks for your work on this!

@kgbook

kgbook commented May 23, 2026

Copy link
Copy Markdown
Author

OK, A new PR #524

@fduncanh

fduncanh commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Hi @kgbook

we are reviewing this. (sorry for delay).

  • Is there a reason you seem to have dropped ipv6 support?

  • Is there a difficulty in including it?

we are looking at initially moving the previous dnssd.[c,h] into a lib/dnssd-service directory,
and your modified dnssd.[c,d] + mdnsd.[c,h] into a lib/dnssd-native directory, with some conditional compilation.

(The "service" could later be removed as deprecated.)

@thiccaxe

thiccaxe commented Jun 4, 2026

Copy link
Copy Markdown

I think the better architectural choice is to remove mDNS code from uxplay entirely. Let uxplay write to a file with the txt records it wants and let a seconday process deal with mDNS. this reduces the technical debt in uxplay and allows others to choose the implementation they would like - whether that is avahi, a future android api, etc.

("files" can be unix sockets too)

Also, has this PR been tested running in conjunction with a system is also using avahi?

@kgbook

kgbook commented Jun 5, 2026

Copy link
Copy Markdown
Author

Hi @kgbook

we are reviewing this. (sorry for delay).

  • Is there a reason you seem to have dropped ipv6 support?
  • Is there a difficulty in including it?

we are looking at initially moving the previous dnssd.[c,h] into a lib/dnssd-service directory, and your modified dnssd.[c,d] + mdnsd.[c,h] into a lib/dnssd-native directory, with some conditional compilation.

(The "service" could later be removed as deprecated.)

You're right that IPv6 support was dropped in this PR. The original Avahi-based implementation had IPv6 support (v1.66 changelog: "Fix IPV6 support").

The new mdnsd.c currently only supports IPv4 multicast (224.0.0.251).

Adding IPv6 support requires:

  1. Creating an IPv6 socket (AF_INET6)
  2. Joining IPv6 mDNS multicast group ff02::fb
  3. Handling IPv6 link-local addresses with zone IDs

I will add IPv6 support in a follow-up PR.

@kgbook

kgbook commented Jun 5, 2026

Copy link
Copy Markdown
Author

I think the better architectural choice is to remove mDNS code from uxplay entirely. Let uxplay write to a file with the txt records it wants and let a seconday process deal with mDNS. this reduces the technical debt in uxplay and allows others to choose the implementation they would like - whether that is avahi, a future android api, etc.

("files" can be unix sockets too)

Also, has this PR been tested running in conjunction with a system is also using avahi?

Your suggestion has merit, but separate process introduces significant tradeoffs for the embedded scenarios I care about:

I'm also developing APlay — an AirPlay receiver focused on ARM/RISC-V devices, targeting Android, ARM Linux, HarmonyOS, and RTOS platforms. These targets make the case for embedded mDNS even stronger:

  • Power/performance: Extra RAM usage, context switching, and IPC overhead. On ARM/RTOS devices this is measurable and directly impacts battery life.
  • Deployment: Requires managing two processes, version compatibility, and startup sequencing — on embedded that's often additional scripting or init systems.
  • Security: Additional attack surface from IPC channels and a privileged helper process.
  • State sync: If helper crashes, uxplay loses mDNS advertising until detected and restarted — problematic for headless/embedded devices.
  • Startup complexity: Needs systemd/init/supervisord on Linux, or a platform-specific startup mechanism on Android/HarmonyOS/RTOS.

For APlay's target platforms (especially RTOS), a standalone mDNS daemon simply isn't available, and spawning one adds integration burden.

The current embedded approach — zero external dependencies, single-process lifecycle, minimal footprint — is the right default for these scenarios.

@kgbook

kgbook commented Jun 5, 2026

Copy link
Copy Markdown
Author

I think the better architectural choice is to remove mDNS code from uxplay entirely. Let uxplay write to a file with the txt records it wants and let a seconday process deal with mDNS. this reduces the technical debt in uxplay and allows others to choose the implementation they would like - whether that is avahi, a future android api, etc.

("files" can be unix sockets too)

Also, has this PR been tested running in conjunction with a system is also using avahi?

Also, an extra mDNS helper process requires extra permission set

@thiccaxe

thiccaxe commented Jun 5, 2026

Copy link
Copy Markdown

Also, an extra mDNS helper process requires extra permission set

I think this is an upside, as it stands avahi-daemon (or mdnsreponder on macos (and windows??)) is the only process that needs to be allowed to access multicast on 5353.

there is already a generic "dns_sd.h" interface, why not provide a header-only implementation of your proposed changes so that you can have all your criteria (single process, deployment, error handling, etc.) while minimizing the maintenance burden (no other uxplay code needs to change at all, simply a compile time flag

@fduncanh

fduncanh commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

A new branch dnssd2 has been created that will use the internal mdns code if compiled with

cmake -DUSE_MDNSD=ON

and otherwise use the unchanged uxplay with dnssd support.

Please use this for testing or PR's

The README changes, and uxplay.service changes are not included yet.

@kgbook

kgbook commented Jun 6, 2026

Copy link
Copy Markdown
Author

Hi @kgbook

we are reviewing this. (sorry for delay).

  • Is there a reason you seem to have dropped ipv6 support?
  • Is there a difficulty in including it?

we are looking at initially moving the previous dnssd.[c,h] into a lib/dnssd-service directory, and your modified dnssd.[c,d] + mdnsd.[c,h] into a lib/dnssd-native directory, with some conditional compilation.

(The "service" could later be removed as deprecated.)

Hi @fduncanhI
The latest commit now includes support for IPv6 mDNS. I’ve successfully tested and verified it on Debian 13.

@fduncanh

fduncanh commented Jun 7, 2026

Copy link
Copy Markdown
Collaborator

@kgbook thanks for the ipv6.

Your work has been refactored a bit in the new branch work_dnssd which include both the avahi/bonjour dnssd and yours.

https://github.com/FDH2/UxPlay/tree/work_dnssd

compile with "cmake -DUSE_MDNSD=1" to get your version.

The README changes and uxplay.service are not yet included, as well as the github/workflows.
(the workflows should be a separate PR)

To make changes, clone work_dnssd and submit a PR to it.

@fduncanh

fduncanh commented Jun 7, 2026

Copy link
Copy Markdown
Collaborator

@kgbook
There is a small anomaly in the IPv6 support in mdnsd

An IPv4 connection is opened first, then replaced with an IPv6 connection...
(MAYBE NOT IMPORTANT?)
see below for (1) using mdsnd (2) using avahi (which only opens an IPv6 connection)

 ./uxplay -p
*** Using internal MDNSD responder for Service Discovery: (UDP port 5353 must be open)
UxPlay 1.73.6: An Open-Source AirPlay mirroring and audio-streaming server.
using network ports UDP 7011 6001 6000 TCP 7100 7000 7001
using system MAC address xx:xx:xx:xx:xx:xx
Initialized server socket(s)
Accepted IPv4 client on socket 25, port 7000
Local : 192.168.1.aa
Remote: 192.168.1.bb
Accepted IPv6 client on socket 26, port 7000
Local : fe80::1111:5555:eeee:bbbb%2
Remote: fe80::1111:5555:eeee:cccc%2
Connection closed on socket 25
connection request from <name>(iPad14,5) with deviceID = <id>
./uxplay -p
UxPlay 1.73.6: An Open-Source AirPlay mirroring and audio-streaming server.
using network ports UDP 7011 6001 6000 TCP 7100 7000 7001
using system MAC address xx:xx:xx:xx:xx:xx
Initialized server socket(s)
Accepted IPv6 client on socket 33, port 7000
Local : 2600:4000:aaaa:bbbb:cccc:dddd:bbbb:aaaa
Remote: 2600:4000:aaaa:bbbb:cccc:aaaa:bbbb:dddd
connection request from <name> (iPad14,5) with deviceID = <id>


mdnsd previously appended both A and AAAA host records to responses sent on both IPv4 and IPv6 sockets. That behavior is only appropriate when the responder treats the physical interface as one dual-stack mDNS interface.

The built-in responder uses separate IPv4 and IPv6 sockets, so treat them as RFC 6762 logical interfaces: emit A records for IPv4 responses and AAAA records for IPv6 responses. This matches the observed Avahi AirPlay behavior more closely and avoids clients learning cross-family host addresses from one response and opening replacement TCP connections.

Also keep the AAAA answer count tied to the IPv6 address record that is actually emitted.
@fduncanh

fduncanh commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

@kgbook
Thanks! for latest mdnsd.c changes.

EDIT: yes, they fix the IPv4/IPv6 double connection reported earlier. good job!

@fduncanh

Copy link
Copy Markdown
Collaborator

@kgbook

The mdnsd code seems to work well , except on macOS, where the announcement is not visible to clients.
The Bonjour-based code works just fine.

If you are not able to test for yourself on macOS, can you perhaps add some tests in the code., that might detect failure to publish the announcement? Or give some suggestions about where such a problem might occur?

(since this is UDP, its obviously not easy to detect this.)

@fduncanh

fduncanh commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

@kgbook

we have resolved the issue on macOS finally using Claude. It was a fight between the native mdns_responder on macOS which "owns" mac_hostname.local and mdnsd.c. Will code the fix and test it.

Immediately mdnsd.c sends an announcement, the native macOS dns-responder thinks "that address belongs to me, no-one else can use it", and sends a new announcement to cancel the mdnsd.c announcement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants