Skip to content

Instantly share code, notes, and snippets.

@insdavm
Last active February 22, 2024 15:33
Show Gist options
  • Save insdavm/b1034635ab23b8839bf957aa406b5e39 to your computer and use it in GitHub Desktop.
Save insdavm/b1034635ab23b8839bf957aa406b5e39 to your computer and use it in GitHub Desktop.
Accessing a subnet that is behind a WireGuard client using a site-to-site setup

WireGuard Site-to-Site

Accessing a subnet that is behind a WireGuard client using a site-to-site setup

Problem Summary

We want to access a local subnet remotely, but it is behind a NAT firewall and we can't setup port forwarding. Outgoing connections work, but all incoming connections get DROPPED by the ISP's routing policy.

Solution Summary

We'll create a site-to-site connection with WireGuard allowing us to access the local subnet on a remote device (smartphone, in this example) by connecting through a cloud server in the middle.

Working Example

First let's define our three hosts. They all have WireGuard installed.

A the Linux machine on the local subnet, behind the NAT/firewall
B the Linux cloud server (VPS, like an Amazon EC2 instance)
C a third WireGuard client; a smartphone in this example

Host 'A'

The Host A's /etc/wireguard/wg0-client.conf:

[Interface]
Address = 10.200.200.5/24                  
PrivateKey = <HOST 'A' PRIVATE-KEY>
ListenPort = 27836                         # optional; will be randomly assigned otherwise
DNS = 1.1.1.1                              # or your own DNS server if you're running one    

[Peer]
PublicKey = <PUBLIC KEY OF HOST 'B'>
Endpoint = host-b-fqdn.tld:51820
AllowedIPs = 0.0.0.0/0, ::/0

PersistentKeepalive = 25                   # to keep connections alive across NAT

Here's what we need to add to Host A's iptables rules, expressed as the commands you would use to ADD them:

# iptables -A FORWARD -i wg0-client -j ACCEPT
# iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

Finally, we need to make sure IP forwarding is enabled in Host A's kernel:

$ sysctl net.ipv4.ip_forward=1

Host 'B'

Host B's /etc/wireguard/wg0.conf:

[Interface]
Address = 10.200.200.1/24
PrivateKey = <HOST 'B' PRIVATE KEY>
ListenPort = 51820

PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o ens3 -j MASQUERADE


# This is the peer that is on the private subnet that we want to access.
#
# Notice the AllowedIPs... without this part, WireGuard will drop the 
# packets destined for the HOST 'A' subnet.  AllowedIPs is acting like
# a routing table and ACL here.

[Peer]
PublicKey = <HOST 'A' PUBLIC KEY>
AllowedIPs = 10.200.200.5/32, 100.10.202.0/24

# The smartphone
[Peer]
PublicKey = <HOST 'C' PUBLIC KEY>
AllowedIPs = 10.200.200.3/32

# An additional peer...
[Peer]
PublicKey = <Additional peer pubkey>
AllowedIPs = 10.200.200.4/32

Like we did with Host A, IP forwarding must also be enabled on Host B:

$ sysctl net.ipv4.ip_forward=1

Host C

Host C's configuration file:

[Interface]
PrivateKey = <HOST 'C' PRIVATE KEY>
Address = 10.200.200.3/24
DNS = 1.1.1.1


[Peer]
PublicKey = <HOST 'B' PUBLIC KEY>
AllowedIPs = 0.0.0.0/0
Endpoint = host-b-fqdn.tld:51820
PersistentKeepalive = 25

You're finished.
Make sure WireGuard is running on both HOSTS A and B, and then on the smartphone (HOST C), after connecting to HOST B with WireGuard you should be able to ping 10.200.200.5.

@insdavm
Copy link
Author

insdavm commented Jan 6, 2022 via email

@goblin
Copy link

goblin commented Jan 6, 2022

The point is not to create a one-sizs-fits-all solution, but to demonstrate how a private subnet that sits behind a NAT can be accessed when port forwarding is not an option.

If given a solution that works, and a solution that works and is both faster (because Host B doesn't have to decrypt and re-encrypt all traffic between A and C) and more secure, people should generally opt for the latter.

You can demonstrate the exact same thing (how a private subnet that sits behind a NAT can be accessed when port forwarding is not an option) without introducing a potential security issue.

You are assuming Host B is a rented cloud server.

Well, it was you who originally assumed it.

if your concern is Host B's security, then just make Host B something you own and control

Not quite, my concern is the security of traffic going from A to C and from C to A. The whole purpose of this set up was to connect host A to host C, not to enable host B to spy on their traffic. So it should also be your concern.

Even if Host B was something I owned and controlled, there is still no need for it to be able to read or re-encrypt traffic between A and C.

For me, the intent is to encrypt the connection between A and C, and work around the inability to port-forward through the NAT router.

Exactly my point. You wanted to encrypt the connection between A and C, but instead you encrypted connection between A and B and between B and C - therefore, you didn't achieve your intent.

This doesn't just work around the inability to port-forward through the NAT router, it introduces a security problem along the way.

The hardening of my cloud instance is another matter and outside the scope of this example.

I didn't talk about hardening of your cloud instance, I talked about properly securing the connection between A and C.

@insdavm
Copy link
Author

insdavm commented Jan 6, 2022 via email

@goblin
Copy link

goblin commented Jan 7, 2022

I don't yet have a straightforward solution. It appears you either need two instances of WireGuard, some other tunnel (like GRE or IPIP or sth), or a more full-fledged option like ZeroTier, Nebula or Tailscale.

@posta246
Copy link

posta246 commented Jan 9, 2022

Hi everybody,
I'm getting crazy, it's two days I'm making changes and reading things, I almost wanna cry.
I've used these configs several time, but now I cannot replicate it on a debian machine.
If In the debian node A config file I added into the allowedIPs the subnet 172.16.1.0/24 to reach it, when I start the interface I obtain:
RTNETLINK answers: File exists
I have seen the ip route table and there is a route to this subnet:
172.16.1.0/24 dev enp2s0 proto kernel scope link src 172.16.1.127
But how can remote? and moreover, Should I? I tried some commands around but it seems that if I del it, the network stopped to work and I have to restart the machine.

This is the full file:

[Interface]
PrivateKey = 
Address = 10.0.10.2/32
ListenPort = 55610

PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o enp2s0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o enp2s0 -j MASQUERADE

# VPS
[Peer]
PublicKey = 
PresharedKey = 
AllowedIPs = 10.0.10.0/24,172.16.1.0/24
PersistentKeepalive = 10
Endpoint = 1.2.3.4:55610

@gliepins
Copy link

Hi guys!
I am trying to accomplish almost the same, except that Instead of accessing the home network, as per this solution, I want to "exit" the mobile wg-client's internet traffic through "home" device A!
The mobile unit C must be using the A internet connection and have, of course, the external IP accordingly.
Any ideas?

@micheljarre
Copy link

Hi,

thank you for these instructions. I am using this setup to get along with my IPV6 dual stack lite internet connection. So Host A is a debian server in my home network, Host B a Cloud Server and C is any Wireguard client. My only problem is that the upload and download performance (data from and to host A and the home network) is quite poor. The internet connection should make 400 MBit upload as well as the Cloud Server, but the real performance is much, much below (about 18 MBit).

Any idea/suggestion to improve that (and to measure the improvement)?

Thank you!

@micheljarre
Copy link

Just in addition: Using an iperf3 client and server between the cloud server and a local server in my home network results in about 380 Mbits/sec. I am still wondering why the speed to other Wireguard clients is so low.

@leoguiders
Copy link

Took me a while to figure out I need to add

iptables -A FORWARD -i eth0 -j ACCEPT

on the client in order to access resources in the client network from the server....

@Spookdot
Copy link

Is there a way to also use this with firewalld and what rules would I need to set? Using firewalld on my VPS because it's the only firewall I remotely understand how to manage

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment