
Setting up VPN Routing on my home server
After my previous post in which I got a laptop installed with Ubuntu and turned into VPN server I went on to try this on my home server. This is also running on Ubuntu but is my home file sharing samba, PS3 DLNA and Apache (this blog) server. Another issue was that I did not want the server itself to pass all its internet traffic through VPN. I simply wanted it to NAT all LAN clients via VPN whose gateway is set to be this server. So the basic requirements are:
– Ubuntu / Debian Linux install (I used Ubuntu with Gnome therefore the steps require to use Network Manager under GUI – I would love to experiment this setup one day with command line control only)
– An advanced router (I use cheap second hand Linksys WRT160N with DD-WRT)
First thing you have to do is to setup your Linux server for VPN and VPN routing:
Create a VPN network account. I used PPTP and for this you have to make sure to use MS-CHAP2 only, remove ticks from all other authentication protocols andEnable MPPE Compression under the Advanced Tab in VPN connection window. Also make sure to click on IPV4 settings tab and tick the option Use this connection only for resources on its network. This will prevent all traffic from your Ubuntu server going out of VPN gateway. You cannot use the Network Manager’s option of automatic connect and allow all users. They are buggy. We will use a command line script to fire up this connection but for that you have to leave keychain password blank when asked for after creating the VPN connection.
Now you should try to connect this VPN to make sure it is all working. Fix any username, password or VPN server address issues here if encountered.
Now we are going to create three script files.
Script #1 will be to start the VPN connection when provided with the VPN connection name via script #2.
Script #2 will launch via “Startup Applications” feature in Ubuntu so that it runs nearly at the end of all startup scripts. You can also add it to rc#.d/ files buy using update-rc.d script defaults command but that will have to be another blog post :). This script checks if there is a VPN connection, reports a fault in case of error with config files or absence of any network interface being up, fires up the named VPN connection in case of positive on network connectivity but absence of VPN connection, fires up Script #3 after each startup of VPN connection and then sleeps for 60 seconds before going through the same process again 😉
Script #3 is fired by Script #2 each time it establishes a VPN connection. This script creates a temporary Script #4 file. Fills in the variables from VPN connection in this script and then fires up this Script #4.
Script #4 has routing commands which will enable route incoming packets from your selected LAN clients via VPN.
Script #1 has to be saved in /usr/local/bin
Script #2 & Script #3 I saved in /etc/init.d/
Script #4 is automatically created by Script #3 in /tmp
make sure that all scripts except #4 are made executable by chmod 755
and I also made root owner of these files. Script #4 gets its chmod via Script #3. Now my system has auto-login on boot with my username. Script #1, #2 and #3 are launched via startup scripts so they have no issues but Script #4 is called via “logged on” user environment so we have a problem. It will not run unless you run this script with sudo and to avoid sudo asking for password you have to add the following line in /etc/sudoers file via “visudo” as root.
username ALL=NOPASSWD: /tmp/firewall_script.sh
replace “username” with the username of autologin user and /tmp/firewall_script.sh with name of script file you create via Script #3.
Script #1
###########
#SETTINGS #
###########
get_connections_paths()
{
dbus-send --system --print-reply --dest="$1" "/org/freedesktop/NetworkManagerSettings" "org.freedesktop.NetworkManagerSettings.ListConnections" \
| grep "object path" | cut -d '"' -f2
}
#
get_connection_settings()
{
dbus-send --system --print-reply --dest="$1" "$2" org.freedesktop.NetworkManagerSettings.Connection.GetSettings
}
#
get_connection_string_setting()
{
echo "$1" | grep -A 1 \""$2"\" | grep variant | cut -d '"' -f2
}
#
get_connection_id()
{
get_connection_string_setting "$1" "id"
}
#
get_connection_type()
{
get_connection_string_setting "$1" "type"
}
#
get_device_type_by_connection_type()
{
echo "$1" | grep -q "ethernet" && echo 1 && return
echo "$1" | grep -q "wireless" && echo 2 && return
echo 0
}
#
find_connection_path()
{
for connection_path in `get_connections_paths "$1"`
do
connection_settings=`get_connection_settings "$1" "$connection_path"`
connection_settings_id=`get_connection_id "$connection_settings"`
[ "$connection_settings_id" = "$2" ] && echo "$1" "$connection_path"
done
}
#
find_connection_path_everywhere()
{
find_connection_path "org.freedesktop.NetworkManagerSystemSettings" "$1"
find_connection_path "org.freedesktop.NetworkManagerUserSettings" "$1"
}
#
print_connections_ids()
{
for connection_path in `get_connections_paths "$1"`
do
connection_settings=`get_connection_settings "$1" "$connection_path"`
connection_settings_id=`get_connection_id "$connection_settings"`
echo "$connection_settings_id"
done
}
#
print_connections_ids_everywhere()
{
print_connections_ids "org.freedesktop.NetworkManagerSystemSettings"
print_connections_ids "org.freedesktop.NetworkManagerUserSettings"
}
#
#
###########
# DEVICES #
###########
#
get_devices_paths()
{
dbus-send --system --print-reply --dest="org.freedesktop.NetworkManager" "/org/freedesktop/NetworkManager" "org.freedesktop.NetworkManager.GetDevices" \
| grep "object path" | cut -d '"' -f2
}
#
get_device_property()
{
dbus-send --system --print-reply --dest="org.freedesktop.NetworkManager" "$1" "org.freedesktop.DBus.Properties.Get" string:"org.freedesktop.NetworkManager.Device" string:"$2" \
| grep variant | awk '{print $3}'
}
#
get_device_type()
{
get_device_property "$1" "DeviceType"
}
#
get_device_path_by_device_type()
{
device_path_by_device_type="/"
for device_path in `get_devices_paths`
do
device_type=`get_device_type "$device_path"`
[ "$device_type" = "$1" ] && device_path_by_device_type="$device_path"
done
echo "$device_path_by_device_type"
}
#
#
#######################
# ACTIVES CONNECTIONS #
#######################
#
get_actives_connections_paths()
{
dbus-send --system --print-reply --dest="org.freedesktop.NetworkManager" "/org/freedesktop/NetworkManager" "org.freedesktop.DBus.Properties.Get" string:"org.freedesktop.NetworkManager" string:"ActiveConnections" \
| grep "object path" | cut -d '"' -f2
}
#
get_last_active_connection_path()
{
get_actives_connections_paths | tail -n 1
}
#
get_parent_connection_path_by_device_type()
{
parent_connection_path="/"
[ "$1" = 0 ] && parent_connection_path=`get_last_active_connection_path`
echo "$parent_connection_path"
}
#
get_active_connection_property()
{
dbus-send --system --print-reply --dest="org.freedesktop.NetworkManager" "$1" "org.freedesktop.DBus.Properties.Get" string:"org.freedesktop.NetworkManager.Connection.Active" string:"$2" \
| grep variant | awk -F '"' '{print $2}'
}
#
get_active_connection_service()
{
get_active_connection_property "$1" "ServiceName"
}
#
get_active_connection_path()
{
get_active_connection_property "$1" "Connection"
}
#
get_active_connection_path_by_connection_path()
{
for active_connection_path in `get_actives_connections_paths`
do
service=`get_active_connection_service $active_connection_path`
path=`get_active_connection_path $active_connection_path`
[ "$service" = "$1" ] && [ "$path" = "$2" ] && echo "$active_connection_path"
done
}
#
print_actives_connections_ids()
{
for active_connection_path in `get_actives_connections_paths`
do
service=`get_active_connection_service $active_connection_path`
path=`get_active_connection_path $active_connection_path`
connection_settings=`get_connection_settings "$service" "$path"`
connection_settings_id=`get_connection_id "$connection_settings"`
echo "$connection_settings_id"
done
}
#
#
##############
# START/STOP #
##############
#
start_connection()
{
my_connection_complete_path=`find_connection_path_everywhere "$1"`
my_connection_settings=`get_connection_settings $my_connection_complete_path`
my_connection_type=`get_connection_type "$my_connection_settings"`
my_connection_device_type=`get_device_type_by_connection_type "$my_connection_type"`
#
my_connection_service=`echo $my_connection_complete_path | awk '{print $1}'`
my_connection_path=`echo $my_connection_complete_path | awk '{print $2}'`
my_connection_device_path=`get_device_path_by_device_type "$my_connection_device_type"`
my_parent_connection_path=`get_parent_connection_path_by_device_type "$my_connection_device_type"`
#
echo "connection_service=$my_connection_service"
echo "connection_path=$my_connection_path"
echo "connection_device_path=$my_connection_device_path"
echo "parent_connection_path=$my_parent_connection_path"
#
dbus-send --system --print-reply --dest="org.freedesktop.NetworkManager" /org/freedesktop/NetworkManager "org.freedesktop.NetworkManager.ActivateConnection" string:"$my_connection_service" objpath:"$my_connection_path" objpath:"$my_connection_device_path" objpath:"$my_parent_connection_path"
}
#
stop_connection()
{
my_connection_complete_path=`find_connection_path_everywhere "$1"`
my_active_connection_path=`get_active_connection_path_by_connection_path $my_connection_complete_path`
#
echo "active_connection_path=$my_active_connection_path"
#
dbus-send --system --print-reply --dest="org.freedesktop.NetworkManager" /org/freedesktop/NetworkManager "org.freedesktop.NetworkManager.DeactivateConnection" objpath:"$my_active_connection_path"
}
#
#
########
# MAIN #
########
#
invalid_arguments()
{
echo "Usage: `basename "$0"` connexion_name start|stop"
echo "---Available Connections:"
print_connections_ids_everywhere
echo "---Active Connections:"
print_actives_connections_ids
exit 0
}
#
[ "$#" != 2 ] && invalid_arguments
#
case "$2" in
"start")
start_connection "$1"
;;
"stop")
stop_connection "$1"
;;
*)
invalid_arguments
;;
esac
This script uses command line based network-manager control and starts the VPN connection whose name is provided via the command. I called this script “vpn_connect” and the usage is:
vpn_connect VPN_CONNECTION_NAME start
where VPN_CONNECTION_NAME is the name you give to your VPN connection in network manager.
Script #2
#!/bin/bash
for (( ; ; )); do
# creating ifinite loop
tested=$(vpn_connect|grep -c VPN_CONNECTION_NAME)
# VPN_CONNECTION_NAME here is the name of desired vpn connection to monitor. The results can be:
# 0 - this connection does not exist
# 1 - connection exists, but not active
# 2 - connection exists, and is active
case $tested in
"0")
echo "something is wrong, cannot find desired connection in avaliable list"
;;
"1")
echo "VPN avaliable, but not connected"
vpn_connect VPN_CONNECTION_NAME start
/etc/init.d/route &
;;
"2")
echo "VPN seems to work"
;;
esac
# enter desired time between checks here.
sleep 60
done
I called this file start_vpn
Replace “VPN_CONNECTION_NAME” with the name of your VPN connection. Also you see in this script that I am firing up Script #3 called “route” just after VPN connection is established. At the end of command line to start the “route” script is & which will launch the route script and will continue processing this script without waiting the “route” script to finish and report back etc.
Script #3
#!/bin/bash
INT=ppp0
echo "sleep 60" > /tmp/firewall_script.sh
echo "/sbin/iptables --table nat --append POSTROUTING --out-interface $INT --jump MASQUERADE" >> /tmp/firewall_script.sh ;
echo "/sbin/iptables --insert FORWARD --protocol tcp --tcp-flags SYN,RST SYN --jump TCPMSS --clamp-mss-to-pmtu" >> /tmp/firewall_script.sh ;
echo "ip rule add from 192.168.x.2 table 200" >> /tmp/firewall_script.sh ;
echo "ip rule add from 192.168.x.3 table 200" >> /tmp/firewall_script.sh ;
echo "ip rule add from 192.168.x.4 table 200" >> /tmp/firewall_script.sh ;
echo "ip rule add from 192.168.x.5 table 200" >> /tmp/firewall_script.sh ;
echo "ip rule add from 192.168.x.6 table 200" >> /tmp/firewall_script.sh ;
echo "ip rule add from 192.168.x.7 table 200" >> /tmp/firewall_script.sh ;
echo "ip rule add from 192.168.x.8 table 200" >> /tmp/firewall_script.sh ;
echo "REMOTEIP=\$(ifconfig $INT | sed -n 's/.*inet *addr:\([0-9\.]*\).*/\1/p')" >> /tmp/firewall_script.sh ;
echo "ip route add default via \$REMOTEIP dev $INT table 200" >> /tmp/firewall_script.sh ;
echo "ip route flush cache" >> /tmp/firewall_script.sh ;
chmod 777 /tmp/firewall_script.sh
sudo /tmp/firewall_script.sh &
This script is called route and creates the Script #4 once the VPN connection is established. You can see that Script #4 gets a sleep time of 60 seconds which allows time for VPN connection to be established by Script #2.
INT is the name of interface which is created for the VPN connection. You need to get this for your server’s VPN connection by using ifconfig after VPN connection is manually created (via Network Manager) and use the value here in this script. In my case it is ppp0. Next thing you would want to change are the lines having my internal LAN client IP addresses who I wish to use this VPN router for. They are static assigned by your router (discussed below). You can add as many or few as you like (add or remove lines of ip rule command as needed). The last two lines of this script are important. It chmod’s the Script #4 and launches it with “sudo”. You can change the name and location of this script but remember to change it in all lines where it occurs. With a regular logged on user chmod will work but you cannot execute this script unless it is launched bu sudo. Explained above how to edit sudoers file to let this happen.
That is it for your VPN server. Reboot it and watch everything getting done by itself.
Now for your router you will have to decide, find and implement specific configuration. Mine is DD_WRT and I can use DNSMasq options to get static IPs assigned to selected MAC addresses with a different gateway than the rest of dynamically assigned IP addresses 😉 I used the guide at this page to accomplish this. One thing I found was that for all the MAC addresses you are assigning the static address and different gateway via the DNSMasq options you should NOT put them in the GUI provided static mapping table.
I will add more information if and when required.