firehol/sbin/vnetbuild
2017-01-21 18:17:29 +00:00

877 lines
20 KiB
Bash
Executable File

#!/bin/bash
#
# vnetbuild - linked network namespace setup for humans...
#
# Copyright
#
# Copyright (C) 2015-2017 Phil Whineray <phil@sanewall.org>
# Copyright (C) 2015-2017 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.
#
READLINK_CMD=${READLINK_CMD:-readlink}
BASENAME_CMD=${BASENAME_CMD:-basename}
DIRNAME_CMD=${DIRNAME_CMD:-dirname}
function realdir {
local r="$1"; local t=$($READLINK_CMD "$r")
while [ "$t" ]; do
r=$(cd $($DIRNAME_CMD "$r") && cd $($DIRNAME_CMD "$t") && pwd -P)/$($BASENAME_CMD "$t")
t=$($READLINK_CMD "$r")
done
$DIRNAME_CMD "$r"
}
PROGRAM_FILE="$0"
PROGRAM_DIR="${FIREHOL_OVERRIDE_PROGRAM_DIR:-$(realdir "$0")}"
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=$NEATO_CMD
gvprog=$DOT_CMD
gv_devices_left_right=
if [ "$gv_devices_left_right" ]
then
# Format graph as host | dev1 | dev2 | ...
gvdevdownopen=""
gvdevdownclose=""
else
# Format graph as host
# dev1
# dev2
# ...
gvdevdownopen="{"
gvdevdownclose="}"
fi
gv_label_outside="Y"
gv_label_outside=""
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
common_require_cmd $PROGRAM_FILE DOT_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"
;;
*.svg)
format=svg
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 <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
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
$MKDIR_CMD $MYTMP/clusters
> $MYTMP/clusterlist
current_name=
declare_namespace() {
errline=$lineno
local current_name="$1"
local NSTMP=$MYTMP/ns/$current_name
if [ -d $NSTMP ]
then
return 0
fi
$MKDIR_CMD $NSTMP
echo $errline > $NSTMP/undefined
$MKDIR_CMD $NSTMP/devices
$MKDIR_CMD $NSTMP/devicepairs
}
create_namespace() {
errline=$lineno
current_name="$1"
local type="$2"
NSTMP=$MYTMP/ns/$current_name
if [ ! -f $NSTMP/undefined ]
then
error="$current_name: $($CAT_CMD $NSTMP/type) already defined"
return 1
fi
$RM_CMD -f $NSTMP/undefined
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
declare_namespace "$1"
create_namespace "$1" host
}
switch() {
errline=$lineno
declare_namespace "$1"
create_namespace "$1" switch
}
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
declare_namespace "$otherns"
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
}
gv_label() {
errline=$lineno
if [ ! "$current_name" ]
then
error="can only specify gv_label in a host or switch"
return 1
fi
if [ -f $NSTMP/gv_label ]
then
echo -n '\\n' >> $NSTMP/gv_label
fi
echo -n "$*" >> $NSTMP/gv_label
return 0
}
gv_omit() {
errline=$lineno
if [ ! "$current_name" ]
then
error="can only specify gv_omit in a host or switch"
return 1
fi
echo > $NSTMP/gv_omit
return 0
}
gv_cluster() {
errline=$lineno
local cluster="$1"
shift
if [ ! "$current_name" ]
then
error="can only specify gv_cluster in a host or switch"
return 1
fi
if [ ! -f $MYTMP/clusters/$cluster ]
then
echo "$cluster" >> $MYTMP/clusterlist
if [ "$*" ]
then
echo "$*" > $MYTMP/clusters/$cluster.label
fi
fi
local type="$($CAT_CMD $NSTMP/type)"
echo "${type}_$current_name" >> $MYTMP/clusters/$cluster
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=scale;" >>$gv
#echo "splines=compound;" >>$gv
echo "node [sep=10];" >>$gv
echo "edge [color=blue,style=dashed];" >>$gv
while read cluster
do
echo "subgraph cluster_$cluster{" >>$gv
if [ -s "$MYTMP/clusters/$cluster.label" ]
then
echo "label=\"`$CAT_CMD $MYTMP/clusters/$cluster.label`\"" >>$gv
fi
while read ns
do
echo "$ns" >>$gv
done < $MYTMP/clusters/$cluster
echo "}" >>$gv
done < $MYTMP/clusterlist
while read ns
do
type="$($CAT_CMD $MYTMP/ns/$ns/type)"
if [ -f $MYTMP/ns/$ns/gv_label ]
then
read label < $MYTMP/ns/$ns/gv_label || true
else
label="$ns"
fi
if [ ! -f $MYTMP/ns/$ns/gv_omit ]
then
if [ "$type" = "switch" ]
then
echo "\"switch_$ns\" [shape=ellipse,label=\"$label\"];" >>$gv
else
echo -n "\"host_$ns\" [shape=record,label=\"$gvdevdownopen$label" >>$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
if [ ! "$gv_label_outside" ]
then
while read ip
do
echo -n "\n$ip" >>$gv
done < $MYTMP/ns/$ns/devices/$dev
fi
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 [ ! "$gv_label_outside" -a ! -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 "$gvdevdownclose\"];" >>$gv
fi
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\""
fromlabel="taillabel=\"\\n\\n\""
else
from="\"host_$ns\":\"$dev\""
fromlabel="taillabel=\"$dev"
while read ip
do
fromlabel="$fromlabel\\n$ip"
done < $MYTMP/ns/$ns/devices/$dev
fromlabel="$fromlabel\""
fi
if [ "$otype" = "switch" ]
then
to="\"switch_$ons\""
tolabel="headlabel=\"\\n\\n\""
else
to="\"host_$ons\":\"$odev\""
tolabel="headlabel=\"$odev"
while read ip
do
tolabel="$tolabel\\n$ip"
done < $MYTMP/ns/$ons/devices/$odev
tolabel="$tolabel\""
fi
alllabels=""
if [ "$gv_label_outside" ]
then
if [ "$fromlabel" -o "$tolabel" ]
then
if [ "$fromlabel" -a "$tolabel" ]
then
alllabels=" [labelfloat=false,$fromlabel,$tolabel]"
else
alllabels=" [labelfloat=false,$fromlabel$tolabel]"
fi
fi
fi
if [ ! -f $MYTMP/ns/$ns/gv_omit -a ! -f $MYTMP/ns/$ons/gv_omit ]
then
echo "$from -- $to$alllabels;" >>$gv
fi
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