Setting up a VPN Gateway
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
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
- There is a mutual connection to the OpenVPN server on the droplet.
- 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 SYN
s (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
- 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.
- Modified this tunnel to use a special
10.64.0.0/24
subnet. - 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.
- Lans behind OpenVPN
- Expanding the scope of the VPN to include additional machines on either the client or server subnet.
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.
-
Really, I would just call this what it is, a VPS, but VPS and VPN is a bit confusing to read together… ↩︎
-
If you want some more details, check out https://serverfault.com/a/738558. ↩︎
-
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. ↩︎ -
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 aSYN
, and[S.]
indicates aSYN-ACK
↩︎