# 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
# 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
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
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:
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() {
# 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
# 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
$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
$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:
# 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
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
# 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
/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