I want my OpenVPN server to listen on multiple ports, e.g. port 3000 to 4000, for some reason. I searched for a solution and the answer I got was basically “just run 1001 instances”.

It’s not practical obviously. But from my understanding of Internet Protocol, it should be doable with IP firewalls such as iptables. After consulting someone know well about server maintenance, I was able to address the problem with a single command:

# iptables -t nat -A PREROUTING -d <server-ip> -i <wan-interface> -p udp -m udp --dport 30000:31000 -j DNAT --to-destination <server-ip>:<the-port-openvpn-is-listening-on>

The command assumes your OpenVPN server accepts UDP packets. server-ip is your server’s public IP address, which OpenVPN server instance should be listening on. wan-interface is the interface which has server-ip. 30000:31000 are additional ports you want your OpenVPN server to listen on. the-port-openvpn-is-listening-on is the port which your OpenVPN server instance is actually listening on.

How does it work?

A UDP/IP packet conveys the following information:

  • Source Address
  • Source Port
  • Destination Address
  • Destination Port

Let’s say, your server’s IP address is 5.6.7.8 and OpenVPN is listening on port 1194. With the iptables rule above — which is a Destination Address Translation rule — applied, all UDP/IP packets with Destination Address of 5.6.7.8 and Destination Port 30000 to 31000 will be modified so that it has the Destination Address 5.6.7.8 but the Destination Port 1194. Then OpenVPN server process can receive the packets.

What about the returning packets? Let’s assume your client — no matter how it’s situated behind NAT gateways — eventually have the public IP address 1.2.3.4 and use port 56677 to send UDP packets, whose parameters are:

  • Source Address: 1.2.3.4
  • Source Port: 56677
  • Destination Address: 5.6.7.8
  • Destination Port: 30234

The UDP packets are then forwarded to 5.6.7.8:1194 by server’s Linux kernel according to the iptables rule. When OpenVPN replies UDP/IP packets, their parameters are:

  • Source Address: 5.6.7.8
  • Source Port: 1194
  • Destination Address: 1.2.3.4
  • Destination Port: 56677

If returning packets are unchanged, they would be discarded by the client or cause errors, because they are expected to have the source port of 30234, the same as the destination port of incoming packets. However, the server’s Linux kernel will bear in mind that 1.2.3.4:56677 was sending UDP packets — whose destination is changed by the kernel — to server’s port 30234. So the returning packets’ source port will be changed to 30234 to “revert” changes made to incoming packets.

Why the rule is in PREROUTING chain? Well, it’s beyond my knowledge at the time of writing. But I believe the explanation above is enough.