adding sliceip on behalf of Giovanni
[vsys-scripts.git] / 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 # fronted 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         local nid=`get_nid $sliver`
87
88         local num_active=`get_num_activated $sliver`
89
90         if ! is_active $sliver $interface; then
91
92                 if [[ $num_active == 0 ]]; then
93                                 #define the routing table for the slice and set some iptables rules in order to mark user's packets;                    
94                                 create_table $sliver
95                 fi
96
97                 #Adds an SNAT rule to set the source IP address of packets that are about to go out through $interface.
98                 #The kernel sets the source address of packets when the first routing process happens (following the rules 
99                 #of the main routing table); when the rerouting process happens (following the user/slice specific rules) 
100                 #the source address is not modified. Consequently, we need to add this rule to change the source ip address 
101                 #to the $interface source address.
102                 exec "iptables -t nat -A POSTROUTING -o $interface -j SNAT --to-source $ip -m mark --mark $nid"
103
104                 set_active $sliver $interface $ip               
105
106         else
107                 local old_ip=`get_ip_from_file $sliver $interface`
108         
109                 if [[ $old_ip != $ip ]]; then
110                         #remove the rule for the old ip
111                         exec "iptables -t nat -D POSTROUTING -o $interface -j SNAT --to-source $old_ip -m mark --mark $nid"
112                         #insert the new rule                    
113                         exec "iptables -t nat -A POSTROUTING -o $interface -j SNAT --to-source $ip -m mark --mark $nid" 
114
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
129         local ip=`get_ip_from_file $sliver $interface`  
130         
131         local nid=`get_nid $sliver`
132
133         if is_active $sliver $interface >/dev/null 2>&1; then
134                 exec "iptables -t nat -D POSTROUTING -o $interface -j SNAT --to-source $ip -m mark --mark $nid"
135
136                 local num_ifn_on=`get_num_activated $sliver`
137                 
138                 if [[ $num_ifn_on == 1 ]]; then
139                         delete_table $sliver
140                 fi
141
142                 set_deactivated $sliver $interface
143         fi
144
145 }
146
147 # wrapper function used to execute system commands
148 function exec(){
149         local command=$1        
150
151         if ! [[ $command ]]; then
152                 echo "Error in exec. No argument."
153                 exit 1  
154         else
155                 if ! $command; then
156                         if [[ $DEBUG == 1 ]]; then echo "Error executing \"$1\""; fi
157                         echo EOF                        
158                         exit 1
159                 fi
160         fi
161
162 }
163
164 # decides wich id to use for the slice routing table
165 function get_table_id(){
166         local sliver=$1
167
168         k=$FIRST_TABLE
169
170         while [[ $k < 255 ]] && grep $k $RT_TABLES >/dev/null 2>&1 ; do
171                 k=$((k+1))
172         done
173
174         if [[ $k == 255 ]]; then
175                 logm "No routing tables available. Exiting."
176                 return 1
177         fi
178         
179         echo $k
180 }
181
182
183 # create the slice-specifig routing table
184 function create_table(){
185         local sliver=$1
186
187         local table_name=`get_table_name $sliver`
188         local table_id=`get_table_id $sliver`   
189         local temp_nid=`get_temp_nid $sliver`
190
191         if ! grep $table_name $RT_TABLES > /dev/null 2>&1; then
192                 echo "$table_id $table_name" >> $RT_TABLES
193   else
194                 echo "WARNING: $table_name routing table already defined."
195         fi
196
197         set_routes $sliver $table_name
198
199         return 0
200 }
201
202 # delete the slice-specific routing table
203 function delete_table(){
204         local sliver=$1
205
206         local table_name=`get_table_name $sliver`
207         local table_id=`get_nid $sliver`        
208         local temp_nid=`get_temp_nid $sliver`
209
210         if ! grep $table_name $RT_TABLES > /dev/null 2>&1; then
211                 return 1                        
212         else
213                 exec "ip route flush table $table_name" >/dev/null 2>&1
214                 unset_routes $sliver $table_name
215                 remove_line "$RT_TABLES" "$table_name"          
216         fi
217
218         return 0
219 }
220
221 # remove a line from a file
222 function remove_line(){
223         local filename=$1
224         local regex=$2
225
226         #remove interface line from the file
227         
228         exec "sed /${regex}/d $filename" > $filename.tmp
229         exec "mv $filename.tmp $filename"
230         
231
232 }
233
234 # get the slice-specific routing table name
235 function get_table_name(){
236         local sliver=$1;
237         echo "${sliver}_slcip"
238 }
239
240 function clean_iproute_conf(){
241         while grep "slcip" $RT_TABLES >/dev/null 2>&1; do
242                 remove_line $RT_TABLES "slcip"
243         done
244
245         rm ${INT_FILE}*
246
247 }
248
249 #get slice network id
250 function get_nid(){
251     id -u ${1}
252 }
253
254 # set the firewall rules. Mainly, it asks VNET+ to set the netfilter mark of packets belonging to the slice to the slice-id
255 # and then associates those packets with the routing table allocated for that slice.
256 function set_routes(){
257         local sliver=$1
258         local table_name=$2
259
260         local sliver_nid=`get_nid $sliver`
261         local temp_nid=`get_temp_nid $sliver`
262
263         if [ $ENVIRONMENT == $PLANET_ENV ]; then        
264                 #Linux kernel triggers a rerouting process, wich is needed to perfom slice-specific routing,  
265                 #if it sees that the netfilter mark has been altered in the iptables mangle chain.
266                 #As VNET+ sets the netfilter mark of some packets in advance (to the slice-id value) before they enter the iptables mangle chain, 
267                 #we need to change it here (otherwise the rerouting process is not triggered for them).
268                 exec "iptables -t mangle -A OUTPUT -m mark --mark $sliver_nid -j MARK --set-mark $temp_nid"
269
270                 #Here we ask VNET+ to set the netfilter mark for the remaining packets (to the slice-id) 
271                 exec "iptables -t mangle -A OUTPUT -j MARK -m mark ! --mark $temp_nid --copy-xid 0x00"
272         elif [ $ENVIRONMENT == $LINUX_ENV ]; then
273                 #the same in the case of a "plain" Linux box. In this case we do not use VNET+ but
274                 #the owner module of iptables.
275                 exec "iptables -t mangle -A OUTPUT -m owner --uid-owner $sliver_nid -j MARK --set-mark $sliver_nid"
276         fi
277         
278         #Here the netfilter mark is restored to the slice-id value for the "strange packets"
279         exec "iptables -t mangle -I POSTROUTING 1 -m mark --mark $temp_nid -j MARK --set-mark $sliver_nid"
280
281         #Set the routing for the slice to be applied following the rules in $table_name"
282         #for the slice packets...
283         exec "ip rule add fwmark $sliver_nid table $table_name" 
284         #...and for the "strange packets"       
285         exec "ip rule add fwmark $temp_nid table $table_name"   
286
287         exec "ip route flush cache"  >/dev/null 2>&1
288 }
289
290 # removes the firewall rules. 
291 function unset_routes(){
292         local sliver=$1
293         local table_name=$2
294
295         local sliver_nid=`get_nid $sliver`
296         local temp_nid=`get_temp_nid $sliver`
297         
298         if [ $ENVIRONMENT == $PLANET_ENV ]; then         
299                 #removes the rules for the netfilter marks (for the "strange packets")
300                 exec "iptables -t mangle -D OUTPUT -m mark --mark $sliver_nid -j MARK --set-mark $temp_nid"
301
302                 #removes the rules that asks VNET+ to set the netfilter mark to the slice-id            
303                 exec "iptables -t mangle -D OUTPUT -m mark ! --mark $temp_nid -j MARK --copy-xid 0x00"
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 OUTPUT -m owner --uid-owner $sliver_nid -j MARK --set-mark $sliver_nid"
307         fi
308
309         exec "iptables -t mangle -D POSTROUTING -m mark --mark $temp_nid -j MARK --set-mark $sliver_nid"
310
311         exec "ip rule del fwmark $temp_nid table $table_name"
312         exec "ip rule del fwmark $sliver_nid table $table_name" 
313
314         exec "ip route flush cache"
315
316 }
317
318 # get the ip address of an interface
319 function get_address(){
320         local interface=$1      
321         local ip=""
322         
323         ip=`ifconfig $interface | grep inet\ addr | cut -d ":" -f 2 | cut -d " " -f 1`;
324
325         if valid_dotted_quad $ip; then 
326                 echo $ip;
327         else 
328                 echo "";
329         fi      
330                         
331 }
332
333 # get the temporary mark to be applied to the slice packets
334 function get_temp_nid(){
335         local sliver_nid=`get_nid $1`
336         local temp_nid=$((0x20000+$sliver_nid))
337         echo $temp_nid
338 }
339
340 # log function
341 function logm(){
342         logger $1
343 }
344
345 # get the name of the filename in wich we store information about
346 # the interfaces in use by the user
347 function get_filename_sliver(){
348         local sliver=$1
349         echo "${INT_FILE}-$sliver"
350 }
351
352 function set_active(){
353         local sliver=$1
354         local interface=$2
355         local ip=$3
356
357         local filename="${INT_FILE}-$sliver"
358
359         echo "$interface $ip" >> $filename
360         
361 }
362
363 function set_deactivated(){
364
365         local sliver=$1
366         local interface=$2
367
368         local filename=`get_filename_sliver $sliver`
369
370         remove_line $filename $interface
371 }
372
373 function is_active(){
374         local sliver=$1
375         local interface=$2
376
377   local filename=`get_filename_sliver $sliver`
378
379         if grep $interface $filename >/dev/null 2>&1; then
380                 return 0
381         else
382                 return 1
383         fi
384 }
385
386 function get_num_activated(){
387         local sliver=$1
388
389   local filename=`get_filename_sliver $sliver`
390
391         if ! [ -e $filename ]; then
392                 echo 0;
393         else
394                 wc -l $filename | cut -f 1 -d " ";
395         fi
396 }
397
398
399 function get_ip_from_file(){
400         local sliver=$1
401         local interface=$2
402
403         local filename=`get_filename_sliver $sliver`
404
405         cat $filename | grep $interface | cut -d " " -f 2
406 }
407
408
409
410 # checks ip addresses  
411 function valid_dotted_quad(){
412     oldIFS=$IFS
413     IFS=.
414     set -f
415     set -- $1
416     if [ $# -eq 4 ]
417     then
418       for seg
419       do
420         case $seg in
421             ""|*[!0-9]*) return 1; break ;; ## Segment empty or non-numeric char
422             *) [ $seg -gt 255 ] && return 2 ;;
423         esac
424       done
425     else
426       return 3 ## Not 4 segments
427     fi
428     IFS=$oldIFS
429     set +f
430     return 0;
431 }
432
433
434 # BEGIN
435 # the script starts here
436
437 if [[ $sliver == "" ]]; then
438         echo "I need the first argument (the sliver name)";
439         exit 1
440 fi
441
442 # read a line from the vsys pipe
443 read line
444
445 # separates the first word of the line from the others
446 command=`echo ${line%% *}`
447 rest=`echo ${line#* }`
448
449 case "$command" in
450   enable)
451         logger "slcip command received from $sliver: $line"     
452         enable_interface $sliver "$rest"
453         ;;
454
455   disable)
456         logger "slcip command received from $sliver: $line"     
457         disable_interface $sliver "$rest"
458         ;;
459
460   *)
461         logger "slcip command received from $sliver: $line"
462         table_sliver=`get_table_name $sliver`
463
464         # adds the rule
465         if ! grep "$table_sliver" $RT_TABLES >/dev/null 2>&1; then
466                 echo "Error. The slice routing table is not defined. Execute sliceip enable <interface>."
467         else 
468                 exec "ip $line table $table_sliver"
469         fi  
470         ;;
471
472 esac
473
474 # make the frontend quit
475 echo "EOF"
476
477 exit 0
478
479
480