Vicente Daie Pinilla

Vicente Daie Pinilla

🇨🇱 - Computer Engineer

Bypassing CGNAT with a cloud VM as a router

Self-hosting applications at home is a great way to have full control over your data and privacy. However, ISP services often have your home network behind a Carrier-Grade NAT (CGNAT), which changes the IP address of the home network, making it impossible to access the home server from the outside using the public IP address.

This guide will show you how to bypass CGNAT with a virtual machine at a cloud provider as a router, using Wireguard VPN.

Requirements

  • A virtual machine at a cloud provider
  • A home server
  • A domain name (optional, but recommended)

What is CGNAT?

Network Address Translation (NAT) is a technique used to allow multiple devices to share a single IP address on a network.

CGNAT

Carrier-Grade NAT (CGNAT) is a large-scale NAT implementation used by Internet Service Providers (ISPs) to conserve IPv4 addresses. With CGNAT, your ISP assigns a single public IP address to multiple customers, essentially putting your home network behind two layers of NAT:

  1. Your router's NAT (translating between your local devices and your router)
  2. The ISP's CGNAT (translating between your router and the internet)

CGNAT

This double-NAT situation creates significant problems for home users who want to:

  • Host websites or services from home
  • Access their home network remotely
  • Use peer-to-peer applications
  • Play certain online games that require direct connections

Traditional port forwarding doesn't work with CGNAT because you don't have a dedicated public IP address that you can configure. Your router may think it has a public IP, but it's actually a private IP within the ISP's network.

The increasing adoption of CGNAT is driven by the global shortage of IPv4 addresses. While IPv6 would solve this problem, its adoption has been slow, making workarounds like the one described in this guide necessary for many home users.

Using a virtual machine as a router with Wireguard

The solution to bypass CGNAT involves setting up a virtual machine (VM) in the cloud that acts as an intermediary router between the internet and your home network. Here's how it works:

  1. Cloud VM with Public IP: You set up a VM at a cloud provider that has a real, dedicated public IP address.

  2. Wireguard VPN Tunnel: You establish a secure VPN tunnel between your home server and the cloud VM using Wireguard, which is fast, modern, and secure.

  3. Port Forwarding: The cloud VM is configured to forward specific ports (like 80 for HTTP, 443 for HTTPS) to your home server through the VPN tunnel.

  4. NAT Rules: The VM uses Network Address Translation (NAT) rules to route traffic between the internet and your home network.

This setup effectively gives your home server a presence on the internet with a real public IP address (the one belonging to your cloud VM). When someone connects to your cloud VM's IP address or domain name, the traffic is securely tunneled to your home server.

The advantages of this approach include:

  • Bypass CGNAT: Your home services become accessible from anywhere on the internet
  • Security: All traffic is encrypted through the Wireguard VPN
  • Cost-effective: Small VMs are inexpensive (or even free with some providers)
  • Flexibility: You can expose only the specific ports you need

Let's see how to set this up step by step.

How to setup a VM as a router

The first step is to create a virtual machine at a cloud provider.

There are several options available, the best one being Oracle Cloud, because their free tier allows you to keep a VM running for free. The VM.Standard.E2.1.Micro shape, with 1 OCPU (2 vCPUs) and 1 GB of RAM, is more than enough for this purpose.

However, I'm going to use Digital Ocean, because I also need a DNS server for more than one domain, which is not possible with Oracle within the free tier, and I also feel comfortable with their interface and support. For this guide, I'm going to use a Droplet with 512 MB of RAM, 1 CPU core and 10 GB of storage.

After creating the VM, we need to generate a DNS record (A record) for the domain name in case of using one, pointing it to the public IP address of the VM.

1. First steps on the VM

After setting up the VM and connecting to it via SSH as the root user, the first thing to do is to update the packages and the system.

apt update apt upgrade -y

After that, we need to create a new user for the VM with sudo privileges.

adduser <username> usermod -aG sudo <username>

Then we switch to the new user and generate an SSH key for this user, if not done when creating the VM.

su - <username> ssh-keygen -t ed25519 -C "your_email@example.com" -f ~/.ssh/id_ed25519 -N "" cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys

We then retrieve the private key and save it in a safe place.

cat ~/.ssh/id_ed25519

After that, we need to disable password authentication, enable key-based authentication, disable root login and restart the SSH service.

sudo sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config sudo sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config sudo sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config sudo systemctl restart sshd

2. Preparing the home server

Before starting the Wireguard configuration, we need to change the home server's SSH port to a non-standard port, to avoid conflicts with the default port 22, which is the port that will be used to connect to the VM. This is necessary because we need to differentiate between the SSH connection to the VM and the SSH connection to the home server.

sudo sed -i 's/#Port 22/Port 9098/' /etc/ssh/sshd_config sudo systemctl restart sshd

We arbitrarily chose port 9098, but you can choose any other port that is not in use.

3. Installing and configuring Wireguard

Wireguard is a fast, secure and easy to configure VPN. We are going to use it to create a VPN between the VM and the local network, which we will use to redirect all the traffic to the VM.

The first step is to enable IP forwarding at the VM:

sudo sysctl -w net.ipv4.ip_forward=1

and then we need to make it persistent across reboots by adding the following line to the /etc/sysctl.conf file:

net.ipv4.ip_forward=1

After that, we need to install the Wireguard package, at both the VM and the home server.

sudo apt install -y wireguard wireguard-tools

Then, on both servers, we need to generate a private and public key for the Wireguard interface.

sudo su - wg genkey | tee /etc/wireguard/private.key > /dev/null wg pubkey < /etc/wireguard/private.key | tee /etc/wireguard/public.key > /dev/null

The private key is saved in the /etc/wireguard/private.key file and the public key is saved in the /etc/wireguard/public.key file.

Now, we need to create the Wireguard configuration file at both servers. The contents of the /etc/wireguard/wg0.conf file at the home server are the following:

[Interface] PrivateKey = <contents of /etc/wireguard/private.key> Address = 10.1.0.2/32 [Peer] PublicKey = <contents of /etc/wireguard/public.key of the VM> AllowedIPs = 0.0.0.0/0 Endpoint = <public IP of the VM>:55108

Similarly, the contents of the /etc/wireguard/wg0.conf file at the VM are the following:

[Interface] PrivateKey = <contents of /etc/wireguard/private.key> ListenPort = 55108 Address = 10.1.0.1/32 PreUp = iptables -t nat -A PREROUTING -d <public IP of the VM> -p tcp --match multiport --dport 80,443,9098 -j DNAT --to-destination 10.1.0.2 PreUp = iptables -t nat -A POSTROUTING -s 10.1.0.2/32 -j SNAT --to-source <public IP of the VM> PostDown = iptables -t nat -D PREROUTING -d <public IP of the VM> -p tcp --match multiport --dport 80,443,9098 -j DNAT --to-destination 10.1.0.2 PostDown = iptables -t nat -D POSTROUTING -s 10.1.0.2/32 -j SNAT --to-source <public IP of the VM> [Peer] PublicKey = <contents of /etc/wireguard/public.key of the home server> AllowedIPs = 10.1.0.2/32

Some breakdown of the configuration:

  • [Interface] refers to the machine that is using this configuration. We set the private key and the address of the Wireguard interface.

  • [Peer] refers to the machine that will be connected to this Wireguard interface. We set the public key and the allowed IPs.

  • PrivateKey and PublicKey are the private and public keys of the Wireguard interface that we generated in the previous steps.

  • Address is the IP address of the Wireguard interface. This is arbitrary, as long as it's not in the same subnet as the local network of the home server. Both servers must have the same subnet, in this case 10.1.0.0/24.

  • ListenPort is the port that Wireguard will listen on for incoming connections.

  • PreUp and PostDown are commands that are executed before the interface is brought up and after it is brought down, respectively. In this case, we're using iptables to set up NAT rules:

    • The PREROUTING rule redirects incoming traffic on ports 80, 443, and 9098 (our custom SSH port) to the home server (10.1.0.2).
    • The POSTROUTING rule ensures that traffic coming from the home server appears to come from the VM's public IP address when going out to the internet.
    • These rules are removed when the interface is brought down to keep the system clean.
  • AllowedIPs defines which traffic should be routed through the Wireguard tunnel. For the VM, we only want traffic destined for the home server (10.1.0.2/32). For the home server, we set it to 0.0.0.0/0 to route all traffic through the VM.

Finally, we can start the Wireguard interface on both servers and enable it to start automatically on boot:

sudo systemctl start wg-quick@wg0 sudo systemctl enable wg-quick@wg0

4. Testing the connection

We can try to access the home server from the outside using telnet:

telnet <public IP of the VM> 80

In case of having a domain name, you can also use it:

telnet <domain name> 80

Regarding SSH, we can use the domain name to connect to the VM:

ssh -i <path to the private key> <username>@<domain name or public IP of the VM>

And we can also use the domain name to connect to the home server, using the custom port we defined earlier:

ssh -i <path to the private key> <username>@<domain name or public IP of the VM> -p 9098

Be sure to protect the SSH connection to the home server, as it will be exposed to the public internet. Use a strong password or key-based authentication, and consider using fail2ban to prevent brute force attacks.

Conclusion

We now have a VM in the cloud that is acting as a router, allowing us to access our home network from anywhere using its public IP address, despite the carrier-grade NAT changing the IP of the home network. Self-hosted services at home can now be accessed from the outside world.

This setup provides a cost-effective way to bypass CGNAT restrictions while maintaining security through encrypted VPN tunnels. It's particularly useful for those who want to self-host services but are limited by their ISP's network configuration.

Credits to this blog post for the NAT and CGNAT diagrams.