UPnP and iptables

Syncthing seems to be a fantastic alternative to btsync albeit a bit harder to configure. I have now spent the last two nights attempting to get this to work with a server, a stationary and a laptop (the latter is going to be off-site at times where it still needs to sync with both devices). The issue lies with iptables.

No matter what rules I invoke it will not allow UPnP until I disable iptables.

This is the current rules I have made (I am fully aware some of these rules are unecessary - I just don’t know which ones)

# Open for UPnP
-A INPUT -s 10.10.10.254/24 -i eno1 -p tcp -m tcp --dport 1900 -j ACCEPT
-I INPUT -s 10.10.10.254/24 -i eno1 -p udp -m udp --dport 1900 -j ACCEPT
-I INPUT -p tcp -m tcp --dport 49152 -j ACCEPT

-A INPUT -i eno1 -s 0.0.0.0/32 -d 224.0.0.1/32 -p igmp -j ACCEPT
-A INPUT -i eno1 -d 224.0.0.0/8 -p igmp -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p igmp -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT

I have tried these in many different combinations, yet I always get:

INFO: No UPnP device detected

This happens on both my laptop and stationary if iptables is running.

systeminfo: Arch linux, using the latest syncthing from the pacman repository.

Cheers, :smile:

UPnP is tricky from a firewall point of view. The query goes from a random port to a multicast address on port 1900, the answer comes from some other address and port to the random port in the query.

Query: 172.16.32.169.62214 > 239.255.255.250.1900: UDP, length 138
Answer: 172.16.32.12.58589 > 172.16.32.169.62214: UDP, length 509

I don’t off hand know what iptables rules would allow this if the usual -m state --state established,related doesn’t catch it, other than allowing UDP in general from your local network.

Thanks for the reply!

So this works:

-A INPUT -s x.x.x.x/x -i eno1 -p udp -m udp -j ACCEPT 

^ address being the gateway

The question is now - how to narrow down the scope as much as possible to create as secure a setup as is possible with the current technology?

You can perhaps use chains/triggers/whatever they are called, so that you allow all outgoing udp traffic to udp 1900, and at the point it goes through that chain, you add a rule for N seconds which allows incoming UDP traffic to the source port.

I am sure there already should be something like that on stack overflow, or can be achieved by reading about port knocking.

Now that seems interesting - I will get on it right away! Thanks for the pointer. I will share what I learn if I am successful :smile:

Aight heres an update. I’ve spent the last two hurs researching but I seem to be a tad stuck…

Here’s what I’ve come up with so far:

    #########       UPnP        ###########

#Opens up for all udp ports on local network - not so good.
#-A INPUT -s 10.10.10.254/24 -i eno1 -p udp -m udp -j ACCEPT

#Opens for the needed ports for syncthing but still too many
#-A INPUT -s 10.10.10.254/24 -i eno1 -p udp --match multiport --dports 40000:65000 -j ACCEPT

#allows for related ports to be opened along with ones already established. Does not work
-A INPUT -s 10.10.10.254/24 -i eno1 -p udp -m state --state RELATED,ESTABLISHED -j ACCEPT

###### Opening op selected portrange when udp package is received on port 1900 for x seconds

#Creating chain STATE0-1

-N STATE0
-A INPUT -j STATE0

#Opening and receiving package from port 1900
-A STATE0 -s 10.10.10.254/24 -i eno1 -m state --state NEW -p udp --dport 1900 -m recent --name UPnPpacket --set -j ACCEPT
-A STATE0 -j DROP

-N STATE1

#Looking at recent with name UPnPpacket and if it exists open ports 40000:65000 for 10 seconds 
###
#-A STATE1 -s 10.10.10.254/24 -i eno1 -m state --state NEW -p udp --match multiport --dports 40000:65000 -m recent --rcheck --seconds 10 --name UPnPpacket -j ACCEPT 
###
#trying this instead
-A STATE1 -m recent --name UPnPpacket --remove
-A STATE1 -s 10.10.10.254/24 -i eno1 -p udp --match multiport --dports 40000:65000 -j ACCEPT
-A STATE1 -j STATE0


#test whether source address of current package is in the list
-A INPUT -m recent --name UPnPpacket --rcheck -j STATE1

Can anyone help build on this/correct me where I am wrong?

Cheers,

Aight - I solved the issue :slight_smile:

WARNING: WILL MAKE YOUR SYSTEM SOMEWHAT VULNERABLE

Here is a script that will listen on port 1900 for udp packages and if one is received open up the ports 40000-65000 from the local subnet.

Just replace 10.10.10.254/24 with your own gateway - i.e. 192.168.0.1/24 etc.

    #########       UPnP        ###########

#allows for related ports to be opened along with ones already established. Does not work on its own
-A INPUT -s 10.10.10.254/24 -i eno1 -p udp -m state --state RELATED,ESTABLISHED -j ACCEPT

# Upon a udp package being received on port 1900 from the local subnet 
# the port range 40000:65000 is opened for 30 seconds.

# Create chain and give packages received name
-N INTO-PHASE2
-A INTO-PHASE2 -m recent --name PHASE1 --remove
-A INTO-PHASE2 -m recent --name PHASE2 --set
#log this event
-A INTO-PHASE2 -j LOG --log-prefix "INTO PHASE2: " --log-level 7

# Name incoming packages
-A INPUT -m recent --update --name PHASE1

# Move packages along the chain
-A INPUT -s 10.10.10.254/24 -i eno1 -p udp -m udp --dport 1900 -m recent --set --name PHASE1
-A INPUT -p udp -s 10.10.10.254/24 --match multiport --dports 40000:65000 -m recent --rcheck --name PHASE1 -j INTO-PHASE2

# Open up for ports 40000:65000 when a package from the local subnet passes is checked for existing in the chain INTO-PHASE2 and is an udp package.
-A INPUT -p udp -s 10.10.10.254/24 --match multiport --dports 40000:65000 -m recent --rcheck --seconds 10 --name PHASE2 -j ACCEPT

#############

Mind you this is only a part of a iptables config - but it is the part necessary for UPnP to work. It is not extremely secure but it will allow you to use syncthing while having iptables up n’ running.

On arch linux this data is entered into the file: /etc/iptables/iptables.rules It will depend from distro to distro.

Future improvements to this setup:

  1. Know the exact range of the ports that syncthing UPnP uses - anyone?
  2. Figure out exactly how long the ports need to stay open for the syncthing UPnP queries to work.

Cheers,

################### EDIT:

It seems did not quite solve it. Don’t know what happened, perhaps I deleted something in the beautification process… (making it readable)

Here is the updated version - I cannot quite figure out the issue

    ########################################
#########       UPnP        ###########

#allows for related ports to be opened along with ones already established. Does not work on its own
-A INPUT -s 10.10.10.254/24 -i eno1 -p udp -m state --state RELATED,ESTABLISHED -j ACCEPT

#Open up for the multicast discovery (THESE SHOULD BE DELETED ONE BY ONE TO TEST WHICH ARE NEEDED)
-A INPUT -i eno1 -d 224.0.0.0/8 -p igmp -j ACCEPT
-A INPUT -i eno1 -s 0.0.0.0/32 -d 224.0.0.1/32 -p igmp -j ACCEPT
-A INPUT -p igmp -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT

# Upon a udp package being received on port 1900 from the local subnet
# the port range 40000:65000 is opened for 30 seconds.

#0 Create chain and give packages received name
-N INTO-PHASE2

#3 Take all packages arriving in chain INTO-PHASE2 and rename them from PHASE1 to PHASE 2 and log the event
-A INTO-PHASE2 -m recent --name PHASE1 --remove
-A INTO-PHASE2 -m recent --name PHASE2 --set
-A INTO-PHASE2 -j LOG --log-prefix "INTO PHASE2: "

#1 Name incoming packages
-A INPUT -s 10.10.10.254/24 -p udp -m recent --update --name PHASE1

#1 Name packages from port 1900 from local subnet and name it PHASE1
-A INPUT -s 10.10.10.254/24 -i eno1 -p udp -m udp --dport 1900 -m recent --set --name PHASE1 -j INTO-PHASE2

# Check for whether a package received on portrange has a sender with the same IP as sender of package PHASE1, if so, pass package into the INTO-PHASE2 chain.
-A INPUT -s 10.10.10.254/24 -p udp --match multiport --dports 30000:65000 -m recent --rcheck --name PHASE1 -j ACCEPT

# Check packages arriving at portrange from local subnet to see if they have the name "PHASE2" - they they do and they are recent open accepting all packages the portrange for 30 seconds
-A INPUT -s 10.10.10.254/24 -p udp --match multiport --dports 30000:65000 -m recent --rcheck --seconds 30 --name PHASE2 -j ACCEPT

#test
#-A INPUT -s 10.10.10.254/24 -p udp --match multiport --dports 30000:65000 -j ACCEPT

#############