OpenVPN Split Tunneling: Retaining Local LAN Access While Using A VPN

From Nearline Storage
Revision as of 04:02, 15 March 2020 by Dlk (talk | contribs) (→‎Adjusting The Configuration)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Jump to navigation Jump to search

My Environment

I have multiple systems on my local LAN that each want to use a VPN to access the internet. While accessing the internet via the VPN, they still want to be able to access hosts that are on the same LAN as they are. In some cases they also want to permit the other hosts on the LAN to access them via SSH and other service ports.

This works without any special configuration on Fedora 29. My media server runs OpenVPN on Fedora 29 using the standard configuration file issued by my VPN vendor. It is able to access the Internet and all of the other hosts that sit on the LAN it is part of. Other LAN hosts can access it using SSH, SMB and Nagios (NRPE) monitoring services. Simple.

This does not work on either the laptop or workstation that are also part of this same LAN. These run Fedora 30 and when they are running OpenVPN using the vendor's standard configuration file, they cannot access anything on the LAN and nothing can access them. The workstation runs bridged networking so that KVM guests that run on it can obtain their own unique IP address assignments from the local DHCP server. The laptop does not run bridged networking. (Neither does the Fedora 29 machine mentioned earlier where everything "just works.") So bridged vs. non-bridged configuration doesn't seem to be a factor. All systems are running the same release of OpenVPN, 2.4.8.

Adjusting The Configuration

To allow the workstation and the laptop access to hosts on the local LAN, the following statements need to be added to the OpenVPN config file:

 pull-filter ignore "dhcp-option DNS"
 route add 192.168.1.0 255.255.255.0 192.168.1.25
 route add 10.0.0.0 255.255.255.0 192.168.1.25

The workstation and laptop are both on the 192.168.1.0 subnet which uses 192.168.1.25 as its router. They both need to communicate with hosts on the 192.168.1.0 and 10.0.0.0 subnets. The 10.0.0.0 subnet is still part of my LAN. It's a firewall-protected playground for IoT devices that's firewalled off from my regular LAN subnet, 192.168.1.0. Devices on the 10.0.0.0 subnet are not allowed to access systems on the 192.168.1.0 subnet. Traffic that is destined for the 10.0.0.0 subnet is routed through 192.168.1.25.

Inbound access from other LAN hosts to the laptop isn't required, only the workstation needs to support this. To allow this a special routing table needs to be set up that will route inbound packets to the workstation's bridge0 interface directly rather than routing them to the VPN. This special table is set up and torn down by a script that is specified in the OpenVPN config file. I called this script /etc/openvpn/client/special-LAN-routing:

 #!/usr/bin/sh
 
 UPDOWN="${1^^}"
 
 if [ "$UPDOWN" == "UP" ]; then
     echo "$0: Setting up inbound routing for LAN traffic"
     /sbin/ip rule add from 192.168.1.20 table 128
     /sbin/ip route add table 128 to 192.168.1.0/24 dev bridge0
     /sbin/ip route add table 128 default via 192.168.1.25
 elif [ "$UPDOWN" == "DOWN" ]; then
     echo "$0: Tearing down inbound routing for LAN traffic"
     /sbin/ip route del table 128 default via 192.168.1.25
     /sbin/ip route del table 128 to 192.168.1.0/24 dev bridge0
     /sbin/ip rule del from 192.168.1.20 table 128
 else
     echo "$0: Unknown command \"$1\""
     exit 1
 fi

192.168.1.20 is the workstation's static IP address. 192.168.1.0/24 is the subnet that the workstation and the other systems that need access to it are connected to. "bridge0" is the workstation's bridge interface. 192.168.1.25 is the router on the subnet. 128 is the name I chose for the table.

Add these statements to the OpenVPN config file to call this script:

 script-security 2
 route-up "/etc/openvpn/client/special-LAN-routing up"
 route-pre-down "/etc/openvpn/client/special-LAN-routing down"

OpenVPN messages, including the output of the script, are written to /var/log/messages.