This is a tool that makes all kinds of multicast-based discovery (and casting) work across networks (or VLANs).
I have posted about it on Reddit (https://www.reddit.com/r/opnsense/comments/1v7616o/netflector_the_only_mdns_ssdp_dial_wsd_wol/) and reception was quite good, so I thought I'll also mention it here.
The plugin is unofficial (for now). A net/netflector FreeBSD port is currently under review (bug 297489 (https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=297489)). When (if?) it is accepted - the plugin has a path toward official inclusion. Repository is signed and I am tracking OPNsense releases with the goal to support the last two versions of OPNsense (there are CI pipelines that build and test netflector on FreeBSD 14 and 15 and OPNsense 26.1 and 26.7). There are builds for Linux (binaries and a multi-arch container), macOS and FreeBSD.
Why this plugin when we have X and Y? Features mostly (and a very responsive author :)). There are many mDNS and some SSDP reflectors out there but none of the tools that I know of support both + WoL, DIAL (TCP), WSD and SSDP unicast response forwarding. Another feature missing from the other tools is recovery after interface re-creation. netflector also tracks current IPv4 and IPv6 addresses of the interfaces and recovers after temporary loss (or change) of the IP address.
The main motivation behind this project for me was to solve a real problem I have at home. My phone and my TV are in different VLANs, so vanilla multicast discovery does not work. While there are plenty of mDNS reflectors out there, my LG TV uses SSDP and DIAL. In order to cast YouTube from my phone to my TV following needs to happen:
- Phone sends M-SEARCH SSDP multicast request which needs to be reflected into TVs VLAN (other reflectors can do that).
- TV responds with the unicast 200 OK, which needs to be forwarded back to the phone. (can be allowed in firewall, less secure than using this tool).
- Phone initiates a device discovery TCP connection to TV (can be allowed in firewall, less secure than using this tool).
- TV replies with the details of its REST endpoint (works fine because established connections are usually allowed anyway).
- Phone initiates another TCP connection to TV's REST endpoint. The connection must be from the TV's subnet (could be allowed and NAT'ed in firewall).
So, to do this I would need to poke holes in the firewall and configure NAT. Additionally to that, I would still need to run some sort of reflector for SSDP multicast. How do I know? This is what I was doing for a couple of years :)
I also needed Wake-on-LAN to work across VLANs (PC -> TV), which none of the existing tools could do (at least I did not find any).
This tool does all that automagically:
- It reflects multicast discovery packets (mDNS / SSDP / WSD).
- It proxies unicast UDP responses (SSDP / WSD).
- It proxies the DIAL TCP connections, rewriting the device's advertised endpoints.
- It reflects Wake-on-Lan packets.
- It supports MAC filtering (so only specific devices are discoverable).
- It tolerates network interface re-creation. As long as new interface has the same name - reflection will continue.
- It has CARP support, including config sync (feature requested by kevinchalet on Reddit).
!!! Before adding any third-party repository: back up your config (System > Configuration > Backups) !!!To install:mkdir -p /usr/local/etc/pkg/keys /usr/local/etc/pkg/repos
fetch -o /usr/local/etc/pkg/keys/netflector-pkg.pub https://netflector.github.io/pkg/netflector-pkg.pub
fetch -o /usr/local/etc/pkg/repos/netflector.conf https://netflector.github.io/pkg/netflector.conf
and install the plugin (it will automatically install netflector as its dependency):
pkg install os-netflectorAfter this all configuration is done under Services > Netflector.
Packages are built for OPNsense 26.1 and 26.7, amd64 and aarch64 (four package trees, each tested on a real OPNsense install before publishing).
To remove:pkg delete os-netflector netflector
rm /usr/local/etc/pkg/repos/netflector.conf /usr/local/etc/pkg/keys/netflector-pkg.pub
rm /usr/local/etc/netflector.toml
Your settings stay in config.xml. If you want those gone too, delete the entries under Services > Netflector before uninstalling.
BSD-2-Clause, full source on GitHub: https://github.com/netflector/netflector
Feedback is welcome.
Updates:2026-08-13: initial announcement (netflector 0.15.2, plugin 0.1.4)
Nice good luck lets see how this goes, seems to fill a real void.
Quote from: Monviech (Cedrik) on August 13, 2026, 04:12:23 PMNice good luck lets see how this goes, seems to fill a real void.
Thank you!
Nice work indeed! If upstream gives you trouble we can consider integrating via our ports tree. Just let me know in a couple of weeks how this progresses.
Thanks,
Franco
Quote from: franco on August 14, 2026, 09:29:59 AMNice work indeed! If upstream gives you trouble we can consider integrating via our ports tree. Just let me know in a couple of weeks how this progresses.
Thanks,
Franco
Will do, thanks!
Looking forward for this plugin.
I have exactly the same problem/use case as you described + as well a problem with Steam play between PC and TV.
This plugin looks like exactly what I would need.
Fingers crossed it will be adopted by FBSD sooner than later!
Regards,
S.
Quote from: Seimus on August 17, 2026, 09:39:47 AMLooking forward for this plugin.
I have exactly the same problem/use case as you described + as well a problem with Steam play between PC and TV.
This plugin looks like exactly what I would need.
Fingers crossed it will be adopted by FBSD sooner than later!
Regards,
S.
You can try it now, before it becomes official (see instructions in the first post). If you do - let me know if works well for you.
Planning to test it once I have more time.
I have two use cases I want to try.
First is same as yours.
Second is Steam link, to stream games from PC to TV.
I will definitely let you know.
Regards,
S.
Without having tried it for lack of need ATM (but will likely arise in the future): does it have a way of restricting discovers reflection to specific (V)LANs, so that, say, the IoT VLAN cannot discover a phone nor TV, while the guest VLAN can see the TV VLAN but not the phones VLAN, but not wake up devices in the server and office VLANs, etc.? Or would that just be set up via conventional firewall rules?
Quote from: drosophila on August 20, 2026, 11:54:14 AMWithout having tried it for lack of need ATM (but will likely arise in the future): does it have a way of restricting discovers reflection to specific (V)LANs, so that, say, the IoT VLAN cannot discover a phone nor TV, while the guest VLAN can see the TV VLAN but not the phones VLAN, but not wake up devices in the server and office VLANs, etc.? Or would that just be set up via conventional firewall rules?
This should work. You would configure several reflector entries. Each reflector entry is unidirectional, so LAN -> IoT lets devices in LAN discover (and wake up) devices in IoT, but not vice-versa. Also, each VLAN pair needs to be configured independently, so there will be no wildcard LAN -> ANY_VLAN things. You explicitly allow discovery between two VLANs in each direction. You could have something like this (each point is a reflector entry with the corresponding protocols enabled):
- LAN -> IoT (mDNS, SSDP)
- LAN -> Cameras (WSD)
- LAN -> Servers (WoL)
- Guest -> IoT (mDNS, SSDP)
This way devices from LAN can discover devices in IoT and Cameras VLANs and wake devices on Servers VLAN. Devices from Guest VLAN can only discover devices in IoT VLAN.
If you need even more control, you could use MAC filters (e.g. to allow devices from LAN to discover TV1 in IoT VLAN and devices from Guest to discover TV2 in the same IoT VLAN).
I hope this answers your question. Let me know if you need any help with the configuration.
Quote from: franco on August 14, 2026, 09:29:59 AMNice work indeed! If upstream gives you trouble we can consider integrating via our ports tree. Just let me know in a couple of weeks how this progresses.
Update (because more than two weeks have passed): I got absolutely no reaction to my new port suggestion. On the bright side: it was not rejected :) So I guess I'll keep waiting...
Years can pass without rejection... ;)
Can you create a PR for https://github.com/opnsense/ports preferably under opnsense/netreflector to avoid clashing with an eventual FreeBSD inclusion? The files can stay the same, but so we know it's out of tree.
Thanks,
Franco
This looks like a very interesting plugin.
Would you consider adding dedicated support for Roon discovery (SOOD/RAAT discovery) as an additional reflector type?
Roon music (https://roon.app/en/) uses UDP port 9003 and relies on both broadcast and multicast (239.255.90.90:9003) for discovery. This normally does not cross routed VLANs or Layer-3 VPNs such as WireGuard or OpenVPN.
For this reason I currently use udp-proxy-2020 directly on OPNsense. It was specifically designed to make Roon discovery work across routed networks and VPN tunnels, and in my case it allows Roon clients/endpoints connected through WireGuard to discover and communicate with the Roon server on the LAN.
One important detail is that simply forwarding UDP/9003 may not be sufficient for all Roon use cases. A remote Roon endpoint connected through a routed VPN must remain reachable on its actual VPN IP after discovery.
Projects such as RoonBroadcastRelay therefore explicitly preserve the original source IP when relaying Roon discovery traffic. This allows the Roon server to subsequently connect back to the endpoint using its real VPN address. It also supports unicast delivery towards roadwarrior VPN clients where multicast/broadcast cannot be delivered directly.
So, if Roon support were added to Netflector, an ideal implementation would probably need to cover:
* UDP 9003
* IPv4 broadcast discovery
* multicast 239.255.90.90:9003
* LAN/VLAN <-> routed Layer-3 VPN interfaces such as WireGuard/OpenVPN
* preservation of the original endpoint/source IP where required
* unicast forwarding towards VPN peers/roadwarrior clients
* preferably either configured VPN peer targets or some form of automatic client/peer learning
The reason Netflector is especially interesting for this is that it already has native OPNsense/FreeBSD integration, interface monitoring, directional reflector configuration and a GUI. If it could handle the Roon-specific discovery semantics as well, it could potentially replace the separate udp-proxy-2020 service completely.
Would a dedicated Roon/SOOD reflector like this fit within the scope of Netflector?
Quote from: RamSense on September 01, 2026, 05:46:59 PMWould a dedicated Roon/SOOD reflector like this fit within the scope of Netflector?
Thanks for the idea! From what you are describing it sounds like Roon/SOOD might be a good candidate to support. I will look into it.
Careful that you don't accumulate too many edge cases and turning it into an unmaintainable swiss army knife without a clear identity.
I know AI helps but long term having less or dedicated tools fits unix philosophy better.
Not saying you cannot add whatever you want though, just saying from some experience. Though everybody should of course make their own experiences.
I think the gist is, define what Netflector is before accepting every discovery protocol it could theoretically support, especially application specific ones you will have to support forever long after the application might have already died.
Thanks both. I completely agree about avoiding a generic "Swiss army knife" with lots of special cases. That wasn't really what I had in mind either.
My suggestion was specifically a dedicated Roon/SOOD reflector, but only if the protocol fits cleanly within Netflector's existing model of explicitly supported discovery protocols such as mDNS, SSDP and WSD.
I mentioned the source-IP and WireGuard/roadwarrior details mainly so that, if Roon support is investigated, the important part of the use case is known from the start. If implementing that would require too many Roon-specific exceptions, keeping a dedicated tool such as udp-proxy-2020 or RoonBroadcastRelay is of course perfectly reasonable.
If it does fit, I'd be happy to test a development version on OPNsense with Roon across a WireGuard roadwarrior connection.
Quote from: Monviech (Cedrik) on September 01, 2026, 06:40:25 PMCareful that you don't accumulate too many edge cases and turning it into an unmaintainable swiss army knife without a clear identity.
Thanks for the insight. I will definitely get familiar with the Roon/RAAT before making a decision. So far, everything netflector supports (except WSD) is something I actually use in my setup. WSD was added mostly because of its similarity to what I had already implemented by then.
Quote from: franco on September 01, 2026, 01:48:45 PMCan you create a PR for https://github.com/opnsense/ports preferably under opnsense/netreflector to avoid clashing with an eventual FreeBSD inclusion? The files can stay the same, but so we know it's out of tree.
Done: https://github.com/opnsense/ports/pull/286.
Quote from: RamSense on September 01, 2026, 05:46:59 PMWould you consider adding dedicated support for Roon discovery (SOOD/RAAT discovery) as an additional reflector type?
I have read a bit about SOOD discovery protocol. There are a few differences from existing protocols, but I still feel like it's a good match. Differences:
- We need to preserve source IPs. This is easy because netflector already uses raw BPF devices for all UDP traffic.
- There are 3 types of devices involved: remotes, server and outputs. Discovery would need to happen for remotes -> server and for server -> outputs. Potentially, all three could be in separate VLANs. For now, I think it would require two reflector entries.
Regarding your specific setup with blackjack and VPNs: I don't think we need to implement anything specific for that to work. If remotes are connected via VPN, that VPN interface would just be a source_if in the entry responsible for remote -> server discovery. I don't think that netflector needs to be aware of anything VPN-specific.
There is also the question of unicast responses. For SSDP and WSD netflector proxies these responses. For SOOD, I'm not yet sure what would be the best approach. The normal discovery flow is not a problem: remote sends "Q", server responds with "R". We can proxy the unicast response fine here, just use netflector's source IP/port when relaying "Q" to the server. However, it looks like "Q" does not necessarily mean Query. Output devices also send "Q" messages to server, which are not discovery queries. For these there is no unicast response, server connects directly to output device instead. So this "Q" must be relayed with the preserved IP/port. Naive solution here would be to not relay unicast responses and always preserve source IP/port for all relayed SOOD packets. This will require allowing UDP unicast packets from server back to remote (and from output back to server) in firewall. In your case you should probably already have that configured.
Thanks for looking into this in more detail. This sounds very promising.
I had a closer look at how udp-proxy-2020 actually handles this, since it is already proven to work with Roon across routed networks and VPN connections.
As far as I can see, udp-proxy-2020 does not implement any SOOD-aware query/response proxying and does not interpret the SOOD payload. It captures the configured UDP traffic and reinjects it onto the other interfaces while preserving the original source IP and UDP source/target ports.
So I think your "naive" solution may actually be the right model for SOOD: preserve the original endpoint and let subsequent unicast communication use normal routing, rather than making Netflector part of that unicast conversation.
That also seems to fit the output-device case you mentioned. Netflector would not need to decide whether a "Q" semantically represents a discovery query from a Remote or an announcement/discovery packet from an output device. It can simply relay the SOOD packet while preserving its original source identity.
There is another detail which seems relevant to your question about unicast responses. Aaron Turner's Roon Wireshark dissector indicates that, besides the normal Roon Server discovery on UDP/9003, there is also a Roon Discovery service where replies may originate from an ephemeral UDP port and the dissector therefore uses conversation tracking. To me that is another argument in favour of preserving the original source IP and port rather than introducing SOOD-specific response translation.
One implementation detail from udp-proxy-2020 may also be worth keeping in mind. In addition to BPF capture/injection it opens a normal UDP listener on the configured port and discards anything received there. That was added because ICMP Port Unreachable responses could otherwise cause problems for Roon clients, particularly iOS clients over VPN. I don't know whether that will be relevant to Netflector on OPNsense, but it seems worth being aware of when testing a first implementation.
So I would definitely start with the simple source-IP/source-port preserving model. If you build an initial SOOD implementation that way, I would be very happy to test it with Roon Remote over WireGuard and also with the Roon server/output side.
Quote from: RamSense on September 02, 2026, 05:44:20 PMThat was added because ICMP Port Unreachable responses could otherwise cause problems for Roon clients
netflector does this trick for some protocols, so this machinery is already built. However, I don't think ICMP Port Unreachable can happen for SOOD. This problem manifests itself when we are capturing a unicast packet addressed to our IP using BPF device. In this case kernel still sees the packet addressed to itself and, since there is no socket waiting for it, replies with the ICMP Port Unreachable. We are going to spoof source IP, which means all unicast responses will be directed (and routed) to the device with that IP (and not to netflector's IP). I'm not sure why udp-proxy-2020 would need to solve ICMP Port Unreachable problem... Maybe I'm missing something.
In any case, thanks for your insights. I think I might implement this as a generic case of payload-unaware reflection.
That sounds like a sensible approach. Generic payload-unaware reflection seems cleaner than adding Roon-specific protocol parsing, provided the original IP/UDP identity and the routed VPN/roadwarrior case can be preserved.
Regarding the UDP listener in udp-proxy-2020: I believe that was added for a slightly different reason. Aaron Turner found that the original UDP/9003 directed-broadcast packet arriving from the VPN client could cause the router to return ICMP Port Unreachable when nothing was listening locally on port 9003. His first workaround was a firewall DROP rule; the listener was later added to avoid needing that rule.
So this may be unrelated to the spoofed source-IP of the reflected packet. It might simply be worth checking during testing whether Netflector/FreeBSD generates such ICMP responses for the original VPN packet.
Quote from: RamSense on September 03, 2026, 08:52:57 PMThat sounds like a sensible approach.
First implementation is ready. Which version of OPNsense are you running? Is it amd64 or arm64? Basically I need to know FreeBSD version and architecture to prepare a binary for you.
Looks good. Especially being CARP aware. Installed it as replacement for UDP Broadcast Relay
Quote from: UnicronHD on September 06, 2026, 11:10:53 PMWhich version of OPNsense are you running? Is it amd64 or arm64? Basically I need to know FreeBSD version and architecture to prepare a binary for you.
Thanks, great to hear that the first implementation is ready. I'm happy to test it.
I'm running:
OPNsense 26.7.3_11-amd64
FreeBSD 15.1-RELEASE-p3
OpenSSL 3.5.8
Architecture: amd64
Quote from: RamSense on September 07, 2026, 05:21:43 PMThanks, great to hear that the first implementation is ready. I'm happy to test it.
I'm running:
OPNsense 26.7.3_11-amd64
FreeBSD 15.1-RELEASE-p3
OpenSSL 3.5.8
Architecture: amd64
Binary:
https://drive.google.com/uc?export=download&id=1VcsmIz9SZDM5Sxqav6a19TTZbojL_gCR
SHA256:
f485f068b0529f36851e8dfeeae06e1bd1f118b5405dadcd67b9eb78fe34a633 netflector-0.16.0-freebsd15-amd64
Here is a config (I assume you will call it roon.toml) you can use for Roon reflection. Feel free to enable more protocols if needed. It assumes that unicast replies are allowed in the firewall. Add more entries if you need reflection between more VLANs.
# "debug" shows every relayed datagram; drop to "info" once it works.
log_level = "debug"
[reflectors.roon]
# Interface names as ifconfig lists them, e.g. igb0 / igb1 or vtnet0 / vtnet1.
source_if = "lan"
target_if = "iot"
udp_ports = [9003]
udp_groups = ["239.255.90.90"]
udp_broadcast = true
bidirectional = true
First smoke test:
./netflector-0.16.0-freebsd15-amd64 --version
./netflector-0.16.0-freebsd15-amd64 --check-config roon.toml
Then run it (for the purity of this experiment make sure that netflector plugin is stopped):
./netflector-0.16.0-freebsd15-amd64 roon.toml
At start the log should show something like this:
reflector roon: lan <-> iot [udp(9003 on 239.255.90.90,broadcast)]
And then each reflected query will log something like this:
reflected UDP relay datagram from <ip>:<port> to ...
Thanks — good news, the first test was functionally successful.
Roon on my iPhone (10.10.10.2) over WireGuard discovers the Roon Server (192.168.1.60), the server also discovers the iPhone as an endpoint, and actual audio playback to the iPhone works.
Packet capture confirms bidirectional UDP/9003 unicast communication with the original addresses preserved, for example:
10.10.10.2:53799 -> 192.168.1.60:9003
192.168.1.60:9003 -> 10.10.10.2:53799
So the important roadwarrior/source-IP use case works.
There is one thing worth looking at though. Netflector logged once:
UDP relay: cannot reflect datagram from 192.168.1.60:50259 to 239.255.90.90:9003: Network is unreachable (os error 51)
The packet capture also shows OPNsense generating ICMP unreachable messages back to the Roon server for some of the reflected multicast/broadcast traffic, e.g.:
ICMP host 239.255.90.90 unreachable
and
ICMP host 10.10.10.255 unreachable
This does not prevent Roon from working in my test, but it looks related to the ICMP behaviour we discussed earlier and may be worth checking for the L3/WireGuard case.
Apart from that, the first Roon/WireGuard test is a success!
@RamSense I have identified the problem with the ICMP unreachable responses. It has to do with the fact that WG tunnel has no broadcast and no multicast domains. I'm working on the solution.
@UnicronHD Thanks for looking into it.
I'm happy to help test the next build/fix when it's ready.
@RamSense
Next iteration is ready. Binary:
https://drive.google.com/uc?export=download&id=1FPDtVM_e-lk2ygqvrxRcMZ9HWt5kVZ_P
SHA256:
e0b0c32a9f062c36c29ddc7cf193ae984e59fc5215ee05167391f3e14cc2f3b4
I've added support for source_peers and target_peers (will be used for source_if and target_if correspondingly). The idea is not to open a fake socket, but to actually fake multicast over WireGuard by delivering unicast messages to peers. Which means discovery in the other direction should properly work. We'll see how it goes :)
If we assume that your WireGuard interface is target_if, then you need to add target_peers with the IP of your phone (or multiple IPs if you have more than one road warrior). Something like this:
log_level = "info"
# Roon for road warriors: the server on the LAN, the phones behind WireGuard.
# wg0 has no broadcast domain, so the server's discovery (multicast and
# broadcast on 9003) goes to each listed peer as a unicast copy instead.
[reflectors.roon-remote]
source_if = "igc0" # the LAN interface, as ifconfig names it
target_if = "wg0" # the WireGuard interface
target_peers = ["10.10.10.2"] # every road-warrior address that should see the server; add more as needed
udp_ports = [9003]
udp_groups = ["239.255.90.90"]
udp_broadcast = true
bidirectional = true
NOTE: peers should work with almost all protocols, not only UDP relay. The only exception is mDNS. It will not accept source_peers, and no peers at all if bidirectional=true. mDNS via WireGuard is a different topic though. RFC requires multicast responses to multicast queries, which we cannot do. I have some ideas, but this I can try myself, after we figure Roon out.
@UnicronHD Good progress with this build!
With target_peers = ["10.10.10.2"] the Roon server is consistently discovered, bidirectional UDP/9003 communication works, and the previous Network is unreachable / ICMP issues for multicast and broadcast are gone.
There is still one issue: the iPhone does not reliably appear as a Roon audio endpoint.
I also captured:
10.10.10.2 > 192.168.1.60: ICMP udp port 9003 unreachable
for a peer-unicast packet from the Roon server to 10.10.10.2:9003.
In addition, I repeatedly see 350-byte UDP/9003 packets on wg0 from the iPhone to its own WG address, but these do not appear on the LAN capture. The LAN side only shows the normal 98-byte discovery packets and 491-byte replies.
This may be worth comparing with udp-proxy-2020: since v0.0.11 it opens a UDP listening socket specifically to prevent ICMP Port Unreachable messages, as these were found to make Roon on iOS close its listening port and prevent the phone from appearing as an audio output. The ICMP direction here is different, so it may not be the exact same issue, but the symptom is very similar.
So the new peer handling clearly fixes the original WireGuard multicast/broadcast problem, but there still seems to be something missing for reliable iPhone endpoint discovery. Happy to test further.
@RamSense Thanks, that confirms the peers change does what it should: the copy carries the server's own source, and the multicast and broadcast errors are gone.
On the endpoint problem. The ICMP you captured goes from the phone to the server, so it tells us the Roon app had no socket open on 9003 at that moment: the phone cannot answer the server's query while its port is closed, which is exactly "not an endpoint". The open question is why the port was closed. Two candidates: the app was in the background (iOS suspends it, and Roon holds its listener only in the foreground), or something made the app drop the socket while it was up. udp-proxy-2020's finding was the second kind, but that needs an ICMP port unreachable arriving at the phone, and there is none in what you sent. netflector's host does not hold port 9003 open, so if the phone ever sends unicast 9003 traffic to one of the OPNsense addresses, OPNsense would answer with exactly that ICMP. Whether that happens depends on where the 350-byte packets go.
Could you check:
- The exact destination address of the 350-byte packets on wg0, and whether their payload is a SOOD query or a reply.
- Any ICMP toward the phone on wg0: tcpdump -ni wg0 icmp and dst host 10.10.10.2.
- The wg0 address and prefix on OPNsense, and the phone's tunnel address and prefix (/24 or /32).
- Whether the Roon app was in the foreground when you captured the port-unreachable from the phone.
- Which OS the Roon Core runs on.
@UnicronHD thanks for the follow-up. I checked your remaining details.
The iPhone WireGuard interface is configured as 10.10.10.2/32. OPNsense has wg0 = 10.10.10.1/24, with 10.10.10.2/32 as the peer AllowedIP.
The ~350-byte packets are actually 348-byte SOOD packets. Their exact destination is the iPhone's own tunnel address:
10.10.10.2:<ephemeral> -> 10.10.10.2:9003
The payload starts with SOOD, message type Q, and includes the iPhone endpoint data (iOS 26.7, raat_version 1.1.48, tcp_port 9200).
During the dedicated capture I saw no ICMP packets toward 10.10.10.2.
Roon Server runs on Linux, Debian 12 (bookworm), x86_64.
Roon was in the foreground during the endpoint test.
One thing that may be relevant is the /32 on the iPhone together with the destination above: the phone's SOOD packet is sourced from 10.10.10.2 and is also addressed to 10.10.10.2:9003, and it does not appear on the LAN capture.
This may also be an interesting difference compared with udp-proxy-2020. Its VPN handling learns the client from UDP/9003 traffic seen on the tunnel interface and forwards that traffic onto the other configured interfaces; its debug examples show WG client traffic being reinjected toward the LAN.
So perhaps the relevant question here is whether UDP/9003 sourced from a configured WG peer should also be treated as discovery input for the opposite interface even when its destination is the peer's own tunnel address (?)
Happy to run any further captures or test another build.
@RamSense, that is the smoking gun, thank you. The 348-byte packet is the phone announcing itself as an endpoint, sent to the broadcast address of its tunnel interface. Roon derives that from the address and mask, and for a /32 the broadcast address is the host's own address, which is why you see 10.10.10.2 -> 10.10.10.2. netflector relays broadcasts from wg0, but that packet is not one, so the Core never hears the announcement and only learns the phone when it happens to answer one of the Core's own queries.
Could you change the iPhone's interface address in the WireGuard app to 10.10.10.2/24 and test again? Roon should then announce to 10.10.10.255, which netflector relays to the LAN broadcast with the phone's source address kept. A capture on the LAN should show the 348-byte packets arriving at 192.168.1.255:9003.
@UnicronHD the /24 change confirms the first half of your diagnosis, but it looks like there is still something missing in the relay.
After changing the iPhone from 10.10.10.2/32 to 10.10.10.2/24, the 348-byte endpoint announcements now correctly go to:
10.10.10.2:<ephemeral> -> 10.10.10.255:9003
I see several of these on wg0.
However, in the simultaneous ax1 capture I do not see corresponding 348-byte packets arriving at:
10.10.10.2:<ephemeral> -> 192.168.1.255:9003
The LAN capture still only contains the usual 98-byte discovery packets and 491-byte replies.
Behaviour also matches that: on the first Roon start the iPhone endpoint did not appear; on the second start it did, presumably because it happened to answer one of the Core's own queries.
So changing to /24 fixes Roon's broadcast destination exactly as expected, but Netflector does not appear to relay those 348-byte 10.10.10.255:9003 broadcasts from wg0 onto the LAN yet.
Happy to capture anything else you need.
@RamSense, thanks, that narrows it down. I rebuilt your setup locally: FreeBSD, a real WireGuard tunnel, the router's wg0 at 10.10.10.1/24, a client at 10.10.10.2/24 sending 348 bytes to 10.10.10.255:9003. There netflector relays the packet to 192.168.1.255:9003 with the client's source kept, so something differs on your box.
Could you set log_level = "trace", restart netflector, open Roon on the phone so it announces a couple of times, then stop it, and send me:
- The output of netflector-0.16.0rc2-freebsd15-amd64 --version and your config file.
- Command line you use to start it, and any NETFLECTOR_* environment variables set for it.
- The full log of that run.
- Whether your LAN is ax1 itself or a VLAN, bridge or lagg on top of it.
Also, please make sure the netflector service from the plugin is not running at the same time.
pgrep -lf netflector should list only the
netflector-0.16.0rc2-freebsd15-amd64 process you started by hand. If it also shows
/usr/local/sbin/netflector or a
daemon: line for it, disable the service on the plugin's page, or stop it with
service netflector onestop.
@UnicronHD thanks. I repeated the tests and have some more useful data.
Setup used:
- Binary: netflector-0.16.0rc2-freebsd15-amd64
- --version: netflector 0.16.0
- SHA256: e0b0c32a9f062c36c29ddc7cf193ae984e59fc5215ee05167391f3e14cc2f3b4
- Command: /tmp/netflector-0.16.0rc2-freebsd15-amd64 /tmp/roon-peers-trace.toml
- No NETFLECTOR_* environment variables are set.
- Only the manually started rc2 process was running; the plugin service was not running.
- LAN is directly on ax1, 192.168.1.1/24. There is no bridge, lagg or VLAN on top of ax1.
- iPhone WireGuard IPv4 interface address is now 10.10.10.2/24.
- The OPNsense peer AllowedIP remains 10.10.10.2/32.
Config:
log_level = "trace"
[reflectors.roon-remote]
source_if = "ax1"
target_if = "wg0"
target_peers = ["10.10.10.2"]
udp_ports = [9003]
udp_groups = ["239.255.90.90"]
udp_broadcast = true
bidirectional = true
In the trace run I did repeated force-close/open tests with the same netflector process:
1: endpoint YES
2: endpoint YES
3: endpoint NO
4: endpoint NO
Netflector continued to log the phone traffic being reflected to:
10.10.10.2:<port> -> 192.168.1.255:9003
during the later failed attempts as well.
I then did a second controlled test with a simultaneous tcpdump on ax1.
Results of that test:
1: endpoint YES
2: endpoint NO
3: endpoint NO
4: endpoint YES
The important result is that the 348-byte iPhone endpoint announcements were actually visible on ax1 during all four attempts, including both failed ones:
10.10.10.2:<ephemeral> -> 192.168.1.255:9003
UDP length 348
The normal 98-byte discovery traffic and 491-byte Core replies were also present during the failed attempts.
So the intermittent endpoint failure no longer seems to correlate with netflector failing to relay the broadcast. The announcements are not only reported as reflected by netflector; they are actually present on the LAN interface even when Roon does not show the iPhone endpoint.
Full trace from the trace run:
2026-09-19T10:20:54Z DEBUG netflector: log level Trace
2026-09-19T10:20:54Z INFO netflector: netflector 0.16.0 starting
2026-09-19T10:20:54Z DEBUG netflector: loading configuration from /tmp/roon-peers-trace.toml with NETFLECTOR_* overrides
2026-09-19T10:20:54Z DEBUG netflector::sys: open-file limit already 466659
2026-09-19T10:20:54Z DEBUG netflector::config: reflector roon-remote: ax1 <-> wg0 [udp(9003 on 239.255.90.90,broadcast)] family=Default
2026-09-19T10:20:54Z DEBUG netflector::config::conflict: no reflector conflicts
2026-09-19T10:20:54Z INFO netflector: loaded 1 reflector
2026-09-19T10:20:54Z DEBUG netflector::dispatch::lifecycle: interface monitor installed
2026-09-19T10:20:54Z DEBUG netflector::capture::bpf: opened BPF capture on ax1 (fd 4, Ethernet, 4096-byte buffer)
2026-09-19T10:20:54Z DEBUG netflector::interface: ax1: ifindex 6
2026-09-19T10:20:54Z DEBUG netflector::interface: ax1: resolved mac 02:00:00:00:00:01, v4 192.168.1.1/24, v6 fe80::1, v6-routable 2001:db8:1::1, mtu 1500
2026-09-19T10:20:54Z INFO netflector::interface: interface ax1: gained IPv6 fe80::1
2026-09-19T10:20:54Z INFO netflector::interface: interface ax1: gained IPv6 routable 2001:db8:1::1
2026-09-19T10:20:54Z INFO netflector::interface: interface ax1: gained MAC 02:00:00:00:00:01
2026-09-19T10:20:54Z INFO netflector::interface: interface ax1: gained IPv4 192.168.1.1
2026-09-19T10:20:54Z DEBUG netflector::dispatch: watching ax1 as capture CaptureKey(0)
2026-09-19T10:20:54Z DEBUG netflector::capture::bpf: opened BPF capture on wg0 (fd 5, DltNull, 4096-byte buffer)
2026-09-19T10:20:54Z DEBUG netflector::interface: wg0: ifindex 11
2026-09-19T10:20:54Z DEBUG netflector::interface: wg0: resolved mac none, v4 10.10.10.1/24, v6 2001:db8:2::1, v6-routable 2001:db8:2::1, mtu 1420
2026-09-19T10:20:54Z INFO netflector::interface: interface wg0: gained IPv6 2001:db8:2::1
2026-09-19T10:20:54Z INFO netflector::interface: interface wg0: gained IPv6 routable 2001:db8:2::1
2026-09-19T10:20:54Z INFO netflector::interface: interface wg0: gained IPv4 10.10.10.1
2026-09-19T10:20:54Z DEBUG netflector::dispatch: watching wg0 as capture CaptureKey(1)
2026-09-19T10:20:54Z INFO netflector: roon-remote: MTU mismatch: ax1 has 1500, wg0 has 1420; packets larger than 1420 bytes cannot cross toward the smaller side and are dropped
2026-09-19T10:20:54Z DEBUG netflector::reflector: UDP relay: joined 239.255.90.90 on ax1
2026-09-19T10:20:54Z INFO netflector::reflector::udp: UDP relay "roon-remote": ax1 -> wg0 on 1 port(s) to 1 group(s) and broadcasts
2026-09-19T10:20:54Z DEBUG netflector::reflector: UDP relay: joined 239.255.90.90 on wg0
2026-09-19T10:20:54Z INFO netflector::reflector::udp: UDP relay "roon-remote": wg0 -> ax1 on 1 port(s) to 1 group(s) and broadcasts
2026-09-19T10:20:54Z DEBUG netflector::reactor: watch fd 4 for HandlerKey(Key { index: 0, generation: 0 }) as RegKey(Key { index: 0, generation: 0 })
2026-09-19T10:20:54Z DEBUG netflector::reactor: watch fd 5 for HandlerKey(Key { index: 0, generation: 0 }) as RegKey(Key { index: 1, generation: 0 })
2026-09-19T10:20:54Z DEBUG netflector::reactor: watch fd 3 for HandlerKey(Key { index: 0, generation: 0 }) as RegKey(Key { index: 2, generation: 0 })
2026-09-19T10:20:54Z INFO netflector: running; press Ctrl-C or send SIGTERM to stop
2026-09-19T10:20:54Z DEBUG netflector::reactor: watch fd 9 for HandlerKey(Key { index: 2, generation: 0 }) as RegKey(Key { index: 3, generation: 0 })
2026-09-19T10:21:00Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:64505 to 192.168.1.255:9003
2026-09-19T10:21:00Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:64505 to 192.168.1.255:9003
2026-09-19T10:21:00Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:51405 to 192.168.1.255:9003
2026-09-19T10:21:00Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:49551 to 192.168.1.255:9003
2026-09-19T10:21:00Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:51405 to 192.168.1.255:9003
2026-09-19T10:21:00Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:57834 to 192.168.1.255:9003
2026-09-19T10:21:00Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:49551 to 192.168.1.255:9003
2026-09-19T10:21:00Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:57834 to 192.168.1.255:9003
2026-09-19T10:21:00Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:55097 to 192.168.1.255:9003
2026-09-19T10:21:00Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:55097 to 192.168.1.255:9003
2026-09-19T10:21:00Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:53241 to 239.255.90.90:9003
2026-09-19T10:21:00Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 239.255.90.90:9003
2026-09-19T10:21:01Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 10.10.10.255:9003
2026-09-19T10:21:01Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 10.10.10.255:9003
2026-09-19T10:21:02Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:55097 to 192.168.1.255:9003
2026-09-19T10:21:02Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:55097 to 192.168.1.255:9003
2026-09-19T10:21:02Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:55097 to 192.168.1.255:9003
2026-09-19T10:21:02Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:55097 to 192.168.1.255:9003
2026-09-19T10:21:03Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:55241 to 192.168.1.255:9003
2026-09-19T10:21:03Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:60392 to 192.168.1.255:9003
2026-09-19T10:21:03Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:55241 to 192.168.1.255:9003
2026-09-19T10:21:03Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:60392 to 192.168.1.255:9003
2026-09-19T10:21:03Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:64552 to 192.168.1.255:9003
2026-09-19T10:21:03Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:63541 to 192.168.1.255:9003
2026-09-19T10:21:03Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:64552 to 192.168.1.255:9003
2026-09-19T10:21:03Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:63541 to 192.168.1.255:9003
2026-09-19T10:21:07Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:57712 to 192.168.1.255:9003
2026-09-19T10:21:07Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:63704 to 192.168.1.255:9003
2026-09-19T10:21:07Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:51022 to 192.168.1.255:9003
2026-09-19T10:21:07Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:57712 to 192.168.1.255:9003
2026-09-19T10:21:07Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:63704 to 192.168.1.255:9003
2026-09-19T10:21:07Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:51022 to 192.168.1.255:9003
2026-09-19T10:21:07Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:65002 to 192.168.1.255:9003
2026-09-19T10:21:07Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:65002 to 192.168.1.255:9003
2026-09-19T10:21:08Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:62406 to 192.168.1.255:9003
2026-09-19T10:21:08Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:62406 to 192.168.1.255:9003
2026-09-19T10:21:08Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:53241 to 239.255.90.90:9003
2026-09-19T10:21:08Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 239.255.90.90:9003
2026-09-19T10:21:08Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:53241 to 239.255.90.90:9003
2026-09-19T10:21:08Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 239.255.90.90:9003
2026-09-19T10:21:08Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 10.10.10.255:9003
2026-09-19T10:21:08Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 10.10.10.255:9003
2026-09-19T10:21:09Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:62406 to 192.168.1.255:9003
2026-09-19T10:21:09Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:62406 to 192.168.1.255:9003
2026-09-19T10:21:09Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:62406 to 192.168.1.255:9003
2026-09-19T10:21:09Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:62406 to 192.168.1.255:9003
2026-09-19T10:21:10Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:53458 to 192.168.1.255:9003
2026-09-19T10:21:10Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:53458 to 192.168.1.255:9003
2026-09-19T10:21:10Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:58420 to 192.168.1.255:9003
2026-09-19T10:21:10Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:63726 to 192.168.1.255:9003
2026-09-19T10:21:10Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:56444 to 192.168.1.255:9003
2026-09-19T10:21:10Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:58420 to 192.168.1.255:9003
2026-09-19T10:21:10Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:63726 to 192.168.1.255:9003
2026-09-19T10:21:10Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:56444 to 192.168.1.255:9003
2026-09-19T10:21:12Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:50271 to 192.168.1.255:9003
2026-09-19T10:21:12Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:53396 to 192.168.1.255:9003
2026-09-19T10:21:12Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:58427 to 192.168.1.255:9003
2026-09-19T10:21:12Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:51933 to 192.168.1.255:9003
2026-09-19T10:21:12Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:50271 to 192.168.1.255:9003
2026-09-19T10:21:12Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:53396 to 192.168.1.255:9003
2026-09-19T10:21:12Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:58427 to 192.168.1.255:9003
2026-09-19T10:21:12Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:51933 to 192.168.1.255:9003
2026-09-19T10:21:12Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:56067 to 192.168.1.255:9003
2026-09-19T10:21:12Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:56067 to 192.168.1.255:9003
2026-09-19T10:21:12Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:53241 to 239.255.90.90:9003
2026-09-19T10:21:12Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 239.255.90.90:9003
2026-09-19T10:21:12Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:53241 to 239.255.90.90:9003
2026-09-19T10:21:12Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 239.255.90.90:9003
2026-09-19T10:21:13Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 10.10.10.255:9003
2026-09-19T10:21:13Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 10.10.10.255:9003
2026-09-19T10:21:14Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:56067 to 192.168.1.255:9003
2026-09-19T10:21:14Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:56067 to 192.168.1.255:9003
2026-09-19T10:21:14Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:56067 to 192.168.1.255:9003
2026-09-19T10:21:14Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:56067 to 192.168.1.255:9003
2026-09-19T10:21:14Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:55869 to 192.168.1.255:9003
2026-09-19T10:21:14Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:62698 to 192.168.1.255:9003
2026-09-19T10:21:14Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:60621 to 192.168.1.255:9003
2026-09-19T10:21:14Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:55869 to 192.168.1.255:9003
2026-09-19T10:21:14Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:62698 to 192.168.1.255:9003
2026-09-19T10:21:14Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:60621 to 192.168.1.255:9003
2026-09-19T10:21:14Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:53664 to 192.168.1.255:9003
2026-09-19T10:21:14Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:53664 to 192.168.1.255:9003
2026-09-19T10:21:24Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:52623 to 192.168.1.255:9003
2026-09-19T10:21:24Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:61699 to 192.168.1.255:9003
2026-09-19T10:21:24Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:52623 to 192.168.1.255:9003
2026-09-19T10:21:24Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:61699 to 192.168.1.255:9003
2026-09-19T10:21:24Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:55696 to 192.168.1.255:9003
2026-09-19T10:21:24Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:58224 to 192.168.1.255:9003
2026-09-19T10:21:24Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:55696 to 192.168.1.255:9003
2026-09-19T10:21:24Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:58224 to 192.168.1.255:9003
2026-09-19T10:21:24Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:50268 to 192.168.1.255:9003
2026-09-19T10:21:24Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:50268 to 192.168.1.255:9003
2026-09-19T10:21:24Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:53241 to 239.255.90.90:9003
2026-09-19T10:21:24Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 239.255.90.90:9003
2026-09-19T10:21:24Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:53241 to 239.255.90.90:9003
2026-09-19T10:21:24Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 239.255.90.90:9003
2026-09-19T10:21:25Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 10.10.10.255:9003
2026-09-19T10:21:25Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 10.10.10.255:9003
2026-09-19T10:21:26Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:50268 to 192.168.1.255:9003
2026-09-19T10:21:26Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:50268 to 192.168.1.255:9003
2026-09-19T10:21:26Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:50268 to 192.168.1.255:9003
2026-09-19T10:21:26Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:50268 to 192.168.1.255:9003
2026-09-19T10:21:26Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:51265 to 192.168.1.255:9003
2026-09-19T10:21:26Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:51265 to 192.168.1.255:9003
2026-09-19T10:21:26Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:57440 to 192.168.1.255:9003
2026-09-19T10:21:26Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:57440 to 192.168.1.255:9003
2026-09-19T10:21:26Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:51906 to 192.168.1.255:9003
2026-09-19T10:21:26Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:51906 to 192.168.1.255:9003
2026-09-19T10:21:26Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:56434 to 192.168.1.255:9003
2026-09-19T10:21:26Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:56434 to 192.168.1.255:9003
2026-09-19T10:21:33Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:62645 to 192.168.1.255:9003
2026-09-19T10:21:33Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:62645 to 192.168.1.255:9003
2026-09-19T10:21:33Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:52706 to 192.168.1.255:9003
2026-09-19T10:21:33Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:52466 to 192.168.1.255:9003
2026-09-19T10:21:33Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:60102 to 192.168.1.255:9003
2026-09-19T10:21:33Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:52706 to 192.168.1.255:9003
2026-09-19T10:21:33Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:52466 to 192.168.1.255:9003
2026-09-19T10:21:33Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:60102 to 192.168.1.255:9003
2026-09-19T10:21:33Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:65044 to 192.168.1.255:9003
2026-09-19T10:21:33Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:65044 to 192.168.1.255:9003
2026-09-19T10:21:34Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:53241 to 239.255.90.90:9003
2026-09-19T10:21:34Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 239.255.90.90:9003
2026-09-19T10:21:34Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:53241 to 239.255.90.90:9003
2026-09-19T10:21:34Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 239.255.90.90:9003
2026-09-19T10:21:34Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 10.10.10.255:9003
2026-09-19T10:21:34Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 192.168.1.60:58287 to 10.10.10.255:9003
2026-09-19T10:21:35Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:65044 to 192.168.1.255:9003
2026-09-19T10:21:35Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:65044 to 192.168.1.255:9003
2026-09-19T10:21:35Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:65044 to 192.168.1.255:9003
2026-09-19T10:21:35Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:65044 to 192.168.1.255:9003
2026-09-19T10:21:37Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:51255 to 192.168.1.255:9003
2026-09-19T10:21:37Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:65460 to 192.168.1.255:9003
2026-09-19T10:21:37Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:53125 to 192.168.1.255:9003
2026-09-19T10:21:37Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:51255 to 192.168.1.255:9003
2026-09-19T10:21:37Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:65460 to 192.168.1.255:9003
2026-09-19T10:21:37Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:53125 to 192.168.1.255:9003
2026-09-19T10:21:37Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:56721 to 192.168.1.255:9003
2026-09-19T10:21:37Z DEBUG netflector::reflector::simple: reflected UDP relay datagram from 10.10.10.2:56721 to 192.168.1.255:9003
Note: I only redacted the public IPv6 addresses and the LAN MAC address in the trace. Internal IPv4 addresses, interface names, ports, timestamps and netflector log contents are otherwise unchanged.
@RamSense, thanks, this run settles the relay question: wg0 resolves with its /24, both directions are registered with broadcasts, and the announcements reach ax1 on every attempt. Sorry about trace, by the way: release builds compile it out, so debug was all that binary could log.
Your log shows something else though: every datagram from the phone is relayed twice. I reproduced that locally once I enabled IP forwarding on my test router. WireGuard interfaces have no broadcast flag, so the router treats 10.10.10.255 as an ordinary host and forwards the packet back out wg0. netflector picks up that forwarded copy too, and the WireGuard driver, finding no peer for 10.10.10.255, answers the sender with an ICMP "host 10.10.10.255 unreachable". So since the /24 change OPNsense has most likely been answering every Roon announcement with an ICMP error to the phone, which is exactly what udp-proxy-2020 found breaks Roon on iOS. Your earlier capture without ICMP was taken with the /32, where this does not happen.
Could you try this:
- First confirm the ICMP: run tcpdump -ni wg0 icmp while opening Roon on the phone. I expect "host 10.10.10.255 unreachable" from 10.10.10.1 to 10.10.10.2.
- Add a firewall rule on the WireGuard interface: action Block (not Reject), direction in, quick, destination single host 10.10.10.255, placed above your pass rules.
- Repeat the ICMP capture, it should now stay empty, and repeat the four open/close attempts.
netflector captures ahead of the firewall, so it still relays the announcement. In my test setup the rule leaves exactly one relayed copy per announcement and no ICMP toward the phone.
@UnicronHD, confirmed.
The inbound rule did not match on my OPNsense setup, but an outbound quick block on wg0 does:
block drop out quick on wg0 inet from 10.10.10.2 to 10.10.10.255
After the test it had:
Packets: 66
Bytes: 20316
With that rule active:
- ICMP capture from 10.10.10.1 to 10.10.10.2 stays completely empty.
- The previous "host 10.10.10.255 unreachable" messages are gone.
- The duplicate relay is also gone.
- On ax1 I now see only TTL 64 copies of the phone traffic.
- The 348-byte SOOD endpoint announcements still reach 192.168.1.255:9003.
- The 98-byte discovery packets also still reach the LAN.
So the forwarding/ICMP issue is confirmed and fixed by blocking the routed egress copy.
However, the iPhone endpoint is still intermittent in Roon. During the latest tests there were attempts where the endpoint did not appear even though the 348-byte announcements were present on ax1 and there were no ICMP errors toward the phone.
So at this point the remaining intermittent endpoint issue no longer appears to correlate with:
- missing netflector relay
- duplicate relay
- or ICMP unreachable responses.
I have not changed anything else in the setup.
Great, thanks for nailing down the rule direction. That closes the netflector side: discovery now crosses correctly both ways. The Core's queries reach the phone, the phone's announcements reach the LAN broadcast once each with the phone's source kept, nothing answers the phone with ICMP anymore, and the Core replies to them.
What is left happens after discovery. From the announcement the Core learns the endpoint's RAAT port (tcp_port 9200 in your capture) and connects to the phone over plain routed TCP, which netflector is not part of. Three things would tell us where it fails:
- A baseline: the same four force-close/open attempts with the phone on your LAN Wi-Fi, WireGuard off. If the endpoint is intermittent there too, it is Roon's own behaviour after a force-close, for example the Core still holding the previous session for that device.
- During a run over WireGuard with both good and failed attempts, capture on both interfaces at once:
tcpdump -ni wg0 -w /tmp/wg0.pcap host 10.10.10.2
tcpdump -ni ax1 -w /tmp/ax1.pcap host 10.10.10.2
Note the time of each attempt and whether the endpoint appeared, then send me both files. I want to see whether the Core opens a TCP connection to 10.10.10.2:9200 on the failed attempts, whether that connection shows up on both interfaces, and what the phone answers. - In a failed attempt, how long did you wait? The phone re-announces every few seconds, so if the endpoint never shows up while announcements keep arriving, the Core is ignoring them or its TCP connect keeps failing.
@UnicronHD, the LAN-only baseline reproduces it too.
WireGuard was completely OFF and the iPhone was on normal LAN Wi-Fi only.
The endpoint behaviour was still intermittent. When it failed, I waited 20-30 seconds and the endpoint still did not appear. It only came back after one or more force-close/reopen cycles of Roon.
So this is not just a 2-3 second discovery delay. The same remaining behaviour exists without WireGuard and without netflector. I did not know that.
That seems to confirm your conclusion that the netflector/discovery side is now closed and the remaining issue is in Roon/iOS endpoint/session behaviour after discovery.
Thanks for running the baseline. If it's just as flaky on plain Wi-Fi with no tunnel and no netflector, then it's Roon itself after a force-close, and there's nothing left to fix on the network side.
Good to know where things stand though. Peers fix discovery over WireGuard, but the phone needs a /24 on its tunnel address, otherwise it sends its endpoint announcement to itself. And the router has to drop 10.10.10.255 on the WireGuard interface, or it forwards the packet back into the tunnel and replies to the phone with ICMP. I'll put both into the docs, we'd never have found them without your captures.
Thanks again for all the testing. I'll prepare a new official release soon.
@UnicronHD, thanks again for all the work and for digging into this with me.
Looking forward to the official release!
netflector 0.16.0 and plugin 0.1.6 are released. The main change is addition of generic UDP relay (udp_ports, udp_groups, udp_broadcast) to support other discovery protocols (like Roon's SOOD) and peers (source_peers, target_peers) to support interfaces without multicast/broadcast domains.
Great, The plugin 0.1.6 with netflector 0.16.0 is installed and working perfectly here.
Thanks again for all the work and support!
Quote from: RamSense on September 21, 2026, 07:30:43 AMGreat, The plugin 0.1.6 with netflector 0.16.0 is installed and working perfectly here.
Thanks again for all the work and support!
Happy to help, thanks for the idea and all the testing.