The complete SSDP + mDNS solution for network segmentation

Issue at Hand
If you’ve segmented your network, you may have encountered difficulty utilizing Chromecast, Airplay, or similar IoT devices that rely on mDNS / Bonjour / ZeroConf or SSDP. This is due to these protocols not being routable or filterable. Numerous individuals face this issue, and while there are several solutions out there, none are entirely foolproof from a security perspective. Issues often include bi-directional traffic reflection, lack of filter options, absence of IPv6 support, and no session tracking.

However, I’ve devised a solution I believe encompasses the best of all possible remedies. It incorporates several different strategies, all created with RouterOS in mind.

What is our Objective?

Assume we have segmented networks in the following manner:

  • A client network, comprising your PCs, laptops, and family smartphones.
  • A guest network, comprising smartphones and other devices of your visitors.
  • An IoT network, consisting of your Chromecast, Airplay, and other IoT devices.

The goal is to utilize our IoT devices from our client network, but only a select number of devices within the IoT network should be accessible from the guest network. We don’t want to facilitate mDNS/SSDP from the IoT network to the client or guest network. Additionally, we want to prevent guests from using mDNS/SSDP to access the client network (and vice versa).

Proposed Solution
The Bonjour-reflector I’ve developed is an upgraded version of another project, with added protocols such as SSDP.

Key features include:

  • Detailed control over your advertised IoT devices based on MAC-addresses. Access can be set on a per-device basis.
  • Protocol inspection for a better understanding of SSDP and mDNS. It can distinguish between query responses and advertisements.
  • Session tracking, ensuring that it tracks which ports are being used for each session and only permits traffic to flow in the appropriate direction.
  • IPv6 support, facilitating usage with both IPv6 and IPv4.
  • A single binary.
  • Compact size, taking up only ~8MB of space.

It should be compatible with most arm(64) + CHR Mikrotiks. For a comprehensive guide, please refer to the markdown on Github.

The howto: https://github.com/nberlee/bonjour-reflector/blob/main/docs/RouterOS/README.md

If you have any queries, don’t hesitate to reach out. I’m here to assist!

Thanks so much for this!

Avahi was giving me a lot of headaches. This solution seems to have no problem at all reflecting my Chromecast and printers across VLANs.

Also a nice bonus is that you can choose which devices get reflected!

It fixed my issue so that I can now see my Chromecast and Apple TV devices on my Clients VLAN while they are in my IOT vlan :slight_smile:

P.S.

This container is only 8MB :open_mouth:

This is really awesome.

The setup was fairly easy, however requires a reboot to get into container mode.

Configuration might look a bit challenging, however once you got the hang of it it is straight forward.

I’ve some Apple TVs, Denon and Sonos devices configured for reflection and works really well.

It is very fast and responsive, I used the Palo Alto Networks bonjour reflector option on the firewall before, but it seems not to reflect everything (ssdp?).
When I open for example the Sonos app on my phone all Sonos devices are there within a second, before this took a while and sometime I had to kill and re-open the app.

Really worth to give a shot !

Thank you very much for this great work.

I was able to set everything up, but unfortunately it does not work. I get the following error message in the log file:

"level=error msg=Unable to remove the hardware vlan filter (rx-vlan-filter): unsupported feature “rx-vlan-filter”.

I must admit that I am a beginner and not a VLAN expert, but do you have any idea how I can overcome this hurdle?

I doubt that message appeared after the very last step in this complicated setup. It’s my bet it occurred about ¼ of the way into the process, in this step, where it sets up the VETH with bridge VLAN filtering disabled.

This shaky bet of mine is based on two facts:


  1. Not all RouterOS devices support bridge VLAN filtering. (See the yellow caution box below the target of the prior link.) You can’t disable something that doesn’t even exist on the host platform.
  2. Your diagnosis is vague, not even telling us details like which RouterOS device you tried this on, permitting me to speculate freely on this point.

There’s a chance I’ve nailed it by luck. If so, then I think it’s worth pulling the bridge VLAN filtering bits out of the tutorial and trying to make it work using whatever alternative VLAN filtering mechanism your device supports instead. I have no doubt that this tutorial’s basic ideas work best with hardware bridge VLAN filtering, but I doubt it’s absolutely essential.

If that doesn’t work, or if I’ve missed the mark, the way to elicit stronger guesses is to provide details to found them upon rather than rely on superior levels of mind-reading and luck than what I’ve been able to bring to bear.

You are very welcome! I learned a lot about SSDP and mDNS and packet layers in general when making this.


Sorry for this confusing error. You may simply ignore it. Is isn’t relevant for Mikrotik routers as it is not connected to a hardware port. It is only needed when directly connected to a physical NIC, so for example on a linux host. the Veth interface on RouterOS does not need this…

I will make some code to first check if its even needed to set this nic feature.


Please see if you see packets coming in with Packet Sniffer, see https://github.com/nberlee/bonjour-reflector/blob/main/docs/RouterOS/README.md#no-mdnsssdp-is-reflected
If you do not see packets with destination 239.255.255.250:1900 or 224.0.0.251:5353 then you know your bridge isn’t receiving or forwarding it to the veth port.

If you do see those packets, start the container in verbose mode by adding

/bonjour-reflector -verbose

to Cmd in the container settings
This will log any packet it needs to process.

hi having issues at network setup:
3. Add veth1-reflector as tagged port to the vlans you want to use.

what am i doing wrong? running on RB5009UG+S+
thanks in advance!
1.jpg

@nicob,
Thank you for the great work ! I tried on hAP ax2 (v 7.11.2). My goal is to let apple TV ( vlan10) available to vlan12 network.

The topology is: Router ↔ CRS328 ↔ hAP ax2, ether1 on ax2 is set as trunk port.

But after getting reflector running, my phone (vlan 12) still cannot see apple TV (vlan10) on the airplay. With packet sniffer, I can see packets going in but not coming out.
Could you please take a look into my config and point me to the right direction ? Thank you !

debug message in log as below:

10:44:41 container,info,debug importing remote image: ghcr.io/nberlee/bonjour-reflector, tag: main
10:44:42 container,info,debug getting layer sha256:2933017b37c42456cc855d00ebcf94b175e0de81c01313003007e689708edfa4
10:44:44 container,info,debug layer sha256:2933017b37c42456cc855d00ebcf94b175e0de81c01313003007e689708edfa4 downloaded
10:44:48 container,info,debug import successful, container 3ec97e5e-b2cb-4158-b9bf-2f64d31ad593
10:44:54 container,info,debug 2023/09/07 17:44:54 maxprocs: Leaving GOMAXPROCS=4: CPU quota undefined

The configuration on the arm64 machine is as follows:

/interface veth
add address=127.1.0.10/32 gateway=127.1.0.1 gateway6=“” name=veth1-reflector

/interface/bridge/port/add bridge=bridge1 edge=yes frame-types=admit-only-vlan-tagged
ingress-filtering=no interface=veth1-reflector learn=yes multicast-router=permanent
point-to-point=yes pvid=999

/interface bridge vlan
add bridge=bridge1 tagged=sfp3-crs312,bridge1,eth5-AP-GR_Closet,veth1-reflector vlan-ids=10
add bridge=bridge1 tagged=sfp3-crs312,bridge1,eth5-AP-GR_Closet,veth1-reflector vlan-ids=12
add bridge=bridge1 comment=“management vlan” tagged=bridge1,sfp3-crs312,eth5-AP-GR_Closet vlan-ids=99


/container config set registry-url=> https://ghcr.io > tmpdir=nvme1-part1/pull

/container mounts add dst=/config name=reflector-config src=/nvme1-part1/bonjour
/system logging add topics=container
/container/add remote-image=ghcr.io/nberlee/bonjour-reflector:main int=veth1-reflector
root-dir=nvme1-part1/bonjour/reflector mounts=reflector-config envlist=bonjour_envs logging=yes
start-on-boot=“yes” comment=“bonjour-reflector”

\

config.toml – upload this file to /nvme1-part1/bonjour

net_interface = “eth0”

[devices]

[devices.“BC:34:00:A0:30:A8”]
description = “Apple tv”
origin_pool = 10
shared_pools = [12]


[vlan]

[vlan.10]
ip_source = “10.10.10.254”

[vlan.12]
ip_source = “10.12.12.254”

net_interface = “eth0”

[devices]
[devices.“DC:56:E7:45:BC:CF”]
description = “BSMT ATV”
origin_pool = 13
shared_pools = [10, 12]

[vlan]
[vlan.10]
ip_source = “10.10.10.254”

[vlan.12]
ip_source = “10.12.12.254”

[vlan.13]
ip_source = “10.13.13.254”

Thank you for putting the effort in in providing me with great context!
This all looks ok… If the following is true
[] 10.13.0/12/13.254 NOT in use by any other device or themikrotik. If you ping the ip on the same network as a client , you should not get a response, however, arp -a should give you the mac addresss of the veth.
[
] Doublecheck the mac adress



With packet sniffer, I can see packets going in but not coming out.

This is great! Are these packets with destination 239.255.255.250:1900 or 224.0.0.251:5353 ?
If yes, now run the container with a different cmd /bonjour-reflector -verbose, then stop and start it, you should at least see in the logs with packet received:



Not sure if it is because the reflector has to be installed on Router instead of the ax2 ?

Its fine either way, I have it on my router (CCR2004), but I also could have it somewhere else. but there are two main things to keep in mind.
The router on where bonjour-reflector runs should see all the multicast traffic and upstream ports should have unknown-multicast=yes. And you should not run multiple bonjour-reflectors in the same vlans.

Sorry for my late reply… I didn’t notice this message.

I was able to replicate this when, bridge-LAN does not exists or more likely when the vlan-id is unknown to the bridge, in this case 1 . The guide assumes

It assumes you have one bridge with vlans configured

So create the vlan on the bridge by assign it to a port, then follow step 3

Thank you for putting the effort in in providing me with great context!
This all looks ok… If the following is true
[] 10.13.0/12/13.254 NOT in use by any other device or themikrotik. If you ping the ip on the same network as a client , you should not get a response, however, arp -a should give you the mac addresss of the veth.
[
] Doublecheck the mac adress

With packet sniffer, I can see packets going in but not coming out.

This is great! Are these packets with destination 239.255.255.250:1900 or 224.0.0.251:5353 ?
If yes, now run the container with a different cmd /bonjour-reflector -verbose, then stop and start it, you should at least see in the logs with packet received:

#################################################################
Thank you for your reply !

#1 10.x.x.254 is NOT in use by any devices.
#2 What do you mean by “double check the mac address of veth1-reflector” ?
#3 yes, both 239.255.255.250:1902 and 224.0.0.251:5353 showed up in the dst.
#4 some logs after running /bonjour-reflector -verbose

I believe i have it right :frowning: Note, that i backed up my config to the start point
2.png

It finally worked if there is only one device to be available to another network.
i.e Apple TV (vlan10) can be available to network vlan12.

But when I want to add devices and add networks, it doesn’t work. The only change I made is the config.toml file. And the error message is " Could not read configuration: duplicate tables".

something wrong with My config.toml file ?

net_interface = "eth0"

[devices]
    [devices."BC:34:00:A0:30:A8"]
    description = "Aries mini"
    origin_pool = 13
    shared_pools = [10, 12]
[devices]
    [devices."BC:34:00:A0:50:03"]
    description = "Altair"
    origin_pool = 13
    shared_pools = [10, 12]
[devices]
    [devices."DC:56:E7:45:BC:CF"]
    description = "Apple Tv Basement"
    origin_pool = 13
    shared_pools = [10, 12]

[vlan]
    [vlan.10]
    ip_source = "10.10.10.254"
    [vlan.12]
    ip_source = "10.12.12.254"
    [vlan.13]
    ip_source = "10.13.13.254"

Great!

Toml is sometimes confusing. [devices] needs defined only once. Use https://www.toml-lint.com for fast checking. The error messages may be implying errors on the line below…

This should work:

net_interface = "eth0"

[devices]
    [devices."BC:34:00:A0:30:A8"]
    description = "Aries mini"
    origin_pool = 13
    shared_pools = [10, 12]

    [devices."BC:34:00:A0:50:03"]
    description = "Altair"
    origin_pool = 13
    shared_pools = [10, 12]

    [devices."DC:56:E7:45:BC:CF"]
    description = "Apple Tv Basement"
    origin_pool = 13
    shared_pools = [10, 12]

[vlan]
    [vlan.10]
    ip_source = "10.10.10.254"
    [vlan.12]
    ip_source = "10.12.12.254"
    [vlan.13]
    ip_source = "10.13.13.254"

In step 3 you are actually editing vlans on the bridge to add the veth bridge port (see screenshot) to the corresponded vlans
Screenshot from 2023-09-08 11-06-54.png
To be aple to edit the ports, you need to have the vlan ids on the bridge.

OMG … DID IT!!! Works like a charm! Many, many, many thanks! Great job!

My bad !!! [Devices] needs to be defined only ONCE ! Now it works perfectly !

Thank you so much for your help !

First of all thanks for the great effort on putting this all together!

I encountered the following issue with this approach though:
Everything seem to work, I was able to successfully proxy an ikea hub / tv / chromecast speaker, but when container starts it kills the network performance.
Here is an iperf3 test from a client vlan to the outside world:

[  5]  40.00-41.00  sec  3.38 MBytes  28.3 Mbits/sec
[  5]  41.00-42.00  sec  4.66 MBytes  39.1 Mbits/sec
[  5]  42.00-43.00  sec  4.28 MBytes  35.9 Mbits/sec
[  5]  43.00-44.00  sec  3.09 MBytes  26.0 Mbits/sec
## container start
[  5]  44.00-45.00  sec  1.19 MBytes  9.96 Mbits/sec
[  5]  45.00-46.00  sec  16.0 KBytes   131 Kbits/sec
[  5]  46.00-47.00  sec  2.02 MBytes  16.9 Mbits/sec
[  5]  47.00-48.00  sec   688 KBytes  5.64 Mbits/sec
[  5]  48.00-49.00  sec   256 KBytes  2.10 Mbits/sec
[  5]  49.00-50.00  sec  48.0 KBytes   393 Kbits/sec
[  5]  50.00-51.00  sec  16.0 KBytes   131 Kbits/sec
[  5]  51.00-52.00  sec  32.0 KBytes   262 Kbits/sec
[  5]  52.00-53.00  sec  0.00 Bytes  0.00 bits/sec
[  5]  53.00-54.00  sec  32.0 KBytes   262 Kbits/sec
[  5]  54.00-55.00  sec  16.0 KBytes   131 Kbits/sec
[  5]  55.00-56.00  sec  32.0 KBytes   262 Kbits/sec
[  5]  56.00-57.00  sec  16.0 KBytes   131 Kbits/sec
[  5]  57.00-58.00  sec  48.0 KBytes   393 Kbits/sec
## container stop
[  5]  58.00-59.00  sec  1.39 MBytes  11.7 Mbits/sec
[  5]  59.00-60.00  sec  3.72 MBytes  31.2 Mbits/sec
[  5]  60.00-61.00  sec  4.00 MBytes  33.6 Mbits/sec

The same happens if I start a packet sniffer on the veth1-reflector or the main bridge. So it seems the problem is some sort of a global promisc mode that container enables? (I have a very vague understanding of how this all works in mikrotiks).
Switching works well though, client → client is fast.

My setup:

model: RB5009UPr+S+
factory-firmware: 7.7
current-firmware: 7.9
upgrade-firmware: 7.11.2

veth:
14  RS  name="veth1-reflector" type="veth" mtu=1500 actual-mtu=1500 mac-address=8E:E9:9F:A3:75:28 ifname="veth16" ifindex=16 id=16 last-link-up-time=2023-11-09 17:22:48 link-downs=0

bridge bport:
 8     interface=veth1-reflector bridge=bridge_lan priority=0x80 path-cost=10 internal-path-cost=10 edge=yes point-to-point=yes learn=auto horizon=none auto-isolate=no restricted-role=no restricted-tcn=no pvid=999 
       frame-types=admit-only-vlan-tagged ingress-filtering=yes unknown-unicast-flood=yes unknown-multicast-flood=yes broadcast-flood=yes tag-stacking=no bpdu-guard=no trusted=no multicast-router=permanent 
       fast-leave=no

[admin@MikroTik] > /interface/bridge/vlan/ print
Columns: BRIDGE, VLAN-IDS, CURRENT-TAGGED, CURRENT-UNTAGGED
# BRIDGE      VLAN-IDS  CURRENT-TAGGED   CURRENT-UNTAGGED
;;; trusted lan
0 bridge_lan      2000  bridge_lan       ether3          
                        veth1-reflector  ether4          
                                         ether5          
                                         ether6          
                                         ether2          
;;; guest
1 bridge_lan      2100  bridge_lan                       
                        veth1-reflector                  
                        ether3                           
                        ether4                           
                        ether5                           
;;; iot
2 bridge_lan      2200  bridge_lan       ether7          
                        veth1-reflector  ether8          
                        ether3                           
                        ether4                           
                        ether5 
                       
[admin@MikroTik] > /container/ print detail
 0 ;;; bonjour-reflector
   name="201b3447-ccb5-490a-a7d8-ab4de86e17d6" tag="ghcr.io/nberlee/bonjour-reflector:main" os="linux" arch="arm64" interface=veth1-reflector root-dir=containers/reflector mounts=reflector-config dns="" workdir="/" 
   logging=yes status=stopped

— config.toml:

net_interface = "eth0"

[devices]

    [devices."28:39:5E:42:93:C3"]
    description = "Samsung TV"
    origin_pool = 2200
    shared_pools = [2000, 2100]

    [devices."88:D0:39:2E:3A:F5"]
    description = "Polk"
    origin_pool = 2200
    shared_pools = [2000, 2100]

    [devices."58:D5:0A:B3:AA:51"]
    description = "Ikea hub"
    origin_pool = 2200
    shared_pools = [2000, 2100]

[vlan]

    [vlan.2000]
    ip_source = "192.168.200.253"

    [vlan.2100]
    ip_source = "192.168.201.253"

    [vlan.2200]
    ip_source = "192.168.202.253"

Any ideas?

btw, everything works with ingress-filtering=yes on the bridge port in my case.

The same happens if I start a packet sniffer on the veth1-reflector or the main bridge.

what actually when using the packet sniffer is that all hardware offload temporarily is disabled. This is the only way the mikrotik controlplane can see the packets.

And this is a good clue. My first guess is that Hardware offload is disabled when you start the container. Please see if your bridge ports have an H flag. Or click all bridge ports and see if HW offload is on (in the bottom right of the window). And if this changes when starting, stopping the container.

Today 7.12 was released with some bridge fixed, two of the fixes:

*) bridge - fixed fast-path forwarding with HW offloaded vlan-filtering (introduced in v7.11);
*) bridge - fixed vlan-filtering stability with HW and non-HW offloaded ports (introduced in v7.10);

That could be related. I would suggest to update.

btw, everything works with ingress-filtering=yes on the bridge port in my case.

That is good news! It might have been broken when I put things together, let me see if I have some time next week to do new tests in 7.11/7.12 and update the guide.

Hw. Offload. stays on when I start the container. At least the indicator is there near each bridge port except for the veth1.
Also, CPU load stays very low - 1-2%, so it does not seem that it tries to process packets in software at all.

Upgrading to 7.12 did not help unfortunately =(