Skip to content

Instantly share code, notes, and snippets.

@nehaljwani
Created July 30, 2017 09:43
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save nehaljwani/f6e9d12102157161bfceb7eea80c319f to your computer and use it in GitHub Desktop.
Save nehaljwani/f6e9d12102157161bfceb7eea80c319f to your computer and use it in GitHub Desktop.
OpenConnect VPN Inside Linux Network Namespace
#!/bin/bash
# start openconnect tunnel and shell inside Linux network namespace
#
# this is a fork of schnouki's script, see original blog post
# https://schnouki.net/posts/2014/12/12/openvpn-for-a-single-application-on-linux/
#
# original script can be found here
# https://gist.github.com/Schnouki/fd171bcb2d8c556e8fdf
# ------------ adjust values below ------------
NS_NAME="myvpn"
NS_EXEC="sudo -E ip netns exec $NS_NAME"
VPN_ENDPOINT="superawesomevpn.io/someplace"
VPN_USER="nwani"
WIRED_INTERFACE="enp1s0"
WIRELESS_INTERFACE="wlp2s0"
OUT_IF="${NS_NAME}0"
IN_IF="${NS_NAME}1"
OUT_IP="10.200.200.1"
IN_IP="10.200.200.2"
read -s -p "VPN Password: " VPN_PASS
set -uxeo pipefail
start_vpn() {
echo "Add network interface"
# create the network namespace
sudo ip netns add "$NS_NAME"
# start the loopback interface in the namespace
$NS_EXEC ip addr add 127.0.0.1/8 dev lo
$NS_EXEC ip link set lo up
# create virtual network interfaces that will let OpenVPN (in the
# namespace) access the real network, and configure the interface in the
# namespace (${IN_IF}) to use the interface out of the namespace (${OUT_IF})
# as its default gateway
sudo ip link add "${OUT_IF}" type veth peer name "${IN_IF}"
sudo ip link set "${OUT_IF}" up
sudo ip link set "${IN_IF}" netns "${NS_NAME}" up
sudo ip addr add "${OUT_IP}"/24 dev "${OUT_IF}"
$NS_EXEC ip addr add "${IN_IP}"/24 dev "${IN_IF}"
$NS_EXEC ip link set dev "${IN_IF}" mtu 1492
$NS_EXEC ip route add default via "${OUT_IP}" dev "${IN_IF}"
# make sure ipv4 forwarding is enabled
sudo sysctl -w net.ipv4.ip_forward=1
# configure the nameserver to use inside the namespace
# TODO use VPN-provided DNS servers in order to prevent leaks
sudo mkdir -p "/etc/netns/${NS_NAME}"
cat <<EOF | sudo tee "/etc/netns/${NS_NAME}/resolv.conf" || exit 1
nameserver 8.8.8.8
nameserver 8.8.4.4
EOF
# IPv4 NAT, you may need to adjust the interface name prefixes
sudo iptables -t nat -A POSTROUTING -o "${WIRED_INTERFACE}" -m mark --mark 0x29a -j MASQUERADE
sudo iptables -t nat -A POSTROUTING -o "${WIRELESS_INTERFACE}" -m mark --mark 0x29a -j MASQUERADE
sudo iptables -t mangle -A PREROUTING -i "${OUT_IF}" -j MARK --set-xmark 0x29a/0xffffffff
# start openconnect in the namespace
echo "Starting VPN"
set +x
echo -e "${VPN_PASS}\npush" | $NS_EXEC /usr/sbin/openconnect --interface vpn0 $VPN_ENDPOINT -u $VPN_USER --passwd-on-stdin &
# wait for the tunnel interface to come up
while ! $NS_EXEC ip link show dev vpn0 >/dev/null 2>&1 ; do sleep .5 ; done
}
stop_vpn() {
echo "Stopping VPN"
sudo ip netns pids $NS_NAME | sudo xargs -rd'\n' kill -SIGINT
# TODO wait for terminate
sleep 2
# clear NAT
sudo iptables -t nat -D POSTROUTING -o "${WIRED_INTERFACE}" -m mark --mark 0x29a -j MASQUERADE
sudo iptables -t nat -D POSTROUTING -o "${WIRELESS_INTERFACE}" -m mark --mark 0x29a -j MASQUERADE
sudo iptables -t mangle -D PREROUTING -i "${OUT_IF}" -j MARK --set-xmark 0x29a/0xffffffff
echo "Delete network interface"
sudo rm -rf "/etc/netns/${NS_NAME}"
sudo ip netns delete "${NS_NAME}"
sudo ip link delete "${OUT_IF}"
}
start_vpn
# start app inside n/w namespace
$NS_EXEC su ${USER}
trap stop_vpn EXIT
@porjo
Copy link

porjo commented Nov 27, 2019

Thanks for the script, just what I was looking for!

My VPN provider also requires one time code to be passed in aswell, so I'm trying to get an expect script working to accommodate that. I'm not having any success.... I can run openconnect inside expect script but if I send it to the background, it seems unable to read the input properly. If I don't background it, it works fine but then it holds up the namespace script and prevents it dropping the user into a shell 😞

@nehaljwani
Copy link
Author

Does using a subshell for the openconnect command help?

@porjo
Copy link

porjo commented Dec 9, 2019

@nehaljwani thanks for the suggestion. I tried that but couldn't make it work. I've ended up getting it working by replacing the openconnect line with this:

$NS_EXEC /usr/sbin/openconnect -b --authgroup=VPN --interface vpn0 $VPN_ENDPOINT -u $VPN_USER

I enter both passwords manually to stdin, then openconnect goes to the background (-b) and the shell script resumes

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