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