Home
Got Linux ?

Blah blah blah... Mostly technical thoughts, rants and gibberish


Home server, remote public IP address, VPN and tagged routing

[2018.06.06]

Like many Linux nerds, I host a bunch of personal services, both at home and on my favorite ISP’s remote server.

Problem you may have with services hosted at home is accessing them remotely.

In Switzerland, public static IP addresses for private customers are expensive enough not to be a very appealing solution. Dynamic DNS is a possible alternative but I personally find it unsatisfactory since: 1. not being 100% reliable; 2. depending on some 3rd-party’s service (e.g. DynDNS).

On the other hand, my favorite ISP’s remote server does have both IPv4 and IPv6 public static addresses readily available.

By using a VPN and some clever routing, one can use those remote public static IP addresses to access home services reliably and efficiently.

Overview

From the perspective of an incoming request packet, the scenario is nothing out of the ordinary:

The catch occurs when the response packet must be routed back to the source IP address! In order to reach that source and not being dropped as a martian, this packet MUST be routed via the remote server. But how to do so when the home default gateway is readily available to route that packet directly (but erroneously) to the source?

One could performed Source Network Address Translation (SNAT) on the incoming request packet at the same time DNAT is performed on the remote server. But that would have the major disadvantage of hiding the true source IP address to the home server.

This is where tagged routing comes into the picture:

Implementation

Network topology, hosts and addressing

To make things simpler, we’ll stick to IPv4 addresses. But everything that is said below can be translated to IPv6 straight-forwardly.

The remote network segment is made of:

While the home network segment is made of:

Destination Network Address Translation (DNAT) [on 192.0.2.200/192.168.200.1]

The first step is to perform Destination Network Address Translation (DNAT) for packets reaching the public remote server:

iptables

# DNAT
# ... VPN server
iptables -t nat -A PREROUTING -i eth0 -d 192.0.2.200 -p udp --dport 1194 -j DNAT --to 192.168.200.2
# ... home service
iptables -t nat -A PREROUTING -i eth0 -d 192.0.2.200 -p tcp --dport 443 -j DNAT --to 192.168.100.3

# Firewall ACL
# ... VPN server
iptables -t filter -A FORWARD -i eth0 -o br0 -d 192.168.200.2 -p udp --dport 1194 -j ACCEPT
# ... home service
iptables -t filter -A FORWARD -i eth0 -o br0 -d 192.168.100.3 -p tcp --dport 443 -j ACCEPT

Routing to the remote VPN server [on 192.0.2.200/192.168.200.1]

We must then provide a route for the DNAT-ed packet to reach its home destination:

/etc/network/if-up.d/routing-home

#!/bin/sh

# Route home network via the VPN server
ip -4 route show | fgrep -q '192.168.100.0/24' || ip -4 route add 192.168.100.0/24 via 192.168.200.2 dev br0

/etc/sysctl.d/ip-forwarding.conf

# Enable routing
net.ipv4.ip_forward = 1

Remote OpenVPN server [on 192.168.200.2]

Nothing here but a very standard OpenVPN server setup.

/etc/openvpn/server.vpn.example.net.conf

## OpenVPN Server Settings
#  Enable/disable with 'systemctl {enable|disable} openvpn@server.vpn.example.net.service'
#  Start/stop with 'service openvpn {start|stop}'

# Server
mode server
dev tun0
proto udp
port 1194

# TLS
tls-server
# ... PKI (*.vpn.example.net)
ca /etc/openvpn/pki/ca.vpn.example.net-cert.pem
cert /etc/openvpn/pki/server.vpn.example.net-cert.pem
key /etc/openvpn/pki/server.vpn.example.net-key.pem

# Encryption
dh /etc/openvpn/server.vpn.example.net-dh.pem
tls-auth /etc/openvpn/server.vpn.example.net-auth.key
cipher AES-256-CBC
auth SHA256
reneg-sec 43200

# Connection
keepalive 5 30
comp-lzo yes
persist-key
persist-tun

# IPv4
ifconfig 10.0.0.1 10.0.0.2
push "route 192.168.200.0 255.255.255.0"

# Clients
client-config-dir /etc/openvpn/client/
# ... home network
route 192.168.100.0 255.255.255.0

# Logging
verb 3
status /var/log/openvpn-server-status.log

/etc/openvpn/client/home.vpn.example.net

## OpenVPN Server Client-specific Settings
#  home.vpn.example.net

# IPv4
ifconfig-push 10.0.0.4 10.0.0.3
iroute 192.168.100.0 255.255.255.0

iptables

# Firewall ACL
iptables -t filter -A FORWARD -i tun0 -o eth0 -j ACCEPT
iptables -t filter -A FORWARD -i eth0 -o tun0 -j ACCEPT

/etc/sysctl.d/ip-forwarding.conf

# Enable routing
net.ipv4.ip_forward = 1

Home OpenVPN client [on 192.168.100.2]

This is where the first elements of the tagged routing are set in place:

/etc/openvpn/home.vpn.example.net.conf

## OpenVPN Server Settings
#  Enable/disable with 'systemctl {enable|disable} openvpn@home.vpn.example.net.service'
#  Start/stop with 'service openvpn {start|stop}'

# Client
client
nobind
dev tun
proto udp
remote 192.0.2.200 1194
tls-auth /etc/openvpn/server.vpn.example.net-auth.key
explicit-exit-notify

# TLS
remote-cert-tls server
# ... PKI (*.vpn.example.net)
ca /etc/openvpn/pki/ca.vpn.example.net-cert.pem
cert /etc/openvpn/pki/home.vpn.example.net-cert.pem
key /etc/openvpn/pki/home.vpn.example.net-key.pem

# Encryption
cipher AES-256-CBC
auth SHA256
reneg-sec 43200

# Connection
comp-lzo yes
persist-key
persist-tun
route-delay 2

# Additional setup
script-security 2
up /etc/openvpn/scripts/server.vpn.example.net-up.sh
down /etc/openvpn/scripts/server.vpn.example.net-down.sh

# Logging
verb 3
status /var/log/openvpn-client-status.log

/etc/openvpn/scripts/server.vpn.example.net-up.sh

#!/bin/sh

# Tagged routing
ip -4 route add default dev tun0 table 42
ip -4 rule add fwmark 42 table 42

# Allow "martians" packets from VPN tunnel
echo 0 > /proc/sys/net/ipv4/conf/tun0/rp_filter

# Done
exit 0

/etc/openvpn/scripts/server.vpn.example.net-down.sh

#!/bin/sh

# Tagged routing
ip -4 route flush table 42
ip -4 rule del fwmark 42 table 42

# Done
exit 0

iptables

# Packets tagging (using the ToS/DSCP IP header field)
# ... remote-originated (set tag)
iptables -t mangle -A PREROUTING -i tun0 -j DSCP --set-dscp 42
# ... return-path (use and clear tag)
iptables -t mangle -A PREROUTING -i eth0 -m dscp --dscp 42 -j MARK --set-mark 42
iptables -t mangle -A POSTROUTING -o tun0 -j DSCP --set-dscp 0

# Firewall ACL
# ... home service
iptables -A FORWARD -i tun0 -o eth0 -d 192.168.100.3 -p tcp --dport 443 -j ACCEPT

/etc/sysctl.d/ip-forwarding.conf

# Enable routing
net.ipv4.ip_forward = 1

Home service [on 192.168.100.3]

This is where tagged routing is finalized:

/etc/network/if-up.d/routing-tagged

#!/bin/sh

# Tagged routing (via the VPN client)
case "${ADDRFAM}-${IFACE}" in
  'inet-eth0')
    ip -4 route flush table 42
    ip -4 rule del fwmark 42 table 42 2>/dev/null
    ip -4 route add default via 192.168.100.2 table 42
    ip -4 rule add fwmark 42 table 42
    ;;
esac

iptables

#!/bin/sh

# Track tagged packets (tag responses identically to requests)
iptables -t mangle -A INPUT -m dscp --dscp 42 -j CONNMARK --set-mark 42
iptables -t mangle -A OUTPUT -j CONNMARK --restore-mark
iptables -t mangle -A OUTPUT -m connmark --mark 42 -j DSCP --set-dscp 42

This is it!

The home service can now be seamlessly accessed via the remote server’s public IP address.

What is also very neat with this setup is that the home network can even be roaming while maintaining connectivity, thanks to the resiliency of OpenVPN.

Enjoy!