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