Hey there,
I really had a hard time to make Simple Service Discovery Protocol (SSDP) work more or less seamlessly through my firewall. One might think, that is pretty common use case. But unfortunately none of the tipps I found on the internet really worked all the way. So, I thought, I'd try and make it easier for those who follow...
The use case is very simple: I have a network drive living in one subnet. And I want to "auto-magically" discover it from another network. Access is already allowed by suitable firewall zones. So, it's really just the convenient DLNA/UPnP/SSDP discovery features that are missing.
The hurdles are as follows:
- SSDP uses multicast
M-SEARCH
andNOTIFY
messages, which are not routed by default:smcroute
comes to the rescue. - Those messages have a time to live (TTL) of
1
, which means they will not survive the router: Amangle
firewall rule will increase the TTL. - Obviously, the firewall needs to accept those messages: Custom rules in the firewall will be required.
- Multicast
M-SEARCH
messages are sent from a random port. The unicastNOTIFY
responses are sent to that same random port; from the server's IP with another random port. They thus have no static way to recognize them as arelated
response:conntrackd
should provide a user-spacehelper
for this. But I could not get it to work. So, I adapted a script from this archived forum topic.
Using smcroute
is quite straight forward. Install it via opkg
and edit your /etc/smcroute.conf
:
- Enable each interface that should be used, e.g.
br-lan
:
phyint br-lan enable ttl-threshold 1
- Setup a forwarding for each route you require (
M-SEARCH
orNOTIFY
):
mroute from br-lan group 239.255.255.250 to br-other
The mangle
rule to increase TTL is quite simple. The script below will also insert it for you:
iptables -w -t mangle -A PREROUTING -d 239.255.255.250 -p udp --dport 1900 -j TTL --ttl-inc 1
You will know best, what firewall rules you require. But read on to have them set by the script.
Unfortunately, the unicast responses require a separate firewall rule for each M-SEARCH
with its specific source IP and source port. For this you can use the following script as /etc/init.d/ssdphelper
:
Script
#!/bin/sh /etc/rc.common
# Helper for SSDP M-Search multicast
# inspired by https://forum.archive.openwrt.org/viewtopic.php?id=67102
EXTRA_COMMANDS="status"
EXTRA_HELP=" status Show status and current rules"
START=90
restart() {
# Avoid stopping ssdphelper twice
start
}
start() {
# First stop ssdphelper
stop
# Create ssdphelper chains
iptables -w -t filter -N ssdphelper_trigger # &> /dev/null
iptables -w -t filter -N ssdphelper_expectations # &> /dev/null
# Create rules for increasing TTL
# The TTL is usually set to 1, meaning it would not be routed.
iptables -w -t mangle -A PREROUTING -d 239.255.255.250 -p udp --dport 1900 -j TTL --ttl-inc 1 -m comment --comment "SSDP: Increase TTL" # &> /dev/null
# Create rules for logging and accepting traffic from zones with SSDP clients into zones with SSDP services
# M-Search packets are smaller then Notify packets, so that's why there's filtering on packet length.
iptables -w -t filter -A forwarding_lan_rule -j ssdphelper_trigger -m comment --comment "SSDP: Check triggers" # &> /dev/null
iptables -w -t filter -A ssdphelper_trigger -d 239.255.255.250 -p udp --dport 1900 -m length --length 0:250 -j LOG --log-prefix "SSDP M-SEARCH: " --log-level 6 -m comment --comment "SSDP: Log M-SEARCH packets" # &> /dev/null
iptables -w -t filter -A ssdphelper_trigger -d 239.255.255.250 -p udp --dport 1900 -m length --length 0:250 -j zone_other_dest_ACCEPT -m comment --comment "SSDP: Allow M-SEARCH" # &> /dev/null
# Create rules for accepting traffic into from zones with SSDP services into zones with SSDP clients
# M-Search packets are smaller then Notify packets, so that's why there's filtering on packet length.
iptables -w -t filter -A forwarding_other_rule -j ssdphelper_expectations -m comment --comment "SSDP: Check expectations" # &> /dev/null
iptables -w -t filter -A ssdphelper_expectations -d 239.255.255.250 -p udp --dport 1900 -m length ! --length 0:250 -j LOG --log-prefix "SSDP NOTIFY: " --log-level 6 -m comment --comment "SSDP: Log NOTIFY packets" # &> /dev/null
iptables -w -t filter -A ssdphelper_expectations -d 239.255.255.250 -p udp --dport 1900 -m length ! --length 0:250 -j zone_lan_dest_ACCEPT -m comment --comment "SSDP: Allow NOTIFY" # &> /dev/null
# Loop to check the log for triggers and add expectation rules
check_log &
}
stop() {
# Check if main loop is running
pid1=$(ps -w | grep "logread -P ssdphelper -f" |grep -v grep | awk '{print $1}')
[[ -z $pid1 ]] && echo "Main loop not found. Daemon not running. You might see errors now."
# Kill check_log loop
kill $pid1 &> /dev/nul
# Remove TTL rule
iptables -w -t mangle -D PREROUTING -d 239.255.255.250 -p udp --dport 1900 -j TTL --ttl-inc 1 -m comment --comment "SSDP: Increase TTL" # &> /dev/null
# Detach ssdphelper chains
iptables -w -t filter -D forwarding_lan_rule -j ssdphelper_trigger -m comment --comment "SSDP: Check triggers" # &> /dev/null
iptables -w -t filter -D forwarding_other_rule -j ssdphelper_expectations -m comment --comment "SSDP: Check expectations" # &> /dev/null
# Clear and remove ssdphelper chains
iptables -w -t filter -F ssdphelper_trigger # &> /dev/null
iptables -w -t filter -X ssdphelper_trigger # &> /dev/null
iptables -w -t filter -F ssdphelper_expectations # &> /dev/null
iptables -w -t filter -X ssdphelper_expectations # &> /dev/null
# Indicate it's over
[[ -z $pid1 ]] && echo "Force-stopped ssdphelper. Errors should not happen anymore."
}
check_log() {
# Read the system logfile $line by $line
logread -P ssdphelper -f | while read line ;
do
# Silently search for $line with text generated from the iptables log rule
echo "$line" | grep -q "SSDP M-SEARCH"
if [ $? = 0 ]
then
# get the needed information from the log line
sourceaddress=$(echo $line | grep -o SRC=[^,]* | cut -d " " -f 1 | cut -d = -f 2)
sourceport=$( echo $line | grep -o SPT=[^,]* | cut -d " " -f 1 | cut -d = -f 2)
# Create expectation rule
iptablesrule="ssdphelper_expectations -t filter -d $sourceaddress -p udp --dport $sourceport -j ACCEPT"
# Check if rule already exists
iptables -w -C $iptablesrule &> /dev/null
if [ $? = 1 ]
then
# Else: add it
iptables -w -A $iptablesrule # &> /dev/null
# create a process that will delete the rule after 5 seconds
(sleep 5; iptables -w -D $iptablesrule &> /dev/null)&
fi
fi
done
}
status() {
# Check if main loop is running
pid1=$(ps -w | grep "logread -P ssdphelper -f" |grep -v grep | awk '{print $1}')
[[ -z $pid1 ]] && echo "Main loop not found. Daemon not running."
if [ ! -z $pid1 ];
then
echo "Main loop found, PID=$pid1. Daemon running."
echo ""
echo "Current TRIGGER Rules:"
iptables -w -v -t filter -L ssdphelper_trigger
echo ""
echo ""
echo "Current EXPECTATIONS Rules:"
iptables -w -v -t filter -L ssdphelper_expectations
echo ""
echo ""
fi
}
Make sure to replace and duplicate lines containing forwarding_lan_rule
and forwarding_other_rule
as you require. They setup the dynamic rules for the unicast NOTIFY
responses.
Also make sure to adapt the lines containing zone_lan_dest_ACCEPT
and zone_other_dest_ACCEPT
. They will open the firewall for the multicast M-SEARCH
and NOTIFY
messages.
The script also inserts the mangle
rule.
Now, start and enable the service:
chmod a+x /etc/init.d/ssdphelper
/etc/init.d/ssdphelper enable
/etc/init.d/ssdphelper start
And that's it. I can access my network share now with automatic discovery. I have this set up and running for not too long, yet. So, if I still run into trouble, I'll fix the guide here.
I hope this helps someone. And please let me know, if there is an easier way to do this. I really was puzzled by the complexity of that stuff...