Setting up a VPN Gateway

Posted on Oct 21, 2019

Beginnings

At the start of the year, I was placed in a new living situation provided by the company I was interning with at the time. Everything was great, but I soon found out that the router was locked behind the ISP’s SSO, meaning I couldn’t edit any of the router settings. I wanted to be able to ssh into my desktop remotely, but being unable to port forward, I was out of luck. After poking around a bit, I realized that setting up an OpenVPN gateway may be just what I needed. If I could access my entire LAN from this gateway, I would never need to forward another port!

From some research, it seemed like this would be a simple matter of setting up OpenVPN with client-to-client enabled and a tad bit of routing. This is where the biggest snag came in: my router’s subnet was 192.168.1.0/24. Given that this is a super common subnet, it meant that I wouldn’t be able to access items on my LAN while also on someone else’s network. Unable to change my router settings, I realized I needed to get a bit craftier.

The Setup

When I say an “OpenVPN gateway,” what I really mean is that I have a box on my LAN that will maintain a persistent VPN connection to an externally accessible box. I can then connect to the externally accessible box and access my entire LAN. Given my inability to port forward, this seemed ideal. I used as NAS on my LAN as a “gateway,” and then set up an OpenVPN server on my DigitalOcean droplet.1

graph TB; subgraph LAN LANEtc[Other LAN'd machines] subgraph NAS NASClient[OpenVPN Client] end end subgraph Droplet NASClient --> DropletServer[OpenVPN Server] end subgraph Laptop LaptopClient[OpenVPN Client] LaptopClient --> DropletServer LaptopClient -.->|via tunnel| LANEtc end

Why would this work? Well, all OpenVPN really does (aside from encrypting traffic) is provide a network interface to tunnel traffic through. If we can somehow expose the clients to each other on this interface, we’re in the clear! In the above diagram, my laptop will be able to access anything on the LAN, thanks to the fact that

  1. There is a mutual connection to the OpenVPN server on the droplet.
  2. The NAS hosting the OpenVPN interface for us, and routing traffic as needed.

Configuration Magic: client-to-client and routing

Backing up slightly, what is client-to-client? In short, it allows any two clients connected to the VPN to talk to each other. In long, it really short-circuits the routing tables on the hosting VPN server, and just forwards all traffic to the appropriate client immediately.2 While this isn’t strictly necessary, it prevents us from having to tear our hair out by poking at the iptables rules of the OpenVPN server.

With that out of the way, this blob of configuration is where most of the magic happens.

openvpn.conf

push "route 192.168.1.0 255.255.255.0"
route 192.168.1.0 255.255.255.0
client-to-client
client-config-dir ccd

ccd/nas3

iroute 192.168.1.0 255.255.255.0

Remember how I said client-to-client wasn’t strictly necessary? Well, it comes in handy here. What we do is we define 192.168.1.0/24 (my LAN’s subnet) as internally routable (note the “i” in iroute) to the NAS. We also tell both the client (using the push "route ..." directive) and server (using the route directive) that 192.168.1.0/24 should be routed through OpenVPN. The NAS will then forward the traffic to the correct destination.

After firing up OpenVPN on my laptop…

[~] nick@bender$ ssh 192.168.1.176
Last login: Mon Oct 21 00:50:28 2019 from 192.168.1.31
Have a lot of fun...
[~] nick@cubert$

Great! We’re done! Right? Not so fast… The careful reader will notice something problematic here. 192.168.1.0/24 is used on a large number of consumer routers. In our configuration, we marked 192.168.1.0/24 as routable through our tunnel, so if I’m connected to another network with that same subnet, I will be unable to access what’s on the LAN while I’m connected to my VPN. No good!

NAT’ing The Problem Away

This step is really the crux of the project. If I could somehow modify my subnet to be something more unique, then I would be home free. Again, I was unable to modify my router’s settings, so I had to hack my way around the problem.

What I decided to do was to setup a basic one-to-one NAT. In short, if any traffic went to 10.64.0.xxx, it should get mapped to 192.168.1.xxx on my LAN. We can take what we learned in the last section and modify our OpenVPN config to hold this new subnet.

openvpn.conf

push "route 10.64.0.0 255.255.255.0"
route 10.64.0.0 255.255.255.0
client-to-client
client-config-dir ccd

ccd/nas

iroute 10.64.0.0 255.255.255.0

Great, so we defined a new subnet to route, but where is this traffic going to go? At the moment, the NAS will get this traffic (we defined the traffic to internally route to it in ccd/nas), but … then what? Nothing, actually. The traffic will get totally dropped on the floor, and that’s not what we want. With this in mind, we can add the following iptables rule on the NAS.

iptables -t nat -A PREROUTING -d 10.64.0.0/24 -j NETMAP --to 192.168.1.0/24

For those not intimately familiar with iptables, this probably looks like some kind of wizardry, and in some sense, it is. What we are doing is telling iptables to map all traffic on 10.64.0.0/24 to the corresponding address on 192.168.1.0/24. The important bit here is that this happens before any routing decisions are made (hence the PRE in PREROUTING. Clever, eh?).

This, alone, however, does not work. It is true that the traffic will reach the destination, but the destination will have no idea how to return the traffic! Take a look at this packet trace from when I attempted to ssh into my desktop over the tunnel.

[~] nick@cubert$ sudo tcpdump -i enp0s31f6 'port 22'
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp0s31f6, link-type EN10MB (Ethernet), capture size 262144 bytes
22:59:22.699195 IP 192.168.255.134.45502 > 192.168.1.176.ssh: Flags [S], seq 4205049038, win 64240, options [mss 1358,sackOK,TS val 853705188 ecr 0,nop,wscale 7], length 0
22:59:22.699254 IP 192.168.1.176.ssh > 192.168.255.134.45502: Flags [S.], seq 4018563165, ack 4205049039, win 65160, options [mss 1460,sackOK,TS val 3571896294 ecr 853705188,nop,wscale 7], length 0
22:59:23.704863 IP 192.168.1.176.ssh > 192.168.255.134.45502: Flags [S.], seq 4018563165, ack 4205049039, win 65160, options [mss 1460,sackOK,TS val 3571897300 ecr 853705188,nop,wscale 7], length 0
22:59:23.718678 IP 192.168.255.134.45502 > 192.168.1.176.ssh: Flags [S], seq 4205049038, win 64240, options [mss 1358,sackOK,TS val 853706204 ecr 0,nop,wscale 7], length 0
22:59:23.718688 IP 192.168.1.176.ssh > 192.168.255.134.45502: Flags [S.], seq 4018563165, ack 4205049039, win 65160, options [mss 1460,sackOK,TS val 3571897314 ecr 853705188,nop,wscale 7], length 0
...

This goes on for a while. You’ll note that despite us receiving SYN packets from 192.168.255.134, and sending SYN-ACK packets back, we continue to receive SYNs (and more importantly, no ACK), indicating that our SYN-ACK was not received.4

For context, 192.168.1.176 is my desktop. What is this wacky 192.168.255.134 address, then? That’s actually my laptop’s IP on the VPN. My desktop is getting a packet with a source of 192.168.255.134, and trying to reply to it, but because my desktop has no idea that the VPN exists (remember, all the traffic is coming from the NAS and forwarding it the LAN), this traffic is not going anywhere. We need one more trick to make this work like we want. On the NAS, we can add the following iptables rule.

iptables -t nat -A POSTROUTING -d 192.168.1.0/24 -j MASQUERADE

What this does is, after routing (note the POST in POSTROUTING), any traffic to the 192.168.1.0/24 subnet will be “masqueraded.” What this means is that the source of the packet will be rewritten to be the address of the NAS. When the NAS gets a reply packet from the destination, the kernel will look at the port number of the received packet, and will be smart enough to route this packet back to where it originally came from: in this case, my laptop on the VPN.

And with that, we have our tunnel!

[~] nick@bender$ ssh 10.64.0.176
Last login: Mon Oct 21 01:39:13 2019 from 192.168.1.32
Have a lot of fun...
[~] nick@cubert$

Wrapping it Up

So, I just threw a lot of terminology and configuration in here all at once. To sum up what we did, we

  1. Created a tunnel that would allow us to access our LAN, by simply pushing routes to the OpenVPN client.
  • Because my apartment used a 192.168.1.0/24 subnet, this was inconvenient as I couldn’t access items on a LAN that used the same subnet.
  1. Modified this tunnel to use a special 10.64.0.0/24 subnet.
  2. Used a box on our LAN to route this traffic to the appropriate destination.

This is only scratching the surface of what can be done with OpenVPN. If you want to do some more reading on what can be done, I highly recommend you check out some of the OpenVPN docs. They’re very thorough, though admittedly slightly overwhelming. Here are a couple of the pages I found helpful.

I make no claims of being an expert on networking or OpenVPN, but this project taught me a lot about both. I moved out of the apartment with this router, so I don’t technically need this anymore. With that said, I still use it daily, as it means I don’t have to worry about any kind of port forwarding on my router, and I don’t have to expose my ssh to the world.


  1. Really, I would just call this what it is, a VPS, but VPS and VPN is a bit confusing to read together… ↩︎

  2. If you want some more details, check out https://serverfault.com/a/738558↩︎

  3. If you don’t have a ccd directory, it should go in the same directory as your openvpn.conf. This is where we will define any client specific configuration directives. ↩︎

  4. If you can’t read this output, I’d recomend you take a look at the man page for tcpdump. To quote the man page, “[flags] are some combination of S (SYN), F (FIN), P (PUSH), R (RST), U (URG), W (ECN CWR), E (ECN-Echo) or .' (ACK), or none’ if no flags are set.” Thus, [S] indicates a SYN, and [S.] indicates a SYN-ACK ↩︎