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