Route Squid Proxy Traffic Through an OpenVPN Gateway
Problem Description
When using a VPN, the default configuration is to route all traffic over the VPN tunnel. However, there are some instances where it is useful to only route application-specific traffic instead of forcing all traffic through the VPN. This isn't always possible, but if application in question supports an HTTP proxy then we can use Squid to transparently pass that traffic off to an OpenVPN gateway.
Implementation Details
iproute2 Configuration
Permanently add a custom routing table for OpenVPN traffic:
echo "100 openvpn" > /etc/iproute2/rt_tables
The addition/deletion of the actual routes will be added by the OpenVPN up/down scripts.
Squid Configuration
Traffic which is accepted by Squid must be emitted on the OpenVPN tunnel interface in order for this to work. Since IP address for the interface is dynamic (assuming the server is not configured to provide static IP addresses) this would require updating the Squid config file every time the VPN connection is established (AFAIK, a hostname cannot be provided in place of the IP address). To get around this limitation, we will emit traffic on a random IP address and then use iptables to forward traffic to the real IP address.
The following lines were added to the /etc/squid3/squid.conf config file:
acl local_net src 192.168.0.0/255.255.255.0 http_access allow local_net forwarded_for delete tcp_outgoing_address 10.202.101.1
The first line defines my local network, the second line instructs Squid to accept connections from any computer on my local network, and the third line prevents Squid from including the X-Forwarded-For header in its outgoing connections. The last line defines the source IP address for which Squid will use to make requests on behalf of users. Note that this IP address should not belong to your local subnet or the OpenVPN subnet.
OpenVPN Client Configuration
Add the following items to the client config file:
route-noexec route-up route-up.sh down down.sh
The route-noexec parameter prevents the client from applying the default routing configuration which is pushed from the server. The route-up parameter must point to a script which will be used to setup the custom routing and the down parameter must point to a script that will remove the custom routing once the VPN client is stopped.
The content of route-up.sh should look like this:
#!/bin/bash source "`dirname $0`/vpn_config.sh" logger "OpenVPN route-up.sh: bridge if: $VPNBRIDGE_IF addr: $VPNBRIDGE_ADDR table: $VPNBRIDGE_TBL" # Add the default route to our custom 'openvpn' routing table ip route add default via $ifconfig_remote dev $dev table $VPNBRIDGE_TBL # Add new interface to act as a middleman between the fixed address used # by Squid and the dynamic address provided by OpenVPN ip tuntap add dev $VPNBRIDGE_IF mode tun ip addr add $VPNBRIDGE_ADDR dev $VPNBRIDGE_IF ip link set dev $VPNBRIDGE_IF up # All packets from Squid are passed to the custom routing table ip rule add from $VPNBRIDGE_ADDR lookup $VPNBRIDGE_TBL # Rewrite the source address to match the one provided by OpenVPN iptables -t nat -A POSTROUTING -s $VPNBRIDGE_ADDR -j SNAT --to-source $ifconfig_local logger "OpenVPN route-up.sh: local: $ifconfig_local remote: $ifconfig_remote device: $dev"
The content of down.sh should look like this:
#!/bin/bash source "`dirname $0`/vpn_config.sh" logger "OpenVPN down.sh: bridge if: $VPNBRIDGE_IF addr: $VPNBRIDGE_ADDR table: $VPNBRIDGE_TBL openvpn local: $ifconfig_local" ip rule del from $VPNBRIDGE_ADDR lookup $VPNBRIDGE_TBL iptables -t nat -D POSTROUTING -s $VPNBRIDGE_ADDR -j SNAT --to-source $ifconfig_local ip tuntap del dev $VPNBRIDGE_IF mode tun
Finally, the common configurable parameters for both of these scripts is found in another script named vpn_config.sh. Its contents should look like this:
#!/bin/bash export VPNBRIDGE_IF=vpnbridge0 export VPNBRIDGE_ADDR=10.202.101.1 export VPNBRIDGE_TBL=openvpn
These three scripts should be placed in the same directory and must be marked as executable (chmod +x).