svn keywords
[vsys-scripts.git] / root-context / exec / ipfw-be
1 #!/bin/sh
2 #
3 # Marta Carbone, Luigi Rizzo
4 # Copyright (C) 2009 Universita` di Pisa
5 #
6 # This script the vsys backend used to configure emulation.
7 # In detail it:
8 # - reads the user's input from the vsys input pipe
9 # - validates the input
10 # - configures the firewall
11 # - writes results on the output vsys pipe
12 #
13 # Configurable variables are at the beginning (only HOOK so far)
14
15 # If HOOK is set, ${HOOK} is called before configuring a rule.
16 # A sample hook can be found in the ipfwroot.rpm package,
17 # it can be used to collect statistical information on dummynet usage.
18 # To configure a hook, set the HOOK variable as follow:
19 # HOOK=/tmp/sample_hook
20
21 #--- You should not touch anything below this line. ----
22 # For documentation see ARCHITECTURE near the end of the file.
23
24 #--- global variables ---
25 VERBOSE=0       # set to !0 to enable debug messages
26 TEST=0          # set to 1 for test mode
27
28 # The database and the lock file
29 DBFILE=/tmp/ff
30 lockfile=/var/lock/ipfw.lock
31
32 # Min and max value (inclusive) for block_index
33 BLOCK_MIN=1
34 BLOCK_MAX=1000
35 M=50            # size of per-slice block of rules
36 # Min and max value (inclusive) for pipe_index
37 PIPE_MIN=1
38 PIPE_MAX=25000
39
40 # These are the actual rule numbers used in ipfw
41 IPFW_RULE_MIN=10000     # initial per-slice rule number
42 IPFW_PIPE_MIN=10000     # initial pipe number
43
44 # The skipto and the generic default rule
45 # these values are used to initialize the firewall
46 SLICE_TABLE=1           # table number used for slice ids lookup
47 S=1000                  # firewall rule number for the skipto rule
48 D=2000                  # default rule for reserved section
49
50 # set slicename and slice_id
51 # these are the credential of the user invoking the backend
52 SLICENAME=$1
53 SLICE_ID=`id -u $SLICENAME`
54 [ x"$SLICE_ID" = x"" ] && echo "No sliver present." && exit
55
56 # programs
57 # XXX check consistency for variables {}
58 SED=/bin/sed
59 SEDOPT=-r
60 [ -x ${SED} ] || { SED=`which sed` ; SEDOPT=-E ; }
61 IPFW=/sbin/ipfw
62 IPFW_CHECK="/sbin/ipfw -n"
63
64 debug() { # $1 message to be displayed
65         [ x"${VERBOSE}" != x"0" ] && echo "ipfw-be: $1"
66 }
67
68 # if the first argument is -v, enable verbose mode
69 set_verbose() {
70     [ x"$1" = x"-v" -o x"$2" = x"-v" ] && VERBOSE=1
71 }
72
73 # set test mode if -q is found
74 set_test() {
75     [ x"$1" = x"-q" -o x"$2" = x"-q" ] || return
76     TEST=1
77     IPFW="/bin/echo ipfw:"
78     IPFW_CHECK="/bin/echo ipfw -n:"
79 }
80
81 abort() { # $1 message to be displayed in case of error
82         release_lock
83         echo "ipfw-be aborting (netconfig help): $1"
84         exit 1
85 }
86
87 # remove dangerous characters from user input
88 # if present, the leading '-v/-q' will be removed
89 filter() { # $* variables to be filtered
90         [ x${1} = x"-v" -o x${1} = x"-q" ] && shift
91         [ x${1} = x"-v" -o x${1} = x"-q" ] && shift
92         # allowed chars are: numbers, uppercase and lowercase letters,
93         # spaces, and the following symbols: .,_-/
94         echo "$*" | ${SED} ${SEDOPT} 's/[^\t0-9a-zA-Z., _\/\{}@-]*//g'
95 }
96
97 # remove all entries from the ipfw config, and create an empty db
98 clean_db() {
99         rm -f ${DBFILE}
100         touch ${DBFILE}
101         # we would like to delete ranges of rules and pipes but this
102         # is not supported so for the time being we kill them all
103         ${IPFW} -q flush
104         ${IPFW} -q pipe flush
105         ${IPFW} -q table $SLICE_TABLE flush
106         #${IPFW} delete ${IPFW_RULE_MIN}-${IPFW_RULE_MAX}
107         #${IPFW} pipe delete ${IPFW_PIPE_MIN}-${IPFW_PIPE_MAX}
108         # since all rules are now deleted, we should initialize the firewall 
109         ipfw_init
110 }
111
112 #
113 # Add the ipfw rule/pipe and update the database.
114 # The pipe-in and pipe-out config are through global variables
115 # rule_in rule_out because they may be long. XXX why ?
116 # Other arguments are on the command line
117 #
118 # the new_rule variable is set if the rule to be installed is new
119 # we need to know this because we do not want to clean
120 # rule counters on pipes reconfiguration
121 add_rule() { # slice_id new_rule type arg ipfw_rule pipe_index timeout
122     local slice_id=$1 new_rule=$2 type=$3 arg=$4
123     local ipfw_rule=$5 pipe_index=$6 timeout=$7
124     local ipfw_pipe_in ipfw_pipe_out check_timeout
125     local p h # used to split the argument
126
127     local h_in h_out
128     # local rule_in rule_out # XXX test if this works
129     # find actual pipe numbers
130     ipfw_pipe_in=$(($IPFW_PIPE_MIN + $((2 * $(($pipe_index - 1)))) ))
131     ipfw_pipe_out=$(($ipfw_pipe_in + 1))
132     local del           # used to delete incompatible configurations
133
134     # split the argument, and prepare PORTLIST (p) and ADDRLIST (h)
135     p=`echo $arg | cut -s -d "@" -f1-`  # empty if no separator
136     if [ "$p" = "" ] ; then
137         p=$arg
138     else
139         p=`echo $arg | cut -d "@" -f1`
140         h=`echo $arg | cut -d "@" -f2`
141     fi
142
143     if [ "$h" = "" ] ; then
144         h_in=""
145         h_out=""
146     else
147         h_in=" src-ip ${h} "
148         h_out=" dst-ip ${h} "
149     fi
150
151     # first, call ipfw -n to check syntax, if ok move on and do the action
152     if [ x"$new_rule" != x"0" ] ; then
153         case $type in
154         SERVER|server)
155             rule_in="dst-port $p"
156             rule_out="src-port $p"
157             del=service
158             ;;
159         CLIENT|client)
160             rule_in="src-port $p"
161             rule_out="dst-port $p"
162             del=service
163             ;;
164         SERVICE|service)
165             rule_in="{ src-port $p or dst-port $p }"
166             rule_out="{ src-port $p or dst-port $p }"
167             del="cli_ser"
168             ;;
169         *)
170             abort "invalid service type $type"
171             ;;
172         esac
173
174         rule_in="pipe ${ipfw_pipe_in} in ${h_in} ${rule_in} // $type $arg $slice_id"
175         rule_out="pipe ${ipfw_pipe_out} out ${h_out} ${rule_out} // $type $arg $slice_id"
176
177         # Move into the user root directory. The profile should be located there
178         ( cd /vservers/${SLICENAME}/`pwd`/ ; ${IPFW_CHECK} add ${ipfw_rule} ${rule_in} ) > /dev/null || \
179                 abort "ipfw syntax error ${rule_in}" 
180         ( cd /vservers/${SLICENAME}/`pwd`/ ; ${IPFW_CHECK} add ${ipfw_rule} ${rule_out} ) > /dev/null || \
181                 abort "ipfw syntax error ${rule_out}" 
182     fi
183
184     # check error reporting
185     ( cd /vservers/${SLICENAME}/`pwd`/ ; ${IPFW_CHECK} pipe ${ipfw_pipe_in} config ${CONFIG_PIPE_IN} ) > /dev/null || \
186                 abort "ipfw syntax error pipe_in" 
187     ( cd /vservers/${SLICENAME}/`pwd`/ ; ${IPFW_CHECK} pipe ${ipfw_pipe_out} config ${CONFIG_PIPE_OUT} ) > /dev/null || \
188                 abort "ipfw syntax error pipe_out"
189
190     # all good, delete and add rules if necessary
191     [ "$del" = "service" ] && do_delete 0 $slice_id service $arg
192     [ "$del" = "cli_ser" ] && do_delete 0 $slice_id client $arg
193     [ "$del" = "cli_ser" ] && do_delete 0 $slice_id server $arg
194     [ "$new_rule" != "0" ] && ${IPFW} add ${ipfw_rule} $rule_in > /dev/null
195     [ "$new_rule" != "0" ] && ${IPFW} add ${ipfw_rule} $rule_out > /dev/null
196     # config pipes
197     ( cd /vservers/${SLICENAME}/`pwd`/ ; ${IPFW} pipe ${ipfw_pipe_in} config ${CONFIG_PIPE_IN} )
198     ( cd /vservers/${SLICENAME}/`pwd`/ ; ${IPFW} pipe ${ipfw_pipe_out} config ${CONFIG_PIPE_OUT} )
199
200     # send output to the user
201     ${IPFW} show ${ipfw_rule}
202     ${IPFW} pipe ${ipfw_pipe_in} show
203     ${IPFW} pipe ${ipfw_pipe_out} show
204
205     # do not write on the database on test-only
206     [ "$TEST" = "1" ] && return
207     # add to the database
208     ( grep -iv -- "^${slice_id} ${type} ${arg} " $DBFILE;  \
209         echo "${slice_id} ${type} ${arg} ${ipfw_rule} ${pipe_index} ${timeout}" ) > ${DBFILE}.tmp
210     mv ${DBFILE}.tmp ${DBFILE}
211 }
212
213 #
214 # Delete a given configuration
215 # if block_deletion !0 free block resources (if necessary)
216 # otherwise leave the block allocated in case
217 # we are adding the first rule
218 do_delete() { # block_deletion slice_id type arg
219     local ipfw_pipe_in ipfw_pipe_out pipe_index ipfw_rule
220     local block_deletion=$1 slice_id=$2 type=$3 arg=$4
221
222     [ "${type}" = "BLOCK" ] && abort "A BLOCK can not be deleted"
223     [ "${arg}" = "" ] && abort "Missing args on 'delete', expected on of {CLIENT|SERVER|SERVICE} arg"
224     set `find_rule $slice_id $type $arg`
225     ipfw_rule=$1; pipe_index=$2
226     [ "$ipfw_rule" = "0" ] && return            # no rules found
227
228     # find actual pipe numbers XXX do as function
229     ipfw_pipe_in=$(($IPFW_PIPE_MIN + $((2 * $(($pipe_index - 1)))) ))
230     ipfw_pipe_out=$(($ipfw_pipe_in + 1))
231
232     echo "removing configuration ${slice_id} ${type} ${arg}"
233     [ "$TEST" = "1" ] && return 0
234     $IPFW delete ${ipfw_rule}
235     $IPFW pipe delete ${ipfw_pipe_in}
236     $IPFW pipe delete ${ipfw_pipe_out}
237     # remove from the database (case insensitive)
238     grep -iv -- "^${slice_id} ${type} ${arg} " $DBFILE > ${DBFILE}.tmp
239     mv ${DBFILE}.tmp ${DBFILE}
240
241     # if there are no more rules for the user
242     # remove the table entry from ipfw and from the db
243     [ $block_deletion = 0 ] && return 0
244
245     local rule_counter=`grep ^${slice_id} ${DBFILE} | wc -l`
246     [ $rule_counter -gt 1 ] && return 0 # there are still user rules
247     # delete the block and clean the table
248     local block_n=`grep "^${slice_id} BLOCK" ${DBFILE} | cut -d " " -f 3`
249     debug "Deleting BLOCK <${block_n}> entry from ipfw and from the database"
250     table_remove $slice_id $block_n 
251 }
252
253 # compare the argument with the first two field of
254 # the database.
255 # On match returns the block number, otherwise returns 0.
256 # no echo inside
257 find_block() { # $1 slice_id
258     local ret
259     ret=`grep -- "^$1 BLOCK " $DBFILE`
260
261     [ x"$ret" = x ] && echo "0" && return       # nothing found
262     # ignore multiple matches. If the db is corrupt we are
263     # screwed anyways
264     set $ret
265     echo "$3"
266 }
267
268 #
269 # remove the default user rule and
270 # the a BLOCK entry from ipfw and update the db
271 # no echo inside
272 table_remove() { # $slice_id $block_n
273     [ "$TEST" = "1" ] && return 0
274
275     # compute and delete the last user rule
276     local ipfw_rulemax=$(($IPFW_RULE_MIN + $(($M *${block_n})) -1))
277     ${IPFW} table $SLICE_TABLE delete $slice_id
278     ${IPFW} delete ${ipfw_rulemax}
279     ( grep -iv -- "^${slice_id} BLOCK ${block_n}" $DBFILE; ) > ${DBFILE}.tmp
280     mv ${DBFILE}.tmp ${DBFILE}
281     return 0
282 }
283
284 #
285 # Find a rule and pipe_index for the given key (xid type arg)
286 # Allocate a new block if first entry for this xid.
287 # Rule and pipe are not written into the database, only the block is.
288 #
289 # Return ipfw_rule pipe_index new_rule
290 # 'new_rule' is 0 if the rule existed, 1 if it is new
291 #
292 # return ipfw_rule = 0 if there are no resources available
293 find_allocate() { # slice_id type arg
294     local slice_id=$1 type=$2 arg=$3
295     local ipfw_rule pipe_index new_block=0
296
297     # search for already allocated rule and pipes
298     set `find_rule $slice_id $type $arg`
299     ipfw_rule=$1; pipe_index=$2
300     [ ! ${ipfw_rule} = 0 ] && echo $ipfw_rule $pipe_index "0" && return 0       # rules found, return
301
302     # no rules found, search for an already existing block, or
303     # allocate a new one
304     local block_n=`find_block ${slice_id}`
305     [ ${block_n} = "0" ] && new_block=1 && block_n=`find_free_block`
306     [ ${block_n} = "0" -o ${block_n} -gt $BLOCK_MAX ] && echo 0 && return 0;
307
308     # We have a valid block, compute the range for user rules
309     local ipfw_rulemin=$(($IPFW_RULE_MIN + $(($M *$(($block_n - 1))))))
310     local ipfw_rulemax=$(($(($ipfw_rulemin + $M)) - 1 ))
311
312     # Find rule and pipes, reserve the last rule for the user's
313     # default rule that catches regular traffic.
314     set `allocate_resources $ipfw_rulemin $(($ipfw_rulemax - 1))`
315     ipfw_rule=$1; pipe_index=$2
316     [ $ipfw_rule = 0 ] && echo 0 && return 0    # no resources
317
318     # If this is a new block, add the slice to the lookup table
319     # and put a default rule at the end of the block.
320     if [ "$TEST" = "0" -a $new_block = 1 ] ; then
321         ${IPFW} table $SLICE_TABLE add ${slice_id} ${ipfw_rulemin} > /dev/null
322         ${IPFW} add ${ipfw_rulemax} allow all from any to any > /dev/null
323         ( echo "${slice_id} BLOCK ${block_n}" ) >> ${DBFILE}
324     fi
325
326     echo $ipfw_rule $pipe_index "1"
327     return 0
328 }
329
330 #
331 # called with the database file as input
332 # compare the tuple <slice_id type arg> with
333 # the current firewall configuration. The database contains
334 #       slice_id type arg ipfw_rule pipe_index timeout
335 # On match returns <ipfw_rule pipe_index timeout>
336 # On non match returns 0 0 0
337 # no echo inside
338 find_rule() { # slice_id type arg
339     local ret
340     ret=`grep -i -- "^$1 $2 $3 " $DBFILE | grep -v BLOCK`
341
342     [ x"$ret" = x ] && echo "0 0 0 " && return  # nothing found
343     # ignore multiple matches. If the db is corrupt we are
344     # screwed anyways
345     set $ret
346     echo "$4 $5 $6"
347 }
348
349 #
350 # Find a hole in a list of numbers within a range (boundaries included)
351 # The input is passed as a sorted list of numbers on stdin.
352 # Return a "0" rule if there is no rule free
353 find_hole() {  # min max
354     local min=$1 cand=$1 max=$2 line
355     while read line ; do
356         [ $line -lt $min ] && continue
357         [ $line -ne $cand ] && break            # found
358         [ $cand -ge $max ] && cand=0 && break   # no space
359         cand=$(($cand + 1))
360     done
361     echo $cand
362 }
363
364 # XXX despite the name this does not allocate but only finds holes.
365 # returns a free rule and pipe base for client|server|service
366 # within a block
367 # Returns r=0 if there are no resources available
368 # no echo inside
369 allocate_resources() { # ipfw_minrule ipfw_maxrule
370     local p r
371     # remove comments, extract field, sort
372     p=`grep -v '^#' $DBFILE | grep -v BLOCK | awk '{print $5}' | sort -n | \
373         find_hole $PIPE_MIN $PIPE_MAX`
374     r=`grep -v '^#' $DBFILE | grep -v BLOCK | awk '{print $4}' | sort -n | \
375         find_hole $1 $2`
376     [ $r = 0 -o $p = 0 ] && r=0                 # no resources available
377     echo $r $p
378 }
379
380
381 # Returns the index of a free block
382 # Returns 0 if there are no resources available
383 # no debug inside
384 find_free_block() {
385     b=`grep -v '^#' $DBFILE | grep BLOCK | awk '{print $3}' | sort -n | \
386         find_hole $BLOCK_MIN $BLOCK_MAX`
387     echo $b
388 }
389
390 # parse the ipfw database and remove expired rules
391 #
392 # Each timeout value stored in the database is compared against
393 # the current time.  If the timeout is older than current,
394 # the rules and related pipes will be deleted.
395 kill_expired() { # slice_id type arg
396     local match timeout
397
398     # if there is no database file exit
399     [ ! -f ${DBFILE} ] && return 0
400
401     # Get the current time
402     now=`date -u +%s`
403
404     cp ${DBFILE} ${DBFILE}.kill
405     cat ${DBFILE}.kill | grep -v BLOCK |
406     while read line; do
407         match=`echo $line|cut -d " " -f 1-3`
408         timeout=`echo $line|cut -d " " -f 6`
409         [ $now -gt $timeout ] && do_delete 1 $match
410     done
411     rm ${DBFILE}.kill
412 }
413
414 # execute functions from root context
415 # can be used from root context as follow:
416 # echo "super $command $args" | /vsys/ipfw-be 0
417 do_super() { # $arguments...
418         case $1 in
419         init)
420             ipfw_init; return 0
421             ;;
422         dbcleanup)
423             clean_db; return 0
424             ;;
425         killexpired)
426             kill_expired; return 0
427             ;;
428         *)
429             abort "Invalid super command"
430             ;;
431         esac
432 }
433
434 # refresh the rule timeout
435 do_refresh() { # slice_id type arg timeout
436     local ipfw_pipe_in ipfw_pipe_out pipe_index 
437     local slice_id=$1 type=$2 arg=$3 timeout=$4
438
439     debug "do_refresh type: <$type> arg: <$arg> timeout: <$timeout>"
440     [ "${type}" = "BLOCK" ] && abort "BLOCK rule not valid"
441     [ "${timeout}" = "" ] && abort "Missing args on 'refresh', expected on of {SERVICE|SERVER|CLIENT} port_number"
442     set `find_rule $slice_id $type $arg`
443     ipfw_rule=$1; pipe_index=$2
444     [ "${ipfw_rule}" = "0" ] && debug "no rules found" && return 0              # no rules found
445
446     [ "$TEST" = "1" ] && return
447     # update the database with the new timeout value
448     ( grep -iv -- "^${slice_id} ${type} ${arg} " $DBFILE;  \
449         echo "${slice_id} ${type} ${arg} ${ipfw_rule} ${pipe_index} ${timeout}" ) > ${DBFILE}.tmp
450     mv ${DBFILE}.tmp ${DBFILE}
451     echo "refreshed timeout for rule ${type} ${arg}"
452 }
453
454 # process a request.
455 # A request is made by a set of arguments formatted as follow:
456 #
457 # config {server|client|service} arg [-t timeout] IN <pipe_conf> OUT <pipe_conf>
458 # show {rules|pipes} [args]
459 # delete type arg
460 # refresh type arg [-t timeout]
461 #
462 # The timeout value is expressed as:
463 # week, day, month or anything else accepted by the date command.
464 # The id of the slice issuing the request is in the $SLICE_ID variable,
465 # set at the beginning of this script.
466 process() {
467     local new_pipe=0
468     local timeout TMP i rule_base pipe_base
469     local cmd=$1 ; shift
470     local debug_args="$*";
471     local type=$1 ; shift
472     local args="$*"
473     debug "Received command: <$cmd> arguments: <$debug_args>"
474
475     # set the timeout value
476     # if present, extract the '-t timeout' substring from the command line
477     timeout=`echo ${args} | ${SED} ${SEDOPT} 's/(.+)( -t [a-zA-Z0-9]+ )(.*)/\2/'`
478     # if the '-t timeout' is specified, use the timeout provided by the user
479     if [ "${timeout}" != "${args}" ] ; then     # match
480         # remove the '-t ' option
481         timeout=`echo ${timeout} | ${SED} ${SEDOPT} 's/-t //'`
482         timeout=`check_timeout ${timeout}`
483         [ $timeout = 0 ] && abort "Date format $1 not valid"
484         # clean the arguments
485         args=`echo ${args} | ${SED} ${SEDOPT} 's/(.+)( -t [a-zA-Z0-9]+ )(.*)/\1 \3/'`
486     else
487         # use the default value, no need to check for correctness, no need to clean arguments
488         timeout=`date --date="1day" +%s`                # default to 1 day
489     fi
490
491     # if the table rule is not present, add it
492     local table_rule=`${IPFW} show $S | grep "skipto tablearg" | grep "lookup jail $SLICE_TABLE"`
493     [ -z "$table_rule" ] && ipfw_init
494
495     debug "Timeout $timeout"
496     # Handle special requests: show and delete
497     case x"$cmd" in 
498     x"config") 
499         case x"$type" in 
500                 xserver|xSERVER|xclient|xCLIENT|xservice|xSERVICE)
501                         do_config $SLICE_ID $timeout $type $args && return 0
502                 ;;
503         esac
504         abort "'config' should be followed by {CLIENT|SERVER|SERVICE}"
505         ;;
506     x"delete") 
507         do_delete 1 $SLICE_ID $type $args
508         ;;
509     x"refresh") 
510         do_refresh $SLICE_ID $type $args $timeout && return 0
511         ;;
512     x"show")
513         # XXX filter out sliver rules
514         [ "$type" = "rules" ] && ${IPFW} show && return 0
515         [ "$type" = "pipes" ] && ${IPFW} pipe show && return 0
516         abort "'show' should be followed by {rules|pipes}"
517         ;;
518     x"super")
519         [ $SLICE_ID = 0 ] && do_super $type $args && return 0
520         abort "no permission for ipfw-be super execution"
521         ;;
522     x"help")
523         do_help && return 0
524         ;;
525     *)
526         # help XXX to be done
527         abort "'command' should be one of {show|config|delete|refresh|release}"
528         ;;
529     esac
530 }
531
532 # validate the timeout
533 check_timeout() { # timeout
534     local tt=`date --date="${1}" +%s`
535     [ "$?" != "0" ] && echo 0 && return
536     echo $tt
537 }
538
539 do_config() { # slice_id timeout type arg IN pipe_conf OUT pipe_conf
540     local slice_id=$1; shift
541     local timeout=$1; shift
542     local type=$1; shift
543     local arg=$1; shift # XXX addr not yet implemented
544     local p h;          # port and optional hostname
545
546     [ "$1" != "IN" ] && abort "Missing addr:port, or IN requested"
547     shift
548
549     # read pipe in configuration
550     i=""
551     while [ "$1" != "" -a "$1" != "OUT" ] ; do
552         i="$i $1"
553         shift
554     done
555     CONFIG_PIPE_IN="$i"         # XXX local ?
556     [ "$CONFIG_PIPE_IN" = "" ] && abort "Missing pipe in configuration"
557
558     [ "$1" != "OUT" ] && abort "Missing pipe in configuration, or missing OUT"
559     shift
560
561     # read pipe out configuration
562     i=""
563     while [ "$1" != "" ] ; do
564         i="$i $1"
565         shift
566     done
567     CONFIG_PIPE_OUT="$i"        # XXX local ?
568     [ "$CONFIG_PIPE_OUT" = "" ] && abort "Missing pipe out configuration"
569
570
571     # process the argument (port and hostname are separated by a @)
572     # split the argument, and prepare the remote host configuration string
573     p=`echo $arg | cut -s -d "@" -f1-`  # empty it there is no separator
574     if [ "$p" = "" ] ; then
575         p=$arg
576     else
577         p=`echo $arg | cut -d "@" -f1`
578         h=`echo $arg | cut -d "@" -f2`
579     fi
580
581     # A port value is mandatory
582     [ "$p" = "" ] && abort "A port value is mandatory."
583
584     # SERVICE do not support remote hostname filtering
585     [ $type = "service" ] && [ "$h" != "" ] && \
586         abort "The service configuration do not support filtering remote hostnames."
587
588     debug "Configuration Required:"
589     debug "slice_id: $SLICE_ID"
590     debug "type: $type"
591     debug "full arg: $arg"
592     debug "mandatory port(s): $p optional hostname(s): $h"
593     debug "timeout: $timeout"
594     debug "IN: $CONFIG_PIPE_IN"
595     debug "OUT: $CONFIG_PIPE_OUT"
596     debug "-----------------------"
597
598     # check if the link is already configured
599     debug "Search for slice_id: ${slice_id} type: ${type} port: ${arg}"
600
601     set `find_allocate ${slice_id} ${type} ${arg}`
602     local ipfw_rule=$1 pipe_index=$2 new_rule=$3
603
604     [ ${ipfw_rule} = 0 ] && abort "No resources available"
605     debug "Found or allocated resources ipfw_rule: ${ipfw_rule} and pipe_index: ${pipe_index}"
606
607     add_rule $slice_id $new_rule $type $arg $ipfw_rule $pipe_index $timeout
608     hook_call $type $port $rule_base $pipe_base $timeout
609     return 0; # link configured, exit
610 }
611
612 #
613 # acquire the lock XXX check lockfile
614 acquire_lock() {
615     [ "$TEST" = 1 ] && return
616     lockfile -s 0 -r 0 $lockfile 2> /dev/null
617     if [ $? -ne 0 ] ; then
618         echo "lock acquisition failed"
619         exit -1
620     fi
621 }
622
623 #
624 # release the lock
625 release_lock() {
626     rm -f $lockfile
627 }
628
629 #
630 # initialize the firewall with PlanetLab default rules
631 ipfw_init() {
632         ${IPFW} -q delete $S
633         ${IPFW} -q delete $D
634         ${IPFW} add $S skipto tablearg lookup jail $SLICE_TABLE
635         ${IPFW} add $D allow all from any to any
636 }
637
638 #
639 # if present, call a hook function
640 # Arguments are:
641 # slice_id type port rule_base pipe_base timeout
642 hook_call() {
643         if [ -n "${HOOK}" -a -x "${HOOK}" ]; then
644                 debug "Calling the hook function."
645                 ${HOOK} ${SLICE_ID} "$*" &
646         fi
647 }
648
649 do_help() {
650         cat << EOF
651 Usage:
652         ./neconfig {CLIENT|SERVER|SERVICE} arg [-t timeout]    \
653                 IN <pipe in configuration> OUT <pipe out configuration>
654         ./netconfig show {rules|pipes}
655         ./netconfig delete {CLIENT|SERVER|SERVICE} arg
656         ./netconfig refresh [-t timeout] {CLIENT|SERVER|SERVICE} arg
657
658 We support three modes of operation:
659
660   CLIENT programs on the node connect to remote ports
661         and/or addresses. Emulation intercepts traffic
662         involving those ports/addresses
663
664   SERVER programs on the node listen on specific ports.
665         Emulation intercepts traffic on those ports,
666         optionally limited to specific client addresses.
667
668   SERVICE the node runs both clients and servers,
669         we can only specify the ports on which emulation
670         is configured.
671
672   'arg' has the form PORTLIST[@ADDRLIST], where ADDRLIST is
673   optional and only supported for CLIENT and SERVER modes.
674   PORTLIST and ADDRLIST can be specified as any valid port
675   or address specifier in ipfw, e.g.
676     - a single value            443 or 10.20.30.40/24
677     - a comma-separated list    1111,2222,3333 1.2.3.4,5.6.7.8
678     - a range                   1111-2222 (only for ports)
679   Addresses can also be specified as symbolic hostnames, and
680   they are resolved when the rule is installed.
681   Note that they always indicate the remote endpoint.
682
683   On a given port a user can have one CLIENT and/or one SERVER
684   configuration or one SERVICE configuration.
685   When a SERVICE configuration is installed any existing CLIENT
686   and SERVER configuration on the same port are removed.
687   When a CLIENT or SERVER configuration is installed any existing
688   SERVICE configuration on the same port is removed.
689
690 The pipe's configuration, both for the upstream and downstream link,
691 follows the dummynet syntax. A quick and not exaustive example
692 of the parameters that can be used to configure the delay,
693 the bandwidth and the packet loss rate for a link follow:
694
695         IN|OUT delay 100ms bw 1Mbit/s plr 0.1
696
697 The profile file, if present, should be located into the sliver's
698 root directory.
699 The full documentation is on the manpage[1].
700
701 The timeout value follow the linux 'date' command format[2]
702 and can be specified as follow:
703         1week
704         2hours
705         3days
706
707 --- References:
708 [1] http://www.freebsd.org/cgi/man.cgi?query=ipfw
709 [2] http://linuxmanpages.com/man1/date.1.php
710 EOF
711 }
712
713 #--- DOCUMENTATION AND INTERNAL ARCHITECTURE ---
714 #
715 # When a user configures an emulated link, we need to allocate
716 # two pipes and one ipfw rule number to store the parameters.
717 # Reconfigurations of existing links reuse the previous resources.
718 # We keep track of all resources (pipes, rules and blocks of rules)
719 # in a database stored in a text file, see DATABASE FORMAT below.
720 #
721 # Pipes are allocated in pairs. In the database each pair is numbered
722 # from PIPE_MIN to PIPE_MAX. The actual pipe numbers for each pair are
723 #
724 #       ipfw_pipein = IPFW_PIPE_MIN + 2*(pipe_index-1)
725 #       ipfw_pipeout = ipfw_pipein + 1
726 #
727 # The rules number is allocated within a block of M consecutive rules
728 # for each slice. The block is allocated at the first configuration
729 # of an emulated link, and deallocated when the last link is removed.
730 # In the database, blocks are numbered from BLOCK_MIN to BLOCK_MAX,
731 # and the range of rules for a given block_index is
732 #
733 #       ipfw_min_rule = RULE_BASE
734 #       ipfw_max_rule = RULE_BASE + ((M-1)*block_index) -1
735 #
736 # All lookups, and the block allocation, are done in find_allocate().
737 # The rule_number and pipe_index are written in the database
738 # by add_rule() after checking the correctness of the request.
739 #
740 #
741 #--- RULESET STRUCTURE ---
742 # The ruleset is made of different sections, as follows:
743 # - an initial block of rules, reserved and configurable by
744 #   the root context only;
745 # - a skipto rule (S), used to jump directly to the block
746 #   associated with a given slice;
747 # - a second block of reserved rules, to catch remaining traffic.
748 #   This ends with rule number D which is an 'accept all';
749 # - after D, we have a block of M rule numbers for each slice.
750 #   Each of these blocks ends with an 'accept all' rule;
751 # - finally, rule 65535 is the firewall's default rule.
752 #
753 # To summarize:
754 #       1...S-1 first block of reserved rules
755 #       S       skipto tablearg lookup jail 1
756 #       S+1..D-1 ... second block of reserved rules
757 #       D       allow ip from any to any
758 #
759 #       RULE_BASE <block of M entries for first user>
760 #       RULE_BASE+M <block of M entry for second user ...>
761 #       ...
762 #
763 #--- DATABASE FORMAT ---
764 # The database is stored in a text file, and contains one record per
765 # line with the following structure
766 #
767 #       XID     TYPE    arg1    arg2    ...
768 #
769 # Whitespace separates the fields. arg1, arg2, ... have different
770 # meaning depending on the TYPE. XID is the slice ID.
771 #
772 # In the database we have the following records:
773 # - one entry of type BLOCK for each slice with configured links.
774 #   This entry represents the block_index of the block of M ipfw
775 #   rules allocated to the slice, as follows:
776 #
777 #       XID     BLOCK   block_index
778 #   (BLOCK_MIN <= block_index <= BLOCK_MAX)
779 #
780 # - one entry for each link (CLIENT, SERVER, SERVICE).
781 #   The database entry for this info has the form
782 #
783 #       XID {CLIENT|SERVER|SERVICE} arg ipfw_rule pipe_index timeout
784
785 #   'TYPE' reflects the configuration mode;
786 #   'arg' is PORTLIST@ADDRLIST and is used as a search key together
787 #       with the XID and TYPE;
788 #   'ipfw_rule' is the unique ipfw rule number used for this
789 #       emulated link. It must be within the block of M rule numbers
790 #       allocated to the slice;
791 #   'pipe_index' is the index of the pair of pipes used for the
792 #       configuration;
793
794 #-- main starts here
795 debug "--- $0 START for $SLICENAME ---"
796
797 # If the db does not exist, create it and clean rules and pipes
798 [ ! -e ${DBFILE} ] && clean_db
799
800 # A request to the vsys backend is composed by a single line of input
801 read REQ                        # read one line, ignore the rest
802 set_verbose ${REQ}              # use inital -v if present
803 set_test ${REQ}         # use inital -t if present
804 REQ="`filter ${REQ}`"   # remove -v and -q and invalid chars
805 debug "--- processing <${REQ}>"
806 acquire_lock                    # critical section
807 process ${REQ}
808 release_lock
809 debug "--- $0 END ---"
810 exit 0