302 lines
9.9 KiB
Bash
Executable File
302 lines
9.9 KiB
Bash
Executable File
#!/bin/sh
|
|
#
|
|
# © 2009 David Woodhouse <dwmw2@infradead.org>
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
#
|
|
################
|
|
#
|
|
# This is a replacement for the standard vpnc-script used with vpnc and
|
|
# openconnect. It sets up VPN routing which doesn't screw over the
|
|
# _normal_ routing of the box.
|
|
#
|
|
# It sets up a new network namespace for the VPN to use, and it runs
|
|
# a Secure Shell dæmon inside that namespace, with full access to all
|
|
# routes on the VPN.
|
|
#
|
|
# It links the 'real' network namespace of the computer to this new one
|
|
# by an IPv6 site-local connection -- you can ssh into the 'VPN namespace'
|
|
# by connecting to the host 'fec0::1'.
|
|
#
|
|
# You don't need any IPv6 configuration or connectivity for this; you
|
|
# only need to have IPv6 support in your kernel. The use of IPv6 is purely
|
|
# local to your machine.
|
|
#
|
|
# This gives you effectively the same service as if your company used a
|
|
# SSH "bastion host" for access, instead of a VPN. It's just that the
|
|
# bastion host is a special network namespace in your _own_ machine.
|
|
#
|
|
# Since your connection to it is _private_, though, you can run a few
|
|
# services that a secure bastion host could not -- like a web proxy,
|
|
# for example. You can also just forward certain points so that, for
|
|
# example, connections to port 25 on your bastion host are automatically
|
|
# forwarded inside the VPN to your internal mail server. There should
|
|
# be a sample xinetd.conf with this script which shows how to do that
|
|
# using 'netcat' started from xinetd.
|
|
#
|
|
# It probably helps if you think of the VPN namespace as if it was a
|
|
# separate machine. From the network point of view, that's what it is.
|
|
# It just happens to share the file system (and a lot of other stuff)
|
|
# with your _real_ computer.
|
|
#
|
|
# You can configure various other services to use this for connections into
|
|
# your VPN, as follows...
|
|
#
|
|
# SOCKS
|
|
#
|
|
# SSH has a built-in SOCKS server. If you run 'ssh -D 1080 fec0::1', SSH
|
|
# will listen on port 1080 and will forward connections through the SSH
|
|
# connection and give you full SOCKS access to the VPN.
|
|
#
|
|
# It might make sense to make this script automatically start a SSH
|
|
# connection with SOCKS enabled, if you want that to be available.
|
|
#
|
|
# SSH
|
|
#
|
|
# The OpenSSH client is capable of connecting to a SSH server by running
|
|
# and arbitrary command and using its stdin/stdout, instead of having to
|
|
# make a direct TCP connection to the server.
|
|
#
|
|
# So you can configure it to SSH into the VPN 'namespace' and use the
|
|
# 'netcat' command for certain connections. You can add something like
|
|
# this to your ~/.ssh/config:
|
|
#
|
|
# Host *.example.internal
|
|
# ProxyCommand ssh fec0::1 exec nc %h %p
|
|
#
|
|
# (This also works if your company has made the mistake of overloading the
|
|
# public 'company.com' domain for internal purposes, instead of doing the
|
|
# sensible thing and using a _separate_ domain.)
|
|
#
|
|
# MAIL
|
|
#
|
|
# Like SSH, most decent mail clients are able to run a command to connect
|
|
# to their IMAP server instead of being limited to a direct TCP connection.
|
|
#
|
|
# Commands you might want to use could look like...
|
|
# ssh $MAILSRV exec /usr/sbin/dovecot --exec-mail imap
|
|
# ssh $MAILSRV exec /usr/sbin/wu-imapd
|
|
# ssh fec0::1 openssl s_client -quiet -connect $MAILSRV:993 -crlf 2>/dev/null
|
|
#
|
|
# Where '$MAILSRV' is the name of your mail server, of course.
|
|
#
|
|
# Note that the first two assume that you've set up SSH as described above,
|
|
# so that SSH connections to the mail server work transparently. For the
|
|
# latter, you probably need to redirect stderr to /dev/null to avoid
|
|
# spurious output from openssl configuring your mail client (openssl doesn't
|
|
# seem to take the -quiet option very seriously).
|
|
#
|
|
# For mail clients which _cannot_ simply run an external command for their
|
|
# connection, first file a bug and then see the 'PORT FORWARDING' section
|
|
# below.
|
|
#
|
|
# WEB
|
|
#
|
|
# Firefox and most other browsers should understand a 'proxy autoconfig'
|
|
# file and that can tell it to use SOCKS (see above) for certain domains.
|
|
# A suitable PAC file might look like this:
|
|
#
|
|
# function FindProxyForURL(url, host)
|
|
# {
|
|
# if (dnsDomainIs(host, "company.com"))
|
|
# return "SOCKS5 localhost:1080";
|
|
#
|
|
# return "DIRECT";
|
|
# }
|
|
#
|
|
# PORT FORWARDING
|
|
#
|
|
# You can use SSH to forward certain ports, of course -- but there's another,
|
|
# simpler option.
|
|
#
|
|
# The included example of xinetd configuration will accept connections on
|
|
# port 25 and 993 of the host fec0::1, and will automatically forward them
|
|
# using netcat to the appropriate hosts within your VPN. This can be extended
|
|
# to forward other ports.
|
|
#
|
|
# OTHER SERVICES
|
|
#
|
|
# Most other services should also be available through SSH, through the
|
|
# SOCKS proxy, or by port forwarding in some way. If all else fails, you
|
|
# can just ssh into the vpn namespace (ssh fec0::1) and have a shell with
|
|
# complete access.
|
|
#
|
|
# BREAKING OUT OF THE VPN
|
|
#
|
|
# If you ssh _into_ your machine from the VPN side, you'll get a shell in
|
|
# the VPN namespace. To 'break out' from there, you may want to ssh to
|
|
# fec0::2 which is the normal machine.
|
|
#
|
|
# CONTROLLING ACCESS TO THE VPN
|
|
#
|
|
# One serious flaw with the _traditional_ VPN setup is that it allows
|
|
# _all_ processes and users on the machine to have free access to the
|
|
# VPN, instead of only the user who is supposed to have access. The
|
|
# approach implemented here allows you to fix that, by running the
|
|
# SSHD in the VPN namespace with a separate configuration that allows
|
|
# only certain users to connect to it.
|
|
#
|
|
# (Be aware that using port forwarding or using SSH to run a SOCKS proxy
|
|
# will negate that benefit, of course)
|
|
#
|
|
# David Woodhouse <dwmw2@infradead.org>
|
|
# 2009-06-06
|
|
|
|
IP=/sbin/ip
|
|
SCRIPTNAME=`basename $0`
|
|
NETNSNAME=$SCRIPTNAME
|
|
|
|
# XINETDCONF=`dirname $0`/xinetd.netns.conf
|
|
|
|
PS4=" \$\$+ "
|
|
connect_parent()
|
|
{
|
|
export PARENT_NETNS=$$
|
|
|
|
$IP link set $TUNDEV down
|
|
if ! $IP link set $TUNDEV netns $$; then
|
|
echo "Setting network namespace for $TUNDEV failed"
|
|
echo "Perhaps you don't have network namespace support in your kernel?"
|
|
exit 1
|
|
fi
|
|
|
|
$IP netns delete $NETNSNAME >/dev/null 2>&1
|
|
if ! $IP netns add $NETNSNAME; then
|
|
echo "Creating network namespace $NETNSNAME failed"
|
|
echo "Perhaps you don't have network namespace support in your kernel?"
|
|
exit 1
|
|
fi
|
|
|
|
$IP link add dev $TUNDEV-vpnssh%d type veth
|
|
# XXX: Assume vpnssh0 and vpnssh1; ip doesn't tell us!
|
|
LOCALDEV=$TUNDEV-vpnssh0
|
|
export REMOTEDEV=$TUNDEV-vpnssh1
|
|
|
|
$IP netns exec $NETNSNAME $0 $@ &
|
|
CHILDPID=$!
|
|
|
|
# XXX: If we do this too soon (before the unshare), we're just
|
|
# giving it to our _own_ netns. which achieves nothing.
|
|
# So give it away until we _can't_ give it away any more.
|
|
while $IP link set $REMOTEDEV netns $CHILDPID 2>/dev/null; do
|
|
sleep 0.1
|
|
done
|
|
|
|
# Give away the real VPN tun device too
|
|
$IP link set $TUNDEV netns $CHILDPID
|
|
|
|
$IP link set $LOCALDEV up
|
|
$IP addr add fec0::2/64 dev $LOCALDEV
|
|
|
|
echo "VPN now accessible through 'ssh fec0::1'"
|
|
if ! grep -q 127.0.0.1 /etc/resolv.conf; then
|
|
echo "WARNING: Your host needs to be running a local dnsmasq or named"
|
|
echo "WARNING: and /etc/resolv.conf needs to point to 127.0.0.1"
|
|
# XXX: We could probably fix that for ourselves...
|
|
fi
|
|
}
|
|
|
|
connect()
|
|
{
|
|
if [ -z "$PARENT_NETNS" ]; then
|
|
connect_parent
|
|
exit 0
|
|
fi
|
|
|
|
# This is the child, which remains running in the background
|
|
|
|
# Wait for the tundev to appear in this namespace
|
|
while ! ip link show $TUNDEV >/dev/null 2>&1 ; do
|
|
sleep 0.1
|
|
done
|
|
|
|
# Set up Legacy IP in the new namespace
|
|
$IP link set lo up
|
|
$IP link set $TUNDEV up
|
|
if [ -n "$INTERNAL_IP4_ADDRESS" ]; then
|
|
$IP -4 addr add $INTERNAL_IP4_ADDRESS dev $TUNDEV
|
|
$IP -4 route add default dev $TUNDEV
|
|
fi
|
|
if [ -n "$INTERNAL_IP6_ADDRESS" ]; then
|
|
$IP -6 addr add $INTERNAL_IP6_ADDRESS dev $TUNDEV
|
|
$IP -6 route add default dev $TUNDEV
|
|
fi
|
|
if [ "$INTERNAL_IP4_MTU" != "" ]; then
|
|
$IP link set $TUNDEV mtu $INTERNAL_IP4_MTU
|
|
fi
|
|
|
|
# Set up the veth back to the real system
|
|
$IP link set $REMOTEDEV up
|
|
$IP -6 addr add fec0::1/64 dev $REMOTEDEV
|
|
|
|
# Run dnsmasq to provide DNS service for this namespace.
|
|
# The host needs to be running its own local nameserver/dnsmasq and
|
|
# /etc/resolv.conf should be pointing to 127.0.0.1 already.
|
|
DNSMASQ_ARGS="--port=53 -k -R"
|
|
for NS in $INTERNAL_IP4_DNS; do
|
|
DNSMASQ_ARGS="$DNSMASQ_ARGS -S $NS"
|
|
done
|
|
for NS in $INTERNAL_IP6_DNS; do
|
|
DNSMASQ_ARGS="$DNSMASQ_ARGS -S $NS"
|
|
done
|
|
/usr/sbin/dnsmasq $DNSMASQ_ARGS &
|
|
DNSMASQ_PID=$!
|
|
|
|
# Set up sshd
|
|
/usr/sbin/sshd -D &
|
|
SSHD_PID=$!
|
|
|
|
XINETD_PID=
|
|
if [ "$XINETDCONF" != "" ] && [ -r "$XINETDCONF" ]; then
|
|
/usr/sbin/xinetd -dontfork -f $XINETDCONF &
|
|
XINETD_PID=$!
|
|
fi
|
|
|
|
# Wait for the veth link to be closed...
|
|
while ip link show $REMOTEDEV >/dev/null 2>&1 ; do
|
|
sleep 1
|
|
done
|
|
|
|
kill -TERM $DNSMASQ_PID
|
|
kill -TERM $SSHD_PID
|
|
if [ "$XINETD_PID" != "" ]; then
|
|
kill -TERM $XINETD_PID
|
|
fi
|
|
# Wait a while to avoid tun BUG() if we quit and the netns goes away
|
|
# before vpnc/openconnect closes its tun fd.
|
|
sleep 1
|
|
}
|
|
|
|
disconnect()
|
|
{
|
|
# Kill our end of the veth link, leaving the child script to clean up
|
|
$IP link del $TUNDEV-vpnssh0
|
|
|
|
while ! $IP netns delete $NETNSNAME >/dev/null 2>&1 ; do
|
|
sleep 0.1
|
|
done
|
|
}
|
|
|
|
case $reason in
|
|
connect)
|
|
connect
|
|
;;
|
|
|
|
disconnect)
|
|
disconnect
|
|
;;
|
|
esac
|
|
|