VR streaming apps like Meta Horizon Link or Steam Link rely heavily on Zeroconf-style device discovery—a design that often fails on complex consumer networks where L2 adjacency can’t be assumed.

There is no easy way to manually input a unicast IP address and connect. On different subnets, the headset simply can’t see the PC.

This design, relying entirely on device discovery (UDP broadcasts, mDNS, or even a central directory), is what I call broadcastism.

A common workaround in enterprise environments is to set up multicast routing (for the mDNS crowd) and use UDP helpers (for UDP broadcast use cases).

Typical consumer networks lack the multicast routing or UDP helpers required to bypass broadcastism. That’s where brdisco comes in, bridging the gaps left by broadcastism.

brdisco
General-purpose bridge for service discovery

brdisco divides and conquers various broadcastisms:

  • UDP broadcasts / multicasts: translating them into a set of UDP unicasts
  • mDNS: spoofing answers, proxying queries or acting as a reflector (like avahi)
  • SSDP / UPnP / …: spoofing, proxying, or reflecting multicast traffic

With interoperability in mind, it supports several operation modes:

  • userspace NAT (raw sockets)
  • Linux ebtables + iptables (not for now: legacy + overly complex)
  • Linux nftables API
  • Linux tc (traffic control) API

Case study: VR streaming across L3 subnets

Setup:

  • Headset: Quest 2 (Wi-Fi, 192.168.1.246/24 @ LAN)
  • Gateway (OpenWrt, 192.168.1.1/24 @ LAN – 192.168.13.254/24)
    • running brdisco
  • Gaming PC (Ethernet, 192.168.42.69/24)

Broadcastism comparison:

  • Meta Horizon Link (Oculus Link): mDNS ONLY
  • Steam Link: broadcast + central directory, but VR gated by local discovery
  • Virtual Desktop: central directory + local broadcast fallback

Meta Horizon Link, the default streaming app for Quest, has no service directory. It struggles with local discovery—the purest form of broadcastism—and finds nothing:

Meta Horizon Link

Under the hood, the discovery is built on mDNS—the headset sends mDNS queries and Meta app on PC is supposed to respond with its own IP and local name.

# Headset
IP 192.168.1.246.5353 > mdns.mcast.net.5353: 1 PTR (QU)? _oculusal_sp_v2._tcp.local. (44)
IP6 fe80::[redacted-q2].5353 > ff02::fb.5353: 1 PTR (QU)? _oculusal_sp_v2._tcp.local. (44)
# PC
No.	Time	Source	Destination	Protocol	Length	Info
1	0.000000	192.168.42.69	224.0.0.251	MDNS	83	Standard query 0x0000 PTR _oculusal_sp._tcp.local, "QM" question
2	0.000455	fe80::[redacted-pc]	ff02::fb	MDNS	103	Standard query 0x0000 PTR _oculusal_sp._tcp.local, "QM" question
3	0.001223	fe80::[redacted-pc]	ff02::fb	MDNS	524	Standard query response 0x0000 PTR user::DESKTOP-EFNUEKE(3)._oculusal_sp._tcp.local PTR user::DESKTOP-EFNUEKE(1)._oculusal_sp._tcp.local SRV 0 0 51873 DESKTOP-EFNUEKE.local SRV 0 0 63196 DESKTOP-EFNUEKE.local A 192.168.42.69 AAAA fe80::[redacted-pc] A 192.168.42.69 AAAA fe80::[redacted-pc]
4	0.001734	192.168.42.69	224.0.0.251	MDNS	504	Standard query response 0x0000 PTR user::DESKTOP-EFNUEKE(3)._oculusal_sp._tcp.local PTR user::DESKTOP-EFNUEKE(1)._oculusal_sp._tcp.local SRV 0 0 51873 DESKTOP-EFNUEKE.local SRV 0 0 63196 DESKTOP-EFNUEKE.local A 192.168.42.69 AAAA fe80::[redacted-pc] A 192.168.42.69 AAAA fe80::[redacted-pc]
5	1.398610	192.168.42.69	224.0.0.251	MDNS	86	Standard query 0x0000 PTR _oculusal_sp_v2._tcp.local, "QM" question
6	1.399063	fe80::[redacted-pc]	ff02::fb	MDNS	106	Standard query 0x0000 PTR _oculusal_sp_v2._tcp.local, "QM" question
7	1.399791	fe80::[redacted-pc]	ff02::fb	MDNS	539	Standard query response 0x0000 PTR user::DESKTOP-EFNUEKE(6)._oculusal_sp_v2._tcp.local PTR user::DESKTOP-EFNUEKE(7)._oculusal_sp_v2._tcp.local SRV 0 0 51872 DESKTOP-EFNUEKE.local SRV 0 0 63097 DESKTOP-EFNUEKE.local A 192.168.42.69 AAAA fe80::[redacted-pc] A 192.168.42.69 AAAA fe80::[redacted-pc]
8	1.400241	192.168.42.69	224.0.0.251	MDNS	519	Standard query response 0x0000 PTR user::DESKTOP-EFNUEKE(6)._oculusal_sp_v2._tcp.local PTR user::DESKTOP-EFNUEKE(7)._oculusal_sp_v2._tcp.local SRV 0 0 51872 DESKTOP-EFNUEKE.local SRV 0 0 63097 DESKTOP-EFNUEKE.local A 192.168.42.69 AAAA fe80::[redacted-pc] A 192.168.42.69 AAAA fe80::[redacted-pc]
9	2.402437	192.168.42.69	224.0.0.251	MDNS	86	Standard query 0x0000 PTR _oculusal_sp_v2._tcp.local, "QM" question

These mDNS advertisements are right here on PC, but they cannot traverse beyond the network where the gear is, due to the nature of how multicast is treated in consumer networks—effectively same as broadcast.

mDNS multicasts on 224.0.0.251 / ff02::fb (or FQDN mdns.mcast.net.). Setting up multicast routing / reflectors is the proper move here, but that’s highly intrusive and not even plausible in consumer networks—each L3 hop between the headset and PC requires additional setup, if any intermediate hop does not support it, “out of luck”.

A more straightforward workaround is to spoof traffic. That’s really hacky and dirty in enterprises but kind of clean in small-scale networks like consumer networks. So right now brdisco has implemented a mDNS response spoofer: it can respond to mDNS queries with the configured address.

[services.oculus.mdns]
[[services.oculus.mdns.entries]]
name = "My Desktop"
service = "_oculusal_sp._tcp"
port = 63196
address = "192.168.42.69"
[[services.oculus.mdns.entries]]
name = "My Desktop"
service = "_oculusal_sp_v2._tcp"
port = 63097
address = "192.168.42.69"

The trick is working like a charm:

01:23:43.857471 IP 192.168.1.246.5353 > mdns.mcast.net.5353: 1 PTR (QU)? _oculusal_sp_v2._tcp.local. (44)
01:23:43.857675 IP6 fe80::[redacted-q2].5353 > ff02::fb.5353: 1 PTR (QU)? _oculusal_sp_v2._tcp.local. (44)
01:23:44.878369 IP 192.168.1.246.5353 > mdns.mcast.net.5353: 2 PTR (QM)? _oculusal_sp_v2._tcp.local. (44)
01:23:44.878501 IP6 fe80::[redacted-q2].5353 > ff02::fb.5353: 2 PTR (QM)? _oculusal_sp_v2._tcp.local. (44)
01:23:44.879295 IP 192.168.1.1.5353 > mdns.mcast.net.5353: 0*- [0q] 1/0/3 PTR My Desktop._oculusal_sp_v2._tcp.local. (120)
01:23:45.877616 IP 192.168.1.246.5353 > mdns.mcast.net.5353: 3 PTR (QM)? _oculusal_sp_v2._tcp.local. (44)
01:23:45.877741 IP6 fe80::[redacted-q2].5353 > ff02::fb.5353: 3 PTR (QM)? _oculusal_sp_v2._tcp.local. (44)
01:23:45.878526 IP 192.168.1.1.5353 > mdns.mcast.net.5353: 0*- [0q] 1/0/3 PTR My Desktop._oculusal_sp_v2._tcp.local. (120)
01:23:49.253433 IP 192.168.1.246.5353 > mdns.mcast.net.5353: 0 [1a] [2q] PTR (QM)? _oculusal_sp_v2._tcp.local. PTR (QM)? _oculusal._tcp.local. (85)
01:23:49.253567 IP6 fe80::[redacted-q2].5353 > ff02::fb.5353: 0 [1a] [2q] PTR (QM)? _oculusal_sp_v2._tcp.local. PTR (QM)? _oculusal._tcp.local. (85)
01:23:50.241335 IP 192.168.1.246.5353 > mdns.mcast.net.5353: 0 [1a] [2q] PTR (QM)? _oculusal_sp_v2._tcp.local. PTR (QM)? _oculusal._tcp.local. (85)
01:23:50.241551 IP6 fe80::[redacted-q2].5353 > ff02::fb.5353: 0 [1a] [2q] PTR (QM)? _oculusal_sp_v2._tcp.local. PTR (QM)? _oculusal._tcp.local. (85)

spoofing Meta Horizon Link responses

In contrast to Meta Horizon Link, Steam Link and Virtual Desktop both send UDP broadcasts to discover local devices. They do support central service directories as well to discover anything beyond the (link-)local network, but with caveats.

# Steam Link
IP 192.168.1.246.27036 > 255.255.255.255.27036: UDP, length 30

# Virtual Desktop
IP 192.168.1.246.40172 > 255.255.255.255.38850: UDP, length 304

Steam Link always requires local discovery for VR streaming even on a remotely paired device, otherwise it will prompt

Streaming VR is only supported on the local network

The only option available here is gamepad mode that’s literally Steam Remote Play without VR Streaming.

Steam Link prompt

Virtual Desktop, on the other hand, is more flexible with remote discovery. However, it can still fail when:

  • Virtual Desktop servers are unreachable:

    • under maintenance
    • system date/time is wrong => TLS handshakes fail (thanks to Meta’s quirks)
    • Meta servers are unreachable (weirdly put as a prerequisite, probably for DRM / to fetch username)
  • Device has been discovered remotely but the return route is wrong:

    • which could happen to PC with multiple NICs

Virtual Desktop: Computer is unreachable Virtual Desktop: Unable to connect

brdisco has implemented userspace UDP helpers to convert a broadcast into unicasts towards configured target hosts.

[services.virtual-desktop.udp]
ports = 38850
targets = ["192.168.42.69"]

[services.steam-link.udp]
ports = 27036
targets = ["192.168.42.69"]

At least for now, it covers the use cases of Steam Link / Virtual Desktop. Hopefully it might also bridge the gaps in other apps that send UDP discovery broadcasts.

13:20:13.705375 IP 192.168.1.246.27036 > 255.255.255.255.27036: UDP, length 30
13:20:13.705809 IP 192.168.13.254.27036 > 192.168.42.69.27036: UDP, length 30
13:20:13.705828 ethertype IPv4, IP 192.168.13.254.27036 > 192.168.42.69.27036: UDP, length 30
13:20:13.708157 ethertype IPv4, IP 192.168.42.69.27036 > 192.168.13.254.27036: UDP, length 41
13:20:13.708157 IP 192.168.42.69.27036 > 192.168.13.254.27036: UDP, length 41
13:20:13.708211 IP 192.168.42.69.27036 > 192.168.1.246.27036: UDP, length 41
13:20:15.444279 ethertype IPv4, IP 192.168.42.69.27036 > 192.168.13.254.27036: UDP, length 244
13:20:15.444279 IP 192.168.42.69.27036 > 192.168.13.254.27036: UDP, length 244
13:20:15.444376 IP 192.168.42.69.27036 > 192.168.1.246.27036: UDP, length 244
13:27:58.872971 IP 192.168.1.246.40172 > 255.255.255.255.38850: UDP, length 304
13:27:58.873367 IP 192.168.13.254.40172 > 192.168.42.69.38850: UDP, length 304
13:27:58.873381 ethertype IPv4, IP 192.168.13.254.40172 > 192.168.42.69.38850: UDP, length 304
13:27:58.874610 ethertype IPv4, IP 192.168.42.69.38850 > 192.168.13.254.40172: UDP, length 1296
13:27:58.874610 IP 192.168.42.69.38850 > 192.168.13.254.40172: UDP, length 1296
13:27:58.874683 IP 192.168.42.69.38850 > 192.168.1.246.40172: UDP, length 1296
13:27:58.874696 IP 192.168.42.69.38850 > 192.168.1.246.40172: UDP, length 1296

To sum up

The ideology of Zeroconf is pervasive—DHCP, often overlooked, is one example. However, users can always fall back on a manually configured IP if DHCP fails.

VR streaming apps differ: even with central directory support, they rely heavily on local discovery and remain vulnerable to single points of failure.

I know it takes a lot of time for devs to implement discovery-based systems. But still, a manual connection should be left as an option since it doesn’t take much time after all.

Hopefully you’ll find brdisco useful in bridging these gaps. If you’re not up for running it, the appendices include some manual tricks to tackle broadcastism. And for those who tinker with VR networks, may your streams always reach their destination—

Long live the unicasts!

Appendix A: Common ports in VR streaming

disclaimer: observed, not official

[updated at 2025-12-27]

Virtual Desktop

Port / RangeProtocolNote
38810TCPN/A
38820TCPN/A
38830TCPN/A
38840TCPN/A
38850TCP/UDPservice discovery
  • Steam Remote Play
Port / RangeProtocolNote
27031UDPN/A
27036TCP/UDPservice discovery
27037TCPN/A
  • VR Streaming
Port / RangeProtocolNote
10400–10401UDPN/A
Port / RangeProtocolNote
63096-63097TCP_oculusal_sp_v2._tcp.local.
63195-63196TCP_oculusal_sp._tcp.local.
61678TCPN/A
4445TCPcasting

Appendix B: Manual workarounds

It might be troublesome to compile or run brdisco on your router, so here come some manual workarounds to fight against VR broadcastisms.

Add a host entry to /etc/avahi/hosts:

192.168.42.69   meta.local

Create a service entry at /etc/avahi/services/meta.service:

<?xml version="1.0" standalone='no'?><!--*-nxml-*-->
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">

<service-group>
  <name replace-wildcards="yes">PC Relayed via %h</name>
  <service>
    <type>_oculusal_sp._tcp</type>
    <host-name>meta.local</host-name>
    <port>63196</port>
  </service>
  <service>
    <type>_oculusal_sp_v2._tcp</type>
    <host-name>meta.local</host-name>
    <port>63097</port>
  </service>
</service-group>

Necessary changes in the configuration:

  • eth0 -> LAN interface name
  • 192.168.42.69 -> remote PC
table netdev broadcastism {
    chain unicastify {
        type filter hook ingress device eth0 priority filter; policy accept

        # Virtual Desktop
        pkttype broadcast ether type ip udp dport 38850 ether daddr set 02:00:00:00:00:01 ip daddr set 192.168.42.69 pkttype set unicast counter

        # Steam Link
        pkttype broadcast ether type ip udp dport 27036 ether daddr set 02:00:00:00:00:01 ip daddr set 192.168.42.69 pkttype set unicast counter
    }
}

Save it at /etc/nftables.conf.brdisco and run with privileges:

nft -f /etc/nftables.conf.brdisco