Getting OpenVPN to run on random ports

As I mentioned in a previous post, I have a friend who’s heading to China. I have an OpenVPN server. I thought the two would match together well, but then China went and started to filter & kill OpenVPN connections, and block those IP/port combinations. People are reporting that using a random port (as supported by their VPN provider) seems to work, and so I looked into randomizing what port OpenVPN ran on.

The trick that I found was not to make OpenVPN run on multiple ports, but instead to get iptables to forward any and all connections coming into a range of ports to the OpenVPN port. However, since I wanted OpenVPN to be running on both UDP and TCP ports, I had to start another instance, which necessitated more iptables config changes.

First off was getting iptables to forward a port range to the OpenVPN port. To do that, I used Destination NATing, or DNAT:

iptables -t nat -A PREROUTING -p udp --match multiport --dport 10000:40000 -j DNAT --to
iptables -t nat -A PREROUTING -p tcp --match multiport --dport 10000:40000 -j DNAT --to

I’m forwarding both UDP and TCP ports because I’ll have two instances of OpenVPN up and running, one listening on UDP and the other listening on TCP. (As a side note, mangling the output isn’t needed because we’re just changing the destination of the packet – to OpenVPN, it’ll appear like the client is connecting directly to it, so it’ll just send the response back to the client’s port.)

Now, if I was just leaving UDP active, that would be enough, since my existing OpenVPN config works fine over UDP. However, additional configuration is needed to get OpenVPN working on TCP. In particular we need to:

  • Configure the TCP instance of OpenVPN not to clobber the UDP instance’s history & log files
  • Configure OpenVPN to use a new subnet
  • Configure iptables to properly forward packets from the new subnet
  • Configure iptables to accept connections to the TCP port OpenVPN will run on

Thankfully, most of it is straightforward. I’m going to go for simplicity and just include the entire OpenVPN server-side config file below:

port 1194
proto tcp
dev tun
ca keys/ca.crt
cert keys/server.crt
key keys/server.key
dh keys/dh1024.pem
ifconfig-pool-persist ipptcp.txt
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS"
push "dhcp-option DNS"
keepalive 5 30
status tcp-server-tcp.log
verb 3
log /var/log/openvpn-tcp.log
tls-auth keys/ta.key 0
link-mtu 1400

The key changes here are to the log, ifconfig-pool-persist and status options – I’ve just appended -tcp to any filenames. I’ve also declared a new subnet – for the TCP instance, so it doesn’t conflict with the UDP instance, as well as changing the protocol to tcp. (Despite people saying otherwise, having proto tcp on the server seems to work.)

I’ve also changed the link-mtu option simply to make it harder for pure protocol detection to detect the packets as OpenVPN packets.

As for the client side,

 dev tun
 proto tcp-client
 remote 1194
 resolv-retry infinite
 ca ca.crt
 cert client1.crt
 key client1.key
 tls-auth ta.key 1
 ns-cert-type server
 verb 3
 keepalive 10 120
 route-method exe
 route-delay 2
 link-mtu 1400

Again, the only thing really changed was the proto option – from udp to tcp-client. I’ve also set the link-mtu option to something other than the default, as well as added the remote-random option – this will be explained in a bit.

On the iptables side of things, I ran a few commands:

# Allow packets from the new subnet to make it out to the Internet
 iptables -A FORWARD -s -j ACCEPT
 # Change the source address on outgoing packets from the new subnet to be the VPS's IP address
 iptables -t nat -A POSTROUTING -s -j SNAT --to-source
 # Accept incoming packets on the TCP port 1194 - change this to your actual OpenVPN port
 iptables -A INPUT -p tcp --dport 1194 -j ACCEPT

Combined together, between the two instances, OpenVPN now accepts connections made to any port between 10000 and 40000 (inclusive).

The final step was making the OpenVPN client connect to random ports. To do this, we’re going to make use of connection profiles – essentially, just declaring multiple combinations of IP addresses and ports that you want OpenVPN to connect to. When combined with the remote-random option in the client side config file, OpenVPN should randomly go through the list and connect to a random port. To simplify matters, I turned to Python to generate the 64 random IP/port combinations to paste into the config file:

for i in range(64):
 print "remote "+str(random.randrange(10000,40000))

( I only printed 64 IP/port combinations, because that’s the maximum the OpenVPN client supports.)

I’m hoping that’s enough. But if it isn’t, I’ll also be looking at running OpenVPN through stunnel, or using SSH as a SOCKS proxy. Also, IPSEC.

Fun times.

  1. #1 by [email protected] on December 31, 2012 - 9:22 am

    I just had the exactly same idea and applied to my openvpn server. you beat me to it, lol

    Im currently in China now. they seem really paying a lot of attention on openvpn instead of ipsec/l2tp and pptp vpn.I have my own vpn server, i was using udp port 53 for openvpn, they blocked it after 2 days(not sure automatically or human). works after change to a different port. Its very interesting how they block udp, the wall sent a fake ICMP port unreachable packet right to the client right after client started TLS negotiation. so the client restarted the tls negotiation then end up in a infinite loop.

    as for openvpn over tcp, they are still using tcp rst packets.
    I have heared using preshared static key instead certificate could avoid detection since c/s start negotiation using psk under encryption.

    drop ICMP type3 code3 packetes on client may also work.

  2. #2 by Kyle Lexmond on December 31, 2012 - 12:01 pm

    I think they’re focusing a lot on OpenVPN because it’s what most VPN providers use. Until IPsec becomes easy to use, they’ll probably focus the more popular VPN types.

    As for the port information, that’s interesting… do you know if OpenVPN will jump to a new connection profile if it gets a port unreachable packet, or will it continue to retry that connection? The basis behind the bunch of connection profiles is that OpenVPN will jump to a new port if it can’t connect, but I’m not sure if it’ll just keep retrying…

    I’m going to have to try this and look at OpenVPN’s behaviour, both with UDP and TCP.

    And thanks for the PSK tip – I didn’t think about alternate authentication methods as a way of escaping the protocol detection.

    As for the dropping ICMP packets, that’s an interesting idea. I’ll have to look into using that on OS X.

  3. #4 by Kyle Lexmond on January 2, 2013 - 2:24 pm

    Not sure if it works, but I’m looking into getting it setup and tested.

  4. #5 by [email protected] on January 3, 2013 - 11:18 pm

    john :

    [email protected] :
    I have heared using preshared static key instead certificate could avoid detection since c/s start negotiation using psk under encryption

    anyone try this ? if it works
    any guide to change server and client scripts
    thanks /John

    I just tried it, not sure if it would avoid detection, but under psk mode, each openvpn instance only support one client, one only. So I guess it is not a very good idea.

    best solutions so far:
    push openvpn community to update the source code, put tls handshake under encryption using tls auth key(ta.key). or change the protocol to make it appear random or at least no significant statistical trace.

    temporary solutions:
    tunnel openvpn in http(native support in openvpn) using http proxy
    tunnel in stunnel,ssh,ipsec,etc…(not very good since GFW recently started to block ssh port forwarding. stunnel was on the list at least a few years ago.)
    tunnel in icmp, dns tunnels, this one is very interesting, i believe currently gfw has no way to block icmp tunnel unless block the ip address.

    currently im using the random port method, its getting popular, but some people reported their server ip address getting blocked after using this method…

(will not be published)