vroute script to permite manipulation of routing tables in a secure manner (ie: doesn...
[vsys-scripts.git] / exec / vroute
1 #!/usr/bin/python
2 # VSYS script to configure routes on virtual network interfaces from the root slice
3 #   Thom Haddow - 06/10/09
4 #
5 # Gets slice name as argv[1]
6 # Takes routing rules on stdin, one per line
7 #   rule := command ' ' target ' gw ' host device
8 #   command := 'add' | 'del'
9 #   target := host | network
10 #   network := host [ '/' netmask ]
11 #   netmask := number
12 #   host := number '.' number '.' number '.' number
13 #   number := [0-9]+
14 #   device := ( tun | tap ) slice '-' number
15 #   slice := <slice id>
16 #
17 # All networks and gateways MUST belong to the virtual
18 # network assigned to the slice (according to the vsys_vnet tag)
19 #
20 # Examples:
21 #
22 #   add 10.0.0.0/24 gw 10.0.0.1 tap754-0
23 #   del 10.0.0.0/24 gw 10.0.0.1 tap754-0
24 #
25
26
27 import sys
28 import pwd
29 import re
30 import socket
31 import struct
32 import os
33 import string
34 import subprocess
35
36 vsys_config_dir = "/etc/planetlab/vsys-attributes"
37
38 if len(sys.argv) != 2: sys.exit(1)
39
40 # VSYS scripts get slicename as $1
41 slicename=sys.argv[1]
42 sliceid = pwd.getpwnam(slicename).pw_uid
43
44 netblock_config=os.path.join(vsys_config_dir,slicename,"vsys_vnet")
45
46 # Read netblock allocation file
47 vnet_base = None
48 vnet_mask = None
49
50 try:
51     for netblock in open(netblock_config,'r'):
52         vnet_base, vnet_mask = netblock.split('/')
53         vnet_mask = int(vnet_mask)
54 except:
55     vnet_base = vnet_mask = None
56
57 if vnet_base is None:
58     #print >>sys.stderr, "Could not find entry for slice %s in netblock config file %s" % (slicename, netblock_config)
59     #sys.exit(1)
60     vnet_base = '192.168.2.0'
61     vnet_mask = 24
62
63 vnet_int = struct.unpack('!L', socket.inet_aton(vnet_base))[0]
64 vnet_int = (vnet_int >> vnet_mask) << vnet_mask
65
66 mask_int = (0xffffffff << vnet_mask) & 0xffffffff
67
68 # rule line regex
69 rule_re = r"(?P<cmd>add|del)\s+(?P<targetbase>(?:\d{1,3}[.]){3}\d{1,3})(?:/(?P<targetprefix>\d{1,2}))?\s+gw\s+(?P<gw>(?:\d{1,3}[.]){3}\d{1,3}) (?P<dev>(?:tun|tap)%d-\d{1,5})" % (sliceid,)
70 rule_re = re.compile(rule_re)
71
72 ### Read args from stdin
73 for argline in sys.stdin:
74     argline = argline.strip()
75     
76     # Match rule against the regex, 
77     # validating overall structure in the process
78     match = rule_re.match(argline)
79     if not match:
80         print >>sys.stderr, "Invalid rule %r:" % (argline,)
81         continue
82     
83     # Validate IPs involved
84     try:
85         target_ip_int = struct.unpack('!L',
86             socket.inet_aton(match.group('targetbase')))[0]
87     except Exception,e:
88         print >>sys.stderr, "Invalid target ip: %s" % (e,)
89         continue
90
91     try:
92         gw_ip_int = struct.unpack('!L',
93             socket.inet_aton(match.group('gw')))[0]
94     except Exception,e:
95         print >>sys.stderr, "Invalid target ip: %s" % (e,)
96         continue
97
98     if match.group('targetprefix'):
99         try:
100             target_mask = int(match.group('targetprefix'))
101         except Exception,e:
102             print >>sys.stderr, "Invalid target mask: %s" % (e,)
103             continue
104     else:
105         # host route
106         target_mask = 32
107     
108     ifname = match.group('dev')
109     if not ifname:
110         # Shouldn't happen, but just in case
111         print >>sys.stderr, "Invalid rule %r: Unspecified interface" % (argline,)
112         continue
113     
114     # Make sure all IPs/networks fall within the assigne vnet
115     if target_mask < vnet_mask:
116         print >>sys.stderr, "Invalid rule %r: target must belong to the %s/%s network" % (argline, vnet_base, vnet_mask)
117         continue
118     
119     if (target_ip_int & mask_int) != vnet_int:
120         print >>sys.stderr, "Invalid rule %r: target must belong to the %s/%s network" % (argline, vnet_base, vnet_mask)
121         continue
122     
123     if (gw_ip_int & mask_int) != vnet_int:
124         print >>sys.stderr, "Invalid rule %r: gateway must belong to the %s/%s network" % (argline, vnet_base, vnet_mask)
125         continue
126     
127     # Revalidate command (just in case)
128     command = match.group("cmd")
129     if command not in ('add','del'):
130         print >>sys.stderr, "Invalid rule %r: invalid command" % (argline,)
131         continue
132     
133     # Apply the rule
134     routeargs = [ "/sbin/route", command ]
135     
136     target_ip_str = socket.inet_ntoa(
137         struct.pack('!L', target_ip_int))
138
139     gw_ip_str = socket.inet_ntoa(
140         struct.pack('!L', gw_ip_int))
141     
142     if target_mask != 32:
143         routeargs.extend([
144             "-net", "%s/%d" % (target_ip_str, target_mask)
145         ])
146     else:
147         routeargs.append(target_ip_str)
148     
149     routeargs.extend([
150         "gw", gw_ip_str,
151         "dev", ifname,
152     ])
153     
154     proc = subprocess.Popen(routeargs)
155     if proc.wait():
156         print >>sys.stderr, "Could not set up route"
157     
158