83168561db477f5d1170f6c46ffe1b2469100401
[vsys-scripts.git] / exec / sliceip
1 #!/bin/bash
2 #
3 # Giovanni Di Stasi
4 # Copyright (C) 2009 UniNa
5 # $Id$
6 #
7 # This is a backend script to be used with
8 # the vsys system. It allows to define and 
9 # manage slice-specific routing tables. 
10 # The routing rules added to these routing 
11 # tables affect only the respective slices.  
12 #
13 # This script is meant to be called from a 
14 # frontend script in the slice environment. 
15 #
16 # Fronted usage:
17 # sliceip enable <interface> 
18 #   This is the first command to issue; it enables the use
19 #   of the interface for the slice. This command has to be issued 
20 #   for each interface involved in the slice-specific routing rules.
21 #
22 # sliceip disable <interface> 
23 #   This is to disable the use of the interface for the slice.
24 #
25 # sliceip route show 
26 #   This command shows the current routing rules of the
27 #   slice-specific routing table
28 #
29 # sliceip route add to <destination> via <gateway> dev <interface>
30 #   This command adds a rule to reach <destination> through the host
31 #   <gateway> that can be reached on the interface <interface>
32 # ...
33
34 # sliceip in general supports all the commands of the "ip" command of
35 # the "iproute2" suite. Refer to ip manpage for further information.
36
37 # Some examples:
38 #       sliceip route add to 143.225.229.142 via 192.168.8.2 dev ath0
39 #       sliceip route add default via 10.1.1.1 dev ath0
40 #
41 # If you want to test the script from the root context you need 
42 # to call it as vsys would do:
43 # sliceip <slice_name>
44 # Then you can issue to its standard input the commands previously explained
45 # (but you have to remove the initial "sliceip"). Ex.:
46 #        route add to 143.225.229.142 via 192.168.8.2 dev ath0
47
48
49 PATH=/bin:/usr/bin:/sbin:/usr/sbin 
50
51 # sliver wich called the script
52 sliver=$1
53
54 #files used
55 RT_TABLES="/etc/iproute2/rt_tables"
56 INT_FILE="/tmp/slcroute_ifns"
57
58 # routing tables from 100 to 255 are used
59 FIRST_TABLE=100
60
61 DEBUG=0
62
63 #this script can also work on a Linux machine, in wich case
64 #it enables user-specific routing tables
65 LINUX_ENV=0 # 0 - Linux host;
66             # does not work for the icmp protocol
67
68 PLANET_ENV=1 # 1 - PlanetLab context (slice-specific routing) 
69
70 #select the PlanetLab environment
71 ENVIRONMENT=$PLANET_ENV
72
73
74 # enable an interface for the slice specific routing rules
75 # and initialise the slice routing table
76 function enable_interface(){ # $1 calling sliver; $2 interface to enable
77         local sliver=$1
78         local interface="$2"
79         local ip=`get_address $interface`
80
81         if [[ "" == $ip ]]; then
82                 echo "Failed in getting address of $interface"
83                 return 1
84         fi
85
86         #create the sliceip netfilter chains
87         check_chains
88
89         local nid=`get_nid $sliver`
90         local num_active=`get_num_activated $sliver`
91
92         if ! is_active $sliver $interface; then
93                 if [[ $num_active == 0 ]]; then
94                                 #define the routing table for the slice and set some iptables rules in order to mark user's packets;                    
95                                 create_table $sliver
96                 fi
97
98                 #Adds an SNAT rule to set the source IP address of packets that are about to go out through $interface.
99                 #The kernel sets the source address of packets when the first routing process happens (following the rules 
100                 #of the main routing table); when the rerouting process happens (following the user/slice specific rules) 
101                 #the source address is not modified. Consequently, we need to add this rule to change the source ip address 
102                 #to the $interface source address.
103                 exec "iptables -t nat -A $NAT_POSTROUTING -o $interface -j SNAT --to-source $ip -m mark --mark $nid"
104
105                 set_active $sliver $interface $ip               
106         else
107                 local old_ip=`get_ip_from_file $sliver $interface`
108         
109                 #if the ip of the interface has changed, set the new SNAT rule
110                 if [[ $old_ip != $ip ]]; then
111                         #remove the rule for the old ip
112                         exec "iptables -t nat -D $NAT_POSTROUTING -o $interface -j SNAT --to-source $old_ip -m mark --mark $nid"
113                         #insert the new rule                    
114                         exec "iptables -t nat -A $NAT_POSTROUTING -o $interface -j SNAT --to-source $ip -m mark --mark $nid"    
115                         set_deactivated $sliver $interface $old_ip
116                         set_active $sliver $interface $ip
117                 fi
118
119
120         fi
121
122 }
123
124 # disable the interface for the slice-specific routing rules
125 function disable_interface(){
126         local sliver=$1
127         local interface=$2
128         local ip=`get_ip_from_file $sliver $interface`  
129         local nid=`get_nid $sliver`
130
131         if is_active $sliver $interface >/dev/null 2>&1; then
132                 exec "iptables -t nat -D $NAT_POSTROUTING -o $interface -j SNAT --to-source $ip -m mark --mark $nid"
133                 
134                 local num_ifn_on=`get_num_activated $sliver`
135                 
136                 if [[ $num_ifn_on == 1 ]]; then
137                         delete_table $sliver
138                 fi
139                 set_deactivated $sliver $interface
140         fi
141
142 }
143
144 # wrapper function used to execute system commands
145 function exec(){
146         local command=$1        
147
148         if ! [[ $command ]]; then
149                 echo "Error in exec. No argument."
150                 exit 1  
151         else
152                 if ! $command; then
153                         if [[ $DEBUG == 1 ]]; then echo "Error executing \"$1\""; fi
154                         exit 1
155                 fi
156         fi
157
158 }
159
160 # decides wich id to use for the slice-specific routing table
161 function get_table_id(){
162         local sliver=$1
163         k=$FIRST_TABLE
164
165         while [[ $k < 255 ]] && grep $k $RT_TABLES >/dev/null 2>&1 ; do
166                 k=$((k+1))
167         done
168
169         if [[ $k == 255 ]]; then
170                 logm "No routing tables available. Exiting."
171                 return 1
172         fi
173         
174         echo $k
175 }
176
177
178 # create the slice-specifig routing table
179 function create_table(){
180         local sliver=$1
181         local table_name=`get_table_name $sliver`
182         local table_id=`get_table_id $sliver`   
183         local temp_nid=`get_temp_nid $sliver`
184
185         if ! grep $table_name $RT_TABLES > /dev/null 2>&1; then
186                 echo "$table_id $table_name" >> $RT_TABLES
187         else
188                 echo "WARNING: $table_name routing table already defined."
189         fi
190
191         set_routes $sliver $table_name
192
193         return 0
194 }
195
196 # delete the slice-specific routing table
197 function delete_table(){
198         local sliver=$1
199         local table_name=`get_table_name $sliver`
200         local table_id=`get_nid $sliver`        
201         local temp_nid=`get_temp_nid $sliver`
202
203         if ! grep $table_name $RT_TABLES > /dev/null 2>&1; then
204                 return 1                        
205         else
206                 exec "ip route flush table $table_name" >/dev/null 2>&1
207                 unset_routes $sliver $table_name
208                 remove_line "$RT_TABLES" "$table_name"          
209         fi
210
211         return 0
212 }
213
214 # remove a line from a file
215 function remove_line(){
216         local filename=$1
217         local regex=$2
218
219         #remove interface line from the file
220         exec "sed -i /${regex}/d $filename"
221 }
222
223 # get the slice-specific routing table name
224 function get_table_name(){
225         local sliver=$1;
226         echo "${sliver}_slcip"
227 }
228
229 #remove files used by sliceip and the added routing tables 
230 function clean_iproute_conf(){
231         while grep "slcip" $RT_TABLES >/dev/null 2>&1; do
232                 remove_line $RT_TABLES "slcip"
233         done
234
235         rm -f ${INT_FILE}-*
236
237 }
238
239 #get slice network id
240 function get_nid(){
241     id -u ${1}
242 }
243
244 # set the firewall rules. Mainly, it asks VNET+ to set the netfilter mark of packets belonging to the slice to the slice-id
245 # and then associates those packets with the routing table allocated for that slice.
246 function set_routes(){
247         local sliver=$1
248         local table_name=$2
249         local sliver_nid=`get_nid $sliver`
250         local temp_nid=`get_temp_nid $sliver`
251
252         if [ $ENVIRONMENT == $PLANET_ENV ]; then        
253                 #Linux kernel triggers a rerouting process, wich is needed to perfom slice-specific routing,  
254                 #if it sees that the netfilter mark has been altered in the iptables mangle chain.
255                 #As VNET+ sets the netfilter mark of some packets in advance (to the slice-id value) before they enter the iptables mangle chain, 
256                 #we need to change it here (otherwise the rerouting process is not triggered for them).
257                 exec "iptables -t mangle -I $MANGLE_OUTPUT 1 -m mark --mark $sliver_nid -j MARK --set-mark $temp_nid"
258
259                 #make sure the netfilter mark of those "strange packets" won't be changed by the following rule
260                 exec "iptables -t mangle -I $MANGLE_OUTPUT 2 -m mark --mark $temp_nid -j RETURN"
261
262                 #Here we ask VNET+ to set the netfilter mark for the remaining packets (to the slice-id) 
263                 #we need to call this only once
264                 if [[ $first_time == 1 ]]; then
265                         exec "iptables -t mangle -A $MANGLE_OUTPUT -j MARK --copy-xid 0x00"
266                 fi              
267
268         elif [ $ENVIRONMENT == $LINUX_ENV ]; then
269                 #the same in the case of a "plain" Linux box. In this case we do not use VNET+ but
270                 #the owner module of iptables.
271                 exec "iptables -t mangle -A $MANGLE_OUTPUT -m owner --uid-owner $sliver_nid -j MARK --set-mark $sliver_nid"
272         fi
273
274         if [ $ENVIRONMENT == $PLANET_ENV ]; then        
275                 #Here the netfilter mark is restored to the slice-id value for the "strange packets"
276                 exec "iptables -t mangle -A $MANGLE_POSTROUTING -m mark --mark $temp_nid -j MARK --set-mark $sliver_nid"
277         fi
278
279         #Set the routing for the slice to be applied following the rules in $table_name"
280         #for the slice packets...
281         exec "ip rule add fwmark $sliver_nid table $table_name" 
282         #...and for the "strange packets"       
283         exec "ip rule add fwmark $temp_nid table $table_name"   
284
285         exec "ip route flush cache"  >/dev/null 2>&1
286 }
287
288
289 # remove the firewall rules. 
290 function unset_routes(){
291         local sliver=$1
292         local table_name=$2
293         local sliver_nid=`get_nid $sliver`
294         local temp_nid=`get_temp_nid $sliver`
295         
296         if [ $ENVIRONMENT == $PLANET_ENV ]; then         
297                 #removes the rules for the netfilter marks (for the "strange packets")
298                 exec "iptables -t mangle -D $MANGLE_OUTPUT -m mark --mark $sliver_nid -j MARK --set-mark $temp_nid"
299                 exec "iptables -t mangle -D $MANGLE_OUTPUT -m mark --mark $temp_nid -j RETURN"
300                 
301                 #removes the rule for restoring the original mark
302                 exec "iptables -t mangle -D $MANGLE_POSTROUTING -m mark --mark $temp_nid -j MARK --set-mark $sliver_nid"
303
304         elif [ $ENVIRONMENT == $LINUX_ENV ]; then
305                 #removes the rules that asks the owner module of iptables to set the netfilter mark to the user-id
306                 exec "iptables -t mangle -D $MANGLE_OUTPUT -m owner --uid-owner $sliver_nid -j MARK --set-mark $sliver_nid"
307         fi
308
309         exec "ip rule del fwmark $temp_nid table $table_name"
310         exec "ip rule del fwmark $sliver_nid table $table_name" 
311
312         exec "ip route flush cache"
313
314 }
315
316 # additional iptables chains where sliceip inserts its rules
317 NAT_POSTROUTING="sliceip"
318 MANGLE_OUTPUT="sliceip_output"
319 MANGLE_POSTROUTING="sliceip_postrouting"
320
321 # create the chains
322 check_chains(){
323         first_time=0
324
325         #create the chain where SNAT is performed
326         if iptables -t nat -N $NAT_POSTROUTING >/dev/null 2>&1; then
327                 #it's the first time sliceip is called, the chain was not defined               
328                 first_time=1            
329                 
330                 #create a chain where the netfilter mark is set
331                 exec "iptables -t mangle -N $MANGLE_OUTPUT"
332
333                 #create a chain where the netfilter mark for some packets is restored (see set_routes)
334                 exec "iptables -t mangle -N $MANGLE_POSTROUTING"
335
336                 #add the rules to take packets to the previously defined chains
337                 exec "iptables -t nat -A POSTROUTING -j $NAT_POSTROUTING"               
338                 exec "iptables -t mangle -A OUTPUT -j $MANGLE_OUTPUT"
339                 exec "iptables -t mangle -I POSTROUTING 1 -j $MANGLE_POSTROUTING"
340
341                 #cleaning up
342                 clean_iproute_conf
343         fi
344 }
345
346 # get the ip address of an interface
347 function get_address(){
348         local interface=$1      
349         local ip=""
350         
351         ip=`ifconfig $interface | grep inet\ addr | cut -d ":" -f 2 | cut -d " " -f 1`;
352
353         if valid_dotted_quad $ip; then 
354                 echo $ip;
355         else 
356                 echo "";
357         fi      
358                         
359 }
360
361 # get the temporary mark to be applied to the packets of the slice
362 function get_temp_nid(){
363         local sliver_nid=`get_nid $1`
364         local temp_nid=$((0x20000+$sliver_nid))
365         echo $temp_nid
366 }
367
368 # log function
369 function logm(){
370         logger $1
371 }
372
373 # get the name of the filename in wich we store information about
374 # the interfaces in use by the user
375 function get_filename_sliver(){
376         local sliver=$1
377         echo "${INT_FILE}-$sliver"
378 }
379
380 function set_active(){
381         local sliver=$1
382         local interface=$2
383         local ip=$3
384         local filename="${INT_FILE}-$sliver"
385         echo "$interface $ip" >> $filename
386 }
387
388 function set_deactivated(){
389         local sliver=$1
390         local interface=$2
391         local filename=`get_filename_sliver $sliver`
392         remove_line $filename $interface
393 }
394
395 function is_active(){
396         local sliver=$1
397         local interface=$2
398         local filename=`get_filename_sliver $sliver`
399
400         if grep $interface $filename >/dev/null 2>&1; then
401                 return 0
402         else
403                 return 1
404         fi
405 }
406
407 function get_num_activated(){
408         local sliver=$1
409         local filename=`get_filename_sliver $sliver`
410
411         if ! [ -e $filename ]; then
412                 echo 0;
413         else
414                 wc -l $filename | cut -f 1 -d " ";
415         fi
416 }
417
418
419 function get_ip_from_file(){
420         local sliver=$1
421         local interface=$2
422
423         local filename=`get_filename_sliver $sliver`
424
425         cat $filename | grep $interface | cut -d " " -f 2
426 }
427
428
429 # check ip addresses  
430 function valid_dotted_quad(){
431     oldIFS=$IFS
432     IFS=.
433     set -f
434     set -- $1
435     if [ $# -eq 4 ]
436     then
437       for seg
438       do
439         case $seg in
440             ""|*[!0-9]*) return 1; break ;; ## Segment empty or non-numeric char
441             *) [ $seg -gt 255 ] && return 2 ;;
442         esac
443       done
444     else
445       return 3 ## Not 4 segments
446     fi
447     IFS=$oldIFS
448     set +f
449     return 0;
450 }
451
452
453 # BEGIN
454 # the script starts here
455
456 if [[ $sliver == "" ]]; then
457         echo "I need the first argument (the sliver name)";
458         exit 1
459 fi
460
461 # read a line from the vsys pipe
462 read line
463
464 # separate the first word of the line from the others
465 command=`echo ${line%% *}`
466 rest=`echo ${line#* }`
467
468 case "$command" in
469   enable)
470         logger "sliceip command received from $sliver: $line"   
471         enable_interface $sliver "$rest"
472         ;;
473
474   disable)
475         logger "sliceip command received from $sliver: $line"   
476         disable_interface $sliver "$rest"
477         ;;
478
479   *)
480         logger "sliceip command received from $sliver: $line"
481         table_sliver=`get_table_name $sliver`
482
483         if ! grep "$table_sliver" $RT_TABLES >/dev/null 2>&1; then
484                 echo "Error. The slice routing table is not defined. Execute sliceip enable <interface>."
485                 exit 1
486         else 
487                 #add the routing rule - ip is called with the same parameters of sliceip but the indication of 
488                 #the table in wich the rule is to be inserted is appended               
489                 exec "ip $line table $table_sliver"
490         fi  
491         ;;
492
493 esac
494
495 exit 0
496
497
498