Skip to content

Add Bonjour/mDNS service discovery for VPN clients#1815

Open
mav2287 wants to merge 3 commits intohwdsl2:masterfrom
mav2287:add-bonjour-mdns-support
Open

Add Bonjour/mDNS service discovery for VPN clients#1815
mav2287 wants to merge 3 commits intohwdsl2:masterfrom
mav2287:add-bonjour-mdns-support

Conversation

@mav2287
Copy link
Copy Markdown

@mav2287 mav2287 commented Mar 29, 2026

Summary

Adds optional Bonjour/mDNS service discovery so VPN clients can see devices on the server's local network — printers, AirPlay, file shares, and other services that advertise via mDNS/DNS-SD.

Closes #1232

How it works

A real-time service watcher uses avahi-browse to passively monitor multicast mDNS traffic on the LAN. When devices appear or disappear, it generates native dnsmasq DNS-SD records (PTR, SRV, TXT) within seconds. VPN clients query dnsmasq through the tunnel and get the same service discovery results they would on the local network.

The watcher is event-driven — zero CPU/network overhead when nothing changes. It listens to multicast packets already on the network.

Supports all three VPN modes:

  • IKEv2: Full support (modecfgdns + modecfgdomains=local)
  • IPsec/XAuth: Full support (modecfgdns + modecfgdomains=local)
  • IPsec/L2TP: Hostname resolution works; automatic service browsing requires manual client DNS config (L2TP does not support pushing search domains)

Detects IKEv2-only mode (ikev1-policy=drop) and skips XAuth/L2TP configuration when active. Detects existing DNS servers (BIND, etc.) on port 53 and uses an alternate IP to avoid conflicts. Handles custom VPN subnets.

Files changed

  • extras/enable_bonjour.sh — Enable script (installs avahi + dnsmasq, sets up real-time service watcher for all detected VPN modes)
  • extras/disable_bonjour.sh — Cleanly reverts all changes from backups
  • extras/vpnuninstall.sh — Added warning to run disable_bonjour.sh before uninstalling VPN
  • docs/advanced-usage.md / docs/advanced-usage-zh.md — New documentation section
  • README.md + all language variants (zh, zh-Hant, ja, ru) — Added link in Advanced usage list

Test plan

  • Ubuntu 24.04: Full E2E with 3 LXC containers (Bonjour device + VPN server + strongSwan IKEv2 client through tunnel) — 24/24 tests passing
  • Alpine 3.22: Install + DNS-SD queries + disable/re-enable verified
  • Production VPN server: iPhone connected via IKEv2, discovered and printed to HP Officejet on LAN via Bonjour
  • Real-time verification: printer powered off → disappeared from VPN client; powered on → reappeared within seconds
  • All three VPN mode configs updated correctly (IKEv2, XAuth, L2TP)
  • IKEv2-only mode detection (ikev1-policy=drop) works correctly
  • DNS port conflict detection (existing BIND9) works correctly
  • Custom VPN subnets detected dynamically from config files
  • Shellcheck passes (same warnings as existing project scripts)
  • Disable script cleanly reverts all changes
  • Existing VPN functionality unaffected (opt-in only)

James added 2 commits March 29, 2026 14:45
Add optional Bonjour/mDNS service discovery so VPN clients can see
devices on the server's local network (printers, AirPlay, file shares,
etc.). Uses a real-time event-driven watcher that monitors multicast
mDNS traffic and generates native dnsmasq DNS-SD records within seconds
of devices appearing or disappearing on the LAN.

Supports IKEv2, IPsec/XAuth, and IPsec/L2TP modes. Detects IKEv2-only
mode, existing DNS servers, and custom VPN subnets.

Closes hwdsl2#1232
- Replace modecfgdomains="local" with modecfgdomains="local, ." to prevent
  split DNS. The "." (root domain) acts as a catch-all so VPN DNS handles
  ALL queries, preventing DNS leak to the client's cellular/WiFi DNS.
- Add strict-order to dnsmasq config to ensure internal DNS server is
  queried before public DNS, preventing NXDOMAIN race conditions for
  internal domains (.private, etc.)
- Add iptables DNAT rule to capture mDNS multicast (224.0.0.251) from
  VPN clients and redirect to dnsmasq as additional fallback
- Remove detect_search_domains() — no longer needed with catch-all approach
@hwdsl2
Copy link
Copy Markdown
Owner

hwdsl2 commented Mar 31, 2026

@mav2287 Hello! Thank you for your contribution. I plan to take a more detailed look when I have time.

From a brief review, please remove the added .gitignore file, and feel free to list your name on the copyright lines in the two added scripts.

@mav2287
Copy link
Copy Markdown
Author

mav2287 commented Mar 31, 2026

Thanks for taking a look, this took a LONG time to figure out so there may need to be some tweaks and adjustments. I updated the files. Just let me know if there is anything else you need.

@hwdsl2
Copy link
Copy Markdown
Owner

hwdsl2 commented Apr 13, 2026

Thanks for the detailed PR. The architecture is well thought out and the documentation is thorough. I've reviewed the scripts and have a few items to address before merging.

Blockers

1. VPN_SUBNET_PREFIX is unset when no address pool is found and a DNS conflict is detected

In enable_bonjour.sh, detect_vpn_subnet(): the else branch sets VPN_SERVER_IP="192.168.43.1" but never sets VPN_SUBNET_PREFIX. The block immediately below then does:

VPN_SERVER_IP="${VPN_SUBNET_PREFIX}.2"  # expands to ".2"

check_ip will reject ".2" and abort. Fix by adding VPN_SUBNET_PREFIX="192.168.43" in the else branch.

2. grep -oP / grep -Po breaks on Alpine Linux

Both scripts use PCRE grep (-oP, -Po) in 14 places, but Alpine's default busybox grep does not support -P. On Alpine every call silently returns empty, leaving VPN_SERVER_IP, SERVER_LAN_IP, VPN_POOL and other variables unset. Alpine 3.22 is in the test plan as passing, so it is possible the test environment had GNU grep installed rather than busybox grep.

These need to be rewritten using POSIX ERE (grep -oE) or awk/sed equivalents. Some examples:

Current Replacement
grep -oP '^\d+\.\d+\.\d+' grep -oE '^[0-9]+\.[0-9]+\.[0-9]+'
grep -oP '(?<=inet\s)\d+(\.\d+){3}' awk '/inet /{print $2}' | cut -d/ -f1
grep -oP 'rightaddresspool=\K...' sed 's/.*rightaddresspool=//' piped to grep -oE

Should Fix

3. Full-tunnel DNS override needs a clear warning

Setting modecfgdomains="local, ." routes all DNS from VPN clients through the VPN server. This is the right choice for Bonjour to work correctly on Apple devices, but it silently replaces any existing split-DNS configuration. The confirmation prompt should make this explicit, e.g. "This will route ALL DNS queries through the VPN server and will replace any existing modecfgdomains setting."

4. rc.local cleanup in disable_bonjour.sh is fragile in the no-backup fallback path

The backup restoration handles the common case correctly. In the fallback (no backup exists), the sed range /# Added by enable_bonjour.sh/,/^$/d has no blank line to stop at, so it extends to EOF and deletes exit 0 along with the added lines. Also, the second sed targets only ${VPN_SERVER_IP} and not ${L2TP_SERVER_IP}, so the L2TP loopback IP line is only removed incidentally by the EOF range. Suggest deleting each added line by its exact known content rather than relying on the range-to-EOF behaviour.

5. bonjour-vpn-resolve runs twice on every systemd service start

ExecStartPre=/usr/local/bin/bonjour-vpn-resolve in the unit file runs it before ExecStart, and bonjour-vpn-watch also calls it as its first action on startup. Removing ExecStartPre is sufficient.

6. Debounce loop in bonjour-vpn-watch has no upper time limit

A chatty device can keep resetting the 3-second timer indefinitely, preventing bonjour-vpn-resolve from ever running and leaving records permanently stale. A hard cap of around 30 seconds would prevent this.

7. configure_nss() modifies the server's /etc/nsswitch.conf unnecessarily

VPN clients use the DNS address pushed over the tunnel, not the server's nsswitch.conf. This change only affects hostname resolution on the VPN server itself. The [NOTFOUND=return] added after mdns4_minimal can also cause latency on the server if avahi-daemon is temporarily unavailable. Could you clarify the intended purpose? If it is not needed for the VPN client use case, removing this function would be cleaner.

Worth Noting

8. enable-reflector=yes in the avahi config forwards VPN client mDNS to the LAN

With the reflector enabled, a VPN client's own mDNS announcements (device name, advertised services) get forwarded to the local LAN, so LAN devices can see what the VPN client is advertising. For a home VPN this is likely acceptable, but it should be documented. Setting allow-interfaces=<LAN_IFACE> in the avahi config would scope the reflector to the LAN interface only if stricter isolation is preferred.

Minor

The vpnuninstall.sh notice is helpful. Consider using the existing confirm_or_abort() pattern so the user must acknowledge before proceeding with Bonjour still active, rather than just seeing an informational message.

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.

Passing through Bonjour/mDNS services

2 participants