Major changes:
[vsys-scripts.git] / exec / ipfw-be
index b841200..801f041 100755 (executable)
 #!/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} <dummynet parameters>
-# 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
+# If HOOK is set the program is called before configuring a rule.
+# A sample hook can be found in the ipfwroot.rpm package,
+# it can be used to collect statistical information on dummynet usage.
+# To configure an hook, set the HOOK variable as follow:
+# HOOK=/tmp/sample_hook
 
-LOG_FILE=/tmp/netconfig.log
+# 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 errors. The format is
+#
+#      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
+lockfile=/var/lock/ipfw.lock
+
+# These values are the keys used in the database for blocks,
+# rules and pipes
+# The index rule numbers allocated to a slice can be computed
+# with the following formula:
+#      index_min_rule = $(($RULE_BASE + $(($M*$block_n))))
+# where block_n is the block number associated with the slice
+# and M is the block size.
+BLOCK_MIN=1
+BLOCK_MAX=1000
+M=50           # block size
+RULE_BASE=10001 # the hightest rule is RULE_BASE + (M*BLOCK_MAX)
+PIPE_MIN=1
+PIPE_MAX=25000
+# These are the actual rule numbers used in ipfw
+IPFW_RULE_MIN=10000
+IPFW_RULE_MAX=59999
+IPFW_PIPE_MIN=10000
+IPFW_PIPE_MAX=59999
+# The mapping between keys and ipfw configuration number follow:
+# rule_nr 10001..20000 are mapped to ipfw_rules 50000..59999 (n+39999)
+# pipe_nr 1..25000 are mapped to ipfw_pipes 10000-59999 (n*2+9998)
+# Rule index can be mapped to ipfw rules with the following formula:
+#      ipfw_rule = $(($index_rule + 39999))
+# Pipes index can be mapped to ipfw pipes with:
+#      ipfw_pipein = $(($index_pipe + $index_pipe + 9998))
+#      ipfw_pipeout = $(($ipfw_pipein + $1))
+#
+# the skipto and the generic default rule
+# these values are used to initialize the firewall
+SLICE_TABLE=1          # table number used for slice ids lookup
+S=1000                 # firewall rule number for the skipto rule
+D=2000                 # default rule for reserved section
+
+# set slicename and slice_id
+# these are the credential of the user invoking the backend
+SLICENAME=$1
+if [ $SLICENAME = 0 ]; then
+       SLICE_ID=0
+else
+       SLICE_ID=`id -u $SLICENAME`
+fi
 
 # programs
-CUT=/usr/bin/cut
+# XXX check consistency for variables {}
 SED=/bin/sed
+SEDOPT=-r
+[ -x ${SED} ] || { SED=`which sed` ; SEDOPT=-E ; }
 IPFW=/sbin/ipfw
-
-# set to 0 to disable debug messages
-DEBUG=0
+IPFW_CHECK="/sbin/ipfw -n"
 
 debug() { # $1 message to be displayed
-       [ x"${DEBUG}" != x"0" ] && echo $1 >>{LOG_FILE};
+       [ x"${VERBOSE}" != x"0" ] && echo "ipfw-be: $1"
 }
 
-abort() { # $1 message to be displayed
-       echo "$1"
-       exit 1
+# if the first argument is -v, enable verbose mode
+set_verbose() {
+    [ x"$1" = x"-v" -o x"$2" = x"-v" ] && 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:"
 }
 
-user_error() { # $1 message to be displayed
-       echo "1 User error: $1"
+abort() { # $1 message to be displayed in case of error
+       release_lock
+       echo "ipfw-be aborting (netconfig help): $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'
-}
-
-# 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
+       [ 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'
+}
 
-       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}
-}
-
-# Delete a given link
-delete_link()
-{
-       ipfw delete ${RULE_N}
-       ipfw pipe delete ${RULE_N}
-}
-
-# 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
-}
-
-# process a single line of input
-# this line has the following format:
-# ipfw
-# pipe
-# port timeout configuration_string
-process()
-{
-       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
+# 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}
+       # since all rules are now deleted, we should initialize the firewall 
+       ipfw_init
+}
 
-       ARGS=`echo $1 | wc -w`
-       if [ $ARGS -le 2 ]; then
-               abort "One or more input parameter is missing"
-       fi
+# 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 type arg rule pipe_base timeout
+    local new_rule=$1 type=$2 arg=$3
+    local rule_base=$4 pipe_base=$5 timeout=$6
+    local pipe_in pipe_out rule_in rule_out check_timeout
+
+    # If we use a profile file, locate the user directory
+    # move in the slice root dir XXX todo
+    [ "$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, if ok move on and do the action
+    pipe_in=$(($pipe_base + $pipe_base + 9998))
+    pipe_out=$(($pipe_in + 1))
+    local del  # anything to delete ?
+    local rule_nr=$(($rule_base + 39999))  # formula for individual rules
+    if [ x"$new_rule" != x"0" ] ; then
+       case $type in
+       server)
+           rule_in="dst-port $arg"
+           rule_out="src-port $arg"
+           del=service
+           ;;
+       client)
+           rule_in="src-port $arg"
+           rule_out="dst-port $arg"
+           del=service
+           ;;
+       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"
+           ;;
+       esac
+
+       rule_in="pipe ${pipe_in} in jail $SLICE_ID ${rule_in} // $type $arg"
+       rule_out="pipe ${pipe_out} out jail $SLICE_ID ${rule_out} // $type $arg"
+       ${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
+
+    # 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" ] && 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
+    ( 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
+do_delete() { # type arg
+    local pipe_in pipe_out pipe_base rule_base rule_nr
+    local type=$1 arg=$2
+
+    [ "${type}" = "BLOCK" ] && abort "A Block can not be deleted"
+    [ "${arg}" = "" ] && abort "Missing args on 'delete', expected on of {SERVICE|SERVER|CLIENT} port_number"
+    set `find_rule $SLICE_ID $type $arg`
+    rule_base=$1; pipe_base=$2
+    [ "$rule_base" = "0" ] && return           # no rules found
+
+    rule_nr=$(($rule_base + 39999))            # XXX only individual rules
+    pipe_in=$(($pipe_base + $pipe_base + 9998))
+    pipe_out=$(($pipe_in + 1))
+
+    $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} ${arg} " $DBFILE > ${DBFILE}.tmp
+    mv ${DBFILE}.tmp ${DBFILE}
+
+    # XXX if the use block is empty
+    # remove the table entry from ipfw and from the db
+    # not yet implemented
+}
+
+# compare the argument with the first two field of
+# the database.
+# On match returns the block number, otherwise returns 0.
+find_block() { # $1 slice_id
+    local ret
+    ret=`grep -- "^$1 BLOCK " $DBFILE`
+
+    [ x"$ret" = x ] && echo "0" && return      # nothing found
+    # ignore multiple matches. If the db is corrupt we are
+    # screwed anyways
+    set $ret
+    echo "$3"
+}
+
+# called with the database file as input
+# compare the tuple <slice_id type arg> with
+# the current firewall configuration. The database contains
+#      slice_id type arg rule_base pipe_base timeout
+# On match returns <rule_base pipe_base timeout>
+# On non match returns 0 0 0
+find_rule() { # $1 slice_id $2 type $3 arg
+    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
+}
 
-       # 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
+# returns a free rule and pipe base for client|server|service
+# within a block
+# Returns r=0 if there are no resources available
+allocate_resources() {
+    local p r
+    # remove comments, extract field, sort
+    p=`grep -v '^#' $DBFILE | grep -v BLOCK | awk '{print $5}' | sort -n | \
+       find_hole $PIPE_MIN $PIPE_MAX`
+    r=`grep -v '^#' $DBFILE | grep -v BLOCK | awk '{print $4}' | sort -n | \
+       find_hole $1 $2`
+    [ $r = 0 -o $p = 0 ] && r=0                # no resources available
+    echo $r $p
+}
+
+#
+# execute functions from root context
+# can be used from root context as follow:
+# echo "super $command $args" | /vsys/ipfw-be 0
+do_super() { # $arguments...
+       case $1 in
+       init)
+           ipfw_init; return 0
+           ;;
+       dbcleanup)
+           clean_db; return 0
+           ;;
+       *)
+           abort "Invalid super command"
+           ;;
+       esac
+}
+
+#
+# show ipfw rules and pipes filtering on slice_id
+# If the first argument is 0 filter rules,
+# otherwise filter pipes
+do_show() { #$1 show rules or pipes
+    local list ipfw_list
+
+    if [ $1 == "0" ]; then
+       list=`grep "^$SLICE_ID " $DBFILE | grep -v BLOCK | cut -d " " -f 4`
+       for i in $list; do ipfw_list="$ipfw_list $(($i + 39999))"; done
+       [ -n "${ipfw_list}" ] && ${IPFW} show $ipfw_list
+    else
+       # ipfw pipe show does not selectively filter pipes
+       # XXX so leave this code commented and show all pipes
+       #list=`grep "^$SLICE_ID " $DBFILE | grep -v BLOCK | cut -d " " -f 5`
+       # the pipe list is build adding two ipfw pipes for each pipe index
+       #for i in $list; do ipfw_list="$ipfw_list $(($i + $i + 9998)) $(($i + $i + 9998 +1)) "; done
+       #[ -n "${ipfw_list}" ] && ${IPFW} pipe show $ipfw_list
+       ${IPFW} pipe show
+    fi
+}
+
+#
+# refresh the rule timeout
+do_refresh() { # type arg timeout
+    local pipe_in pipe_out pipe_base rule_base rule_nr
+    local type=$1 arg=$2 timeout=$3
+
+    debug "do_refresh type: <$type> arg: <$arg> timeout: <$timeout>"
+    [ "${type}" = "BLOCK" ] && abort "BLOCK rule not valid"
+    [ "${timeout}" = "" ] && abort "Missing args on 'refresh', expected on of {SERVICE|SERVER|CLIENT} port_number"
+    set `find_rule $SLICE_ID $type $arg`
+    rule_base=$1; pipe_base=$2
+    [ "${rule_base}" = "0" ] && debug "no rules found" && return 0             # no rules found
+
+    rule_nr=$(($rule_base + 39999))            # XXX only individual rules
+    pipe_in=$(($pipe_base + $pipe_base + 9998))
+    pipe_out=$(($pipe_in + 1))
+    debug "ipfw rule and pipes value: rule: <$rule_nr> pipe in: <$pipe_in> pipe_out: <$pipe_out>"
+
+    [ "$TEST" = "1" ] && return
+    # update the database with the new timeout value
+    ( grep -v -- "^${SLICE_ID} ${type} ${arg} " $DBFILE;  \
+       echo "${SLICE_ID} ${type} ${arg} ${rule_base} ${pipe_base} ${timeout}" ) > ${DBFILE}.tmp
+    mv ${DBFILE}.tmp ${DBFILE}
+    echo "refreshed timeout for rule ${type} ${arg}"
+}
+
+# process a request.
+# A request is made by a set of arguments formatted as follow:
+#
+# config {server|client|service} arg [-t timeout] PIPE_IN <pipe_conf> PIPE_OUT <pipe_conf>
+# show {rules|pipes} [args]
+# delete type arg
+#
+# 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_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
+    # if present, extract the '-t timeout' substring from the command line
+    timeout=`echo ${args} | ${SED} ${SEDOPT} 's/(.+)( -t [a-zA-Z0-9]+ )(.*)/\2/'`
+    # if the '-t timeout' is specified, use the user define timeout value
+    if [ "${timeout}" != "${args}" ] ; then    # match
+       # remove the '-t ' option
+       timeout=`echo ${timeout} | ${SED} ${SEDOPT} 's/-t //'`
+       check_timeout ${timeout}        # abort on error
+       # clean the arguments
+       args=`echo ${args} | ${SED} ${SEDOPT} 's/(.+)( -t [a-zA-Z0-9]+ )(.*)/\1 \3/'`
+    else
+       # use the default value, no need to check for correctness, no need to clean arguments
+       timeout=`date --date="1day" +%s`                # default to 1 day
+    fi
+
+    debug "Timeout $timeout"
+    # Handle special requests: show and delete
+    case x"$cmd" in 
+    x"config") 
+       [ "$type" = "server" ] && do_config $timeout $type $args && return 0
+       [ "$type" = "client" ] && do_config $timeout $type $args && return 0
+       [ "$type" = "service" ] && do_config $timeout $type $args && return 0
+       abort "'config' should be followed by {server|client|service}"
+       ;;
+    x"delete") 
+       do_delete ${SLICE_ID} $type $args
+       ;;
+    x"refresh") 
+       do_refresh $type $args $timeout && return 0
+       ;;
+    x"show")
+       [ "$type" = "rules" ] && do_show 0 && return 0
+       [ "$type" = "pipes" ] && do_show 1 && return 0
+       abort "'show' should be followed by {rules|pipes}"
+       ;;
+    x"super")
+       [ $SLICE_ID = 0 ] && do_super $type $args && return 0
+       abort "no permission for ipfw-be super execution"
+       ;;
+    x"help")
+       do_help && return 0
+       ;;
+    *)
+       # help XXX to be done
+       abort "'command' should be one of {show|config|delete|refresh|release}"
+       ;;
+    esac
+}
+
+# validate the timeout
+check_timeout() { # timeout
+    local tt=`date --date="${1}" +%s`
+    [ "$?" != "0" ] && abort "Date format $1 not valid"
+}
+
+do_config() { # timeout type arg PIPE_IN pipe_conf PIPE_OUT pipe_conf
+    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"                # XXX local ?
+    [ "$CONFIG_PIPE_IN" = "" ] && abort "Missing pipe in configuration"
+
+    [ "$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"       # XXX local ?
+    [ "$CONFIG_PIPE_OUT" = "" ] && abort "Missing pipe out configuration"
+
+    debug "Configuration Required:"
+    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} ${arg}"
+
+    set `find_rule ${SLICE_ID} ${type} ${arg}`
+    local rule_base=$1
+    local pipe_base=$2
+    local new_pipe=0
+
+    if [ ! ${rule_base} = "0" ] ; then
+       debug "Rule found, just changing the pipe configuration"
+       add_rule $new_pipe $type $arg $rule_base $pipe_base $timeout
+       hook_call $type $port $rule_base $pipe_base $timeout
+       return 0; # link configured, exit
+    fi
+
+    debug "link not found, search for a block already allocated to the user"
+
+    # Search if there is a block already allocated to the slice_id
+    set `find_block ${SLICE_ID}`
+    local block_n=$1
+    if [ ${block_n} = "0" ] ; then
+       debug "Block not found, allocate a new block"
+       # blocks are allocated in sequence, get the first free
+       block_n=`grep BLOCK $DBFILE | tail -1 | cut -d " " -f 3`
+       if [ -z $block_n ]; then
+               block_n=$(($BLOCK_MIN - 1))
        fi
+       RULE_IN_MIN=$(($RULE_BASE + $(($M*$block_n))))
+       block_n=$(($block_n +1))
+       debug "Allocated new block $block_nr to user $SLICE_ID"
+       [ $block_n -gt $BLOCK_MAX ] && abort "no block resources available"
+
+       # add the rule into the firewall table.
+       # note that the rule number into the table are not database number,
+       # so we need to compute the firewall number before the table insertion
+       local ipfw_rule_nr=$(($RULE_IN_MIN + 39999))  # XXX formula for individual rules
+       debug "Configuring table: <${IPFW_CHECK} table $SLICE_TABLE add ${SLICE_ID} ${ipfw_rule_nr}>"
+       ${IPFW_CHECK} table $SLICE_TABLE add ${SLICE_ID} ${rule_nr} > /dev/null || \
+               abort "ipfw syntax error $rule_out"
+       ${IPFW} table $SLICE_TABLE add ${SLICE_ID} ${ipfw_rule_nr} > /dev/null
+
+       [ "$TEST" = "1" ] && return
+       # add the block declaration to the database
+       ( grep -v -- "^${SLICE_ID} BLOCK " $DBFILE;  \
+               echo "${SLICE_ID} BLOCK ${block_n}" ) > ${DBFILE}.tmp
+       mv ${DBFILE}.tmp ${DBFILE}
+    else
+       debug "Block $block_n found for user $SLICE_ID"
+    fi
+
+    RULE_IN_MAX=$(($RULE_BASE + $(($M * $block_n))))
+    RULE_IN_MIN=$(($RULE_IN_MAX - $M))
+    debug  "Block $block_n, with rules <${RULE_IN_MIN}:${RULE_IN_MAX}>" 
+    debug  "Corresponding to ipfw rules <$(($RULE_IN_MIN + 39999)):$(($RULE_IN_MAX + 39999))>"
+    debug "where the last rule number belongs to the next slice."
+
+    new_pipe=1
+    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"
+
+    add_rule $new_pipe $type $arg $rule_base $pipe_base $timeout
+    hook_call $type $port $rule_base $pipe_base $timeout
+}
 
+#
+# acquire the lock XXX check lockfile
+acquire_lock() {
+    [ "$TEST" = 1 ] && return
+    lockfile -s 0 -r 0 $lockfile 2> /dev/null
+    if [ $? -ne 0 ] ; then
+       echo "lock acquisition failed"
+       exit -1
+    fi
 }
 
-# main starts here
+#
+# release the lock
+release_lock() {
+    rm -f $lockfile
+}
 
-       debug "Debug activated"
-       requests=[]
-       i=0
+#
+# initialize the firewall with PlanetLab default rules
+ipfw_init() {
+       ${IPFW} add $S skipto tablearg lookup jail $SLICE_TABLE
+       ${IPFW} add $D allow all from any to any
+}
 
-       while read request
-       do
-               # read -a read arguments in array
-               # XXX skip lines starting with #
-               requests[$i]=$request;
-               let i=$i+1
-       done
+#
+# if present, call a hook function
+# Arguments are:
+# slice_id type port rule_base pipe_base timeout
+hook_call() {
+       if [ -n "${HOOK}" -a -x "${HOOK}" ]; then
+               ${HOOK} ${SLICE_ID} "$*" &
+       fi
+}
 
-       # create the lock
+do_help() {
+       cat << EOF
+Usage:
+        ./neconfig [SERVER|CLIENT|SERVICE] port [-t timeout]    \
+                PIPE_IN <pipe in configuration> PIPE_OUT <pipe out configuration>
+        ./netconfig show [rules|pipes]
+        ./netconfig delete [SERVER|CLIENT|SERVICE] port
+        ./netconfig refresh [-t timeout] [SERVER|CLIENT|SERVICE] port
+
+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 pipe configuration, both for the upstream and downstream link,
+follow the dummynet syntax. A quick and not exaustive example
+of the parameters that can be used to configure the delay,
+the bandwidth and the packet loss rate for a link follow:
+
+        PIPE_IN|PIPE_OUT delay 100ms bw 1Mbit/s plr 0.1
+
+The full documentation is on the manpage[1].
+
+The timeout value follow the linux 'date' command format[2]
+and can be specified as follow:
+        1week
+        2hours
+        3days
+
+--- References:
+[1] http://www.freebsd.org/cgi/man.cgi?query=ipfw
+[2] http://linuxmanpages.com/man1/date.1.php
+EOF
+}
 
-       # process requests
-       for i in `/usr/bin/seq 0 $((${#requests[*]} - 1))`
-       do
-               process "${requests[$i]}"
-       done
+# ALLOCATION OF RULES AND PIPES
+# The ruleset is composed by different sections, as follow:
+# - a first set of rules is reserved and is configurable by
+#   the root context only;
+# - the skipto rule (S), used to optimize the slice rule search;
+# - a second block of reserved rules;
+# - a default (D) rule for the generic configuration;
+# - the slice reserved rules, a block of M rules for each slice;
+# - the firewall default rule.
+#
+# To summarize:
+#      1...S-1 first block of reserved rules
+#      S       skipto tablearg lookup jail 1
+#      S+1..D-1 ... second block of reserved rules
+#      D       allow ip from any to any
+#
+#      RULE_BASE <block of M entries for first user>
+#      RULE_BASE+M <block of M entry for second user ...>
+#      ...
+#
+# Out of 64k rules, we allocate a block of M=50 consecutive
+# rules to each slice using emulation. Within this block,
+# each configuration uses one rule number and two pipes.
+#
+# Pipes are allocated starting from PIPE_BASE, a couple
+# of pipes for each configuration.
+#
+# DATABASE FORMAT
+# The database is stored on a file, and contains
+# one line per record with this general structure
+#      XID     TYPE    arg1    arg2    ...
+# whitespace separates the fields. arg1, arg2, ...
+# have different meaning depending on the type.
+#
+# In the database we have the following records:
+# - one entry for each slice that has active emulation entries.
+#   For each of these slices we reserve a block of M ipfw rules
+#   starting at some RULE_BASE rule number.
+#   The database entry for this info has the form
+#      XID     BLOCK   block_number
+#   where blocks are numbered sequentially from 1.
+#   The actual rule number is RULE_BASE + M*(block_number)
+#   (we don't care if we waste some rules)
+#
+# - one entry for each predefined config (CLIENT, SERVER, SERVICE).
+#   The database entry for this info has the form
+#      XID     {CLIENT|SERVER|SERVICE} arg     rule_nr pipe_index
+#   rule_nr is the absolute rule number for this configuration
+#   (it must be within the block of M rules allocated to the slice)
+#   pipe_index is the index of the couple of pipes used for the
+#   configuration. pipe_index starts from 1.
+#
 
-       # delete the lock
-       exit 0
+#-- 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
+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