Skip to content

Instantly share code, notes, and snippets.

@petr-ujezdsky
Last active October 10, 2023 22:53
Show Gist options
  • Save petr-ujezdsky/08c0b38607f2970319e6dac6b4feeb19 to your computer and use it in GitHub Desktop.
Save petr-ujezdsky/08c0b38607f2970319e6dac6b4feeb19 to your computer and use it in GitHub Desktop.
Sharing OpenVPN connection to local subnet

The goal

The goal is to connect some network device(s), like TV, to the VPN. The device itself does not support any VPN functionality.

What I have

  • Home LAN network with range 10.0.1.0/24
    • 10.0.1.1 - gateway
    • 10.0.1.2 - Raspberry Pi
    • 10.0.1.3 - TV
    • 10.0.1.4 - NAS
  • OpenVPN setup with range 10.8.0.0/24
    • 10.8.0.1 - gateway
    • 10.8.0.2 - connected client

The main idea is to connect RPi to the VPN and route all traffic from TV through NAT into the VPN. To be able to still access the local NAS server, the traffic to 10.0.1.0/24 is not routed into the VPN. The RPi itself still uses your local gateway.

This way the only thing the TV must support is to manually set IP address, IP address of default gateway and IP address of DNS server.

I was using extra subnet 10.0.3.1/24 for TV and RPi (interface alias) before, but then the NAS (on subnet 10.0.1.1/24) was not accessible via DLNA. It uses broadcasts which are not routed between subnets.

I have almost zero knowledge of routing so there could be better solution. But hey, this works for me and might work for you too.

TV setup

Manually set IP address, gateway and DNS on your TV you want to share the VPN connection with

IP address: 10.0.1.208
Gateway:    10.0.1.2
DNS:        8.8.8.8 (or any other DNS server, even your local at 10.0.1.x subnet)

I've had trouble using the same RPi also as DNS server (setting DNS to 10.0.1.2). Another DNS server from the local network worked fine.

Prepare routing

All these changes are lost on device restart. It could be persisted somehow. But this has the advantage that if you screw something up, simply restart the RPi and everything should reset.

Enable IP forwarding (this was already enabled for me)

sudo sysctl -w net.ipv4.ip_forward=1

Create new routing table name, eg. ovpn with id 7. This step is persistent.

cat /etc/iproute2/rt_tables
#
# reserved values
#
255	local
254	main
253	default
0	unspec
#
# local
#
#1	inr.ruhep
7	ovpn

Assign routing table ovpn to the range 10.0.1.208/29 (this includes the TV with IP 10.0.1.208).

sudo ip rule add from 10.0.1.208/29 table ovpn

Verify

sudo ip rule list
0:	from all lookup local
32765:	from 10.0.1.208/29 lookup ovpn
32766:	from all lookup main
32767:	from all lookup default

Allow forwarding in firewall

sudo iptables -A FORWARD -j ACCEPT

Now the TV should work like without any changes. You should be able to see NAS (using DLNA) and access internet (YouTube, browser etc.).

Establishing OpenVPN connection

Download openvpn client

sudo apt install openvpn

Ensure the config.ovpn file contains route-nopull clause. Otherwise you will be cut-off from ssh after connecting to VPN.

Prepare VPN credentials into file credentials.txt (this is pretty insecure!)

cat credentials.txt
your-vpn-username
your-vpn-password

Connect to VPN

sudo openvpn --config config.ovpn --auth-user-pass credentials.txt --daemon

Note for scripting - when the command above finishes, the VPN might not be established yet. You have to wait a second or two.

This will create new tun0 device with 10.8.0.2 IP address, the default VPN gateway is 10.8.0.1

ip add
...
15: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 500
    link/none
    inet 10.8.0.2/24 scope global tun0
       valid_lft forever preferred_lft forever
    inet6 xxxx::xxxx:xxxx:xxxx:xxxx/64 scope link stable-privacy
       valid_lft forever preferred_lft forever

Routing to VPN

Traffic from TV to local network should not go through the RPi as they are on the same network 10.0.1.0/24. So we can safely route all the traffic on the RPi from 10.0.1.208/29 to the VPN gateway 10.8.0.1 on tun0 device

sudo ip route add default via 10.8.0.1 dev tun0 table ovpn

The table should look like this

sudo ip route show table ovpn
default via 10.8.0.1 dev tun0

Enable NAT on the tun0 device for VPN router to know how to send the traffic back

sudo iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE

Now you should still be able to access your local NAS. However the internet traffic should be routed via VPN. Check your public IP address via browser (eg. http://ifconfig.me).

Closing OpenVPN connection

To disconnect from VPN simply kill the openvpn process

sudo killall openvpn

This will also automatically remove the default route so all your traffic will go through your local gateway.

sudo ip route show table ovpn

The NAT is also automatically removed.

Connecting more devices

You can have more devices connected to the VPN. Just setup each device like the TV above, except the IP address - choose a different from range 10.0.1.208/29, that is 10.0.1.208 - 10.0.1.215.

You can also enlarge the subnet first removing the prior settings and adding new setting. To add 8 more devices

sudo ip rule del from 10.0.1.208/29 table ovpn
sudo ip rule add from 10.0.1.208/28 table ovpn

I choose the starting IP address 10.0.1.208 because my router assigns the range 10.0.1.100 - 10.0.1.200 to WiFi devices. See this online range calculator (be aware of the starting IP address as the actual range could be a bit different than you might think).

Debugging

Emulating device

You can emulate connected device's traffic on your RPi. This is useful when you can not run commands like ping and curl on the device (eg. TV).

First add unused IP address from the range 10.0.1.208/29 to RPi's ethernet card eth0. I choose 10.0.1.209

sudo ip addr add 10.0.1.209/24 dev eth0

Verify

ip add
...
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 10.0.1.2/24 brd 10.0.1.255 scope global dynamic noprefixroute eth0
       valid_lft 1841sec preferred_lft 1391sec
    inet 10.0.1.209/24 scope global secondary eth0
       valid_lft forever preferred_lft forever
    inet6 xxxx::xxxx:xxxx:xxxx:xxxx/64 scope link
       valid_lft forever preferred_lft forever
...

Now you can send ping from the IP address 10.0.1.209

ping -I 10.0.1.209 google.com -c 1
PING google.com (142.251.37.110) from 10.0.1.209 : 56(84) bytes of data.
64 bytes from xxx (yy.yy.yy.yy): icmp_seq=1 ttl=116 time=12.1 ms

and see it in the tcpdump

sudo tcpdump -i eth0 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
00:03:52.412033 IP 10.0.1.209 > xxx: ICMP echo request, id 4, seq 1, length 64
00:03:53.141391 IP xxx > 10.0.1.209: ICMP echo reply, id 4, seq 1, length 64

You can also check your public IP address using curl. If you have established the VPN connection you can simply verify the routing. This should give you the public IP address of the VPN provider

curl --silent --interface 10.0.1.209 ifconfig.me
ww.xx.yy.zz

You should see something like this in the tcpdump

sudo tcpdump -i eth0 src net 10.0.1.209/32
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
00:13:52.400380 IP 10.0.1.209.50687 > xxx.bc.googleusercontent.com.http: Flags [S], seq 3384149146, win 64240, options [mss 1460,sackOK,TS val 1078011361 ecr 0,nop,wscale 7], length 0
00:13:52.412553 IP 10.0.1.209.50687 > xxx.bc.googleusercontent.com.http: Flags [.], ack 2725034648, win 502, options [nop,nop,TS val 1078011373 ecr 1415169667], length 0
00:13:52.412623 IP 10.0.1.209.50687 > xxx.bc.googleusercontent.com.http: Flags [P.], seq 0:75, ack 1, win 502, options [nop,nop,TS val 1078011373 ecr 1415169667], length 75: HTTP: GET / HTTP/1.1
00:13:52.544447 IP 10.0.1.209.50687 > xxx.bc.googleusercontent.com.http: Flags [.], ack 284, win 501, options [nop,nop,TS val 1078011505 ecr 1415169797], length 0
00:13:52.544870 IP 10.0.1.209.50687 > xxx.bc.googleusercontent.com.http: Flags [F.], seq 75, ack 284, win 501, options [nop,nop,TS val 1078011506 ecr 1415169797], length 0
00:13:52.557463 IP 10.0.1.209.50687 > xxx.bc.googleusercontent.com.http: Flags [.], ack 285, win 501, options [nop,nop,TS val 1078011518 ecr 1415169810], length 0

You can also check the public address of your local gateway

curl --silent --interface 10.0.1.2 ifconfig.me
aa.bb.cc.dd

After disconnecting from VPN you should have the same public address from both IP addresses

curl --silent --interface 10.0.1.209 ifconfig.me
aa.bb.cc.dd

When you are done, remove the assigned IP address from eth0

sudo ip addr del 10.0.1.209/24 dev eth0

Useful commands

Monitor traffic on eth0 interface from your connected devices

sudo tcpdump -i eth0 src net 10.0.1.208/29

Monitor ping traffic on eth0 interface

sudo tcpdump -i eth0 icmp

Monitor all traffic on tun0 device

sudo tcpdump -i tun0

Inspect dropping packets (the numbers should not go up)

sudo iptables -v -L FORWARD
Chain FORWARD (policy DROP 250 packets, 20642 bytes)
...

Or view more

sudo iptables -v -L

View NAT rules

sudo iptables -v -t nat -L

Show ovpn routing table

sudo ip route show table ovpn

Show how the ovpn table is used

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