diff --git a/install.sh b/install.sh index c5a45ed..9bdf6e0 100755 --- a/install.sh +++ b/install.sh @@ -126,4 +126,5 @@ install_data blink/51-blink.rules "$DESTDIR$udev_rulesdir install_data clusterd/clusterd.service "$DESTDIR$systemdunitdir/clusterd.service" install_exec clusterd/clusterd "$DESTDIR$usr_sbindir/clusterd" install_exec setuid/setuid "$DESTDIR$usr_sbindir/setuid" +install_exec uvpn/uvpn "$DESTDIR$usr_bindir/uvpn" exit diff --git a/uvpn/uvpn b/uvpn/uvpn new file mode 100755 index 0000000..c678551 --- /dev/null +++ b/uvpn/uvpn @@ -0,0 +1,303 @@ +#! /bin/bash + +die() { + echo "$@">&2 + exit 1 +} + +die_usage() { + cat </var/run/uvpn/$USER/dhclient-script << '_EOF_' +#!/bin/bash + +ip=/sbin/ip + +### +### DHCPv4 Handlers +### + +if [ x$new_broadcast_address != x ]; then + new_broadcast_arg="broadcast $new_broadcast_address" +fi +if [ x$old_broadcast_address != x ]; then + old_broadcast_arg="broadcast $old_broadcast_address" +fi +if [ x$new_subnet_mask != x ]; then + new_subnet_arg="netmask $new_subnet_mask" +fi +if [ x$old_subnet_mask != x ]; then + old_subnet_arg="netmask $old_subnet_mask" +fi +if [ x$alias_subnet_mask != x ]; then + alias_subnet_arg="netmask $alias_subnet_mask" +fi +if [ x$new_interface_mtu != x ]; then + mtu_arg="mtu $new_interface_mtu" +fi +if [ x$IF_METRIC != x ]; then + metric_arg="metric $IF_METRIC" +fi + +if [ x$reason = xBOUND ] || [ x$reason = xRENEW ] || \ + [ x$reason = xREBIND ] || [ x$reason = xREBOOT ]; then + + if [ x$old_ip_address != x ] && [ x$alias_ip_address != x ] && \ + [ x$alias_ip_address != x$old_ip_address ]; then + # Possible new alias. Remove old alias. + ifconfig $interface:0- inet 0 + fi + if [ x$old_ip_address != x ] && [ x$old_ip_address != x$new_ip_address ]; then + # IP address changed. Bringing down the interface will delete all routes, + # and clear the ARP cache. + ifconfig $interface inet 0 down + + fi + if [ x$old_ip_address = x ] || [ x$old_ip_address != x$new_ip_address ] || \ + [ x$reason = xBOUND ] || [ x$reason = xREBOOT ]; then + + ifconfig $interface inet $new_ip_address $new_subnet_arg \ + $new_broadcast_arg $mtu_arg + # Add a network route to the computed network address. + for router in $new_routers; do + if [ "x$new_subnet_mask" = "x255.255.255.255" ] ; then + ip route add $router dev $interface + fi + ip route add default via $router dev $interface $metric_arg + done + else + # we haven't changed the address, have we changed other options + # that we wish to update? + if [ x$new_routers != x ] && [ x$new_routers != x$old_routers ] ; then + # if we've changed routers delete the old and add the new. + for router in $old_routers; do + ip route del default via $router + done + for router in $new_routers; do + if [ "x$new_subnet_mask" = "x255.255.255.255" ] ; then + ip route add $router dev $interface + fi + ip route add default via $router dev $interface $metric_arg + done + fi + fi + if [ x$new_ip_address != x$alias_ip_address ] && [ x$alias_ip_address != x ]; + then + ifconfig $interface:0- inet 0 + ifconfig $interface:0 inet $alias_ip_address $alias_subnet_arg + ip route add $alias_ip_address dev $interface:0 + fi + exit 0 +fi + +if [ x$reason = xEXPIRE ] || [ x$reason = xFAIL ] || [ x$reason = xRELEASE ] \ + || [ x$reason = xSTOP ]; then + if [ x$alias_ip_address != x ]; then + # Turn off alias interface. + ifconfig $interface:0- inet 0 + fi + if [ x$old_ip_address != x ]; then + # Shut down interface, which will delete routes and clear arp cache. + ifconfig $interface inet 0 down + fi + if [ x$alias_ip_address != x ]; then + ifconfig $interface:0 inet $alias_ip_address $alias_subnet_arg + ip route add $alias_ip_address dev $interface:0 + fi + exit 0 +fi + +if [ x$reason = xTIMEOUT ]; then + if [ x$alias_ip_address != x ]; then + ifconfig $interface:0- inet 0 + fi + ifconfig $interface inet $new_ip_address $new_subnet_arg \ + $new_broadcast_arg $mtu_arg + set $new_routers + if ping -q -c 1 $1; then + if [ x$new_ip_address != x$alias_ip_address ] && \ + [ x$alias_ip_address != x ]; then + ifconfig $interface:0 inet $alias_ip_address $alias_subnet_arg + ip route add $alias_ip_address dev $interface:0 + fi + for router in $new_routers; do + if [ "x$new_subnet_mask" = "x255.255.255.255" ] ; then + ip route add $router dev $interface + fi + ip route add default via $router dev $interface $metric_arg + done + exit 0 + fi + ifconfig $interface inet 0 down + exit 1 +fi + +exit 0 +_EOF_ + chmod +x /var/run/uvpn/$USER/dhclient-script +} + +have_interface() { + local if="$1" + ip link show "$1" >/dev/null 2>&1 +} + +cmd_start() { + test $# -eq 0 || die_usage + + sudo $0 start_as_root || exit 1 + create_dhclient-script + cat >/var/run/uvpn/$USER/resolv.conf<<_EOF_ +search molgen.mpg.de +nameserver 141.14.16.1 +_EOF_ + cmd_exec ip link set lo up + cmd_exec ip link set "uvpn.$USER.1" up + cmd_exec mount --bind /var/run/uvpn/$USER/resolv.conf /etc/resolv.conf + cmd_exec dhclient -4 -v \ + -lf /var/run/uvpn/$USER/dhclient.leases \ + -pf /var/run/uvpn/$USER/dhclient.pid \ + -sf /var/run/uvpn/$USER/dhclient-script \ + uvpn.$USER.1 + exit +} + +cmd_start_as_root() { + test $# -eq 0 || die_usage + + test $(id -u) -eq 0 || die "must be root" + user="$SUDO_USER" + test -n "$user" || die "must be called via sudo" + uid=$(id -u "$user") 2>/dev/null || die "$user: no such user" + umask 022 + have_interface vlan.guest0 || die "vlan.guest0 not available. This system is not prepared to run $0. Please contact helpdesk@molgen.mpg.de" + + findmnt "/run/uvpn/$user/ns" >/dev/null 2>&1 && die "container already started" + + if ! have_interface "uvpn.$user.0"; then + ip link add "uvpn.$user.0" type veth peer name "uvpn.$user.1" + fi + if ! have_interface br.guest0; then + brctl addbr br.guest0 + brctl addif br.guest0 vlan.guest0 + fi + brctl addif br.guest0 "uvpn.$user.0" + ip link set "uvpn.$user.0" up + ip link set vlan.guest0 up + ip link set br.guest0 up + + mkdir -p "/run/uvpn/$user/ns" + chown "$user" "/run/uvpn/$user" + for ns in user net mnt;do + touch "/run/uvpn/$user/ns/$ns" + done + mount --bind "/run/uvpn/$user/ns" "/run/uvpn/$user/ns" + mount --make-private "/run/uvpn/$user/ns" + + pid=$(setuid $uid unshare --mount --user --net bash -c 'sleep 30 > /dev/null&echo $!') + for ns in user net mnt;do + mount --bind /proc/$pid/ns/$ns "/run/uvpn/$user/ns/$ns" + done + echo "0 $uid 1" > /proc/$pid/uid_map + echo "0 $(id -g "$user") 1" > /proc/$pid/gid_map + ip link set "uvpn.$user.1" netns $pid + kill $pid +} + +cmd_stop() { + test $# -eq 0 || die_usage + user=$USER + uid=$(id -u "$user") + if findmnt "/run/uvpn/$user/ns/net" > /dev/null; then + inode_net=$(stat --format %i "/run/uvpn/$user/ns/net") + for pid in $(cd /proc;ls -1d [0-9]*|sort -n); do + inode="$(stat -L --format %i /proc/$pid/ns/net 2>/dev/null)" || continue + test $inode == $inode_net || continue + cmd=$(cat /proc/$pid/cmdline|sed 's/\x0/ /g') + echo "kill --> $pid $cmd" + kill -9 $pid + found_processes=1 + done + test -n "$found_processes" && sleep 1 + fi + sudo $0 stop_as_root +} + +cmd_stop_as_root() { + test $# -eq 0 || die_usage + test $(id -u) -eq 0 || die "must be root" + user="$SUDO_USER" + test -n "$user" || die "must be called via sudo" + uid=$(id -u "$user") 2>/dev/null || die "$user: no such user" + + for ns in user net mnt;do + while umount "/run/uvpn/$user/ns/$ns" 2>/dev/null; do true; done + done + while umount "/run/uvpn/$user/ns" 2>/dev/null; do true; done + ip link del "uvpn.$user.0" 2>/dev/null +} + +cmd_exec() { + findmnt "/run/uvpn/$USER/ns" >/dev/null 2>&1 || die "container not started" + nsenter \ + --wd=$(pwd) \ + --net="/run/uvpn/$USER/ns/net" \ + --user="/run/uvpn/$USER/ns/user" \ + --mount="/run/uvpn/$USER/ns/mnt" \ + "$@" +} + +cmd_show() { + user="$1";shift + test $# -eq 0 || die_usage + test -z "$user" && user=$USER + uid=$(id -u "$user") 2>/dev/null || die "$user: no such user" + findmnt "/run/uvpn/$user/ns/net" >/dev/null 2>/dev/null || { echo "Container not started"; exit; } + inode_net=$(stat --format %i "/run/uvpn/$user/ns/net") + echo "Processes running in this container:" + for pid in $(cd /proc;ls -1d [0-9]*|sort -n); do + inode="$(stat -L --format %i /proc/$pid/ns/net 2>/dev/null)" || continue + test $inode == $inode_net || continue + cmd=$(cat /proc/$pid/cmdline|sed 's/\x0/ /g') + echo " $pid $cmd" + found_processes=1 + done + test -n "$found_processes" || echo " (none)" +} + +cmd="$1";shift +case "$cmd" in + start) + cmd_start "$@" + ;; + start_as_root) + cmd_start_as_root "$@" + ;; + stop) + cmd_stop "$@" + ;; + stop_as_root) + cmd_stop_as_root "$@" + ;; + exec) + cmd_exec "$@" + ;; + show) + cmd_show "$@" + ;; + *) + die_usage + ;; +esac