miniupnp: Miniupnpd + nftables does not seem to work properly

Ever since I switched to nftables, I have been unable to get miniupnpnd to work. I have been running it for years before without issues using iptables / netfilter.

The base ruleset I have configured is as follows:

table ip filter {
	chain input {
		type filter hook input priority 0; policy accept;
		ct state established,related accept
		ip protocol icmp counter packets 146 bytes 17104 drop
		tcp dport { ssh, http, https } ct state new counter packets 74 bytes 3944 accept
		iifname "lo" accept
		iifname "enp2s0" counter packets 3792 bytes 1012502 accept
		iifname "eno0" counter packets 996 bytes 93929 drop
		drop
	}

	chain output {
		type filter hook output priority 100; policy accept;
	}

	chain forward {
		type filter hook forward priority 0; policy drop;
		iifname "enp2s0" oifname "eno0" accept
		iifname "eno0" oifname "enp2s0" ct state established,related accept
	}
}
table ip nat {
	chain prerouting {
		type nat hook prerouting priority 0; policy accept;
	}

	chain postrouting {
		type nat hook postrouting priority 100; policy accept;
		oifname "eno0" masquerade random,persistent
	}
}

When I start miniupnpd, the ruleset becomes as follows as the miniupnpd tables and chains are being added:

table ip filter {
	chain input {
		type filter hook input priority 0; policy accept;
		ct state established,related accept
		ip protocol icmp counter packets 147 bytes 17188 drop
		tcp dport { ssh, http, https } ct state new counter packets 82 bytes 4364 accept
		iifname "lo" accept
		iifname "enp2s0" counter packets 3980 bytes 1058250 accept
		iifname "eno0" counter packets 1017 bytes 95803 drop
		drop
	}

	chain output {
		type filter hook output priority 100; policy accept;
	}

	chain forward {
		type filter hook forward priority 0; policy drop;
		iifname "enp2s0" oifname "eno0" accept
		iifname "eno0" oifname "enp2s0" ct state established,related accept
	}
}
table ip nat {
	chain prerouting {
		type nat hook prerouting priority 0; policy accept;
	}

	chain postrouting {
		type nat hook postrouting priority 100; policy accept;
		oifname "eno0" masquerade random,persistent
	}

	chain MINIUPNPD {
	}

	chain MINIUPNPD-POSTROUTING {
	}
}
table inet filter {
	chain MINIUPNPD {
	}
}

So far so good. However, when I run a service on the LAN that tries to open a port, it either does not get added to the chains, or it does get added but the app always says the port it not open to the outside world.

For example, when I start Transmission while debugging (miniupnpd -d -f miniupnpd.conf), I regularly see the following line:

miniupnpd[24596]: rule with label 'Transmission at 53732' is not a IGD pinhole

The two TCP and UDP rules have now indeed been added:

add rule ip nat MINIUPNPD iif "eno0" tcp dport 53732 dnat to 192.168.2.10: 53732
# new generation 557 by process 24914 (miniupnpd)
add rule inet filter MINIUPNPD iif "eno0" @th,16,16 53732 @nh,128,32 3232236052 @nh,72,8 6 accept
# new generation 558 by process 24914 (miniupnpd)
table ip nat {
	...

	chain MINIUPNPD {
		iif "eno0" tcp dport 53732 dnat to 192.168.2.10:53732
		iif "eno0" udp dport 53732 dnat to 192.168.2.10:53732
	}

	chain MINIUPNPD-POSTROUTING {
	}
}
table inet filter {
	chain MINIUPNPD {
		iif "eno0" @th,16,16 53732 @nh,128,32 3232236052 @nh,72,8 6 accept
		iif "eno0" @th,16,16 53732 @nh,128,32 3232236052 @nh,72,8 17 accept
	}
}

However, Transmission will will keep saying the port is closed:

Screen_Shot_2019-09-12_at_00_02_18

I have similar issues with Plex Media Server:

add rule ip nat MINIUPNPD iif "eno0" tcp dport 23945 dnat to 192.168.2.10:32400
# new generation 604 by process 26870 (miniupnpd)
add rule inet filter MINIUPNPD iif "eno0" @th,16,16 32400 @nh,128,32 3232236042 @nh,72,8 6 accept
# new generation 605 by process 26870 (miniupnpd)

However, miniupnpd -d -f miniupnpd.conf shows several occurrences:

rule with label 'Plex Media Server' is not a IGD pinhole

Pasted_Image_12_09_2019__01_04

An observation is that the MINIUPNPD-POSTROUTING chain is always empty?

Do you have any clue why the services are not accessible? I’m not sure what an IGD pinhole exactly is? Might my initial configuration have something invalid in there, dropping the packets?

Any help is appreciated 😃

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 32 (21 by maintainers)

Commits related to this issue

Most upvoted comments

should be fixed by @paul-chambers work

Thanks, @4np. Glad those changes fixed the issue, since I didn’t know how I was going to diagnose a problem I couldn’t reproduce. Sounds like I can submit a pull request with a clear conscience 😃

I’ve been testing with NoMachine, Plex Media Server and Zerotier One, on a firewall machine also running firewalld and sshguard with their respective nftables backends.

Yes, that’s the order I’d expect. Strange…

What happens if you do:

table inet filter {
	...
	chain forward {
		type filter hook forward priority 0; policy accept;
		iifname "enp2s0" oifname "eno0" accept
		iifname "eno0" oifname "enp2s0" ct state established,related accept
	}
}
table inet networking {
	chain forward {
		type filter hook forward priority 25; policy drop;
	}
}

I’ll play around with transmission later, after my day job 😃

hmm… I suspect changing your default policy is letting packets through from the filter/forward chain to allow miniupnpd/forward chain to process them too. If they are both the same priority, I doubt there’s a guarantee what order the two chains will be processed in, and if one is default drop, well, the other never gets to see it.

The convention appears to be for all filter chains to be priority 0. I would suggest you leave your forward/filter chain at priority 0 and change the policy to accept, and define another forward chain with a default drop policy, with a decreased priority (e.g. +25) so that all forward/filter chains at priority 0 will see the packets, and and the default drop policy will only apply if no chains at the default filter priority (NF_IP_PRI_FILTER, i.e. zero) handle the packet. Could you give that a try?

I’ve increased the priority of miniupnpd’s forward chain (i.e. making it more negative) to guarantee that it runs before your filter/forward chain. If you would be so kind as to pull and build again, and let me know? thank you

@4np Jeroen, I have something that seems to work, would you like to give it a try?

https://github.com/paul-chambers/miniupnp branch ‘nftables’

I’d like to make sure someone else can replicate my results before submitting the pull request to @miniupnp/Thomas. It’s not a small patch.

You’re right, of course. But where’s the fun in that? 😉

I’m only beginning to get into the code, but already suspect I’ll be making a structural change or two. For one thing, I would like to implement some privilege separation between the front end (UPnP server) and back end (iptables, nftables, etc. support).

The back end has to run with root privileges (or at least CAP_NET_ADMIN) to be able to update the rules, but running userspace code that receives and processes network packets with root privileges makes me a little nervous. Not that the UPnP protocol is exactly a paragon of security, but still…

I’m currently thinking that the classic pipe()/fork() approach and dropping privileges on one of the two processes may be both simple and secure enough. It would also help impose a cleaner boundary between the two, rarely a bad thing.

p.s. I’m also looking to enable remote access to my Plex Media Server 😃

It doesn’t work for me, either. I’m hoping to work on it as my time allows, if no-one else has the bandwidth. It may take me a while, though, spare time is scarce.

I think it would be better to create a separate table for miniupnpd, to keep everything related to miniupnpd separate. Something like this:

table ip miniupnpd {
	chain input {
		type filter hook input priority 100; policy accept;
		tcp dport 32400 ct state established,related,new counter packets 0 bytes 0 accept comment "accept Plex Media Server"
	}

	chain forward {
		type filter hook forward priority 100; policy accept;
		iifname "eno0" oif "enp2s0" ip daddr 192.168.2.10 tcp dport 32400 ct state new accept comment "forward port 32400 to 192.168.2.10"
	}

	chain prerouting {
		type nat hook prerouting priority 100; policy accept;
		iifname "eno0" dnat to tcp dport map { 32400 : 192.168.2.10 } comment "forward port 32400 to 192.168.2.10"
	}
}

Something like this:

# Remove the existing miniupnpd table if present
nft --check list table miniupnpd > /dev/null 2>&1
if [ $? -eq "0" ]; then
	echo "delete table miniupnpd"
	nft delete table miniupnpd
fi

# Create miniupnpd table
nft add table ip miniupnpd
nft add chain ip miniupnpd input '{ type filter hook input priority 100; policy accept; }'
nft add chain ip miniupnpd forward '{ type filter hook forward priority 100; policy accept; }'
nft add chain ip miniupnpd prerouting '{ type nat hook prerouting priority 100; policy accept; }'

nft add rule ip miniupnpd input tcp dport $plex_port ct state new,established,related counter packets 0 bytes 0 accept comment \"accept Plex Media Server\"
nft add rule ip miniupnpd forward iifname $wan oif $lan ip daddr $plex_ip tcp dport $plex_port ct state new accept comment \"forward port 32400 to 192.168.2.10\"
nft add rule ip miniupnpd prerouting iifname $wan dnat to tcp dport map { $plex_port : $plex_ip } comment \"forward port 32400 to 192.168.2.10\"

Unfortunately I have been unable to get this to work. It looks like nftables does not accept the traffic to 32400, even though it is processing the miniupnpd table. Tracing shows continue but not accept (tcp dport 32400 nftrace set 1).