Home Internet Away From Home
My friend’s family is split across America and Denmark, and they travel often. He presented me with a problem: “I’d like to have a connection in Denmark to my home internet, so I can access American Netflix and other services. And I want it to be simple enough that, if I leave it there, my non-technical father-in-law can use it.”
Getting your home internet when you’re away is a common task, and quite easy to do with a Raspberry Pi (or other device on your home network), and Wireguard (a modern, open-source VPN protocol that is easy to setup). You setup Wireguard on a device in your home network that is always on (we often use a Raspberry Pi for this since it’s cheap and low power), and then tell your laptop, phone, etc to connect to it – and then you’re done – all traffic goes through your home network.
But what my friend wanted was different – they wanted a hardware connection to their home. They wanted an ethernet jack in Denmark that they could plug a TV into and watch American shows, all without setting anything up on the device.
Also, as it turns out, quite doable with a Raspberry Pi and Wireguard. But how it works, and why we have to set it up the way we do, will take us through how networks work.
While I find the theory fascinating, I acknowledge some of you will want to just skip to the instructions. If that’s you, click here (but you’ll miss an awesome explanation on the differences between routers and switches).
Bridges?
If you know a bit about networks, this is the first thing that might have jumped into your mind. Your Wireguard VPN shows up on your computer as a virtual network device (sort of like a second wireless card). And your computer already has a function to create bridges (tie two networking devices together) - so shouldn’t this be as simple as “setup Wireguard, plug in the second ethernet dongle, bridge, and be done?
Not exactly. But to understand why, we need to discuss how bridging works, and more generally, what’s the difference between a switch, and a router.
Switches, and Levels: FYI
What actually happens when we use a bridge on our computer?
The computer creates a virtual network switch and plugs both of the network cards into it. Switches exist in the physical world too – you’ve likely seen one – a device with a row of ethernet jacks. But there’s something notable about switches: they don’t know, nor care, about IP addresses.
That’s really important – switches are “level 2” devices – they operate on ethernet frames (and ports). When you send data on an ethernet wire, it’s encoded into a frame, containing the hardware address (the MAC address) of the sending ethernet card and the receiving card. When that frame hits the switch it sees the destination hardware address, and looks in a table to figure out which port that machine is connected to. If it doesn’t know, it sends the frame out on every port. It also makes a note of the source address of your frame, and marks down which port it came from (so it knows your machine is attached to port 2, for example).
All this is to say, switches don’t understand anything other than ethernet frames. To put a switch between two things, they both need to speak ethernet. So… why doesn’t this work with our Wireguard device? Because it’s a TUN device.
TAP and TUN
When we talk about VPNs, we often talk about TAP and TUN adapters. Both are virtual network adapters, and often you, as the user, don’t care which gets used. But there’s a difference.
TAP devices operate on layer two – when you talk to a VPN over a TAP device, you’re sending full ethernet frames, with hardware addresses, through the pipe. TUN, in contrast, works at the IP level, so there is no ethernet information transmitted.
And thus, we can see our problem – Wireguard connections don’t speak ethernet. Putting a virtual switch between it and a physical network jack won’t work. So what we need is a device that operates at level 3 – the IP layer, and can route packets around.
This is a router.
Routers Route
We’ve discussed how switches are pretty simple devices – they simply get a hardware ID, look it a table, and send it to another port. Routers are different – they work with addresses and routes.
Let’s take a step back – when you type in a site, say Google.com - how does that connection work?
Well, first, you convert the name to an internet address, which is done
via the domain name system. DNS tells you Google.com is
142.250.217.110
. OK – now what?
You don’t connect to Google directly – you’re going to send a message, a
packet, to another machine (your router, most likely), that will send it
to another machine (somewhere in your internet service provider), that
will send it to another machine (perhaps some other inter-connect
provider that provides connections between ISPs), and so on and so on
until it hits Google. When Google responds, this entire chain happens in
reverse. You can see this by running traceroute google.com
(or tracert google.com
on Windows).
But how does this all work? How does your computer know where to send things?
Internally, your computer has a thing called a routing table. You can
see it yourself by typing route print
on a Windows command prompt,
or just route
on Linux/Mac. The computer uses this to figure out
“which network card should this be sent out on” and “what’s the next
hop.”
If your computer is on the same subnet as the destination (say you’re
talking to your printer on your home network), you don’t need your
router. If your computer knows your printer is located at
192.168.1.18
, it uses arp, or address resolution protocol, to find
the hardware address of your printer, and then it can send this out on
your ethernet card, directly to the device – any switches will process
it accordingly, and your router is never involved.
If you’re not on the same subnet, the system looks at the routing table,
and goes “do I have an entry for this address”. For example, maybe (like
me), you have multiple subnets in your house – and there’s a routing
table entry that says “if you want to talk to something in the
192.168.100.0
subnet, forward it to 192.168.1.16
.” For anything
bound for 192.168.100.x
, the next hop is 192.168.1.16
.
One entry in the routing table is the default route, which is the “if I don’t know where to send myself, send it here.” This is also referred to as the gateway, and in your home, it’s your router.
When your router receives a packet, it does the same thing your computer does when sending it – consults its own routing table, figures out if the packet is bound for something else in your house, or if its bound for something external, and if external it sends it out to your ISP.
So, going back to our VPN problem, what we need is a way to make our Raspberry Pi act as a router – it’s going to take in packets in Denmark, and forward them up through the VPN tunnel, instead of to the Denmark ISP. Thankfully, we can do that.
The Procedure
As a note, I’m going to focus on how we can modify the Pi, running in Denmark, to act as a router and forward packets to the Pi in America, instead of setting up Wireguard itself. If you have never setup Wireguard before, please go check out a Wireguard-focused tutorial (like this one, or this one), or the amazing article on the ArchWiki, then come back here!
Before we begin, you should have a working Wireguard setup at home, including:
-
A device in your home network that is running Wireguard, and properly configured to work as a VPN (this means you have a MASQUERADE or a SNAT command in your Wireguard config and you have enabled packet forwarding).
-
A home router, configured to forward inbound Wireguard traffic to the device running Wireguard, via Port Forwarding.
-
A Dynamic DNS name that points to your home IP (DuckDNS, No-IP, and Afraid.org all are free, and will provide you one).
You’ll also need a Raspberry Pi and a USB-Ethernet dongle (I used the TP-Link UE300), which will function as our Wireguard client in Denmark. Again, I’m going to gloss over the basic install and setup of Ubuntu Server and Wireguard on the Raspberry Pi, since I’d like to focus on how this setup differs from a “normal” Wireguard setup. But in brief:
-
Install Ubuntu LTS onto the Pi (we’re using it over Raspbian/Raspberry Pi OS since it will get several years of updates without requiring attention, and there are Wireguard packages readily available).
-
Make sure SSH is enabled (so you can login remotely if something goes wrong).
-
Install and enable unattended-upgrades, and set the device to automatically reboot when needed (ideally in the early morning).
-
Install Wireguard, and configure it as a VPN client, following the instructions in the ArchWiki. The critical bit is that the
AllowedIPs
entry is0.0.0.0/0, ::/0
. This will make our lives easy – Wireguard will automatically set the default route of the Pi to travel through the VPN tunnel! No routing work required for us. -
Set the new client to send keep alive packets every 25 seconds or so. Without this, you won’t be able to reliably SSH into your device in Denmark to provide help, since it won’t have a stable IP address and the router in Denmark isn’t forwarding the Wireguard port. You can do this by setting
PersistentKeepalive
in the wg0.conf file. -
Configure the Wireguard connection to start on boot (assuming you’re on Ubuntu, this is
systemctl enable wg-quick@wg0
, if your configured Wireguard tunnel iswg0
).
At this point, we should have a working Wireguard server on our home
network (that we can reach from the outside world), and we should have a
“client” Raspberry Pi can connect to our home network via Wireguard. If
all is going well, you should be able to login to the client Pi by using
ssh: ssh username@client_pi_wireguard_ip
from another device
connected to the Wireguard network.
But our goal isn’t to connect to the client. Our goal is for stuff connected to the client (a wireless access point, for example), to forward traffic to America over the Wireguard link.
So, before we’re ready – we do need to do a few more things. First, we’ll want a DHCP server to run on the Pi, so multiple devices can connect to the tunnel. We’ll also need to set a few things to allow the Pi to forward traffic to and from the dongle.
I’m making a few assumptions in this tutorial, including that both the Raspberry Pi in Denmark (our client), and the receiving machine in America, are behind routers, and are not directly connected to the internet. As a result, we’re not configuring firewalls on the Raspberry Pi in Denmark (because nothing should be able to directly connect to it from the internet).
If you are planning on having the Raspberry Pi in Demark (or wherever you are) directly connected to the internet, you’ll want to setup a firewall, but that’s outside the scope of this tutorial. Check out this IP tables tutorial for more information, but the jist is you’ll want to set your default policy to drop unsolicited packets from the ethernet jack that’s directly connected to the internet.
Setting Up Our Multiple Network Devices
Since we’re using Ubuntu Server, we’re going to be using netplan to configure the network – it comes pre-installed on Ubuntu.
To begin, login to the client device over SSH, and take a look at the
network interfaces by running ip addr
. You should see eth0
,
which is the built in Pi ethernet dongle, and eth1
which is the USB
ethernet adapter. We want to configure the system so eth0
, which
will be connected to the router in Denmark, gets an IP address
automatically, while the dongle gets a static IP address. Netplan can
help us with that.
Netplan stores its files under /etc/netplan/
, and by default the
pre-configured ubuntu plan is 50-cloud-init.yaml
. We can simply edit
that file so it reads:
network:
ethernets:
eth0:
dhcp4: true
optional: true
eth1:
addresses: [192.168.10.1/24]
optional: true
version: 2
This says to automatically get an IP address from the router on the built-in
ethernet port, but to use a static IP on the USB ethernet port. Once you’ve edited
this file, run sudo netplan apply
to make it take effect.
We’re now ready to make our Pi give out IP addresses and route them all to America.
Setting up DHCP
First, install our DHCP server on the pi with sudo apt install dnsmasq
. When you install,
you might get errors that it couldn’t start the dnsmasq service - don’t worry, this is normal.
We’re going to modify the setup used in this tutorial
to get a working DHCP server that hands out addresses in the 192.168.10.100-192.168.10.200
range.
That way they don’t conflict with the addresses given out by the router.
Create the file /etc/dnsmasq.d/dhcp.conf
and place within it:
# Set the interface on which dnsmasq operates.
# Set this to our USB interface
interface=eth1
# Enable DNS server so our DHCP clients can use this device for
# name resolution. We'll use cloudflare's DNS service here, but
# feel free to adjust.
port=53
server=1.1.1.1
# To enable dnsmasq's DHCP server functionality,
# we specify the address range, the subnet mask, and the
# DHCP lease length (12 hours)
dhcp-range=192.168.10.100,192.168.10.200,255.255.255.0,12h
# Set static IPs of other PCs if you wanted to assign them.
#dhcp-host=31:25:99:36:c2:bb,server-right,192.168.0.3,infinite # PC1
#dhcp-host=ac:97:0e:f2:6f:ab,yul-x230,192.168.0.13,infinite # PC2
# Set the Raspberry Pi as the gateway (default route) for DHCP
# clients. The following two lines are identical.
#dhcp-option=option:router,192.168.0.1
dhcp-option=3,192.168.10.1
# Tell DHCP clients to use this device as their DNS server.
dhcp-option=6,192.168.10.1
# Logging. Turn this on for debugging, but otherwise
# you'll likely want this off, both to avoid writing
# massive logs to the pi, but also for privacy reasons.
log-facility=/var/log/dnsmasq.log # logfile path.
log-async
#log-queries # log DNS queries.
#log-dhcp # log dhcp related messages.
Before we can apply this change we need to stop the built in DNS server within Ubuntu (so dnsmasq can do its job). To do that, run the following:
# Disable the built in stub listener via sed
# Alternatively, you can just edit the file, and set the DNSStubListener to no
sudo sed -r -i.orig 's/#?DNSStubListener=yes/DNSStubListener=no/g' /etc/systemd/resolved.conf
# Restart the system resolver
sudo systemctl restart systemd-resolved
# Bring up the DNS and DHCP server within dnsmasq
sudo systemctl start dnsmasq
If you now plug a computer into the the USB ethernet dongle connected to the Raspberry Pi, it should
now get an IP address in the 192.168.10.100-200
range, and it should have the DNS server set to
the Raspberry Pi. Neat.
But internet still isn’t going to work. This is normal.
Setting Up Forwarding (and IPTables Rules)
Before we can have internet work, we have to make two changes. First, we need to tell the Raspberry Pi to allow forwarding of packets through it (so it can handle traffic not for itself).
We can do that with this simple command: sudo sysctl -w net.ipv4.ip_forward=1
.
To make this persist through a reboot, run
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.d/90-allow-ipv4-forward.conf
Once that’s done, the system will allow traffic to move through it, but you’ll note internet still isn’t working.
This is because, if your computer connected to your Pi tries to go to a site,
the source IP address will be listed as 192.168.10.154
, for example. That
packet will be passed without modification to the other end of the Wireguard
tunnel, which will happily send it off to the next hop. But when it gets a
response, it will look at the address and go “I have no idea how to send
something to 192.168.10.154
- I only know about my own address and the
Wireguard address. Oh well. And your response is gone. What we need to do is
tell the Raspberry Pi “hey, when you forward this packet, use your own address
as the source, so the next item in the chain knows how to respond to it.
Thankfully, this is easy.
If we didn’t have Wireguard running, we’d run a single command:
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
, which says “for
any packet that is outbound on interface eth0
, make sure the source address is
me. When I get a response, I’ll keep track of if it’s actually for me, or for
something connected to me.”
But, since our traffic is bound for the Wireguard tunnel, we can can simply add
a similar command to our wg0.conf
Wireguard file:
[Interface]
... # other stuff in this section like your private key, etc
PostUp = iptables -t nat -A POSTROUTING -o %i -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o %i -j MASQUERADE
This says that, when Wireguard is connected, make a rule that all traffic bound for Wireguard should be masqueraded (have the source set to the Raspberry Pi).
Add this to your configuration, and restart the wireguard tunnel with sudo systemctl restart wg-quick@wg0
.
You should now have internet! Make sure Wireguard is set to start automatically with sudo systemctl enable wg-quick@wg0
Configuration Recap
Before we continue, we should just pause here and briefly cover what the Wireguard configs we’re using look like. Here’s what we have on the “client” machine in Denmark:
[Interface]
PrivateKey=[YOUR PRIVATE KEY HERE]
Address=192.168.2.20/24 # adjust to your Wireguard addresses
PostUp = iptables -t nat -A POSTROUTING -o %i -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o %i -j MASQUERADE
[Peer]
PublicKey=[PUBLIC KEY OF SERVER]
AllowedIPs=0.0.0.0/0
Endpoint=[DDNS ADDRESS OF YOUR DEVICE IN AMERICA]:[YOUR PORT NUMBER]
PersistentKeepalive = 25
And here’s the server in America:
[Interface]
Address = 192.168.2.1/24 # adjust for your network accordingly
ListenPort = 51623 # similarly, adjust as you'd like
PrivateKey = [SERVER PRIVATE KEY]
# post up/down can be specified multiple times to make it more readable
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
PublicKey=[PUBLIC KEY OF CLIENT]
AllowedIPs=192.168.2.20/32 # adjust based on what you set in your client config
At this point, we’re done, and you should be good to go! But there are to more things you may want to consider.
Rate limiting
This is optional, but since you’re sharing your home internet connection with your family, probably a good idea. Given how common data caps are these days, we don’t want your relatives streaming 4K Netflix over your home connection (it will slow your network to a crawl and potentially run up your bill).
To prevent this, we’re going to use a tool on called Wondershaper. Ideally, you should do this step on the server (so your technically savvy family members can’t adjust their bandwidth themselves), but you can also do it on the client.
On Ubuntu, you can install it with sudo apt install wondershaper
, and then the syntax is simply:
wondershaper [interface] [downlink speed] [uplink speed]
. So, on our Wireguard server,
we can simply adjust our PostUp lines to:
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT;
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;
PostUp = wondershaper %i 6000 6000
This would limit the connection to 6Mbps, which should still be enough to watch Netflix in high definition comfortably. That said, if you’re on a limited residential internet plan, where your upload bandwidth is very limited, you might want to go further and adjust the command to 3000 - which is 3Mbps (Netflix’s minimum for standard definition).
Policy routing and split tunneling
This is the most optional of optional things, and I’m mostly just including it for a reference, in case anyone is wondering how to do it.
Right now, we have all traffic from our Raspberry Pi going through the tunnel, including both traffic from our external ethernet adapter, and from the Pi itself. This means things like software updates for the Pi go through the tunnel. This isn’t a horrible thing, but perhaps you want to make it so only traffic connected to the USB ethernet jack goes through the tunnel, and everything else works normally, going to the Danish internet.
We can do that with Policy-Based Routing.
When we told Wireguard to use 0.0.0.0/0
for allowed IPs, it actually did
something behind the scenes. It updated the Raspberry Pi’s internal routing
table so everything goes through Wireguard. Normally, we’d want this in our VPN
setup. However, we can be a bit smarter here and split our traffic.
To start, let’s update our client configuration with two changes:
[Interface]
PrivateKey=[Client Private Key]
Address=192.168.2.20/24 # your client address here
PostUp = iptables -t nat -A POSTROUTING -o %i -j MASQUERADE; ip rule add iif eth1 table 1234
PostDown = iptables -t nat -D POSTROUTING -o %i -j MASQUERADE; ip rule delete iif eth1 table 1234
Table=1234
[Peer]
PublicKey=[PUBLIC KEY OF SERVER]
AllowedIPs=0.0.0.0/0
Endpoint=[DDNS ADDRESS OF YOUR DEVICE IN AMERICA]:[YOUR PORT NUMBER]
PersistentKeepalive = 25
We’ve updated our PostUp/Down rules, and added a line called Table
. So what
does this mean?
We are saying here to Wireguard: write the routing rules to routing table number
1234, instead of the default routes table. If you make these changes, and run
ip route show table 1234
, you’ll see a single line:
default dev wg0 scope link
- route everything on this table through Wireguard
wg0.
But if you do a traceroute google.com
from your Pi right now, you’ll notice
that traffic isn’t going through the Wireguard tunnel. That’s because we need to
tell it which traffic should use the alternate routing table. This is where
those ip rule
commands come in.
The rule commands say ‘for all traffic inbound from interface eth1, use the
alternate routing table 1234.’ You can again test this with our friend trace
route - if you connect a computer to the ethernet dongle, and run
traceroute google.com
(or tracert google.com
on Windows), you’ll see traffic
goes though the Wireguard tunnel. But if you run the command from the Pi itself,
you’ll see it directly goes to the local network router, and off to the Danish
internet.
However, there’s a catch. If you do this, DNS queries will still go through the Raspberry Pi to the Danish Internet, rather than being forwarded through the tunnel. In some ways, this is ideal if you’re not trying to hide your browsing traffic. Not routing DNS through the tunnel will make it faster.
But, if you want DNS to go through the tunnel (and, if you’re trying to use a
video streaming service, you may have to), you have two options. Option 1 is to
modify your /etc/dnsmasq.d/dhcp.conf
file, and change the DNS server
advertised to DHCP clients (dhcp-option=6,1.1.1.1
). This will tell devices
that connect to the USB dongle to talk directly to the Cloudflare DNS server,
rather than to the Raspberry Pi’s DNS server. That traffic will go through the
tunnel, since it’s traffic from the dongle to somewhere else (not the
Raspberry Pi itself).
The second option is to use another rule to forward all DNS traffic through
the tunnel (including queries from the Pi itself). We can do that with
ip rule add to 1.1.1.1 table 1234
(forward all traffic for 1.1.1.1
via the
routing table that says to shove everything in the tunnel). This can be added as
another PostUp rule (you’ll need a matching PostDown rule to go with it).
But remember - if you’re not on a metered connection, you don’t have to do any of this - just have all traffic go through the tunnel and ignore split-tunneling.
Using the Raspberry Pi as the Wifi Access Point (Extra Credit)
Fun fact: you can actually use the Raspberry Pi 3 or 4 as a wireless access point. If we were to do that - we wouldn’t need any extra hardware! That said, the Pi isn’t ideal for this - it doesn’t have the big antennas more common to a purpose-built access point, but… if you only need your tunneled-wifi in a single room, or if you’re significantly limiting the speed at the other end of the pipe, then this could work for you.
I am not going to cover this in detail, but you can find the official
documentation for this
here.
And you’ve already done most of this work in this tutorial! You’ve already
installed a DHCP server, and enabled forwarding. You’ll just need to tweak your
configuration so that, instead of assigning a static IP address on eth1
and
running the DHCP server on that interface, you have a static address on wlan0
and have the DHCP server run there.
Also, a small gotcha: the Raspberry Pi docs assume you’re running
Raspbian/Raspberry Pi OS (not Ubuntu). So you’ll want to set your static IP
address via netplan
(as we did earlier), rather than dhcpcd.conf
.
Flags used in the Diagram were from Wikimedia Commons