#!/bin/sh # # Marta Carbone, Luigi Rizzo # Copyright (C) 2009 Universita` di Pisa # # This script the vsys backend used to configure emulation. # In detail it: # - reads the user's input from the vsys input pipe # - validates the input # - configures the firewall # - writes results on the output vsys pipe # # Configurable variables are at the beginning (only HOOK so far) # If HOOK is set, ${HOOK} 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 a hook, set the HOOK variable as follow: # HOOK=/tmp/sample_hook #--- You should not touch anything below this line. ---- # For documentation see ARCHITECTURE near the end of the file. #--- global variables --- VERBOSE=0 # set to !0 to enable debug messages TEST=0 # set to 1 for test mode # The database and the lock file DBFILE=/tmp/ff lockfile=/var/lock/ipfw.lock # Min and max value (inclusive) for block_index BLOCK_MIN=1 BLOCK_MAX=1000 M=50 # size of per-slice block of rules # Min and max value (inclusive) for pipe_index PIPE_MIN=1 PIPE_MAX=25000 # These are the actual rule numbers used in ipfw IPFW_RULE_MIN=10000 # initial per-slice rule number IPFW_PIPE_MIN=10000 # initial pipe number # 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 SLICE_ID=`id -u $SLICENAME` [ x"$SLICE_ID" = x"" ] && echo "No sliver present." && exit # programs # XXX check consistency for variables {} SED=/bin/sed SEDOPT=-r [ -x ${SED} ] || { SED=`which sed` ; SEDOPT=-E ; } IPFW=/sbin/ipfw IPFW_CHECK="/sbin/ipfw -n" debug() { # $1 message to be displayed [ 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 } # set test mode if -q is found 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 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/-q' will be removed filter() { # $* variables to be filtered [ 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} -q table $SLICE_TABLE 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 } # # Add the ipfw rule/pipe and update the database. # The pipe-in and pipe-out config are through global variables # rule_in rule_out because they may be long. XXX why ? # Other arguments are on the command line # # the new_rule variable is set if the rule to be installed is new # we need to know this because we do not want to clean # rule counters on pipes reconfiguration add_rule() { # slice_id new_rule type arg ipfw_rule pipe_index timeout local slice_id=$1 new_rule=$2 type=$3 arg=$4 local ipfw_rule=$5 pipe_index=$6 timeout=$7 local ipfw_pipe_in ipfw_pipe_out check_timeout local p h # used to split the argument local h_in h_out # local rule_in rule_out # XXX test if this works # find actual pipe numbers ipfw_pipe_in=$(($IPFW_PIPE_MIN + $((2 * $(($pipe_index - 1)))) )) ipfw_pipe_out=$(($ipfw_pipe_in + 1)) local del # used to delete incompatible configurations # split the argument, and prepare PORTLIST (p) and ADDRLIST (h) p=`echo $arg | cut -s -d "@" -f1-` # empty if no separator if [ "$p" = "" ] ; then p=$arg else p=`echo $arg | cut -d "@" -f1` h=`echo $arg | cut -d "@" -f2` fi if [ "$h" = "" ] ; then h_in="" h_out="" else h_in=" src-ip ${h} " h_out=" dst-ip ${h} " fi # first, call ipfw -n to check syntax, if ok move on and do the action if [ x"$new_rule" != x"0" ] ; then case $type in SERVER|server) rule_in="dst-port $p" rule_out="src-port $p" del=service ;; CLIENT|client) rule_in="src-port $p" rule_out="dst-port $p" del=service ;; SERVICE|service) rule_in="{ src-port $p or dst-port $p }" rule_out="{ src-port $p or dst-port $p }" del="cli_ser" ;; *) abort "invalid service type $type" ;; esac rule_in="pipe ${ipfw_pipe_in} in ${h_in} ${rule_in} // $type $arg $slice_id" rule_out="pipe ${ipfw_pipe_out} out ${h_out} ${rule_out} // $type $arg $slice_id" # Move into the user root directory. The profile should be located there ( cd /vservers/${SLICENAME}/`pwd`/ ; ${IPFW_CHECK} add ${ipfw_rule} ${rule_in} ) > /dev/null || \ abort "ipfw syntax error ${rule_in}" ( cd /vservers/${SLICENAME}/`pwd`/ ; ${IPFW_CHECK} add ${ipfw_rule} ${rule_out} ) > /dev/null || \ abort "ipfw syntax error ${rule_out}" fi # check error reporting ( cd /vservers/${SLICENAME}/`pwd`/ ; ${IPFW_CHECK} pipe ${ipfw_pipe_in} config ${CONFIG_PIPE_IN} ) > /dev/null || \ abort "ipfw syntax error pipe_in" ( cd /vservers/${SLICENAME}/`pwd`/ ; ${IPFW_CHECK} pipe ${ipfw_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 0 $slice_id service $arg [ "$del" = "cli_ser" ] && do_delete 0 $slice_id client $arg [ "$del" = "cli_ser" ] && do_delete 0 $slice_id server $arg [ "$new_rule" != "0" ] && ${IPFW} add ${ipfw_rule} $rule_in > /dev/null [ "$new_rule" != "0" ] && ${IPFW} add ${ipfw_rule} $rule_out > /dev/null # config pipes ( cd /vservers/${SLICENAME}/`pwd`/ ; ${IPFW} pipe ${ipfw_pipe_in} config ${CONFIG_PIPE_IN} ) ( cd /vservers/${SLICENAME}/`pwd`/ ; ${IPFW} pipe ${ipfw_pipe_out} config ${CONFIG_PIPE_OUT} ) # send output to the user ${IPFW} show ${ipfw_rule} ${IPFW} pipe ${ipfw_pipe_in} show ${IPFW} pipe ${ipfw_pipe_out} show # do not write on the database on test-only [ "$TEST" = "1" ] && return # add to the database ( grep -iv -- "^${slice_id} ${type} ${arg} " $DBFILE; \ echo "${slice_id} ${type} ${arg} ${ipfw_rule} ${pipe_index} ${timeout}" ) > ${DBFILE}.tmp mv ${DBFILE}.tmp ${DBFILE} } # # Delete a given configuration # if block_deletion !0 free block resources (if necessary) # otherwise leave the block allocated in case # we are adding the first rule do_delete() { # block_deletion slice_id type arg local ipfw_pipe_in ipfw_pipe_out pipe_index ipfw_rule local block_deletion=$1 slice_id=$2 type=$3 arg=$4 [ "${type}" = "BLOCK" ] && abort "A BLOCK can not be deleted" [ "${arg}" = "" ] && abort "Missing args on 'delete', expected on of {CLIENT|SERVER|SERVICE} arg" set `find_rule $slice_id $type $arg` ipfw_rule=$1; pipe_index=$2 [ "$ipfw_rule" = "0" ] && return # no rules found # find actual pipe numbers XXX do as function ipfw_pipe_in=$(($IPFW_PIPE_MIN + $((2 * $(($pipe_index - 1)))) )) ipfw_pipe_out=$(($ipfw_pipe_in + 1)) echo "removing configuration ${slice_id} ${type} ${arg}" [ "$TEST" = "1" ] && return 0 $IPFW delete ${ipfw_rule} $IPFW pipe delete ${ipfw_pipe_in} $IPFW pipe delete ${ipfw_pipe_out} # remove from the database (case insensitive) grep -iv -- "^${slice_id} ${type} ${arg} " $DBFILE > ${DBFILE}.tmp mv ${DBFILE}.tmp ${DBFILE} # if there are no more rules for the user # remove the table entry from ipfw and from the db [ $block_deletion = 0 ] && return 0 local rule_counter=`grep ^${slice_id} ${DBFILE} | wc -l` [ $rule_counter -gt 1 ] && return 0 # there are still user rules # delete the block and clean the table local block_n=`grep "^${slice_id} BLOCK" ${DBFILE} | cut -d " " -f 3` debug "Deleting BLOCK <${block_n}> entry from ipfw and from the database" table_remove $slice_id $block_n } # compare the argument with the first two field of # the database. # On match returns the block number, otherwise returns 0. # no echo inside 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" } # # remove the default user rule and # the a BLOCK entry from ipfw and update the db # no echo inside table_remove() { # $slice_id $block_n [ "$TEST" = "1" ] && return 0 # compute and delete the last user rule local ipfw_rulemax=$(($IPFW_RULE_MIN + $(($M *${block_n})) -1)) ${IPFW} table $SLICE_TABLE delete $slice_id ${IPFW} delete ${ipfw_rulemax} ( grep -iv -- "^${slice_id} BLOCK ${block_n}" $DBFILE; ) > ${DBFILE}.tmp mv ${DBFILE}.tmp ${DBFILE} return 0 } # # Find a rule and pipe_index for the given key (xid type arg) # Allocate a new block if first entry for this xid. # Rule and pipe are not written into the database, only the block is. # # Return ipfw_rule pipe_index new_rule # 'new_rule' is 0 if the rule existed, 1 if it is new # # return ipfw_rule = 0 if there are no resources available find_allocate() { # slice_id type arg local slice_id=$1 type=$2 arg=$3 local ipfw_rule pipe_index new_block=0 # search for already allocated rule and pipes set `find_rule $slice_id $type $arg` ipfw_rule=$1; pipe_index=$2 [ ! ${ipfw_rule} = 0 ] && echo $ipfw_rule $pipe_index "0" && return 0 # rules found, return # no rules found, search for an already existing block, or # allocate a new one local block_n=`find_block ${slice_id}` [ ${block_n} = "0" ] && new_block=1 && block_n=`find_free_block` [ ${block_n} = "0" -o ${block_n} -gt $BLOCK_MAX ] && echo 0 && return 0; # We have a valid block, compute the range for user rules local ipfw_rulemin=$(($IPFW_RULE_MIN + $(($M *$(($block_n - 1)))))) local ipfw_rulemax=$(($(($ipfw_rulemin + $M)) - 1 )) # Find rule and pipes, reserve the last rule for the user's # default rule that catches regular traffic. set `allocate_resources $ipfw_rulemin $(($ipfw_rulemax - 1))` ipfw_rule=$1; pipe_index=$2 [ $ipfw_rule = 0 ] && echo 0 && return 0 # no resources # If this is a new block, add the slice to the lookup table # and put a default rule at the end of the block. if [ "$TEST" = "0" -a $new_block = 1 ] ; then ${IPFW} table $SLICE_TABLE add ${slice_id} ${ipfw_rulemin} > /dev/null ${IPFW} add ${ipfw_rulemax} allow all from any to any > /dev/null ( echo "${slice_id} BLOCK ${block_n}" ) >> ${DBFILE} fi echo $ipfw_rule $pipe_index "1" return 0 } # # called with the database file as input # compare the tuple with # the current firewall configuration. The database contains # slice_id type arg ipfw_rule pipe_index timeout # On match returns # On non match returns 0 0 0 # no echo inside find_rule() { # slice_id type arg local ret ret=`grep -i -- "^$1 $2 $3 " $DBFILE | grep -v BLOCK` [ 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 } # XXX despite the name this does not allocate but only finds holes. # returns a free rule and pipe base for client|server|service # within a block # Returns r=0 if there are no resources available # no echo inside allocate_resources() { # ipfw_minrule ipfw_maxrule 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 } # Returns the index of a free block # Returns 0 if there are no resources available # no debug inside find_free_block() { b=`grep -v '^#' $DBFILE | grep BLOCK | awk '{print $3}' | sort -n | \ find_hole $BLOCK_MIN $BLOCK_MAX` echo $b } # parse the ipfw database and remove expired rules # # Each timeout value stored in the database is compared against # the current time. If the timeout is older than current, # the rules and related pipes will be deleted. kill_expired() { # slice_id type arg local match timeout # if there is no database file exit [ ! -f ${DBFILE} ] && return 0 # Get the current time now=`date -u +%s` cp ${DBFILE} ${DBFILE}.kill cat ${DBFILE}.kill | grep -v BLOCK | while read line; do match=`echo $line|cut -d " " -f 1-3` timeout=`echo $line|cut -d " " -f 6` [ $now -gt $timeout ] && do_delete 1 $match done rm ${DBFILE}.kill } # 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 ;; killexpired) kill_expired; return 0 ;; *) abort "Invalid super command" ;; esac } # refresh the rule timeout do_refresh() { # slice_id type arg timeout local ipfw_pipe_in ipfw_pipe_out pipe_index local slice_id=$1 type=$2 arg=$3 timeout=$4 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` ipfw_rule=$1; pipe_index=$2 [ "${ipfw_rule}" = "0" ] && debug "no rules found" && return 0 # no rules found [ "$TEST" = "1" ] && return # update the database with the new timeout value ( grep -iv -- "^${slice_id} ${type} ${arg} " $DBFILE; \ echo "${slice_id} ${type} ${arg} ${ipfw_rule} ${pipe_index} ${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] IN OUT # show {rules|pipes} [args] # delete type arg # refresh type arg [-t timeout] # # 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 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 timeout provided by the user if [ "${timeout}" != "${args}" ] ; then # match # remove the '-t ' option timeout=`echo ${timeout} | ${SED} ${SEDOPT} 's/-t //'` timeout=`check_timeout ${timeout}` [ $timeout = 0 ] && abort "Date format $1 not valid" # 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 # if the table rule is not present, add it local table_rule=`${IPFW} show $S | grep "skipto tablearg" | grep "lookup jail $SLICE_TABLE"` [ -z "$table_rule" ] && ipfw_init debug "Timeout $timeout" # Handle special requests: show and delete case x"$cmd" in x"config") case x"$type" in xserver|xSERVER|xclient|xCLIENT|xservice|xSERVICE) do_config $SLICE_ID $timeout $type $args && return 0 ;; esac abort "'config' should be followed by {CLIENT|SERVER|SERVICE}" ;; x"delete") do_delete 1 $SLICE_ID $type $args ;; x"refresh") do_refresh $SLICE_ID $type $args $timeout && return 0 ;; x"show") # XXX filter out sliver rules [ "$type" = "rules" ] && ${IPFW} show && return 0 [ "$type" = "pipes" ] && ${IPFW} pipe show && 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" ] && echo 0 && return echo $tt } do_config() { # slice_id timeout type arg IN pipe_conf 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 local p h; # port and optional hostname [ "$1" != "IN" ] && abort "Missing addr:port, or IN requested" shift # read pipe in configuration i="" while [ "$1" != "" -a "$1" != "OUT" ] ; do i="$i $1" shift done CONFIG_PIPE_IN="$i" # XXX local ? [ "$CONFIG_PIPE_IN" = "" ] && abort "Missing pipe in configuration" [ "$1" != "OUT" ] && abort "Missing pipe in configuration, or missing 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" # process the argument (port and hostname are separated by a @) # split the argument, and prepare the remote host configuration string p=`echo $arg | cut -s -d "@" -f1-` # empty it there is no separator if [ "$p" = "" ] ; then p=$arg else p=`echo $arg | cut -d "@" -f1` h=`echo $arg | cut -d "@" -f2` fi # A port value is mandatory [ "$p" = "" ] && abort "A port value is mandatory." # SERVICE do not support remote hostname filtering [ $type = "service" ] && [ "$h" != "" ] && \ abort "The service configuration do not support filtering remote hostnames." debug "Configuration Required:" debug "slice_id: $SLICE_ID" debug "type: $type" debug "full arg: $arg" debug "mandatory port(s): $p optional hostname(s): $h" debug "timeout: $timeout" debug "IN: $CONFIG_PIPE_IN" debug "OUT: $CONFIG_PIPE_OUT" debug "-----------------------" # check if the link is already configured debug "Search for slice_id: ${slice_id} type: ${type} port: ${arg}" set `find_allocate ${slice_id} ${type} ${arg}` local ipfw_rule=$1 pipe_index=$2 new_rule=$3 [ ${ipfw_rule} = 0 ] && abort "No resources available" debug "Found or allocated resources ipfw_rule: ${ipfw_rule} and pipe_index: ${pipe_index}" add_rule $slice_id $new_rule $type $arg $ipfw_rule $pipe_index $timeout hook_call $type $port $rule_base $pipe_base $timeout return 0; # link configured, exit } # # 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 } # # release the lock release_lock() { rm -f $lockfile } # # initialize the firewall with PlanetLab default rules ipfw_init() { ${IPFW} -q delete $S ${IPFW} -q delete $D ${IPFW} add $S skipto tablearg lookup jail $SLICE_TABLE ${IPFW} add $D allow all from any to any } # # 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 debug "Calling the hook function." ${HOOK} ${SLICE_ID} "$*" & fi } do_help() { cat << EOF Usage: ./neconfig {CLIENT|SERVER|SERVICE} arg [-t timeout] \ IN OUT ./netconfig show {rules|pipes} ./netconfig delete {CLIENT|SERVER|SERVICE} arg ./netconfig refresh [-t timeout] {CLIENT|SERVER|SERVICE} arg We support three modes of operation: CLIENT programs on the node connect to remote ports and/or addresses. Emulation intercepts traffic involving those ports/addresses SERVER programs on the node listen on specific ports. Emulation intercepts traffic on those ports, optionally limited to specific client addresses. SERVICE the node runs both clients and servers, we can only specify the ports on which emulation is configured. 'arg' has the form PORTLIST[@ADDRLIST], where ADDRLIST is optional and only supported for CLIENT and SERVER modes. PORTLIST and ADDRLIST can be specified as any valid port or address specifier in ipfw, e.g. - a single value 443 or 10.20.30.40/24 - a comma-separated list 1111,2222,3333 1.2.3.4,5.6.7.8 - a range 1111-2222 (only for ports) Addresses can also be specified as symbolic hostnames, and they are resolved when the rule is installed. Note that they always indicate the remote endpoint. 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's configuration, both for the upstream and downstream link, follows 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: IN|OUT delay 100ms bw 1Mbit/s plr 0.1 The profile file, if present, should be located into the sliver's root directory. 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 } #--- DOCUMENTATION AND INTERNAL ARCHITECTURE --- # # When a user configures an emulated link, we need to allocate # two pipes and one ipfw rule number to store the parameters. # Reconfigurations of existing links reuse the previous resources. # We keep track of all resources (pipes, rules and blocks of rules) # in a database stored in a text file, see DATABASE FORMAT below. # # Pipes are allocated in pairs. In the database each pair is numbered # from PIPE_MIN to PIPE_MAX. The actual pipe numbers for each pair are # # ipfw_pipein = IPFW_PIPE_MIN + 2*(pipe_index-1) # ipfw_pipeout = ipfw_pipein + 1 # # The rules number is allocated within a block of M consecutive rules # for each slice. The block is allocated at the first configuration # of an emulated link, and deallocated when the last link is removed. # In the database, blocks are numbered from BLOCK_MIN to BLOCK_MAX, # and the range of rules for a given block_index is # # ipfw_min_rule = RULE_BASE # ipfw_max_rule = RULE_BASE + ((M-1)*block_index) -1 # # All lookups, and the block allocation, are done in find_allocate(). # The rule_number and pipe_index are written in the database # by add_rule() after checking the correctness of the request. # # #--- RULESET STRUCTURE --- # The ruleset is made of different sections, as follows: # - an initial block of rules, reserved and configurable by # the root context only; # - a skipto rule (S), used to jump directly to the block # associated with a given slice; # - a second block of reserved rules, to catch remaining traffic. # This ends with rule number D which is an 'accept all'; # - after D, we have a block of M rule numbers for each slice. # Each of these blocks ends with an 'accept all' rule; # - finally, rule 65535 is the firewall's 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 # RULE_BASE+M # ... # #--- DATABASE FORMAT --- # The database is stored in a text file, and contains one record per # line with the following structure # # XID TYPE arg1 arg2 ... # # Whitespace separates the fields. arg1, arg2, ... have different # meaning depending on the TYPE. XID is the slice ID. # # In the database we have the following records: # - one entry of type BLOCK for each slice with configured links. # This entry represents the block_index of the block of M ipfw # rules allocated to the slice, as follows: # # XID BLOCK block_index # (BLOCK_MIN <= block_index <= BLOCK_MAX) # # - one entry for each link (CLIENT, SERVER, SERVICE). # The database entry for this info has the form # # XID {CLIENT|SERVER|SERVICE} arg ipfw_rule pipe_index timeout # # 'TYPE' reflects the configuration mode; # 'arg' is PORTLIST@ADDRLIST and is used as a search key together # with the XID and TYPE; # 'ipfw_rule' is the unique ipfw rule number used for this # emulated link. It must be within the block of M rule numbers # allocated to the slice; # 'pipe_index' is the index of the pair of pipes used for the # configuration; #-- main starts here debug "--- $0 START for $SLICENAME ---" # If the db does not exist, create it and 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 -q and invalid chars debug "--- processing <${REQ}>" acquire_lock # critical section process ${REQ} release_lock debug "--- $0 END ---" exit 0