Skip to content

Instantly share code, notes, and snippets.

@plmcgrn
Last active July 12, 2024 15:58
Show Gist options
  • Save plmcgrn/eead9fbe125a9464e673ef91ed688d95 to your computer and use it in GitHub Desktop.
Save plmcgrn/eead9fbe125a9464e673ef91ed688d95 to your computer and use it in GitHub Desktop.
Unifi UDM and Sonos home theater with multiple VLAN's

Overview

This goal of this setup is to put the Sonos speakers on an untrusted network to keep all but the required traffic away from the trusted network where devices like personal computers, phones, etc. live. This write-up assumes you already have two networks setup and working.

Important Note on Unifi OS 3.x

UI broke cross-VLAN multicast DNS in this version. See below for steps to install the multicast-relay script to re-enable this. Without it, your Sonos controller app will not be able to discover your speakers on the other VLAN.

System

I have a Sonos Playbar, Sub, and 2 Play:3's as rear surrounds as one home theater setup connected to a UDM (non-Pro, but this should work on Pro too). Some of this setup may be easier for people with non-paired speakers, as Sonos does some shenanigans with which speaker is actively sending traffic to your wifi.

Networks

MAIN network: Personal computers, phones, tablets, and trusted devices IOT network: Untrusted devices like smart plugs, smart thermostats, and the Sonos gear

Prep

In order to get everything ready, we'll prep the entire non-firewall configuration first.

If you have a Sonos home theater setup, you'll need to create DHCP reservations/fixed IP's for every device in the setup. Sonos seems to let the other speakers in a system actively impersonate the "main" speaker, and the result is the firewall blocking that traffic if you don't explicity allow every speaker's IP.

  1. In the Sonos controller app, go to Settings (gear) > System > About My System Note each MAC address and which speaker it corresponds to.
  2. In UDM > Network > Client Devices, find your existing Sonos speaker. This is the "current" main, but may not be the "actual" main as you see it, but that's not important. Click it, go to Settings, and assign a Fixed IP. For convenience, I left this one as the IP it currently had, and just added my other speakers for fixed IP's adjacent to it.
  3. Click the Add Device button on the top right of Client Devices, and use this to add each of the other speakers by MAC address, assigning IP's as desired.

Now that those are added, you can create the Profiles (IP and Port Groups) to cover the traffic classifications in the UDM firewall.

Create Profiles

Go to Network > Settings > Profiles > IP Groups

Here's you'll create IP address and port groups

Name Type Value(s)
MULTICAST Address Group 224.0.0.0/24
SONOS-SPEAKERS Address Group ALl the IP's you reserved earlier for your speakers
MAIN-SUBNET Address Group Your main network, default is 192.168.1.0/24
IOT-SUBNET Address Group Your IOT network, mine is 192.168.3.0/24 in this build
SONOS-SPEAKERS-TCP Port group 1400, 3400-3401, 3500, 4444, 1443
SONOS-SPEAKERS-UDP Port group 1900-1901, 6969, 30000-60000

Verify Multicast DNS

In UDM > Networks, ensure that Multicast DNS is enabled. You can do this on each individual network, or just use the global setting and add both IOT and MAIN to it, if not already there by default.

Firewall Rules

That's it for the prep, now we can create the firewall rules in Network > Firewall & Security. These rules are all in the LAN IN category.

ALLOW-MULTICAST

In my network, I'm OK with all multicast being cross-network. The firewall will prevent anything undesired. Example, my printer was "discoverable" by the IOT network, even though it's on the main network, but nothing in IOT can actually connect back to it due to a firewall drop rule created further down.

Field Value
Rule Applied Applied before pre-defined rules
Action Accept
IPv4 Protocol All Protocols
Source Type IP/Port Group
Source IPv4 Address Group Any
Source Port Group Any
Destination IPv4 Address Group MULTICAST
Destination IPv4 Port Group Any

LAN-ALLOW-ESTABLISHED

This allows for TCP connections to work without "reverse" firewall rules for the return traffic. In my network, I allow anything in the main VLAN to attempt communications with the IOT VLAN (inboudn to IOT from Main). The drop rule will weed out any unexpected IOT -> Main traffic that isn't initiated from Main.

Field Value
Rule Applied Before pre-defined rules
Action Accept
IPv4 Protocol All Protocols
Source IPv4 Address Group Any
Source Port Group Any
Destination IPv4 Address Group Any
Destination Port Group Any

Under Advanced, enable the Match State Established and Match State Related checkboxes (IMPORTANT, otherwise this rule is an allow any/any for all traffic).

SONOS-CONTROLLER-UDP

Field Value
Rule Applied Before pre-defined rules
Source Type Port/IP Group
Source IPv4 Address Group SONOS-SPEAKERS
Source Port Group SONOS-CONTROLLER-UDP
Destination Type Port/IP Group
Destination IPv4 Address Group MAIN-SUBNET
Destination Port Group Any

Under Advanced, I have all the Match State checkboxes enabled. This is a relatively safe rule since it's scoped only to the Sonos IP address group.

SONOS-CONTROLLER-TCP

Field Value
Rule Applied Before pre-defined rules
Source Type Port/IP Group
Source IPv4 Address Group SONOS-SPEAKERS
Source Port Group SONOS-CONTROLLER-TCP
Destination Type Port/IP Group
Destination IPv4 Address Group MAIN-SUBNET
Destination Port Group Any

Under Advanced, I have all the Match State checkboxes enabled. This is a relatively safe rule since it's scoped only to the Sonos IP address group.

BLOCK-IOT-TO-MAIN

Field Value
Source Type Port/IP Group
Source IPv4 Address Group IOT-SUBNET
Source Port Group Any
Destination IPv4 Address Group MAIN-SUBNET
Destination IPv4 Port Group Any

This rule drops any traffic that is directed from IOT to MAIN that is not explicitly from the Sonos speakers on their defined ports.

Verification

That's it for configuration. You'll want to verify:

  1. Sonos controller on MAIN subnet can see the system. If not, this could either be the firewall rules or mDNS, verify both. See the important update below on mDNS
  2. Sonos controller can play music and updates what is playing on-screen. I had issues with the app when switching my phone between wifi networks, so ensure that when you start the app, you're already connected to the MAIN network.
  3. Devices on IOT network cannot communicate with anything on MAIN network. You can enable the DROP firewall rule for Logging, and go to System Log > Triggers, to see if any traffic attempts are actually dropped.

UI broke cross-VLAN multicast DNS in some version after 3.x, though I'm not sure which specific build. These steps will install the multicast-relay script to re-enable this. Without it, your Sonos controller app will not be able to discover your speakers on the other VLAN, whereas if your controller and speakers are on the same VLAN (you connect your phone to the IOT network) the controller will work normally.

SSH into UDM

mkdir /data/custom/multicast-relay cd /data/custom/multicast-relay wget https://raw.githubusercontent.com/alsmith/multicast-relay/master/multicast-relay.py chmod +x ./multicast-relay.py

Test it (br0 is the default network, and in this example, br3 is my IOT network) /usr/bin/python3 /data/custom/multicast-relay/multicast-relay.py --interfaces br0 br3

Sonos contorller should now see your speakers

Set it as an on-boot script

This uses the UDM Utility "On-Boot-Script" which allows for custom scripts to run init.d style curl -fsL "https://raw.githubusercontent.com/unifi-utilities/unifios-utilities/HEAD/on-boot-script/remote_install.sh" | /bin/sh

cd /data/on_boot.d

Create a new script

vi 10-multicast-relay.sh

Add the content and !wq to save it

#!/bin/sh /usr/bin/python3 /data/custom/multicast-relay/multicast-relay.py --interfaces br0 br20 br30

Make it executable

chmod +x 10-multicast-relay.sh

It'll now run on next boot, or you can just execute it manually (make sure to use the correct VLAN number(s) for your networks

/usr/bin/python3 /data/custom/multicast-relay/multicast-relay.py --interfaces br0 b3

@miked1313
Copy link

You are spot on with Multicast DNS being broken for a UDM Pro running 3.2.7.

I moved from a USG3 to a UDM Pro running 3.2.7 and restored my config. I had a strong feeling that my firewall rules were not the issue as I did not see any denied traffic from my IoT to Main VLAN. My iPhone which already had the Sonos discovery previously completed still worked and PCs and Macs would not complete discovery. Disabling Multicast DNS and using the multicast-relay.py script fixed my issue. This was driving me nuts for a few days. Thank you @plmcgrn and Happy Holidays!

@jeffreyb5478
Copy link

Thanks for the script it works like a charm! Well manually, when i want to set it as a boot script i'm facing the following error:

"Feb 01 19:07:20 UDMPro systemd[1]: Starting Run On Startup UDM...
Feb 01 19:07:20 UDMPro bash[2802]: udm-boot.service: running /data/on_boot.d/05-install-cni-plugins.sh
Feb 01 19:07:22 UDMPro bash[2803]: Pouring /data/.cache/cni-plugins/cni-plugins-linux-arm64-v1.4.0.tgz
Feb 01 19:07:25 UDMPro bash[4046]: udm-boot.service: running /data/on_boot.d/06-cni-bridge.sh
Feb 01 19:07:25 UDMPro bash[4058]: udm-boot.service: running /data/on_boot.d/10-multicast-relay.sh
Feb 01 19:07:25 UDMPro bash[4060]: /bin/sh: 0: cannot open /usr/bin/python3 /data/custom/multicast-relay/multicast-relay.>
Feb 01 19:07:25 UDMPro systemd[1]: udm-boot.service: Control process exited, code=exited, status=123/n/a
Feb 01 19:07:25 UDMPro systemd[1]: udm-boot.service: Failed with result 'exit-code'.
Feb 01 19:07:25 UDMPro systemd[1]: Failed to start Run On Startup UDM.
Feb 01 19:07:25 UDMPro systemd[1]: udm-boot.service: Consumed 2.490s CPU time."

It's also not possible to open the /usr/bin/python3 dir. Can you tell me what i'm doing wrong?

@plmcgrn
Copy link
Author

plmcgrn commented Feb 2, 2024

Thanks for the script it works like a charm! Well manually, when i want to set it as a boot script i'm facing the following error:

"Feb 01 19:07:20 UDMPro systemd[1]: Starting Run On Startup UDM... Feb 01 19:07:20 UDMPro bash[2802]: udm-boot.service: running /data/on_boot.d/05-install-cni-plugins.sh Feb 01 19:07:22 UDMPro bash[2803]: Pouring /data/.cache/cni-plugins/cni-plugins-linux-arm64-v1.4.0.tgz Feb 01 19:07:25 UDMPro bash[4046]: udm-boot.service: running /data/on_boot.d/06-cni-bridge.sh Feb 01 19:07:25 UDMPro bash[4058]: udm-boot.service: running /data/on_boot.d/10-multicast-relay.sh Feb 01 19:07:25 UDMPro bash[4060]: /bin/sh: 0: cannot open /usr/bin/python3 /data/custom/multicast-relay/multicast-relay.> Feb 01 19:07:25 UDMPro systemd[1]: udm-boot.service: Control process exited, code=exited, status=123/n/a Feb 01 19:07:25 UDMPro systemd[1]: udm-boot.service: Failed with result 'exit-code'. Feb 01 19:07:25 UDMPro systemd[1]: Failed to start Run On Startup UDM. Feb 01 19:07:25 UDMPro systemd[1]: udm-boot.service: Consumed 2.490s CPU time."

It's also not possible to open the /usr/bin/python3 dir. Can you tell me what i'm doing wrong?

That's an executable, not a directory. If you don't have it, that's the issue.

@jeffreyb5478
Copy link

jeffreyb5478 commented Feb 2, 2024

Thank you for your reply! I'm fairly new to this, do you mean that i must install python3 on my udm pro first? I thought that Python3 was already installed because when i started the script manually it works. I also see a Python3@ under /usr/bin.

@plmcgrn
Copy link
Author

plmcgrn commented Feb 2, 2024

Thank you for your reply! I'm fairly new to this, do you mean that i must install python3 on my udm pro first? I thought that Python3 was already installed because when i started the script manually it works. I also see a Python3@ under /usr/bin.

in the CLI for yours, type
python --version

If you get anything other than output like below, yeah, you're missing python or it's not in the path in my example

root@UDM:~# python --version
Python 2.7.18

If you do get the version, try running which to find the actual path to it on your device.

root@UDM:~# which python
/usr/bin/python

Welcome to Linux

@jeffreyb5478
Copy link

jeffreyb5478 commented Feb 2, 2024

Hi, i get the following output:
root@UDMPro:~# python --version
Python 2.7.18

Does it mean that i have to upgrade python?

root@UDMPro:~# which python
/usr/bin/python

I'm running Unifi OS 3.2.9

Under /usr/bin i have the following python files/folders:

python@
python2@
python2.7*
python3@
python3.9*

@jeffreyb5478
Copy link

jeffreyb5478 commented Feb 4, 2024

I have installed the python3 client again, but the installer says that the package was already installed. I also changed the parameters in the 10-multicast-relay.sh file to use : "#!/bin/sh /usr/bin/python /data/custom/multicast-relay/multicast-relay.py --interfaces br0 br10 "

But that did not fixed the problem . Also the Python --version remains the same. (2.7.18)

So this is working: "/usr/bin/python3 /data/custom/multicast-relay/multicast-relay.py --interfaces br0 br10 "

And this is not: "#!/bin/sh /usr/bin/python3 /data/custom/multicast-relay/multicast-relay.py --interfaces br0 br10"

Any suggestions?

@plmcgrn
Copy link
Author

plmcgrn commented Feb 5, 2024

I have installed the python3 client again, but the installer says that the package was already installed. I also changed the parameters in the 10-multicast-relay.sh file to use : "#!/bin/sh /usr/bin/python /data/custom/multicast-relay/multicast-relay.py --interfaces br0 br10 "

But that did not fixed the problem . Also the Python --version remains the same. (2.7.18)

So this is working: "/usr/bin/python3 /data/custom/multicast-relay/multicast-relay.py --interfaces br0 br10 "

And this is not: "#!/bin/sh /usr/bin/python3 /data/custom/multicast-relay/multicast-relay.py --interfaces br0 br10"

Any suggestions?

Is that all one line? The first part is meant to be the first line, the part starting with /usr is a separate line.

@plmcgrn
Copy link
Author

plmcgrn commented Feb 5, 2024

My startup/bash script in /data/on_boot.d/10-multicast-relay.sh (note your interfaces are likely going to be different than mine. Note this is 2 lines, not one line as you typed it in your previous reply.

#!/bin/sh
/usr/bin/python3 /data/custom/multicast-relay/multicast-relay.py --interfaces br0 br20 br30

Make sure your bash script is executable
chmod +x /data/on_boot.d/10-multicast-relay.sh

ls -al /data/on_boot.d/ should have it looking like this

-rwxr-xr-x 1 root root  103 Oct 21 17:14 10-multicast-relay.sh

@jeffreyb5478
Copy link

jeffreyb5478 commented Feb 6, 2024

Solved! Once the startup script was seperated over two lines it starts to work! Thank you for your support!

@plmcgrn
Copy link
Author

plmcgrn commented Feb 6, 2024

Solved! Once the startup script was seperated over two lines it starts to work! Thank you for your support!

Unsure. Maybe the on_boot stuff needs it, or one of its dependencies does.

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