From a5ad1d26dfad88a0b758af66de23ee3507101ad2 Mon Sep 17 00:00:00 2001 From: Urban Wallasch Date: Wed, 24 Apr 2019 13:02:58 +0200 Subject: [PATCH] * Added `DEBUG` option to enable verbose debugging output. * Only consider the first configured IP address in get_ip_pfl(). * Perform a simple sanity check before killing arbitrary PIDs. * Use single combined $XCMD option (fallback to $XCMD1 plus $XCMD1_ARGS provided). * Avoid gratuitous use of cat. * Use absolute paths for basic utilities. * Lots of small improvements. --- nns.sh | 120 +++++++++++++++++++++++++----------------------- nns_example.cfg | 9 ++-- 2 files changed, 67 insertions(+), 62 deletions(-) diff --git a/nns.sh b/nns.sh index 271b6eb..d195eab 100755 --- a/nns.sh +++ b/nns.sh @@ -42,45 +42,45 @@ BRIDGE_MAC= NAMESRV= # Program with optional arguments to run in the new network namespace: -XCMD1= -XCMD1_ARGS= +XCMD= # Virtual Ethernet to connect new network namespace to default namespace: VETH_HOST=veth_host VETH_GUEST=veth_guest +# Set to 'true' to enable debugging output: +DEBUG=false + # # End of configuration. ###################################################################### -#set -x - -SHORTNAME=${0##*/} - usemsg() { - echo "Usage: $1 name start|stop|restart|exec cmd [arg ...]" + echo "Usage: $1 name start|stop|restart" + echo " $1 name exec cmd [arg ...]" echo " where name is the name of a config file or a network namespace name," echo " and cmd is a command to execute in the context of an existing namespace." } +SHORTNAME=${0##*/} if [ $# -lt 2 ]; then usemsg $SHORTNAME exit 1 fi -# Become root if not already: -[ "root" != "$USER" ] && exec sudo -E $0 "$@" - # Check, if first argument looks like a config file name and # if so source it. Otherwise $1 is interpreted as namespace name. if [ -f "$1" ] && [ -r "$1" ] ; then . "$1" else - # Clear the proposed namespace name of blank characters: - NNSNAME=${1//[[:space:]]} + NNSNAME="$1" fi +[ "$DEBUG" == "true" ] && set -x + +# Clear the proposed namespace name of blank characters: +NNSNAME=${NNSNAME//[[:space:]]} # Check and or sanitize some settings: if [ -z "$NNSNAME" ] ; then echo "No network namespace name provided." @@ -95,43 +95,40 @@ fi [ -z "$BRIDGE_IF" ] && BRIDGE_IF="br0" [ -z "$VETH_HOST" ] && VETH_HOST="veth_host" [ -z "$VETH_GUEST" ] && VETH_GUEST="veth_guest" +[ -z "$XCMD" ] && XCMD="$XCMD1 $XCMD1_ARGS" - -# Locate essential commands: -IP=$(command -v ip) -if [ -z $IP ] ; then - echo "please install the iproute2 package" - exit 1 -fi -IPTABLES=$(command -v iptables) -if [ -z $IPTABLES ] ; then - echo "please install the iptables package" - exit 1 -fi -if [ -z "$BRIDGE_ADDR" ] || [ -z "$GUEST_ADDR" ] ; then - DHCLIENT=$(command -v dhclient) - if [ -z $DHCLIENT ] ; then - echo "please install the dhclient package" +locate_cmd() { + local cmd=$(command -v "$2") + if [ -z "$cmd" ] ; then + local pkg="$2" ; [ -n "$3" ] && pkg="$3" + echo "please install the $pkg package" exit 1 fi + eval "$1=$cmd" +} + +# Become root if not already: +if [ "$EUID" != "0" ] ; then + locate_cmd SUDO sudo + exec $SUDO -E $0 "$@" fi -# Locate user provided command: -if [ -n "$XCMD1" ] ; then - XCMD=$(command -v $XCMD1) - if [ -z $XCMD ] ; then - echo "command not found: $XCMD1" - exit 1 - fi +# Locate essential commands: +locate_cmd GREP grep +locate_cmd AWK awk +locate_cmd IP ip iproute2 +locate_cmd IPTABLES iptables +if [ -z "$BRIDGE_ADDR" ] || [ -z "$GUEST_ADDR" ] ; then + locate_cmd DHCLIENT dhclient fi # Assign and create directory to keep runtime information: RUNDIR=/var/run/newns/$NNSNAME /bin/mkdir -p $RUNDIR -# Get IPv4 address/prefix_length of an interface: +# Get first IPv4 address/prefix_length of an interface: get_ip_pfl() { - $IP addr show $1 | grep -F 'inet ' | awk -F ' ' '{print $2}' + $IP addr show $1 | $GREP -m 1 -F 'inet ' | $AWK -F ' ' '{print $2}' } # IPv4 math helpers; shamelessly ripped from: @@ -175,7 +172,22 @@ network() { # Check whether a namespace with given name exists: exist_nns() { - $IP netns list | /bin/grep -F $1 1>/dev/null 2>&1 + $IP netns list | $GREP -F $1 1>/dev/null 2>&1 +} + + +# Run specified command inside a network namespace: +run_nns() { + if ! exist_nns $1 ; then + echo "Network namespace $1 does not exist, please choose another name." + exit 1 + fi + locate_cmd SU su "su (util-linux?)" + local NNS=$1 + shift + local bname=${1##*/} + $IP netns exec $NNS $SU -p -c "$*" $SUDO_USER & + $IP netns exec $NNS echo "$!" > "$RUNDIR/$bname.$!.pid" } @@ -187,7 +199,7 @@ start_nns() { fi # Enable IPv4 forwarding after saving the previous state: - cat /proc/sys/net/ipv4/ip_forward > "$RUNDIR/ipv4fwd.sav" + echo $( "$RUNDIR/ipv4fwd.sav" echo 1 > /proc/sys/net/ipv4/ip_forward # Take care of DNS in the new namespace: @@ -198,7 +210,7 @@ start_nns() { echo "nameserver $NS" >> /etc/netns/$1/resolv.conf done else - cp /etc/resolv.conf /etc/netns/$1/resolv.conf + /bin/cp /etc/resolv.conf /etc/netns/$1/resolv.conf fi # Create the new namespace, the veth cable and the bridge interface: @@ -241,8 +253,7 @@ start_nns() { # Run the configured command, if any, inside the namespace: if [ -n "$XCMD" ] ; then - $IP netns exec $1 su -p -c "$XCMD $XCMD1_ARGS" $SUDO_USER & - $IP netns exec $1 echo "$!" > "$RUNDIR/xcmd_.pid" + run_nns $1 $XCMD fi } @@ -258,13 +269,19 @@ stop_nns() { for PIDF in $(find $RUNDIR -maxdepth 1 -name '*.pid' 2> /dev/null) ; do # Note: This can lead to nasty surprises in case a process # already terminated and its PID has been reused! - /bin/kill -TERM $(cat "$PIDF") + # The check below is insufficient and by no means safe: + local cmd=${PIDF##*/} + if $GREP -F "${cmd%%.*}" "/proc/$(<"$PIDF")/cmdline" 1>/dev/null 2>&1 ; then + /bin/kill -TERM $(<"$PIDF") + else + echo "Not killing $(<"$PIDF") [$cmd]: no matching process found." + fi /bin/rm "$PIDF" done # Restore saved state of IPv4 forwarding: - cat $RUNDIR/ipv4fwd.sav > /proc/sys/net/ipv4/ip_forward - rm $RUNDIR/ipv4fwd.sav + echo $(<"$RUNDIR/ipv4fwd.sav") > /proc/sys/net/ipv4/ip_forward + /bin/rm $RUNDIR/ipv4fwd.sav # Burn the bridge: IP_PFL=$(get_ip_pfl $BRIDGE_IF) @@ -293,19 +310,6 @@ stop_nns() { } -# Run a specified command inside a network namespace: -run_nns() { - if ! exist_nns $1 ; then - echo "Network namespace $1 does not exist, please choose another name." - exit 1 - fi - NNS=$1 - shift - # Run the specified command inside the namespace: - $IP netns exec $NNS su -p -c "$*" $SUDO_USER & -} - - case $2 in start) start_nns $NNSNAME diff --git a/nns_example.cfg b/nns_example.cfg index 2b55d0f..9cfa16a 100644 --- a/nns_example.cfg +++ b/nns_example.cfg @@ -28,11 +28,12 @@ BRIDGE_MAC=FE:17:90:00:BA:BE # namespace. If empty, the host resolv.conf will be copied: NAMESRV="8.8.8.8 8.8.4.4" -# Program with optional arguments to run in the new network namespace: -XCMD1="xterm" -XCMD1_ARGS="-title $NNSNAME" - # Virtual Ethernet to connect new network namespace to default namespace: VETH_HOST=veth_host VETH_GUEST=veth_guest +# Program with optional arguments to run in the new network namespace: +XCMD="xterm -title $NNSNAME" + +# Set to 'true' to enable debugging output: +DEBUG=false -- 2.30.2