#!/bin/bash # # vnetbuild - linked network namespace setup for humans... # # Copyright # # Copyright (C) 2015 Phil Whineray # Copyright (C) 2015 Costa Tsaousis # # License # # 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, see . # # See the file COPYING for details. # PROGRAM_FILE="$(/bin/readlink $0)" PROGRAM_FILE="${PROGRAM_FILE:-$0}" if [ -d "${FIREHOL_OVERRIDE_PROGRAM_DIR}" ] then PROGRAM_DIR="${FIREHOL_OVERRIDE_PROGRAM_DIR}" else PROGRAM_DIR="$(/usr/bin/dirname "$PROGRAM_FILE")" fi PROGRAM_PWD="${PWD}" declare -a PROGRAM_ORIGINAL_ARGS=("${@}") for functions_file in install.config functions.common do if [ -r "$PROGRAM_DIR/$functions_file" ] then source "$PROGRAM_DIR/$functions_file" else 1>&2 echo "Cannot access $PROGRAM_DIR/$functions_file" exit 1 fi done common_disable_localization || exit marksreset() { :; } markdef() { :; } if [ -r "${FIREHOL_CONFIG_DIR}/firehol-defaults.conf" ] then source "${FIREHOL_CONFIG_DIR}/firehol-defaults.conf" || exit 1 fi status=$? test $status -eq 0 || exit $status needroot=Y haderror="" #gvprog=dot #gvprog=sfdp gvprog=$NEATO_CMD setup="$1" mode="$2" outfile="$3" case "$mode" in ""|-h|help|-v|version) mode= needroot= haderror="Y" ;; start|stop|status) : ;; graphviz) common_require_cmd $PROGRAM_FILE NEATO_CMD needroot= case "$outfile" in *.gv|"") graphviz=$CAT_CMD ;; *.ps) format=ps graphviz="$gvprog -T$format" ;; *.pdf) format=pdf graphviz="$gvprog -T$format" ;; *.png) format=png graphviz="$gvprog -T$format" ;; *) 1>&2 echo "Unrecognised file extension: $mode" haderror="Y" ;; esac ;; *) 1>&2 echo "Unrecognised mode: $mode" haderror="Y" needroot= ;; esac if [ "$mode" = "" ] then $CAT_CMD <<-EOF FireHOL vnetbuild $VERSION (C) Copyright 2015 Phil Whineray (C) Copyright 2015 Costa Tsaousis FireHOL is distributed under the GPL v2+. Home Page: http://firehol.org ------------------------------------------------------------------------ Get notified of new FireHOL releases by subscribing to the mailing list: http://lists.firehol.org/mailman/listinfo/firehol-support/ ------------------------------------------------------------------------ EOF fi if [ "$needroot" -a "${UID}" != "0" ] then echo "Error: must be root to use '$mode'" haderror="Y" fi if [ "$haderror" -o $# -lt 2 ] then echo "" echo "Usage: sudo vnetbuild CONFIGFILE stop|start|status" echo " or: vnetbuild CONFIGFILE graphviz OUTFILE.{gv,png,pdf,ps}" exit 1 else shift shift fi setupbase="${setup##*/}" errline="" error="" if ! MYTMP="`$MKTEMP_CMD -d -t vnetbuild-XXXXXX`" then echo >&2 echo >&2 echo >&2 "Cannot create temporary directory." echo >&2 exit 1 fi myexit() { status=$? if [ "$error" != "" ] then echo "$setupbase: line $errline: $error" fi $RM_CMD -rf $MYTMP exit $status } trap myexit INT trap myexit HUP trap myexit 0 CURDIR=`pwd`/ export CURDIR set -e $MKDIR_CMD $MYTMP/setup $SED_CMD = "$setup" > $MYTMP/withnum (echo "cd $CURDIR"; $SED_CMD -e 'N;s/\n/\t/' -e 's/^/lineno=/' -e '/exec\|pre_up/s/[<>|&]/\\&/g' $MYTMP/withnum) > $MYTMP/setup/$setupbase $MKDIR_CMD $MYTMP/ns $MKDIR_CMD $MYTMP/runtime-lines current_name= create_namespace() { errline=$lineno local type="$1" current_name="$2" NSTMP=$MYTMP/ns/$current_name if [ -d $NSTMP ] then error="$current_name: $($CAT_CMD $NSTMP/type) already defined" return 1 fi $MKDIR_CMD $NSTMP $MKDIR_CMD $NSTMP/devices $MKDIR_CMD $NSTMP/devicepairs echo $type > $NSTMP/type echo 0 > $NSTMP/forward > $NSTMP/routes > $NSTMP/devlist > $NSTMP/pairlist > $NSTMP/bridgelist echo $current_name >> $MYTMP/nslist echo $errline > $MYTMP/runtime-lines/$current_name } host() { errline=$lineno create_namespace host "$1" } switch() { errline=$lineno create_namespace switch "$1" } dev() { errline=$lineno device="$1" shift if [ ! "$current_name" ] then error="cannot define dev outside of a host or switch" return 1 fi if [ -f $NSTMP/devices/$device ] then error="$current_name/$device: already defined" return 1 fi local otherns= local otherdev= case $1 in */[a-zA-Z]*) otherns=$(echo $1 | $CUT_CMD -f1 -d/) otherdev=$(echo $1 | $CUT_CMD -f2 -d/) shift if [ -f $MYTMP/ns/$otherns/devicepairs/$otherdev ] then error="$otherns/$otherdev: already has paired device" return 1 fi ;; esac local type="$($CAT_CMD $NSTMP/type)" if [ "$*" != "" -a "$type" = "switch" ] then error="device in switch may not specify an IP address" return 1 fi f=$NSTMP/devices/$device > $f for ip in "$@" do case $ip in */*) echo "$ip" >> $f ;; *) error="IP address should be expressed as ip/mask" return 1 ;; esac done if [ "$otherdev" ] then if [ ! -d $MYTMP/ns/$otherns ] then error="$otherns undefined" return 1 fi echo "$current_name $device" > $MYTMP/ns/$otherns/devicepairs/$otherdev echo "n/a n/a" > $NSTMP/devicepairs/$device echo "$otherns $otherdev" >> $NSTMP/pairlist echo $errline > $MYTMP/runtime-lines/$otherns-pair-$otherdev fi echo $device >> $NSTMP/devlist echo $errline > $MYTMP/runtime-lines/$current_name-dev-$device return 0 } route() { errline=$lineno if [ ! "$current_name" ] then error="can only specify route in a host" return 1 fi local type="$($CAT_CMD $NSTMP/type)" if [ "$type" = "switch" ] then error="can only specify route in a host" return 1 fi echo "$*" >> $NSTMP/routes echo $errline >> $MYTMP/runtime-lines/$current_name-routes return 0 } bridgedev() { errline=$lineno device="$1" shift if [ ! "$current_name" ] then error="can only specify bridgedev in a host" return 1 fi local type="$($CAT_CMD $NSTMP/type)" if [ "$type" = "switch" ] then error="can only specify bridgedev in a host" return 1 fi if [ -f $NSTMP/devices/$device ] then error="$current_name/$device: already defined" return 1 fi ipf=$NSTMP/devices/$device devf=$ipf-bridged > $ipf > $devf for ipordev in "$@" do case $ipordev in */*) echo "$ipordev" >> $ipf ;; *) echo "$ipordev" >> $devf ;; esac done echo $device >> $NSTMP/bridgelist echo $errline > $MYTMP/runtime-lines/$current_name-dev-$device return 0 } exec() { errline=$lineno if [ ! "$current_name" ] then error="can only specify exec in a host or switch" return 1 fi echo "$*" >> $NSTMP/exec echo $errline >> $MYTMP/runtime-lines/$current_name-exec return 0 } pre_up() { errline=$lineno device="$1" shift if [ ! "$current_name" ] then error="can only specify pre_up in a host or switch" return 1 fi if [ "$($CAT_CMD $NSTMP/type)" = "switch" -a "$device" = "switch" ] then : elif [ ! -f $NSTMP/devices/$device ] then error="$current_name/$device: device not defined" return 1 fi echo "$*" >> $NSTMP/pre-up-$device echo $errline >> $MYTMP/runtime-lines/$current_name-pre-up-$device return 0 } is_ipv6() { case "$1" in *:*) return 0 ;; esac return 1 } cd $MYTMP/setup . $setupbase errline="" cd $CURDIR exists_ns() { if [ "$($IP_CMD netns list | $GREP_CMD -E "^$1([[:space:]]+\(|\$)")" ] then return 0 else return 1 fi } dev_in_ns() { $IP_CMD netns exec $1 $IP_CMD link list | $GREP_CMD "^[0-9]" | $CUT_CMD -d: -f2 | $CUT_CMD -f1 -d'@' | $TR_CMD -d ' ' } get_pids() { # Not in all versions: # $IP_CMD netns pids $1 $FIND_CMD -L /proc/[0-9]*/ns -maxdepth 1 -samefile /var/run/netns/$1 2>/dev/null | $CUT_CMD -f3 -d/ } shutdown_ns() { for i in $(dev_in_ns $1) do $IP_CMD netns exec $1 $IP_CMD link set $i down done pids=$(get_pids $1) if [ "$pids" ]; then kill $pids; $SLEEP_CMD 1; fi pids=$(get_pids $1) if [ "$pids" ]; then kill -9 $pids; fi } startup_ns() { for i in $(dev_in_ns $1) do if [ -f $MYTMP/ns/$ns/pre-up-$i ] then errline=$($TR_CMD "\n" "/" < $MYTMP/runtime-lines/$ns-pre-up-$i | $SED_CMD -e s:/$::) error="running pre-up-$i for $ns" $IP_CMD netns exec $ns $SH_CMD -e $MYTMP/ns/$ns/pre-up-$i fi $IP_CMD netns exec $1 $IP_CMD link set $i up done } while read ns do while read dev do read errline < $MYTMP/runtime-lines/$ns-dev-$dev if [ ! -f $MYTMP/ns/$ns/devicepairs/$dev ] then error="$ns/$dev has no paired device" exit 1 fi done < $MYTMP/ns/$ns/devlist while read otherns otherdev do read errline < $MYTMP/runtime-lines/$otherns-pair-$otherdev if [ ! -f $MYTMP/ns/$otherns/devices/$otherdev ] then error="$otherns/$otherdev not defined to be paired with" exit 1 fi done < $MYTMP/ns/$ns/pairlist done < $MYTMP/nslist if [ "$mode" = "stop" -o "$mode" = "start" ] then while read ns do read errline < $MYTMP/runtime-lines/$ns error="shutting down namespace" exists_ns $ns && shutdown_ns $ns done < $MYTMP/nslist while read ns do read errline < $MYTMP/runtime-lines/$ns error="deleting namespace" exists_ns $ns && $IP_CMD netns del $ns done < $MYTMP/nslist error="" fi if [ "$mode" = "stop" ] then exit 0 fi if [ "$mode" = "start" ] then while read ns do read errline < $MYTMP/runtime-lines/$ns error="adding namespace" type="$($CAT_CMD $MYTMP/ns/$ns/type)" $IP_CMD netns add $ns if [ "$type" = "switch" ] then error="adding bridge to switch namespace" $IP_CMD netns exec $ns $IP_CMD link add name switch type bridge fi done < $MYTMP/nslist while read ns do type="$($CAT_CMD $MYTMP/ns/$ns/type)" while read dev do read errline < $MYTMP/runtime-lines/$ns-dev-$dev read ons odev < $MYTMP/ns/$ns/devicepairs/$dev if [ "$ons" != "n/a" ] then error="adding virtual ethernet to $type namespace" #$IP_CMD link add $dev netns $ns type veth peer netns $ons name $odev $IP_CMD link add $dev netns $ns type veth peer name $odev $IP_CMD link set $odev netns $ons else : # gets set up from the other end fi if [ "$type" = "switch" ] then error="adding virtual ethernet to bridge" $IP_CMD netns exec $ns $IP_CMD link set dev $dev master switch fi while read ip do error="adding ip address to virtual ethernet" if is_ipv6 $ip; then bc=""; v6="-6"; else bc="broadcast +"; v6=""; fi $IP_CMD netns exec $ns $IP_CMD $v6 addr add $ip $bc dev $dev done < $MYTMP/ns/$ns/devices/$dev done < $MYTMP/ns/$ns/devlist while read bridge do read errline < $MYTMP/runtime-lines/$ns-dev-$bridge error="adding bridge to host namespace" $IP_CMD netns exec $ns $IP_CMD link add name $bridge type bridge while read dev do error="adding virtual interface to bridge" $IP_CMD netns exec $ns $IP_CMD link set dev $dev master $bridge done < $MYTMP/ns/$ns/devices/$bridge-bridged while read ip do error="adding ip to virtual interface" if is_ipv6 $ip; then bc=""; v6="-6"; else bc="broadcast +"; v6=""; fi $IP_CMD netns exec $ns $IP_CMD $v6 addr add $ip $bc dev $bridge done < $MYTMP/ns/$ns/devices/$bridge done < $MYTMP/ns/$ns/bridgelist done < $MYTMP/nslist while read ns do echo "Starting namespace $ns" read errline < $MYTMP/runtime-lines/$ns error="starting namespace" startup_ns $ns while read route do errline=$($TR_CMD "\n" "/" < $MYTMP/runtime-lines/$ns-routes | $SED_CMD -e s:/$::) error="adding route to $ns" if is_ipv6 "$route"; then bc=""; v6="-6"; else bc="broadcast +"; v6=""; fi $IP_CMD netns exec $ns $IP_CMD $v6 route add $route done < $MYTMP/ns/$ns/routes if [ -f $MYTMP/ns/$ns/exec ] then errline=$($TR_CMD "\n" "/" < $MYTMP/runtime-lines/$ns-exec | $SED_CMD -e s:/$::) error="running exec for $ns" $IP_CMD netns exec $ns $SH_CMD -e $MYTMP/ns/$ns/exec fi done < $MYTMP/nslist error="" fi if [ "$mode" = "status" ] then while read ns do echo "---------------------- $ns --------------------" if exists_ns $ns then # We remove the @whatever part of the device names since these # appear to be highly unreliable when creating devices in different # namespaces, at least under linux 4.3. Adding in two steps, like # this did not help: # $IP_CMD link add $dev netns $ns type veth peer name $odev # $IP_CMD link set $odev netns $ons echo echo "# $IP_CMD addr show" $IP_CMD netns exec $ns $IP_CMD addr show | $SED_CMD 's/@[^:]*:/:/' echo echo "# $IP_CMD route show" $IP_CMD netns exec $ns $IP_CMD route show echo echo "# $BRIDGE_CMD link show" $IP_CMD netns exec $ns $BRIDGE_CMD link show else echo "Namespace not running" fi echo "" done < $MYTMP/nslist fi if [ "$mode" = "graphviz" ] then gv=$MYTMP/gv echo "/* process e.g.: $gvprog -Tps filename.gv -o filename.ps */" >$gv echo "graph NET {" >>$gv if [ "$format" != "png" ] then echo "size=7; /* Max size 7 inches */" >>$gv fi echo "overlap=prism;" >>$gv echo "edge [color=blue,style=dashed];" >>$gv while read ns do type="$($CAT_CMD $MYTMP/ns/$ns/type)" if [ "$type" = "switch" ] then echo "switch_$ns [shape=polygon,sides=4,skew=.4,label=\"$ns\"];" >>$gv else echo -n "host_$ns [shape=record,label=\"$ns" >>$gv while read route do echo -n "\\n$route" >>$gv done < $MYTMP/ns/$ns/routes while read bridge do echo -n "|{<$bridge> $bridge" >>$gv while read ip do echo -n "\\n$ip" >>$gv done < $MYTMP/ns/$ns/devices/$bridge while read dev do echo -n "|{" >>$gv echo -n "<$dev> $dev" >>$gv while read ip do echo -n "\n$ip" >>$gv done < $MYTMP/ns/$ns/devices/$dev echo -n "}" >>$gv echo "$bridge" > $MYTMP/ns/$ns/suppress-$dev done < $MYTMP/ns/$ns/devices/$bridge-bridged echo -n "}" >>$gv done < $MYTMP/ns/$ns/bridgelist while read dev do if [ ! -f $MYTMP/ns/$ns/suppress-$dev ] then echo -n "|<$dev> $dev" >>$gv while read ip do echo -n "\\n$ip" >>$gv done < $MYTMP/ns/$ns/devices/$dev fi done < $MYTMP/ns/$ns/devlist echo "\"];" >>$gv fi done < $MYTMP/nslist while read ns do type="$($CAT_CMD $MYTMP/ns/$ns/type)" while read dev do read ons odev < $MYTMP/ns/$ns/devicepairs/$dev if [ "$ons" != "n/a" ] then otype="$($CAT_CMD $MYTMP/ns/$ons/type)" if [ "$type" = "switch" ] then from="switch_$ns" else from="host_$ns:$dev" fi if [ "$otype" = "switch" ] then to="switch_$ons" else to="host_$ons:$odev" fi echo "$from -- $to;" >>$gv else : # gets set up from the other end fi done < $MYTMP/ns/$ns/devlist done < $MYTMP/nslist echo "}" >>$gv if [ "$outfile" = "" ] then $graphviz $gv else $graphviz $gv > "$outfile" fi fi exit 0