From f46b2291fb7414163f26b612eeb72909ac49cd8c Mon Sep 17 00:00:00 2001 From: Marta Carbone Date: Wed, 11 Nov 2009 16:51:18 +0000 Subject: [PATCH] Update --- exec/ipfw-be | 531 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 367 insertions(+), 164 deletions(-) diff --git a/exec/ipfw-be b/exec/ipfw-be index b841200..9cf2864 100755 --- a/exec/ipfw-be +++ b/exec/ipfw-be @@ -1,217 +1,420 @@ #!/bin/sh # -# Marta Carbone -# Copyright (C) 2009 UniPi +# Marta Carbone, Luigi Rizzo +# Copyright (C) 2009 Universita` di Pisa # $Id$ # -# This script is the backend to be used with -# the vsys system. -# It allows to configure dummynet pipes and queues. +# This script the vsys backend used to configure emulation. # In detail it: -# - read the user's input from the input pipe -# - validate the input -# - set the firewall -# - put results on the output vsys pipe +# - reads the user's input from the vsys input pipe +# - validates the input +# - configures the firewall +# - writes results on the output vsys pipe # -# This script expect to read from the input vsys -# pipe a line formatted as follow: -# ${PORT} ${TIMEOUT} -# the timeout value is expressed as: -# week, day, month or anything else accepted by the date command +# Configurable variables are at the beginning -# save the slicename -SLICE=$1 +DEBUG=0 # set to 0 to disable debug messages -LOG_FILE=/tmp/netconfig.log +# if HOOK is set the program is called befor configuring a rule. +# A sample hook can be found in the ipfw.rpm package +# HOOK=/tmp/sample_hook +# XXX HOOK="" + +# You should not touch anything below. + +# We assume three type of connections +# SERVER we know the local port P, and do the +# bind/listen/accept on the local socket. +# pipe_in in dst-port P +# pipe_out out src-port P +# +# CLIENT we know the remote port P, and do a connect to it +# (src and dst are swapped wrt the previous case) +# pipe_in in src-port P +# pipe_out out dst-port P +# +# SERVICE we run a server on local port P, and also connect +# from local clients to remote servers on port P. +# pipe_in in { dst-port P or src-port P } +# pipe_out out { src-port P or dst-port P } +# +# On a given port a user can have one CLIENT and/or one SERVER +# configuration or one SERVICE configuration. +# When a SERVICE configuration is installed any existing CLIENT +# and SERVER configuration on the same port are removed. +# When a CLIENT or SERVER configuration is installed any existing +# SERVICE configuration on the same port is removed. +# +# The following is a case that is implemented as SERVER +# D we run a server on local port P, and also connect +# to remote servers but doing a bind(P) before connect(). +# In terms of rules, this is not distinguishable from +# the SERVER case, however it would be different if we +# had a way to tell SERVER from CLIENT sockets +# pipe_in in dst-port P +# pipe_out out src-port P +# +# The database of current ipfw and dummynet configuration is in a +# file which is regenerated on error. +# The format is +# +# slice_id service_type port rule_nr pipe_base timeout +# +# (lines starting with '#' are comments and are ignored) +# For each configuration we allocate one rule number in ipfw, +# and two sequential pipe numbers. + +# globals, do not touch below +DBFILE=/tmp/ff +LOG_FILE=/tmp/netconfig.log # XXX when running from daemon +lockfile=/var/lock/ipfw.lock +PIPE_MIN=1000 +PIPE_MAX=30000 # programs -CUT=/usr/bin/cut +# XXX check consintency variable {} SED=/bin/sed +#IPFW="/bin/echo ipfw:" IPFW=/sbin/ipfw -# set to 0 to disable debug messages -DEBUG=0 +# Call arguments are +SLICENAME="$1" # save the slice XXX name or id ? +SLICE_ID=`id -u $SLICENAME` debug() { # $1 message to be displayed - [ x"${DEBUG}" != x"0" ] && echo $1 >>{LOG_FILE}; + #echo "ipfw-be: $1" + [ x"${DEBUG}" != x"0" ] && echo "ipfw-be: $1" >>{LOG_FILE}; } abort() { # $1 message to be displayed - echo "$1" + release_lock + echo "ipfw-be aborting: $1" exit 1 } user_error() { # $1 message to be displayed - echo "1 User error: $1" + echo "ipfw-be: user error: $1" exit 1 } +# remove dangerous characters from user input filter() { # $* variables to be filtered # allowed chars are: numbers, upcase and lowecase # chars, and the following symbols: . _ - / - echo "$*" | ${SED} -r 's/[^0-9a-zA-Z. _\/\-]*//g' + echo "$*" | ${SED} -r 's/[^0-9a-zA-Z. _\/\-{}]*//g' } -# Add ipfw pipe and rules -# We use the PORT number to configure the -# pipe, and add rules for that port. -# The default directory is the slicename root -add_rules() { # $1 timeout value $2 delete - local EXPIRE - - debug "Add a new rule, check for deletion flag"; - if [ ${2} -eq 1 ]; then - #echo "Rules and pipes deleted"; - return; - fi - - debug "Add a new rule" - # schedule the rule deletion - EXPIRE=`date --date="${TIMEOUT}" +%s` - [ x"${EXPIRE}" = x"" ] && abort "Date format $1 not valid" - - # move in the slice root dir - cd /vservers/${SLICE}/root - #echo ${CONFIG_STRING} | ${SED} -e "s/ profile \(.[^ ]\)/ profile \/vservers\/${SLICE}\/\1/g" - - # check syntax, if ok execute - # add rules - local IPFW_CHECK="${IPFW} -n " - local ERROR=0 - - [ $ERROR -eq 0 ] && \ - ${IPFW_CHECK} add ${RULE_N} pipe ${PIPE_N} ip from ${ME} to any src-port ${PORT} // ${EXPIRE} ${SLICE} - let "ERROR += $?" - [ $ERROR -eq 0 ] && \ - ${IPFW_CHECK} add ${RULE_N} pipe ${PIPE_N} ip from any to ${ME} dst-port ${PORT} - - let "ERROR += $?" - [ $ERROR -eq 0 ] && \ - ${IPFW_CHECK} pipe ${PIPE_N} config ${CONFIG_STRING} - - if [ ! $ERROR -eq 0 ]; then - echo "Some errors occurred not executing" - user_error "ipfw syntax error" - fi - - # add rules - ${IPFW} add ${RULE_N} pipe ${PIPE_N} ip from ${ME} to any src-port ${PORT} // ${EXPIRE} ${SLICE} - ${IPFW} add ${RULE_N} pipe ${PIPE_N} ip from any to ${ME} dst-port ${PORT} - - # config pipe - ${IPFW} pipe ${PIPE_N} config ${CONFIG_STRING} +# Add the ipfw rule/pipe and update the database. +# The pipe-in and pipe_out config are through global variables +# CONFIG_IN CONFIG_OUT because they may be long. +# Other arguments are on the command line +add_rule() { # new_rule slice_id type port rule pipe_base timeout + local new_rule=$1 slice_id=$2 type=$3 port=$4 rule_nr=$5 pipe_base=$6 timeout=$7 + local pipe_in pipe_out rule_in rule_out check_timeout + + # XXX validate the timeout + # schedule the rule deletion + check_timeout=`date --date="${timeout}" +%s` + [ x"${check_timeout}" = x"" ] && abort "Date format $1 not valid" + # XXX tbd + timeout="fake_timeout" + + # we could use a profile, so locate the user directory + # move in the slice root dir XXX todo + cd /vservers/${SLICENAME}/root + #echo ${CONFIG_STRING} | ${SED} -e "s/ profile \(.[^ ]\)/ profile \/vservers\/${SLICENAME}\/\1/g" + + # first, call ipfw -n to check syntax + # check syntax, if ok move on and do the action + local IPFW_CHECK="${IPFW} -n " + + pipe_in=$(($pipe_base + $pipe_base)) + pipe_out=$(($pipe_in + 1)) + local del # which one to delete ? + if [ x"$new_rule" != x"0" ] ; then + case $type in + SERVER) + rule_in="dst-port $port" + rule_out="src-port $port" + del=SERVICE + ;; + CLIENT) + rule_in="src-port $port" + rule_out="dst-port $port" + del=SERVICE + ;; + SERVICE) + rule_in="{ src-port $port or dst-port $port }" + rule_out="{ src-port $port or dst-port $port }" + del="CLI_SER" + ;; + *) + abort "invalid service type $type" + ;; + esac + + rule_in="pipe ${pipe_in} in uid $slice_id ${rule_in}" + rule_out="pipe ${pipe_out} out uid $slice_id ${rule_out}" + ${IPFW_CHECK} add ${rule_nr} $rule_in || \ + user_error "ipfw syntax error $rule_in" + ${IPFW_CHECK} add ${rule_nr} $rule_out || \ + user_error "ipfw syntax error $rule_out" + fi + + # XXX check error reporting + ${IPFW_CHECK} pipe ${pipe_in} config ${CONFIG_PIPE_IN} || \ + user_error "ipfw syntax error pipe_in" + ${IPFW_CHECK} pipe ${pipe_out} config ${CONFIG_PIPE_OUT} || \ + user_error "ipfw syntax error pipe_out" + + # all good, delete and add rules if necessary + [ "$del" = "SERVICE" ] && delete_config $slice_id SERVICE $port + [ "$del" = "CLI_SER" ] && delete_config $slice_id CLIENT $port + [ "$del" = "CLI_SER" ] && delete_config $slice_id SERVER $port + [ "$new_rule" != "0" ] && ${IPFW} add ${rule_nr} $rule_in + [ "$new_rule" != "0" ] && ${IPFW} add ${rule_nr} $rule_out + # config pipes + ${IPFW} pipe ${pipe_in} config ${CONFIG_PIPE_IN} + ${IPFW} pipe ${pipe_out} config ${CONFIG_PIPE_OUT} + + # add to the database, at least to adjust the timeout + ( grep -v -- "^${slice_id} ${type} ${port}" $DBFILE; \ + echo "${slice_id} ${type} ${port} ${rule_nr} ${pipe_base} ${timeout}" ) > ${DBFILE}.tmp + mv ${DBFILE}.tmp ${DBFILE} + } -# Delete a given link -delete_link() -{ - ipfw delete ${RULE_N} - ipfw pipe delete ${RULE_N} +# Delete a given configuration +delete_config() { # slice_id type port + local pipe_in pipe_out pipe_base + local slice_id=$1 type=$2 port=$3 + + # XXX test + [ $# -lt 3 ] && abort "One or more input parameter is missing" + set `find_rule $slice_id $type $port` + rule=$1; pipe_base=$2 + [ "$rule" = "0" ] && return # no rules found + + pipe_in=$(($pipe_base + $pipe_base)) + pipe_out=$(($pipe_in + 1)) + + $IPFW delete ${rule} + $IPFW pipe delete ${pipe_in} + $IPFW pipe delete ${pipe_out} + # remove from the database + grep -v -- "^${slice_id} ${type} ${port}" $DBFILE > ${DBFILE}.tmp + mv ${DBFILE}.tmp ${DBFILE} } -# The rule we want to configure already exist. -# Check for slice owner matching. -modify_rule() -{ - local RULE - - RULE=`ipfw list ${PORT} 2>&1 | cut -d ' ' -f 12`; - if [ "${RULE}" = "${SLICE}" ] ; then # replace the link configuration - debug "The rule already exist, the owner match, delete old rule" - echo "Owner match" - delete_link - add_rules ${TIMEOUT} ${DELETE} - else - user_error "the rule already exist, ant you are not the slice owner, try later" - fi +# called with the database file as input +# compare the tuple with +# the current firewall configuration. The database contains +# slice_id local_port remote_port rule_nr pipe_nr timeout +# On match returns +# On non match returns 0 0 0 +find_rule() { # $1 slice_id $2 type $3 port + local ret + ret=`grep -- "^$1 $2 $3 " $DBFILE` + + [ x"$ret" = x ] && echo "0 0 0 " && return # nothing found + # ignore multiple matches. If the db is corrupt we are + # screwed anyways + set $ret + echo "$4 $5 $6" +} + + +# Find a hole in a list of numbers within a range (boundaries included) +# The input is passed as a sorted list of numbers on stdin. +# Return a "0" rule if there is no rule free +find_hole() { # min max + local min=$1 cand=$1 max=$2 line + while read line ; do + [ $line -lt $min ] && continue + [ $line -ne $cand ] && break # found + [ $cand -ge $max ] && cand=0 && break # no space + cand=$(($cand + 1)) + done + echo $cand +} + +# returns a free rule and pipe base +# Returns r=0 if there are no resources available +# +# This function returns values using echo, +# this means that we can not easily debug the function +allocate_resources() { + local p r + # remove comments, extract field, sort + p=`grep -v '^#' $DBFILE | awk '{print $5}' | sort -n | find_hole 1 10000` + r=`grep -v '^#' $DBFILE | awk '{print $4}' | sort -n | find_hole $PIPE_MIN $PIPE_MAX` + [ $r = 0 -o $p = 0 ] && r=0 # no resources available + echo $r $p } -# process a single line of input -# this line has the following format: -# ipfw -# pipe -# port timeout configuration_string -process() + +# +# process a request. +# A request is made by a set of arguments formatted as follow: +# +# CONFIG ${type} ${port} ${timeout} PIPE_IN PIPE_OUT +# IPFW_SHOW +# PIPE_SHOW +# DELETE ${type} ${port} +# +# where uppercase values are keywords. +# The timeout value is expressed as: +# week, day, month or anything else accepted by the date command. +# The id of the slice issuing the request is in the $SLICE_ID variable, +# set at the beginning of this script. +process() { + local new_pipe=0 + local timeout TMP i rule_nr pipe_base + local type=$2 + local port=$3 + + debug "Received from the input pipe: $*" + + # Handle special requests: show and delete + case x"$1" in + x"IPFW_SHOW") + ${IPFW} show + return 0 + ;; + x"PIPE_SHOW") + $IPFW pipe show + return 0 + ;; + x"DELETE") + delete_config ${SLICE_ID} $type $port + return 0 + ;; + x"CONFIG") + ;; + *) + abort "Command not recognized" + ;; + esac + shift + + debug "processed initial command, rest of line: $*" + # check if we have enough parameters + [ $# -lt 9 ] && abort "One or more input parameter is missing" + + type=$1; shift + port=$1; shift + timeout=$1; shift + # XXX check/compute timeout + + [ "$1" != "PIPE_IN" ] && abort "PIPE_IN requested" + shift + + i="" + while [ "$1" != "" -a "$1" != "PIPE_OUT" ] ; do + i="$i $1" + shift + done + CONFIG_PIPE_IN="$i" + + [ "$1" != "PIPE_OUT" ] && abort "PIPE_OUT requested" + shift + + i="" + while [ "$1" != "" ] ; do + i="$i $1" + shift + done + CONFIG_PIPE_OUT="$i" + + debug "Configuration Required:" + debug "TYPE: $type" + debug "PORT: $port" + debug "TIMEOUT: $timeout" + debug "pipe in config $CONFIG_PIPE_IN" + debug "pipe in config $CONFIG_PIPE_OUT" + debug "-----------------------" + + # check if the link is already configured + debug "Search for ${SLICE_ID} ${type} ${port}" + + set `find_rule ${SLICE_ID} ${type} ${port}` + rule_nr=$1 + pipe_base=$2 + + if [ ${rule_nr} = "0" ] ; then + debug "Rule not found, new installation" + new_pipe=1 + set `allocate_resources` + rule_nr=$1; pipe_base=$2 + [ $rule_nr = 0 ] && abort "no resources available" + debug "found free resources rule: $rule_nr pipe: $pipe_base" + else + debug "Rule found, just changing the pipe configuration" + fi + add_rule $new_pipe $SLICE_ID $type $port $rule_nr $pipe_base $timeout + + # if present, call a hook in order to collect statistical + # information on dummynet usage + if [ -n "${HOOK}" -a -x "${HOOK}" ]; then + # XXX + ${HOOK} $SLICE_ID $type $port $rule_nr $pipe_base $timeout & + fi +} + +# +# acquire the lock XXX check lockfile +acquire_lock() { - local TMP; # temporary var - - debug "Received from the input pipe: $1" - - # allow netconfig ipfw show - # allow netconfig pipe show - - CMD=`echo $1 | cut -d\ -f 1` - if [ x${CMD} == x"ipfw" ]; then - ipfw show - return 0 - else if [ x${CMD} == x"pipe" ]; then - ipfw pipe show - return 0 - fi - fi - - ARGS=`echo $1 | wc -w` - if [ $ARGS -le 2 ]; then - abort "One or more input parameter is missing" - fi - - # filter input - TMP=`echo $1 | cut -d\ -f 1` - PORT=`filter $TMP` - TMP=`echo $1 | cut -d\ -f 2` - TIMEOUT=`filter $TMP` - TMP=`echo $1 | cut -d\ -f 3` - DELETE=`filter $TMP` - TMP=`echo $1 | cut -d\ -f 4-` - CONFIG_STRING=`filter $TMP` - - debug "PORT: $PORT" - debug "DELETE: $DELETE" - debug "TIMEOUT: $TIMEOUT" - debug "configuration string: $CONFIG_STRING" - - # find the ip address - ME=`/sbin/ip -o addr show | grep -v "1:\ lo" | grep "inet " | cut -d " " -f7 | cut -d "/" -f1 | head -n1` - - # deny port <= 1024 - [ ${PORT} -le 1024 ] && user_error "it is not allowed to modify the port range [0-1024]" - - # start to configure pipes and rules - PIPE_N=${PORT} - RULE_N=${PORT} - - # check if the link is already configured - ipfw list ${PORT} 2>&1 - - if [ x"$?" != x"0" ]; then # new rule, add and set owner/timeout - add_rules ${TIMEOUT} ${DELETE} - else # the rule already exist, check owner - modify_rule - fi + lockfile -s 0 -r 0 $lockfile 2> /dev/null + if [ $? -ne 0 ] ; then + echo "lock acquisition failed" + exit -1 + fi +} +# +# release the lock +release_lock() +{ + rm -f $lockfile } # main starts here - debug "Debug activated" + debug "$0 START" + + # create the DBFILE if not exist + [ ! -e ${DBFILE} ] && touch ${DBFILE} + requests=[] i=0 - while read request - do + # lock acquisition + acquire_lock + + # A request to the vsys backend is composed by a single line of input + while read request; do # read -a read arguments in array # XXX skip lines starting with # - requests[$i]=$request; - let i=$i+1 + debug "Received <$request>" + requests[$i]="$request" + requests[$i]=`filter $request` + debug "Filtered ${requests[$i]}" + i=$(($i + 1)) done - # create the lock - # process requests - for i in `/usr/bin/seq 0 $((${#requests[*]} - 1))` - do - process "${requests[$i]}" + i=0 + n_req=${#requests[*]} + debug "Received $n_req request" + while [ $i -lt $n_req ] ; do + debug "processing request $i of $n_req" + debug "<${requests[$i]}>" + process ${requests[$i]} + i=$(($i + 1)) done - # delete the lock + # lock release + release_lock + debug "$0 END" exit 0 -- 2.43.0