First thing you need to know is how UPnP IGD protocol is working. You choose random local UDP port and from it you send discovery request to well-known multicast address 239.255.255.250 and UDP port 1900. UPnP IGD server (running on your router) listen for those multicast queries and send you back unicast UDP reply from randomly chosen port to your ip address and port from which discovery request was sent. But such reply is not paired by conntrack iptables module to your sent request so, received reply is dropped by iptables. This is why enabling all UDP ports or disabling firewall helped. In that UDP reply is location of your UPnP IGD server and client then establish classic TCP connection with UPnP IGD server. So the only problem is how to write a rule for receiving UDP reply to that multicast discovery request.
Via ipset it is possible. I described it in answer at https://serverfault.com/a/911286:
$ ipset create upnp hash:ip,port timeout 3
$ iptables -A OUTPUT -d 239.255.255.250/32 -p udp -m udp --dport 1900 -j SET --add-set upnp src,src --exist
$ iptables -A INPUT -p udp -m set --match-set upnp dst,dst -j ACCEPT
In IPv6 are UPnP packets sent to multicast address ff02::c or ff05::c. So rules would look like:
$ ipset create upnp6 hash:ip,port timeout 3 family inet6
$ ip6tables -A OUTPUT -d ff02::c/128 -p udp -m udp --dport 1900 -j SET --add-set upnp6 src,src --exist
$ ip6tables -A OUTPUT -d ff05::c/128 -p udp -m udp --dport 1900 -j SET --add-set upnp6 src,src --exist
$ ip6tables -A INPUT -p udp -m set --match-set upnp6 dst,dst -j ACCEPT
Some UPnP servers (but not all) periodically (e.g. every 30s) announce theirself via multicast UDP packet to well-known address/port. If you have such server and also client which is listening for these multicast packets, then iptables rule very is simple:
$ iptables -A INPUT -d 239.255.255.250/32 -p udp -m udp --dport 1900 -j ACCEPT
And equivalent for IPv6:
$ ip6tables -A INPUT -d ff02::c/128 -p udp -m udp --dport 1900 -j ACCEPT
$ ip6tables -A INPUT -d ff05::c/128 -p udp -m udp --dport 1900 -j ACCEPT
In your question you described something similar to above iptables rule, but you have did one big mistake: You specified source port, instead of destination: --sport 1900
. UPnP UDP packets are sent from random source ports to fixed destination port 1900.
You also described that upnp-inspector can detect your UPnP IGD router after you added port 1900 to trusted (probably both source and destination), but it was slower as disabling firewall. This perfectly matches above description of periodic announcement as upnp-inspector was waiting when your router send next announce packet.
Best Answer
You seem to be close to the answer. The easiest thing to do is to temporarily turn off the firewall let your media boxes run for a couple of minutes and then check the output from lsof
The -i lists "files" corresponding to an open port, use -i4 to restrict to IPv4 only. The number list restricts this to a list of port numbers - miss it off if you want everything. The +c bit just gives you more meaningfull command names associated with the ports
This lists all of the active ports along with their protocol and source/target address.
With this information, you can build a script to set ufw correctly. Here is my script by way of example:
You should be able to see from the Mediatomb section that uPNP is working on the standard port 1900 over UDP (not TCP) and is open in both directions, this is the main port for you. But you can also see that there are numerous other ports required for specific services.