Major changes:
[vsys-scripts.git] / exec / ipfw-be
1 #!/bin/sh
2 #
3 # Marta Carbone, Luigi Rizzo
4 # Copyright (C) 2009 Universita` di Pisa
5 # $Id$
6 #
7 # This script the vsys backend used to configure emulation.
8 # In detail it:
9 # - reads the user's input from the vsys input pipe
10 # - validates the input
11 # - configures the firewall
12 # - writes results on the output vsys pipe
13 #
14 # Configurable variables are at the beginning
15
16 # If HOOK is set the program is called before configuring a rule.
17 # A sample hook can be found in the ipfwroot.rpm package,
18 # it can be used to collect statistical information on dummynet usage.
19 # To configure an hook, set the HOOK variable as follow:
20 # HOOK=/tmp/sample_hook
21
22 # You should not touch anything below.
23
24 # We assume three type of connections
25 #  SERVER we know the local port P, and do the
26 #       bind/listen/accept on the local socket.
27 #               pipe_in in dst-port P
28 #               pipe_out out src-port P
29 #
30 #  CLIENT we know the remote port P, and do a connect to it
31 #       (src and dst are swapped wrt the previous case)
32 #               pipe_in in src-port P
33 #               pipe_out out dst-port P
34 #
35 #  SERVICE we run a server on local port P, and also connect
36 #       from local clients to remote servers on port P.
37 #               pipe_in in { dst-port P or src-port P }
38 #               pipe_out out { src-port P or dst-port P }
39
40 #  On a given port a user can have one CLIENT and/or one SERVER
41 #  configuration or one SERVICE configuration.
42 #  When a SERVICE configuration is installed any existing CLIENT
43 #  and SERVER configuration on the same port are removed.
44 #  When a CLIENT or SERVER configuration is installed any existing
45 #  SERVICE configuration on the same port is removed.
46 #
47 #  The following is a case that is implemented as SERVER
48 #  D    we run a server on local port P, and also connect
49 #       to remote servers but doing a bind(P) before connect().
50 #       In terms of rules, this is not distinguishable from
51 #       the SERVER case, however it would be different if we
52 #       had a way to tell SERVER from CLIENT sockets
53 #               pipe_in in dst-port P
54 #               pipe_out out src-port P
55 #
56 # The database of current ipfw and dummynet configuration is in a
57 # file which is regenerated on errors. The format is
58 #
59 #       slice_id type arg rule_base pipe_base timeout
60 #
61 # (lines starting with '#' are comments and are ignored)
62 # For each configuration we allocate one rule number in ipfw,
63 # and two sequential pipe numbers.
64
65 # globals, do not touch below
66 VERBOSE=0       # set to !0 to enable debug messages
67 TEST=0          # set to 1 for test mode
68
69 DBFILE=/tmp/ff
70 lockfile=/var/lock/ipfw.lock
71
72 # These values are the keys used in the database for blocks,
73 # rules and pipes
74 # The index rule numbers allocated to a slice can be computed
75 # with the following formula:
76 #       index_min_rule = $(($RULE_BASE + $(($M*$block_n))))
77 # where block_n is the block number associated with the slice
78 # and M is the block size.
79 BLOCK_MIN=1
80 BLOCK_MAX=1000
81 M=50            # block size
82 RULE_BASE=10001 # the hightest rule is RULE_BASE + (M*BLOCK_MAX)
83 PIPE_MIN=1
84 PIPE_MAX=25000
85 # These are the actual rule numbers used in ipfw
86 IPFW_RULE_MIN=10000
87 IPFW_RULE_MAX=59999
88 IPFW_PIPE_MIN=10000
89 IPFW_PIPE_MAX=59999
90 # The mapping between keys and ipfw configuration number follow:
91 # rule_nr 10001..20000 are mapped to ipfw_rules 50000..59999 (n+39999)
92 # pipe_nr 1..25000 are mapped to ipfw_pipes 10000-59999 (n*2+9998)
93 # Rule index can be mapped to ipfw rules with the following formula:
94 #       ipfw_rule = $(($index_rule + 39999))
95 # Pipes index can be mapped to ipfw pipes with:
96 #       ipfw_pipein = $(($index_pipe + $index_pipe + 9998))
97 #       ipfw_pipeout = $(($ipfw_pipein + $1))
98 #
99 # the skipto and the generic default rule
100 # these values are used to initialize the firewall
101 SLICE_TABLE=1           # table number used for slice ids lookup
102 S=1000                  # firewall rule number for the skipto rule
103 D=2000                  # default rule for reserved section
104
105 # set slicename and slice_id
106 # these are the credential of the user invoking the backend
107 SLICENAME=$1
108 if [ $SLICENAME = 0 ]; then
109         SLICE_ID=0
110 else
111         SLICE_ID=`id -u $SLICENAME`
112 fi
113
114 # programs
115 # XXX check consistency for variables {}
116 SED=/bin/sed
117 SEDOPT=-r
118 [ -x ${SED} ] || { SED=`which sed` ; SEDOPT=-E ; }
119 IPFW=/sbin/ipfw
120 IPFW_CHECK="/sbin/ipfw -n"
121
122 debug() { # $1 message to be displayed
123         [ x"${VERBOSE}" != x"0" ] && echo "ipfw-be: $1"
124 }
125
126 # if the first argument is -v, enable verbose mode
127 set_verbose() {
128     [ x"$1" = x"-v" -o x"$2" = x"-v" ] && VERBOSE=1
129 }
130
131 set_test() {
132     [ x"$1" = x"-q" -o x"$2" = x"-q" ] || return
133     TEST=1
134     IPFW="/bin/echo ipfw:"
135     IPFW_CHECK="/bin/echo ipfw -n:"
136 }
137
138 abort() { # $1 message to be displayed in case of error
139         release_lock
140         echo "ipfw-be aborting (netconfig help): $1"
141         exit 1
142 }
143
144 # remove dangerous characters from user input
145 # if present, the leading '-v/-t' will be removed
146 filter() { # $* variables to be filtered
147         [ x${1} = x"-v" -o x${1} = x"-q" ] && shift
148         [ x${1} = x"-v" -o x${1} = x"-q" ] && shift
149         # allowed chars are: numbers, uppercase and lowercase letters,
150         # spaces, and the following symbols: .,_-/
151         echo "$*" | ${SED} ${SEDOPT} 's/[^\t0-9a-zA-Z., _\/\{}-]*//g'
152 }
153
154 # remove all entries from the ipfw config, and create an empty db
155 clean_db() {
156         rm -f ${DBFILE}
157         touch ${DBFILE}
158         # we would like to delete ranges of rules and pipes but this
159         # is not supported so for the time being we kill them all
160         ${IPFW} -q flush
161         ${IPFW} -q pipe flush
162         # ${IPFW} delete ${IPFW_RULE_MIN}-${IPFW_RULE_MAX}
163         # ${IPFW} pipe delete ${IPFW_PIPE_MIN}-${IPFW_PIPE_MAX}
164         # since all rules are now deleted, we should initialize the firewall 
165         ipfw_init
166 }
167
168 # Add the ipfw rule/pipe and update the database.
169 # The pipe-in and pipe_out config are through global variables
170 # CONFIG_IN CONFIG_OUT because they may be long.
171 # Other arguments are on the command line
172 add_rule() { # new_rule type arg rule pipe_base timeout
173     local new_rule=$1 type=$2 arg=$3
174     local rule_base=$4 pipe_base=$5 timeout=$6
175     local pipe_in pipe_out rule_in rule_out check_timeout
176
177     # If we use a profile file, locate the user directory
178     # move in the slice root dir XXX todo
179     [ "$TEST" != "1" ] && cd /vservers/${SLICENAME}/root
180     #echo ${CONFIG_STRING} | ${SED} -e "s/ profile \(.[^ ]\)/ profile \/vservers\/${SLICENAME}\/\1/g"
181
182     # first, call ipfw -n to check syntax, if ok move on and do the action
183     pipe_in=$(($pipe_base + $pipe_base + 9998))
184     pipe_out=$(($pipe_in + 1))
185     local del   # anything to delete ?
186     local rule_nr=$(($rule_base + 39999))  # formula for individual rules
187     if [ x"$new_rule" != x"0" ] ; then
188         case $type in
189         server)
190             rule_in="dst-port $arg"
191             rule_out="src-port $arg"
192             del=service
193             ;;
194         client)
195             rule_in="src-port $arg"
196             rule_out="dst-port $arg"
197             del=service
198             ;;
199         service)
200             rule_in="{ src-port $arg or dst-port $arg }"
201             rule_out="{ src-port $arg or dst-port $arg }"
202             del="cli_ser"
203             ;;
204         *)
205             abort "invalid service type $type"
206             ;;
207         esac
208
209         rule_in="pipe ${pipe_in} in jail $SLICE_ID ${rule_in} // $type $arg"
210         rule_out="pipe ${pipe_out} out jail $SLICE_ID ${rule_out} // $type $arg"
211         ${IPFW_CHECK} add ${rule_nr} $rule_in > /dev/null || \
212                 abort "ipfw syntax error $rule_in"
213         ${IPFW_CHECK} add ${rule_nr} $rule_out > /dev/null || \
214                 abort "ipfw syntax error $rule_out"
215     fi
216
217     # check error reporting
218     ${IPFW_CHECK} pipe ${pipe_in} config ${CONFIG_PIPE_IN} > /dev/null || \
219                 abort "ipfw syntax error pipe_in"
220     ${IPFW_CHECK} pipe ${pipe_out} config ${CONFIG_PIPE_OUT} > /dev/null || \
221                 abort "ipfw syntax error pipe_out"
222
223     # all good, delete and add rules if necessary
224     [ "$del" = "service" ] && do_delete $SLICE_ID service $arg
225     [ "$del" = "cli_ser" ] && do_delete $SLICE_ID client $arg
226     [ "$del" = "cli_ser" ] && do_delete $SLICE_ID server $arg
227     [ "$new_rule" != "0" ] && ${IPFW} add ${rule_nr} $rule_in > /dev/null
228     [ "$new_rule" != "0" ] && ${IPFW} add ${rule_nr} $rule_out > /dev/null
229     # config pipes
230     ${IPFW} pipe ${pipe_in} config ${CONFIG_PIPE_IN}
231     ${IPFW} pipe ${pipe_out} config ${CONFIG_PIPE_OUT}
232
233     # send output to the user
234     ${IPFW} show ${rule_nr}
235     ${IPFW} pipe ${pipe_in} show
236     ${IPFW} pipe ${pipe_out} show
237
238     [ "$TEST" = "1" ] && return
239     # add to the database
240     ( grep -v -- "^${SLICE_ID} ${type} ${arg} " $DBFILE;  \
241         echo "${SLICE_ID} ${type} ${arg} ${rule_base} ${pipe_base} ${timeout}" ) > ${DBFILE}.tmp
242     mv ${DBFILE}.tmp ${DBFILE}
243 }
244
245 # Delete a given configuration
246 do_delete() { # type arg
247     local pipe_in pipe_out pipe_base rule_base rule_nr
248     local type=$1 arg=$2
249
250     [ "${type}" = "BLOCK" ] && abort "A Block can not be deleted"
251     [ "${arg}" = "" ] && abort "Missing args on 'delete', expected on of {SERVICE|SERVER|CLIENT} port_number"
252     set `find_rule $SLICE_ID $type $arg`
253     rule_base=$1; pipe_base=$2
254     [ "$rule_base" = "0" ] && return            # no rules found
255
256     rule_nr=$(($rule_base + 39999))             # XXX only individual rules
257     pipe_in=$(($pipe_base + $pipe_base + 9998))
258     pipe_out=$(($pipe_in + 1))
259
260     $IPFW delete ${rule_nr}
261     $IPFW pipe delete ${pipe_in}
262     $IPFW pipe delete ${pipe_out}
263     echo "removed configuration ${SLICE_ID} ${type} ${arg}"
264     [ "$TEST" = "1" ] && return
265     # remove from the database
266     grep -v -- "^${SLICE_ID} ${type} ${arg} " $DBFILE > ${DBFILE}.tmp
267     mv ${DBFILE}.tmp ${DBFILE}
268
269     # XXX if the use block is empty
270     # remove the table entry from ipfw and from the db
271     # not yet implemented
272 }
273
274 # compare the argument with the first two field of
275 # the database.
276 # On match returns the block number, otherwise returns 0.
277 find_block() { # $1 slice_id
278     local ret
279     ret=`grep -- "^$1 BLOCK " $DBFILE`
280
281     [ x"$ret" = x ] && echo "0" && return       # nothing found
282     # ignore multiple matches. If the db is corrupt we are
283     # screwed anyways
284     set $ret
285     echo "$3"
286 }
287
288 # called with the database file as input
289 # compare the tuple <slice_id type arg> with
290 # the current firewall configuration. The database contains
291 #       slice_id type arg rule_base pipe_base timeout
292 # On match returns <rule_base pipe_base timeout>
293 # On non match returns 0 0 0
294 find_rule() { # $1 slice_id $2 type $3 arg
295     local ret
296     ret=`grep -- "^$1 $2 $3 " $DBFILE`
297
298     [ x"$ret" = x ] && echo "0 0 0 " && return  # nothing found
299     # ignore multiple matches. If the db is corrupt we are
300     # screwed anyways
301     set $ret
302     echo "$4 $5 $6"
303 }
304
305
306 # Find a hole in a list of numbers within a range (boundaries included)
307 # The input is passed as a sorted list of numbers on stdin.
308 # Return a "0" rule if there is no rule free
309 find_hole() {  # min max
310     local min=$1 cand=$1 max=$2 line
311     while read line ; do
312         [ $line -lt $min ] && continue
313         [ $line -ne $cand ] && break            # found
314         [ $cand -ge $max ] && cand=0 && break   # no space
315         cand=$(($cand + 1))
316     done
317     echo $cand
318 }
319
320 # returns a free rule and pipe base for client|server|service
321 # within a block
322 # Returns r=0 if there are no resources available
323 allocate_resources() {
324     local p r
325     # remove comments, extract field, sort
326     p=`grep -v '^#' $DBFILE | grep -v BLOCK | awk '{print $5}' | sort -n | \
327         find_hole $PIPE_MIN $PIPE_MAX`
328     r=`grep -v '^#' $DBFILE | grep -v BLOCK | awk '{print $4}' | sort -n | \
329         find_hole $1 $2`
330     [ $r = 0 -o $p = 0 ] && r=0                 # no resources available
331     echo $r $p
332 }
333
334 #
335 # execute functions from root context
336 # can be used from root context as follow:
337 # echo "super $command $args" | /vsys/ipfw-be 0
338 do_super() { # $arguments...
339         case $1 in
340         init)
341             ipfw_init; return 0
342             ;;
343         dbcleanup)
344             clean_db; return 0
345             ;;
346         *)
347             abort "Invalid super command"
348             ;;
349         esac
350 }
351
352 #
353 # show ipfw rules and pipes filtering on slice_id
354 # If the first argument is 0 filter rules,
355 # otherwise filter pipes
356 do_show() { #$1 show rules or pipes
357     local list ipfw_list
358
359     if [ $1 == "0" ]; then
360         list=`grep "^$SLICE_ID " $DBFILE | grep -v BLOCK | cut -d " " -f 4`
361         for i in $list; do ipfw_list="$ipfw_list $(($i + 39999))"; done
362         [ -n "${ipfw_list}" ] && ${IPFW} show $ipfw_list
363     else
364         # ipfw pipe show does not selectively filter pipes
365         # XXX so leave this code commented and show all pipes
366         #list=`grep "^$SLICE_ID " $DBFILE | grep -v BLOCK | cut -d " " -f 5`
367         # the pipe list is build adding two ipfw pipes for each pipe index
368         #for i in $list; do ipfw_list="$ipfw_list $(($i + $i + 9998)) $(($i + $i + 9998 +1)) "; done
369         #[ -n "${ipfw_list}" ] && ${IPFW} pipe show $ipfw_list
370         ${IPFW} pipe show
371     fi
372 }
373
374 #
375 # refresh the rule timeout
376 do_refresh() { # type arg timeout
377     local pipe_in pipe_out pipe_base rule_base rule_nr
378     local type=$1 arg=$2 timeout=$3
379
380     debug "do_refresh type: <$type> arg: <$arg> timeout: <$timeout>"
381     [ "${type}" = "BLOCK" ] && abort "BLOCK rule not valid"
382     [ "${timeout}" = "" ] && abort "Missing args on 'refresh', expected on of {SERVICE|SERVER|CLIENT} port_number"
383     set `find_rule $SLICE_ID $type $arg`
384     rule_base=$1; pipe_base=$2
385     [ "${rule_base}" = "0" ] && debug "no rules found" && return 0              # no rules found
386
387     rule_nr=$(($rule_base + 39999))             # XXX only individual rules
388     pipe_in=$(($pipe_base + $pipe_base + 9998))
389     pipe_out=$(($pipe_in + 1))
390     debug "ipfw rule and pipes value: rule: <$rule_nr> pipe in: <$pipe_in> pipe_out: <$pipe_out>"
391
392     [ "$TEST" = "1" ] && return
393     # update the database with the new timeout value
394     ( grep -v -- "^${SLICE_ID} ${type} ${arg} " $DBFILE;  \
395         echo "${SLICE_ID} ${type} ${arg} ${rule_base} ${pipe_base} ${timeout}" ) > ${DBFILE}.tmp
396     mv ${DBFILE}.tmp ${DBFILE}
397     echo "refreshed timeout for rule ${type} ${arg}"
398 }
399
400 # process a request.
401 # A request is made by a set of arguments formatted as follow:
402 #
403 # config {server|client|service} arg [-t timeout] PIPE_IN <pipe_conf> PIPE_OUT <pipe_conf>
404 # show {rules|pipes} [args]
405 # delete type arg
406 #
407 # The timeout value is expressed as:
408 # week, day, month or anything else accepted by the date command.
409 # The id of the slice issuing the request is in the $SLICE_ID variable,
410 # set at the beginning of this script.
411 process() {
412     local new_pipe=0
413     local timeout TMP i rule_base pipe_base
414     local slicename=${SLICENAME}
415     local cmd=$1 ; shift
416     local debug_args="$*";
417     local type=$1 ; shift
418     local args="$*"
419     debug "Received command: <$cmd> arguments: <$debug_args>"
420
421     # set the timeout value
422     # if present, extract the '-t timeout' substring from the command line
423     timeout=`echo ${args} | ${SED} ${SEDOPT} 's/(.+)( -t [a-zA-Z0-9]+ )(.*)/\2/'`
424     # if the '-t timeout' is specified, use the user define timeout value
425     if [ "${timeout}" != "${args}" ] ; then     # match
426         # remove the '-t ' option
427         timeout=`echo ${timeout} | ${SED} ${SEDOPT} 's/-t //'`
428         check_timeout ${timeout}        # abort on error
429         # clean the arguments
430         args=`echo ${args} | ${SED} ${SEDOPT} 's/(.+)( -t [a-zA-Z0-9]+ )(.*)/\1 \3/'`
431     else
432         # use the default value, no need to check for correctness, no need to clean arguments
433         timeout=`date --date="1day" +%s`                # default to 1 day
434     fi
435
436     debug "Timeout $timeout"
437     # Handle special requests: show and delete
438     case x"$cmd" in 
439     x"config") 
440         [ "$type" = "server" ] && do_config $timeout $type $args && return 0
441         [ "$type" = "client" ] && do_config $timeout $type $args && return 0
442         [ "$type" = "service" ] && do_config $timeout $type $args && return 0
443         abort "'config' should be followed by {server|client|service}"
444         ;;
445     x"delete") 
446         do_delete ${SLICE_ID} $type $args
447         ;;
448     x"refresh") 
449         do_refresh $type $args $timeout && return 0
450         ;;
451     x"show")
452         [ "$type" = "rules" ] && do_show 0 && return 0
453         [ "$type" = "pipes" ] && do_show 1 && return 0
454         abort "'show' should be followed by {rules|pipes}"
455         ;;
456     x"super")
457         [ $SLICE_ID = 0 ] && do_super $type $args && return 0
458         abort "no permission for ipfw-be super execution"
459         ;;
460     x"help")
461         do_help && return 0
462         ;;
463     *)
464         # help XXX to be done
465         abort "'command' should be one of {show|config|delete|refresh|release}"
466         ;;
467     esac
468 }
469
470 # validate the timeout
471 check_timeout() { # timeout
472     local tt=`date --date="${1}" +%s`
473     [ "$?" != "0" ] && abort "Date format $1 not valid"
474 }
475
476 do_config() { # timeout type arg PIPE_IN pipe_conf PIPE_OUT pipe_conf
477     local timeout=$1; shift
478     local type=$1; shift
479     local arg=$1; shift # XXX addr not yet implemented
480
481     [ "$1" != "PIPE_IN" ] && abort "Missing addr:port, or PIPE_IN requested"
482     shift
483
484     # read pipe in configuration
485     i=""
486     while [ "$1" != "" -a "$1" != "PIPE_OUT" ] ; do
487         i="$i $1"
488         shift
489     done
490     CONFIG_PIPE_IN="$i"         # XXX local ?
491     [ "$CONFIG_PIPE_IN" = "" ] && abort "Missing pipe in configuration"
492
493     [ "$1" != "PIPE_OUT" ] && abort "Missing pipe in configuration, or missing PIPE_OUT"
494     shift
495
496     # read pipe out configuration
497     i=""
498     while [ "$1" != "" ] ; do
499         i="$i $1"
500         shift
501     done
502     CONFIG_PIPE_OUT="$i"        # XXX local ?
503     [ "$CONFIG_PIPE_OUT" = "" ] && abort "Missing pipe out configuration"
504
505     debug "Configuration Required:"
506     debug "slice_id: $SLICE_ID"
507     debug "type: $type"
508     debug "arg: $arg"
509     debug "timeout: $timeout"
510     debug "PIPE_IN: $CONFIG_PIPE_IN"
511     debug "PIPE_OUT: $CONFIG_PIPE_OUT"
512     debug "-----------------------"
513
514     # check if the link is already configured
515     debug "Search for ${SLICE_ID} ${type} ${arg}"
516
517     set `find_rule ${SLICE_ID} ${type} ${arg}`
518     local rule_base=$1
519     local pipe_base=$2
520     local new_pipe=0
521
522     if [ ! ${rule_base} = "0" ] ; then
523         debug "Rule found, just changing the pipe configuration"
524         add_rule $new_pipe $type $arg $rule_base $pipe_base $timeout
525         hook_call $type $port $rule_base $pipe_base $timeout
526         return 0; # link configured, exit
527     fi
528
529     debug "link not found, search for a block already allocated to the user"
530
531     # Search if there is a block already allocated to the slice_id
532     set `find_block ${SLICE_ID}`
533     local block_n=$1
534     if [ ${block_n} = "0" ] ; then
535         debug "Block not found, allocate a new block"
536         # blocks are allocated in sequence, get the first free
537         block_n=`grep BLOCK $DBFILE | tail -1 | cut -d " " -f 3`
538         if [ -z $block_n ]; then
539                 block_n=$(($BLOCK_MIN - 1))
540         fi
541         RULE_IN_MIN=$(($RULE_BASE + $(($M*$block_n))))
542         block_n=$(($block_n +1))
543         debug "Allocated new block $block_nr to user $SLICE_ID"
544         [ $block_n -gt $BLOCK_MAX ] && abort "no block resources available"
545
546         # add the rule into the firewall table.
547         # note that the rule number into the table are not database number,
548         # so we need to compute the firewall number before the table insertion
549         local ipfw_rule_nr=$(($RULE_IN_MIN + 39999))  # XXX formula for individual rules
550         debug "Configuring table: <${IPFW_CHECK} table $SLICE_TABLE add ${SLICE_ID} ${ipfw_rule_nr}>"
551         ${IPFW_CHECK} table $SLICE_TABLE add ${SLICE_ID} ${rule_nr} > /dev/null || \
552                 abort "ipfw syntax error $rule_out"
553         ${IPFW} table $SLICE_TABLE add ${SLICE_ID} ${ipfw_rule_nr} > /dev/null
554
555         [ "$TEST" = "1" ] && return
556         # add the block declaration to the database
557         ( grep -v -- "^${SLICE_ID} BLOCK " $DBFILE;  \
558                 echo "${SLICE_ID} BLOCK ${block_n}" ) > ${DBFILE}.tmp
559         mv ${DBFILE}.tmp ${DBFILE}
560     else
561         debug "Block $block_n found for user $SLICE_ID"
562     fi
563
564     RULE_IN_MAX=$(($RULE_BASE + $(($M * $block_n))))
565     RULE_IN_MIN=$(($RULE_IN_MAX - $M))
566     debug  "Block $block_n, with rules <${RULE_IN_MIN}:${RULE_IN_MAX}>" 
567     debug  "Corresponding to ipfw rules <$(($RULE_IN_MIN + 39999)):$(($RULE_IN_MAX + 39999))>"
568     debug "where the last rule number belongs to the next slice."
569
570     new_pipe=1
571     set `allocate_resources $RULE_IN_MIN $RULE_IN_MAX`
572     rule_base=$1; pipe_base=$2
573
574     [ $rule_base = 0 ] && abort "no resources available"
575     debug "found free resources rule: $rule_base pipe: $pipe_base"
576
577     add_rule $new_pipe $type $arg $rule_base $pipe_base $timeout
578     hook_call $type $port $rule_base $pipe_base $timeout
579 }
580
581 #
582 # acquire the lock XXX check lockfile
583 acquire_lock() {
584     [ "$TEST" = 1 ] && return
585     lockfile -s 0 -r 0 $lockfile 2> /dev/null
586     if [ $? -ne 0 ] ; then
587         echo "lock acquisition failed"
588         exit -1
589     fi
590 }
591
592 #
593 # release the lock
594 release_lock() {
595     rm -f $lockfile
596 }
597
598 #
599 # initialize the firewall with PlanetLab default rules
600 ipfw_init() {
601         ${IPFW} add $S skipto tablearg lookup jail $SLICE_TABLE
602         ${IPFW} add $D allow all from any to any
603 }
604
605 #
606 # if present, call a hook function
607 # Arguments are:
608 # slice_id type port rule_base pipe_base timeout
609 hook_call() {
610         if [ -n "${HOOK}" -a -x "${HOOK}" ]; then
611                 ${HOOK} ${SLICE_ID} "$*" &
612         fi
613 }
614
615 do_help() {
616         cat << EOF
617 Usage:
618         ./neconfig [SERVER|CLIENT|SERVICE] port [-t timeout]    \
619                 PIPE_IN <pipe in configuration> PIPE_OUT <pipe out configuration>
620         ./netconfig show [rules|pipes]
621         ./netconfig delete [SERVER|CLIENT|SERVICE] port
622         ./netconfig refresh [-t timeout] [SERVER|CLIENT|SERVICE] port
623
624 We assume three type of connections
625   SERVER we know the local port P, and do the
626        bind/listen/accept on the local socket.
627                pipe_in in dst-port P
628                pipe_out out src-port P
629
630   CLIENT we know the remote port P, and do a connect to it
631        (src and dst are swapped wrt the previous case)
632                pipe_in in src-port P
633                pipe_out out dst-port P
634
635   SERVICE we run a server on local port P, and also connect
636        from local clients to remote servers on port P.
637                pipe_in in { dst-port P or src-port P }
638                pipe_out out { src-port P or dst-port P }
639
640   On a given port a user can have one CLIENT and/or one SERVER
641   configuration or one SERVICE configuration.
642   When a SERVICE configuration is installed any existing CLIENT
643   and SERVER configuration on the same port are removed.
644   When a CLIENT or SERVER configuration is installed any existing
645   SERVICE configuration on the same port is removed.
646
647 The pipe configuration, both for the upstream and downstream link,
648 follow the dummynet syntax. A quick and not exaustive example
649 of the parameters that can be used to configure the delay,
650 the bandwidth and the packet loss rate for a link follow:
651
652         PIPE_IN|PIPE_OUT delay 100ms bw 1Mbit/s plr 0.1
653
654 The full documentation is on the manpage[1].
655
656 The timeout value follow the linux 'date' command format[2]
657 and can be specified as follow:
658         1week
659         2hours
660         3days
661
662 --- References:
663 [1] http://www.freebsd.org/cgi/man.cgi?query=ipfw
664 [2] http://linuxmanpages.com/man1/date.1.php
665 EOF
666 }
667
668 # ALLOCATION OF RULES AND PIPES
669 # The ruleset is composed by different sections, as follow:
670 # - a first set of rules is reserved and is configurable by
671 #   the root context only;
672 # - the skipto rule (S), used to optimize the slice rule search;
673 # - a second block of reserved rules;
674 # - a default (D) rule for the generic configuration;
675 # - the slice reserved rules, a block of M rules for each slice;
676 # - the firewall default rule.
677 #
678 # To summarize:
679 #       1...S-1 first block of reserved rules
680 #       S       skipto tablearg lookup jail 1
681 #       S+1..D-1 ... second block of reserved rules
682 #       D       allow ip from any to any
683 #
684 #       RULE_BASE <block of M entries for first user>
685 #       RULE_BASE+M <block of M entry for second user ...>
686 #       ...
687 #
688 # Out of 64k rules, we allocate a block of M=50 consecutive
689 # rules to each slice using emulation. Within this block,
690 # each configuration uses one rule number and two pipes.
691 #
692 # Pipes are allocated starting from PIPE_BASE, a couple
693 # of pipes for each configuration.
694 #
695 # DATABASE FORMAT
696 # The database is stored on a file, and contains
697 # one line per record with this general structure
698 #       XID     TYPE    arg1    arg2    ...
699 # whitespace separates the fields. arg1, arg2, ...
700 # have different meaning depending on the type.
701 #
702 # In the database we have the following records:
703 # - one entry for each slice that has active emulation entries.
704 #   For each of these slices we reserve a block of M ipfw rules
705 #   starting at some RULE_BASE rule number.
706 #   The database entry for this info has the form
707 #       XID     BLOCK   block_number
708 #   where blocks are numbered sequentially from 1.
709 #   The actual rule number is RULE_BASE + M*(block_number)
710 #   (we don't care if we waste some rules)
711 #
712 # - one entry for each predefined config (CLIENT, SERVER, SERVICE).
713 #   The database entry for this info has the form
714 #       XID     {CLIENT|SERVER|SERVICE} arg     rule_nr pipe_index
715 #   rule_nr is the absolute rule number for this configuration
716 #   (it must be within the block of M rules allocated to the slice)
717 #   pipe_index is the index of the couple of pipes used for the
718 #   configuration. pipe_index starts from 1.
719 #
720
721 #-- main starts here
722 debug "--- $0 START for $SLICENAME ---"
723
724 # If the db does not exist, create it and we clean rules and pipes
725 [ ! -e ${DBFILE} ] && clean_db
726
727 # A request to the vsys backend is composed by a single line of input
728 read REQ                        # read one line, ignore the rest
729 set_verbose ${REQ}              # use inital -v if present
730 set_test ${REQ}         # use inital -t if present
731 REQ="`filter ${REQ}`"   # remove -v and -t and invalid chars
732 debug "--- processing <${REQ}>"
733 acquire_lock                    # critical section
734 process ${REQ}
735 release_lock
736 debug "--- $0 END ---"
737 exit 0