From: Marta Carbone Date: Mon, 16 Nov 2009 22:34:32 +0000 (+0000) Subject: Allocated a part of rules for user free configuration and a part X-Git-Tag: vsys-scripts-0.95-12~6 X-Git-Url: http://git.onelab.eu/?p=vsys-scripts.git;a=commitdiff_plain;h=e1b97bd77ee69b5ad32e2c1124a3eac2b72f9a99 Allocated a part of rules for user free configuration and a part for server|client|service configuration. Added test and quiet options. --- diff --git a/exec/ipfw-be b/exec/ipfw-be index 9cf2864..accda0b 100755 --- a/exec/ipfw-be +++ b/exec/ipfw-be @@ -13,9 +13,7 @@ # # Configurable variables are at the beginning -DEBUG=0 # set to 0 to disable debug messages - -# if HOOK is set the program is called befor configuring a rule. +# If HOOK is set the program is called before configuring a rule. # A sample hook can be found in the ipfw.rpm package # HOOK=/tmp/sample_hook # XXX HOOK="" @@ -55,98 +53,130 @@ DEBUG=0 # set to 0 to disable debug messages # 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 +# file which is regenerated on errors. The format is # -# slice_id service_type port rule_nr pipe_base timeout +# slice_id type arg rule_base 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 +VERBOSE=0 # set to !0 to enable debug messages +TEST=0 # set to 1 for test mode + DBFILE=/tmp/ff -LOG_FILE=/tmp/netconfig.log # XXX when running from daemon lockfile=/var/lock/ipfw.lock -PIPE_MIN=1000 -PIPE_MAX=30000 + +# There values are the keys used in the database for rules and pipes +# rule_nr 1..10000 are mapped to rules 10000..49999 (n*4+9996) +# rule_nr 10001..20000 are mapped to rules 50000..59999 (n+39999) +# pipe_nr 1..25000 are mapped to pipes 10000-59999 (n*2+9998) +RULE_BL_MIN=1 +RULE_BL_MAX=10000 +RULE_IN_MIN=10001 +RULE_IN_MAX=20000 +PIPE_MIN=1 +PIPE_MAX=25000 +# These are the rule numbers used in ipfw +IPFW_RULE_MIN=10000 +IPFW_RULE_MAX=59999 +IPFW_PIPE_MIN=10000 +IPFW_PIPE_MAX=59999 + +# set slicename and slice_id +# there represents the credential of the user +SLICENAME=$1 +SLICE_ID=`id -u $SLICENAME` +[ $? != 0 ] && abort "Invalid slicename $SLICENAME" # programs -# XXX check consintency variable {} +# XXX check consistency for variables {} SED=/bin/sed -#IPFW="/bin/echo ipfw:" +SEDOPT=-r +[ -x ${SED} ] || { SED=`which sed` ; SEDOPT=-E ; } IPFW=/sbin/ipfw - -# Call arguments are -SLICENAME="$1" # save the slice XXX name or id ? -SLICE_ID=`id -u $SLICENAME` +IPFW_CHECK="/sbin/ipfw -n" debug() { # $1 message to be displayed - #echo "ipfw-be: $1" - [ x"${DEBUG}" != x"0" ] && echo "ipfw-be: $1" >>{LOG_FILE}; + [ x"${VERBOSE}" != x"0" ] && echo "ipfw-be: $1" +} +# if the first argument is -v, enable verbose mode +set_verbose() { + [ x"$1" = x"-v" -o x"$2" = x"-v" ] && VERBOSE=1 + echo "in set_verbose have $VERBOSE $1" +} +set_test() { + [ x"$1" = x"-q" -o x"$2" = x"-q" ] || return + TEST=1 + IPFW="/bin/echo ipfw:" + IPFW_CHECK="/bin/echo ipfw -n:" } + abort() { # $1 message to be displayed release_lock echo "ipfw-be aborting: $1" exit 1 } -user_error() { # $1 message to be displayed - echo "ipfw-be: user error: $1" - exit 1 -} - # remove dangerous characters from user input +# if present, the leading '-v/-t' will be removed 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' + [ x${1} = x"-v" -o x${1} = x"-q" ] && shift + [ x${1} = x"-v" -o x${1} = x"-q" ] && shift + # allowed chars are: numbers, uppercase and lowercase letters, + # spaces, and the following symbols: .,_-/ + echo "$*" | ${SED} ${SEDOPT} 's/[^\t0-9a-zA-Z., _\/\{}-]*//g' +} + +# remove all entries from the ipfw config, and create an empty db +clean_db() { + rm -f ${DBFILE} + touch ${DBFILE} + # we would like to delete ranges of rules and pipes but this + # is not supported so for the time being we kill them all + ${IPFW} -q flush + ${IPFW} -q pipe flush + # ${IPFW} delete ${IPFW_RULE_MIN}-${IPFW_RULE_MAX} + # ${IPFW} pipe delete ${IPFW_PIPE_MIN}-${IPFW_PIPE_MAX} } # 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 +add_rule() { # new_rule slice_id type arg rule pipe_base timeout + local new_rule=$1 slice_id=$2 type=$3 arg=$4 + local rule_base=$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 + # If we use a profile file, locate the user directory # move in the slice root dir XXX todo - cd /vservers/${SLICENAME}/root + [ "$TEST" != "1" ] && 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)) + # first, call ipfw -n to check syntax, if ok move on and do the action + pipe_in=$(($pipe_base + $pipe_base + 9998)) pipe_out=$(($pipe_in + 1)) - local del # which one to delete ? + local del # anything to delete ? + local rule_nr=$(($rule_base + 39999)) # XXX formula for individual rules if [ x"$new_rule" != x"0" ] ; then case $type in - SERVER) - rule_in="dst-port $port" - rule_out="src-port $port" - del=SERVICE + server) + rule_in="dst-port $arg" + rule_out="src-port $arg" + del=service ;; - CLIENT) - rule_in="src-port $port" - rule_out="dst-port $port" - del=SERVICE + client) + rule_in="src-port $arg" + rule_out="dst-port $arg" + del=service ;; - SERVICE) - rule_in="{ src-port $port or dst-port $port }" - rule_out="{ src-port $port or dst-port $port }" - del="CLI_SER" + service) + rule_in="{ src-port $arg or dst-port $arg }" + rule_out="{ src-port $arg or dst-port $arg }" + del="cli_ser" ;; *) abort "invalid service type $type" @@ -155,64 +185,71 @@ add_rule() { # new_rule slice_id type port rule pipe_base timeout 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" + ${IPFW_CHECK} add ${rule_nr} $rule_in > /dev/null || \ + abort "ipfw syntax error $rule_in" + ${IPFW_CHECK} add ${rule_nr} $rule_out > /dev/null || \ + abort "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" + # check error reporting + ${IPFW_CHECK} pipe ${pipe_in} config ${CONFIG_PIPE_IN} > /dev/null || \ + abort "ipfw syntax error pipe_in" + ${IPFW_CHECK} pipe ${pipe_out} config ${CONFIG_PIPE_OUT} > /dev/null || \ + abort "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 + [ "$del" = "service" ] && do_delete $slice_id service $arg + [ "$del" = "cli_ser" ] && do_delete $slice_id client $arg + [ "$del" = "cli_ser" ] && do_delete $slice_id server $arg + [ "$new_rule" != "0" ] && ${IPFW} add ${rule_nr} $rule_in > /dev/null + [ "$new_rule" != "0" ] && ${IPFW} add ${rule_nr} $rule_out > /dev/null # config pipes ${IPFW} pipe ${pipe_in} config ${CONFIG_PIPE_IN} ${IPFW} pipe ${pipe_out} config ${CONFIG_PIPE_OUT} + # send output to the user + ${IPFW} show ${rule_nr} + ${IPFW} pipe ${pipe_in} show + ${IPFW} pipe ${pipe_out} show + + [ "$TEST" = "1" ] && return # 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 + ( grep -v -- "^${slice_id} ${type} ${arg}" $DBFILE; \ + echo "${slice_id} ${type} ${arg} ${rule_base} ${pipe_base} ${timeout}" ) > ${DBFILE}.tmp mv ${DBFILE}.tmp ${DBFILE} - } # 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 +do_delete() { # slice_id type arg + local pipe_in pipe_out pipe_base rule_base rule_nr + local slice_id=$1 type=$2 arg=$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 + [ "${arg}" = "" ] && abort "Missing arg on 'delete'" + set `find_rule $slice_id $type $arg` + rule_base=$1; pipe_base=$2 + [ "$rule_base" = "0" ] && return # no rules found - pipe_in=$(($pipe_base + $pipe_base)) + rule_nr=$(($rule_base + 39999)) # XXX only individual rules + pipe_in=$(($pipe_base + $pipe_base + 9998)) pipe_out=$(($pipe_in + 1)) - $IPFW delete ${rule} + $IPFW delete ${rule_nr} $IPFW pipe delete ${pipe_in} $IPFW pipe delete ${pipe_out} + echo "removed configuration $slice_id} ${type} ${arg}" + [ "$TEST" = "1" ] && return # remove from the database - grep -v -- "^${slice_id} ${type} ${port}" $DBFILE > ${DBFILE}.tmp + grep -v -- "^${slice_id} ${type} ${arg}" $DBFILE > ${DBFILE}.tmp mv ${DBFILE}.tmp ${DBFILE} } # called with the database file as input -# compare the tuple with +# 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 +# slice_id type arg rule_base pipe_base timeout +# On match returns # On non match returns 0 0 0 -find_rule() { # $1 slice_id $2 type $3 port +find_rule() { # $1 slice_id $2 type $3 arg local ret ret=`grep -- "^$1 $2 $3 " $DBFILE` @@ -238,133 +275,181 @@ find_hole() { # min max echo $cand } -# returns a free rule and pipe base +# returns a free rule and pipe base for client|server|service # 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` + p=`grep -v '^#' $DBFILE | awk '{print $5}' | sort -n | \ + find_hole $PIPE_MIN $PIPE_MAX` + r=`grep -v '^#' $DBFILE | awk '{print $4}' | sort -n | find_hole $1 $2` [ $r = 0 -o $p = 0 ] && r=0 # no resources available echo $r $p } - -# # 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} +# config {server|client|service} arg [-t timeout] PIPE_IN PIPE_OUT +# show {rules|pipes} [args] +# delete type arg +# +# XXX not implemented yet +# config {rule|pipe} num +# alloc rules|pipes [-t timeout] # returns a block of NUM_RULES or NUM_PIPES +# release rules|pipes args # release the entire block +# refresh rules|pipes args [-t timeout] # # 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() { +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: $*" + local timeout TMP i rule_base pipe_base + local slicename=${SLICENAME} + local cmd=$1 ; shift + local debug_args="$*"; + local type=$1 ; shift + local args="$*" + debug "Received command: <$cmd> arguments: <$debug_args>" + + # set the timeout value + # clean args from the timeout keyword + timeout=`echo ${args} | ${SED} ${SEDOPT} 's/(.+)( -t [a-zA-Z0-9]+ )(.*)/\2/'` + if [ "${timeout}" != "${args}" ] ; then # match + timeout=`echo ${timeout} | ${SED} ${SEDOPT} 's/-t //'` + check_timeout ${timeout} # abort on error + args=`echo ${args} | ${SED} ${SEDOPT} 's/(.+)( -t [a-zA-Z0-9]+ )(.*)/\1 \3/'` + else + timeout=1day # default to 1 day + fi + debug "Timeout $timeout" # Handle special requests: show and delete - case x"$1" in - x"IPFW_SHOW") - ${IPFW} show - return 0 + case x"$cmd" in + x"alloc") + abort "XXX unimplemented " && return 0 + ;; + x"config") + [ "$type" = "server" ] && do_config $SLICE_ID $timeout $type $args && return 0 + [ "$type" = "client" ] && do_config $SLICE_ID $timeout $type $args && return 0 + [ "$type" = "service" ] && do_config $SLICE_ID $timeout $type $args && return 0 + [ "$type" = "rule" ] && abort "XXX unimplemented " && return 0 + [ "$type" = "pipe" ] && abort "XXX unimplemented " && return 0 + abort "'config' should be followed by {server|client|service|rule|pipe}" ;; - x"PIPE_SHOW") - $IPFW pipe show - return 0 + x"delete") + do_delete ${SLICE_ID} $type $args ;; - x"DELETE") - delete_config ${SLICE_ID} $type $port - return 0 + x"refresh") + abort "XXX unimplemented " && return 0 + do_refresh ${SLICE_ID} $type $args $timeout ;; - x"CONFIG") + x"release") + abort "XXX unimplemented " && return 0 + do_release ${SLICE_ID} $type $args + ;; + x"show") + # XXX should filter on uid + [ "$type" = "rules" ] && ${IPFW} show && return 0 + [ "$type" = "pipes" ] && ${IPFW} pipe show && return 0 + abort "'show' should be followed by {rules|pipes}" ;; *) - abort "Command not recognized" + # help XXX to be done + abort "'command' should be one of {show|config|delete|refresh|release}" ;; 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" +# validate the timeout +check_timeout() { # timeout + local tt=`date --date="${1}" +%s` + [ "$?" != "0" ] && abort "Date format $1 not valid" +} - type=$1; shift - port=$1; shift - timeout=$1; shift - # XXX check/compute timeout +do_release() { # slice_id type args timeout + return +} + +do_refresh() { # slice_id ttype args + return +} - [ "$1" != "PIPE_IN" ] && abort "PIPE_IN requested" +do_config() { # slice_id timeout type arg PIPE_IN pipe_conf PIPE_OUT pipe_conf + local slice_id=$1; shift + local timeout=$1; shift + local type=$1; shift + local arg=$1; shift # XXX addr not yet implemented + + [ "$1" != "PIPE_IN" ] && abort "Missing addr:port, or PIPE_IN requested" shift + # read pipe in configuration i="" while [ "$1" != "" -a "$1" != "PIPE_OUT" ] ; do i="$i $1" shift done - CONFIG_PIPE_IN="$i" + CONFIG_PIPE_IN="$i" # XXX local ? + [ "$CONFIG_PIPE_IN" = "" ] && abort "Missing pipe in configuration" - [ "$1" != "PIPE_OUT" ] && abort "PIPE_OUT requested" + [ "$1" != "PIPE_OUT" ] && abort "Missing pipe in configuration, or missing PIPE_OUT" shift + # read pipe out configuration i="" while [ "$1" != "" ] ; do i="$i $1" shift done - CONFIG_PIPE_OUT="$i" + CONFIG_PIPE_OUT="$i" # XXX local ? + [ "$CONFIG_PIPE_OUT" = "" ] && abort "Missing pipe out configuration" 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 "slice_id: $slice_id" + debug "type: $type" + debug "arg: $arg" + debug "timeout: $timeout" + debug "PIPE_IN: $CONFIG_PIPE_IN" + debug "PIPE_OUT: $CONFIG_PIPE_OUT" debug "-----------------------" # check if the link is already configured - debug "Search for ${SLICE_ID} ${type} ${port}" + debug "Search for ${slice_id} ${type} ${arg}" - set `find_rule ${SLICE_ID} ${type} ${port}` - rule_nr=$1 - pipe_base=$2 + set `find_rule ${slice_id} ${type} ${arg}` + local rule_base=$1 + local pipe_base=$2 + local new_pipe=0 - if [ ${rule_nr} = "0" ] ; then + if [ ${rule_base} = "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" + set `allocate_resources $RULE_IN_MIN $RULE_IN_MAX` + rule_base=$1; pipe_base=$2 + [ $rule_base = 0 ] && abort "no resources available" + debug "found free resources rule: $rule_base 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 + + add_rule $new_pipe $slice_id $type $arg $rule_base $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 & + ${HOOK} $slice_id $type $port $rule_base $pipe_base $timeout & fi } # # acquire the lock XXX check lockfile -acquire_lock() -{ +acquire_lock() { + [ "$TEST" = 1 ] && return lockfile -s 0 -r 0 $lockfile 2> /dev/null if [ $? -ne 0 ] ; then echo "lock acquisition failed" @@ -374,47 +459,43 @@ acquire_lock() # # release the lock -release_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 - - # 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 # - debug "Received <$request>" - requests[$i]="$request" - requests[$i]=`filter $request` - debug "Filtered ${requests[$i]}" - i=$(($i + 1)) - done - - # process requests - 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 - - # lock release - release_lock - debug "$0 END" - exit 0 +# ALLOCATION OF PIPES AND RULES +# pipes are always allocated in pairs +# rules are either individual or in groups of size NUM_RULES (e.g. 4) +# and are allocated in two different parts of the rule namespace +# (e.g. blocks from 10000 to 49999 and individuals from 50000 to 59999) +# Internally allocator uses the base number for each item, e.g. +# rule 10000..49999 -> rule_base=1..10000 +# rule 50000..59999 -> rule_base=10001..20000 +# pipe 10000..59999 -> pipe_base=1..25000 +# a bit of math lets us compute the correct numbers. +# For CLIENT, SERVER, SERVICE the database contains entries as +# XID TYPE arg rule_base pipe_base +# For blocks the entries are +# XID RULE - rule_base - +# XID PIPE - - pipe_base +# When a rule or pipe is referenced we first check that the owner owns it. +# more details below. + +#-- main starts here +debug "--- $0 START for $SLICENAME ---" + +# If the db does not exist, create it and we clean rules and pipes +[ ! -e ${DBFILE} ] && clean_db + +# A request to the vsys backend is composed by a single line of input +read REQ # read one line, ignore the rest +echo "read ${REQ}" +set_verbose ${REQ} # use inital -v if present +set_test ${REQ} # use inital -t if present +REQ="`filter ${REQ}`" # remove -v and -t and invalid chars +debug "--- processing <${REQ}>" +acquire_lock # critical section +process ${REQ} +release_lock +debug "--- $0 END ---" +exit 0