From af2dd1d97513011c72f82a6aa1c94d6b5dfe5d5d Mon Sep 17 00:00:00 2001 From: Urban Wallasch Date: Tue, 23 Apr 2019 10:52:17 +0200 Subject: [PATCH] * Fixed iptables invocation that broke a few commits back. * Added DHCP support for bridge interface configuration. * Calculate bridge network from its IP address and prefix length. * Moved most of the configuration presets to separate nns_example.cfg file. * Several minor fixes and improvements. --- nns.sh | 180 ++++++++++++++++++++++++++++++++++++------------ nns_example.cfg | 38 ++++++++++ 2 files changed, 175 insertions(+), 43 deletions(-) create mode 100644 nns_example.cfg diff --git a/nns.sh b/nns.sh index 2605635..b0e1e39 100755 --- a/nns.sh +++ b/nns.sh @@ -5,43 +5,62 @@ # the host (default) network namespace. # -############################### -# Default configuration; can be overriden by specifying -# a shell script fragment as first parameter: +###################################################################### +# To-Do: +# ------ +# +# * Find a nice way to keep dhclient when run within a network namespace +# from littering the system with /etc/resolv.conf.dhclient-new.XXXX +# files while attempting to overwrite /etc/resolv.conf. +# Current workaround is to indiscriminately remove such files. +# Cf: https://stackoverflow.com/questions/38102481/how-can-dhclient-be-made-namespace-aware ) +# +##### + +###################################################################### +# Default configuration; can be overridden by passing the name +# of a shell script fragment containing appropriate variable +# assignments (see nns_example.cfg) as the first argument. +# +# Note: +# - Empty *_ADDR parameters will lead to DHCP being used. +# - Empty *_BCAST parameters will set to '+', i.e. auto calculate. +# +# Name of network namespace: NNSNAME= +# Base (e.g. VPN) host interface to link network namespace to: BASE_IF=vpn_tap1 -# If GUEST_ADDR is empty, DHCP is used to figure stuff out: +# Properties of virtual interface to create in network namespace: GUEST_ADDR= -GUEST_NET=192.168.30.0 -# '+' means auto calculate broadcast address: -GUEST_BCAST=+ -GUEST_PLEN=24 -GUEST_MAC=FE:17:90:00:00:00 +GUEST_PLEN= +GUEST_BCAST= +GUEST_MAC= +# Properties of bridge interface to connect with network namespace: BRIDGE_IF=br0 -BRIDGE_ADDR=192.168.30.3 -# '+' means auto calculate broadcast address: -BRIDGE_BCAST=+ -BRIDGE_PLEN=24 -BRIDGE_MAC=FE:17:90:00:00:01 - -# List of nameservers to provide DNS in the new network namespace; -# If empty, host resolv.conf will be used. -NAMESRV="8.8.8.8 8.8.4.4" - -# Program to run in the new network namespace: -XCMD1=xterm +BRIDGE_ADDR= +BRIDGE_PLEN= +BRIDGE_BCAST= +BRIDGE_MAC= + +# Space separated list of nameservers to provide DNS in the new network +# namespace. If empty, the host resolv.conf will be copied: +NAMESRV= + +# Program with optional arguments to run in the new network namespace: +XCMD1= XCMD1_ARGS= # Virtual Ethernet to connect new network namespace to default namespace: VETH_HOST=veth_host VETH_GUEST=veth_guest -# Configuration end. -############################### +# +# End of configuration. +###################################################################### #set -x @@ -62,8 +81,8 @@ fi # Become root if not already: [ "root" != "$USER" ] && exec sudo -E $0 "$@" -# Check, if first parameter looks like a config file name. -# If so: source it. Otherwise take $1 as namespace name. +# 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 @@ -71,6 +90,22 @@ else NNSNAME=${1//[[:space:]]} fi +# Check and or sanitize some settings: +if [ -z "$NNSNAME" ] ; then + echo "No network namespace name provided." + exit 1 +fi +if [ -z "$BASE_IF" ] ; then + echo "No base interface specified." + exit 1 +fi +[ -z "$GUEST_BCAST" ] && GUEST_BCAST="+" +[ -z "$BRIDGE_BCAST" ] && BRIDGE_BCAST="+" +[ -z "$BRIDGE_IF" ] && BRIDGE_IF="br0" +[ -z "$VETH_HOST" ] && VETH_HOST="veth_host" +[ -z "$VETH_GUEST" ] && VETH_GUEST="veth_guest" + + # Locate essential commands: IP=$(command -v ip) if [ -z $IP ] ; then @@ -82,10 +117,12 @@ if [ -z $IPTABLES ] ; then echo "please install the iptables package" exit 1 fi -DHCLIENT=$(command -v dhclient) -if [ -z $DHCLIENT ] ; then - echo "please install the dhclient package" - exit 1 +if [ -z "$BRIDGE_ADDR" ] || [ -z "$GUEST_ADDR" ] ; then + DHCLIENT=$(command -v dhclient) + if [ -z $DHCLIENT ] ; then + echo "please install the dhclient package" + exit 1 + fi fi # Locate user provided command: @@ -101,6 +138,49 @@ fi RUNDIR=/var/run/newns/$NNSNAME /bin/mkdir -p $RUNDIR +# Get IPv4 address/prefix_length of an interface: +get_ip_pfl() { + $IP addr show $1 | grep -F 'inet ' | awk -F ' ' '{print $2}' +} + +# IPv4 math helpers; shamelessly ripped from: +# https://stackoverflow.com/questions/15429420/given-the-ip-and-netmask-how-can-i-calculate-the-network-address-using-bash#32690695 +ip2int() { + local a b c d + { IFS=. read a b c d; } <<< $1 + echo $(((((((a << 8) | b) << 8) | c) << 8) | d)) +} + +int2ip() { + local ui32=$1; shift + local ip n + for n in 1 2 3 4; do + ip=$((ui32 & 0xff))${ip:+.}$ip + ui32=$((ui32 >> 8)) + done + echo $ip +} + +netmask() { + # Example: netmask 24 => 255.255.255.0 + local mask=$((0xffffffff << (32 - $1))); shift + int2ip $mask +} + +broadcast() { + # Example: broadcast 192.0.2.0 24 => 192.0.2.255 + local addr=$(ip2int $1); shift + local mask=$((0xffffffff << (32 -$1))); shift + int2ip $((addr | ~mask)) +} + +network() { + # Example: network 192.0.2.0 24 => 192.0.2.0 + local addr=$(ip2int $1); shift + local mask=$((0xffffffff << (32 -$1))); shift + int2ip $((addr & mask)) +} + # Check whether a namespace with given name exists: exist_nns() { @@ -111,8 +191,7 @@ exist_nns() { # Set up a new network namespace: start_nns() { if exist_nns $1 ; then - echo "Network namespace $1 already exists," - echo "please choose another name." + echo "Network namespace $1 already exists, please choose another name." exit 1 fi @@ -134,14 +213,22 @@ start_nns() { # Create the new namespace, the veth cable and the bridge interface: $IP netns add $1 $IP link add $VETH_HOST type veth peer name $VETH_GUEST - $IP link set dev $VETH_GUEST address $GUEST_MAC + [ -n "$GUEST_MAC" ] && $IP link set dev $VETH_GUEST address $GUEST_MAC $IP link add $BRIDGE_IF type bridge - $IP link set dev $BRIDGE_IF address $BRIDGE_MAC + [ -n "$BRIDGE_MAC" ] && $IP link set dev $BRIDGE_IF address $BRIDGE_MAC $IP link set $BASE_IF master $BRIDGE_IF $IP link set $VETH_HOST master $BRIDGE_IF - $IP addr add $BRIDGE_ADDR/$BRIDGE_PLEN broadcast $BRIDGE_BCAST dev $BRIDGE_IF $IP link set dev $VETH_HOST up - $IP link set dev $BRIDGE_IF up + if [ -z "$BRIDGE_ADDR" ] ; then + $DHCLIENT -pf "$RUNDIR/dhclient.$BRIDGE_IF.pid" -v $BRIDGE_IF + local IP_PFL=$(get_ip_pfl $BRIDGE_IF) + BRIDGE_ADDR=${IP_PFL%/*} + BRIDGE_PLEN=${IP_PFL#*/} + else + $IP addr add $BRIDGE_ADDR/$BRIDGE_PLEN broadcast $BRIDGE_BCAST dev $BRIDGE_IF + $IP link set dev $BRIDGE_IF up + fi + BRIDGE_NET=$(network $BRIDGE_ADDR $BRIDGE_PLEN) # Assign the veth guest interface to the new network namespace, # set an IP address (either static or via dhclient) to it, bring @@ -149,6 +236,8 @@ start_nns() { $IP link set $VETH_GUEST netns $1 if [ -z "$GUEST_ADDR" ] ; then $IP netns exec $1 $DHCLIENT -pf "$RUNDIR/dhclient.$VETH_GUEST.pid" -v $VETH_GUEST + # Remove dhclients useless remains: + rm /etc/resolv.conf.dhclient-new.* else $IP netns exec $1 $IP addr add $GUEST_ADDR/$GUEST_PLEN broadcast $GUEST_BCAST dev $VETH_GUEST $IP netns exec $1 $IP link set dev $VETH_GUEST up @@ -156,7 +245,7 @@ start_nns() { $IP netns exec $1 $IP link set dev lo up # Set up routing: - $IPTABLES -t nat -A POSTROUTING --source $GUEST_NET --jump MASQUERADE + $IPTABLES -t nat -A POSTROUTING --source $BRIDGE_NET/$BRIDGE_PLEN --jump MASQUERADE $IP netns exec $1 $IP route add default via $BRIDGE_ADDR # Run the configured command, if any, inside the namespace: @@ -170,13 +259,14 @@ start_nns() { # Tear down a previously created network namespace and clean up the mess: stop_nns() { if ! exist_nns $1 ; then - echo "Network namespace $1 does not exist," - echo "please choose another name" + echo "Network namespace $1 does not exist, please choose another name." exit 1 fi # Kill processes started in the separate namespace: - for PIDF in $RUNDIR/*.pid ; do + 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") /bin/rm "$PIDF" done @@ -186,6 +276,11 @@ stop_nns() { rm $RUNDIR/ipv4fwd.sav # Burn the bridge: + IP_PFL=$(get_ip_pfl $BRIDGE_IF) + BRIDGE_ADDR=${IP_PFL%/*} + BRIDGE_PLEN=${IP_PFL#*/} + BRIDGE_NET=$(network $BRIDGE_ADDR $BRIDGE_PLEN) + $IPTABLES -t nat -D POSTROUTING --source $BRIDGE_NET/$BRIDGE_PLEN --jump MASQUERADE $IP link set dev $BRIDGE_IF down $IP link set dev $BASE_IF nomaster $IP link set dev $VETH_HOST nomaster @@ -193,9 +288,8 @@ stop_nns() { # Unplug the virtual cable and remove network namespace: $IP netns exec $1 $IP link del $VETH_GUEST - $IP link del $VETH_HOST + $IP link del $VETH_HOST 2> /dev/null $IP netns del $1 - $IPTABLES -t nat -D POSTROUTING --source $GUEST_NET --jump MASQUERADE # Cleanup namespace DNS configuration: /bin/rm /etc/netns/$1/resolv.conf @@ -205,11 +299,11 @@ stop_nns() { /bin/rmdir $RUNDIR } + # Run a specified command inside a network namespace: run_nns() { if ! exist_nns $1 ; then - echo "Network namespace $1 does not exist," - echo "please choose another name" + echo "Network namespace $1 does not exist, please choose another name." exit 1 fi NNS=$1 diff --git a/nns_example.cfg b/nns_example.cfg new file mode 100644 index 0000000..2b55d0f --- /dev/null +++ b/nns_example.cfg @@ -0,0 +1,38 @@ +# Example configuration for use with nns.sh script. +# +# Note: +# - Empty *_ADDR parameters will lead to DHCP being used. +# - Empty *_BCAST parameters will set to '+', i.e. auto calculate. +# + +# Name of network namespace: +NNSNAME=cafebabe + +# Base (e.g. VPN) host interface to link network namespace to: +BASE_IF=vpn_tap1 + +# Properties of virtual interface to create in network namespace: +GUEST_ADDR= +GUEST_PLEN= +GUEST_BCAST= +GUEST_MAC=FE:17:90:00:CA:FE + +# Properties of bridge interface to connect with network namespace: +BRIDGE_IF=br0 +BRIDGE_ADDR= +BRIDGE_PLEN= +BRIDGE_BCAST= +BRIDGE_MAC=FE:17:90:00:BA:BE + +# Space separated list of nameservers to provide DNS in the new network +# 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 + -- 2.30.2