firehol/sbin/vnetbuild.in
2015-11-11 07:54:28 +00:00

855 lines
18 KiB
Bash
Executable File

#!/bin/bash
#
# vnetbuild - linked network namespace setup for humans...
#
# Copyright
#
# Copyright (C) 2015 Phil Whineray <phil@sanewall.org>
# Copyright (C) 2015 Costa Tsaousis <costa@tsaousis.gr>
#
# 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 <http://www.gnu.org/licenses/>.
#
# See the file COPYING for details.
#
# make sure sbin is included in the path
# it seems that pppd ip-up.d script need this
export PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin"
get_version() {
GIT_REF='$Format:%d,commit-%h$'
local IFS=":(), "
set -- "$GIT_REF"
ver='$Id$'
for i in $@
do
case "$i" in
*[0-9].[0-9]*)
echo "$i" | $SED_CMD -e 's/^v//'
return 0
;;
commit-[0-9a-zA-Z]*)
ver="$i"
;;
esac
done
echo "$ver"
return 0
}
if [ "@AUTOCONF_RUN@" = "Y" ]
then
FIREHOL_CONFIG_DIR="@FIREHOL_CONFIG_DIR@"
else
FIREHOL_CONFIG_DIR="/etc/firehol"
fi
marksreset() { :; }
markdef() { :; }
if [ -r "${FIREHOL_CONFIG_DIR}/firehol-defaults.conf" ]
then
source "${FIREHOL_CONFIG_DIR}/firehol-defaults.conf" || exit 1
fi
# Load commands vnetbuild will need.
which_cmd() {
local name="$1"
shift
if [ "$1" = ":" ]
then
eval $name=":"
return 0
fi
unalias $1 >/dev/null 2>&1
local cmd=
IFS= read cmd <<-EOF
$(which $1 2> /dev/null)
EOF
if [ $? -gt 0 -o ! -x "${cmd}" ]
then
return 1
fi
shift
if [ $# -eq 0 ]
then
eval $name="'${cmd}'"
else
eval $name="'${cmd} ${@}'"
fi
return 0
}
require_cmd() {
local var= val= block=1
if [ "$1" = "-n" ]
then
block=0
shift
fi
var="$1"
shift
eval val=\$\{${var}\}
if [ "${val}" ]
then
local cmd="${val/ */}"
if [ ! -x "$cmd" ]
then
echo >&2
if [ $block -eq 0 ]
then
echo >&2 "WARNING: optional command does not exist or is not executable ($cmd)"
echo >&2 "please add or correct $var in firehol-defaults.conf"
val=""
else
echo >&2 "ERROR: required command does not exist or is not executable ($cmd)"
echo >&2 "please add or correct $var in firehol-defaults.conf"
exit 1
fi
fi
# link-balancer calls itself; export our findings so
# we do not repeat all of the lookups
eval export "$var"
return 0
elif [ $block -eq 0 ]
then
eval set -- "$@"
for cmd in "$@"
do
eval "NEED_${var}"="\$NEED_${var}' ${cmd/ */}'"
done
return 0
fi
if [ $# -eq 0 ]
then
eval set -- "\$NEED_${var}"
fi
echo >&2
echo >&2 "ERROR: LINK-BALANCER REQUIRES ONE OF THESE COMMANDS:"
echo >&2
echo >&2 " ${@}"
echo >&2
echo >&2 " You have requested the use of a link-balancer"
echo >&2 " feature that requires certain external programs"
echo >&2 " to be installed in the running system."
echo >&2
echo >&2 " Please consult your Linux distribution manual to"
echo >&2 " install the package(s) that provide these external"
echo >&2 " programs and retry."
echo >&2
echo >&2 " Note that you need an operational 'which' command"
echo >&2 " for link-balancer to find all the external programs it"
echo >&2 " needs. Check it yourself. Run:"
echo >&2
for x in "${@}"
do
echo >&2 " which $x"
done
exit 1
}
which_all() {
local cmd_var="$1"
eval set -- "$2"
for cmd in "$@"
do
which_cmd $cmd_var $cmd && break
done
}
# Where required = Y, if a command is not found, FireHOL will refuse to run.
# Where required = N, the command only required when it is actually used
#
# If a command is specified in /etc/firehol/firehol-defaults.conf it will
# be used. Otherwise, if the script has been configured with ./configure
# the detected versions will be used. If the script has not been configured
# then the list of possible commands is autodetected.
while IFS="|" read required cmd_var autoconf possibles
do
if [ "@AUTOCONF_RUN@" = "Y" ]
then
case "$autoconf" in
"@"*) autoconf=""; ;;
esac
fi
eval set_in_defaults=\"\$$cmd_var\"
if [ "$set_in_defaults" ]
then
:
elif [ "@AUTOCONF_RUN@" = "Y" -a ! -z "$autoconf" ]
then
eval $cmd_var=\"$autoconf\"
else
dirname="${0%/*}"
if [ "$dirname" = "$0" ]; then dirname="."; fi
PATH="/bin:/usr/bin:/sbin:/usr/sbin:$PATH:$dirname" which_all $cmd_var "$possibles"
fi
if [ "$required" = "Y" ]
then
require_cmd $cmd_var $possibles
else
require_cmd -n $cmd_var $possibles
fi
done <<-!
Y|IP_CMD|@IP@|ip
Y|BRCTL_CMD|@BRCTL@|brctl
Y|GREP_CMD|@GREP@|grep
Y|FIND_CMD|@FIND@|find
Y|SH_CMD|@SH@|sh bash ksh
Y|CUT_CMD|@CUT@|cut
Y|CAT_CMD|@CAT@|cat
Y|SED_CMD|@SED@|sed
Y|TR_CMD|@TR@|tr
Y|SLEEP_CMD|@SLEEP@|sleep
Y|MKDIR_CMD|@MKDIR@|mkdir
Y|RM_CMD|@RM@|rm
Y|MKTEMP_CMD|@MKTEMP@|mktemp
N|NEATO_CMD|@NEATO@|neato
!
VERSION=$(get_version)
emit_version() {
$CAT_CMD <<EOF
FireHOL vnetbuild $VERSION
(C) Copyright 2015 Phil Whineray <phil@firehol.org>
(C) Copyright 2015 Costa Tsaousis <costa@tsaousis.gr>
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
}
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)
require_cmd 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
emit_version
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/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
}
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 "^$1\$")" ]
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
$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 $BRCTL_CMD addbr switch
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 $BRCTL_CMD addif switch $dev
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 $BRCTL_CMD addbr $bridge
while read dev
do
error="adding virtual interface to bridge"
$IP_CMD netns exec $ns $BRCTL_CMD addif $bridge $dev
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
$IP_CMD netns exec $ns $IP_CMD addr show | $SED_CMD 's/@[^:]*:/:/'
$IP_CMD netns exec $ns $IP_CMD route show
$IP_CMD netns exec $ns $BRCTL_CMD 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