27 December 2024

Already a few years ago, I wanted to make my NAS reachable from the internet so that I can host a Nextcloud on my very own hardware, so I don’t have to move my personal data to an (untrusted) machine. There is only one issue: My home network is only reachable via IPv6, but there are so many networks outside that are IPv4 only (e.g. 2 out of 3 mobile networks in Germany). One option would be to pay my ISP to give me an IPv4 address, but this was something like 5€ extra per month, and then I’d still have to fight downtimes due to daily changing IPv4 addresses.

Simple Proxy

After a little research, I found a cheaper solution I’d like to share here: Use a cheap VPS as an IPv4 to IPv6 proxy.

Disclaimer: I don’t run this exact setup myself anymore, but I’m pretty sure that it still works.

A VPS is a cheap virtual machine in a data center you can easily configure online. The cheapest configuration should be sufficient, as long as there is a static IPv4 included. At least a few years ago, this was often available for around 3€/month.

So this is what the setup will look like:

The IPv4 to IPv6 proxy setup

The figure already reveals that all software we need to run on the VPS is socat. Socat is a stock-standard command line utility that can be installed on virtually any UNIX like operating system and is incredibly versatile.

To forward any traffic on a certain port to a different host, we can use the following command:

socat TCP4-LISTEN:80,fork,su=nobody,reuseaddr "TCP6:[1234:ab12::4321]:80"

So let’s decipher this a little bit:

  • TCP4-LISTEN:80: socat should listen to any traffic on port 80
  • fork: create a new process on every new connection. This is necessary to support multiple connections simultaneously.
  • su=nobody: Not 100% sure, but I think this changes the newly created processes to the unprivileged nobody user, reducing the risk of security issues.
  • reuseaddr: With this option, socat does not bind to port 80 exclusively. This is very helpful when restarting the script, as after killing a program using a socket, it takes several seconds, until it is available again.
  • "TCP6:[1234:ab12:0:...:4321]:80": This tells socat to forward any traffic to the specified IPv6 address (e.g., 1234:ab12::4321) on port 80.

A script to rule it all

Ok, fairly simple, and for testing purposes it is sufficient to run the above command in a terminal on the VPS, but to automate this, I’ve written the following script:

#! /bin/bash

UPDATE_INTERVAL=15 # check for new IPv6 addresses every 15 seconds
IP_ADDR_FILE=/home/jonathan/ipv6address.txt

function start_socat {
        socat TCP4-LISTEN:80,fork,su=nobody,reuseaddr "TCP6:[$1]:80" &
        ID_SOCAT_80=$!
        socat TCP4-LISTEN:443,fork,su=nobody,reuseaddr "TCP6:[$1]:443" &
        ID_SOCAT_443=$!
}

SERVERADDR=$(cat $IP_ADDR_FILE)
start_socat $SERVERADDR

# recreate socat processes if necessary
while true; do
        SERVERADDR_NEW=$(cat $IP_ADDR_FILE)

        if [ "$SERVERADDR_NEW" != "$SERVERADDR" ]; then
                SERVERADDR=$SERVERADDR_NEW
                date
                echo "IP_difference: IP address: $SERVERADDR new IP address: $SERVERADDR_NEW"
                kill $ID_SOCAT_80 $ID_SOCAT_443
                sleep 5
                start_socat $SERVERADDR
        fi
        sleep $UPDATE_INTERVAL
done

What this script does is, that it reads my NAS’ IPv6 address from a file ipv6address.txt and starts two socat processes - one for port 80 (http) and one for port 443 (https). The remainder of the script is simply polling the file with the IPv6 address and recreates the socat processes with adjusted IPv6 addresses if necessary. At the same time, a cronjob on my NAS was constantly updating the IPv6 address file via SSH, something like:

IPv6_ADDRESS=$(ip -6 a | grep 'global' | grep -v 'deprecated'| \
               awk '{print $2}' | grep -v '^fd' | grep -v '^fc' | \
               sed 's|/.*||' | head -n1)
ssh user@vps-server 'echo IPv6_address > /home/jonathan/ipv6address.txt'

Systemd Service

Ok, last thing to do is to create a systemd service, so that the script above is started automatically and output is logged nicely with journald. Create /etc/systemd/socat_forward.service with the following content:

[Unit]
Description=Socat IPv4 to IPv6 forward

[Service]
ExecStart=/home/jonathan/socatscript.sh
Restart=on-failure

[Install]
WantedBy=multi-user.target

Execute systemctl enable socat_forward.service && systemctl start socat_forward.service, and the VPS forwards all port 80 & 443 traffic to the NAS.

I’d be happy to hear if you are using this technique or something similar! 🙂👍