eb75879627f4aa5bd812544e9d0b68ae8536e928
[vsys-scripts.git] / root-context / exec / vif_up
1 #!/usr/bin/python
2 # VSYS script to configure per-slice virtual network interfaces from the root slice
3 #   Thom Haddow - 06/10/09
4 #
5 # Gets slice name as argv[1]
6 # Takes remaining args on stdin:
7 #   - Interface name (eg [tun|tap]<sliceid>-<n>)
8 #   - (private) IP address (eg 1.2.3.4)
9 #   - Netmask (as int, e.g. 24)
10 #   - Followed by options as name=value pairs
11 #
12 # Options:
13 #   - pointopoint=IP: other endpoint's (private) IP (sets routing)
14 #   - snat=1: enables SNAT ip rules
15 #   - dropkern=1: drops RST packets generated by the kernel
16 #   - txqueuelen=N: sets TX queue
17 #   - gre=<> : enable GRE tunnelling - several formats supported
18 #   -   gre=true|yes|name : computes GRE key as a hash of slice name (so it's valid across federations)
19 #   -   gre=id            : uses for GRE key the slice id (the one from the myplc database)
20 #   -   gre=somestring    : computes GRE key as a hash of the provided string
21 #   -   gre=somekey       : use the provided key as is
22 #   - remote=IP : when using GRE, the (public) IP for the remote endpoint
23 #   - mtu=N  : set the MTU for the device
24
25 import sys
26 import pwd
27 import re
28 import socket
29 import struct
30 import os
31 import string
32
33 vsys_config_dir = "/etc/planetlab/vsys-attributes"
34
35 if len(sys.argv) != 2: sys.exit(1)
36
37 # VSYS scripts get slicename as $1
38 slicename=sys.argv[1]
39 # the local xid for this slice
40 sliceid = pwd.getpwnam(slicename).pw_uid
41
42 netblock_config=os.path.join(vsys_config_dir,slicename,"vsys_vnet")
43
44 # Read netblock allocation file
45 base = None
46
47 for netblock in open(netblock_config,'r'):
48     base, mask = netblock.split('/')
49
50 if base is None:
51     print >>sys.stderr, "Could not find entry for slice %s in netblock config file %s" % (slicename, netblock_config)
52     sys.exit(1)
53
54
55 ### Read args from stdin
56 arglines = map(string.strip, sys.stdin.readlines())
57
58 if len(arglines)<3:
59     print >>sys.stderr, "Insufficient argument lines."
60     sys.exit(1)
61
62 vif = arglines[0] # interface name
63 vip = arglines[1] # IP
64 vmask = int(arglines[2]) # netmask as int
65
66 # Create options list
67 if len(arglines)>3:
68     options = arglines[3:]
69 else:
70     options = []
71
72 # Convert network base addr to int format by unpacking as 32bit net-ordered long (!L)
73 base_int = struct.unpack('!L',socket.inet_aton(base))[0]
74 mask = int(mask)
75
76
77 ### Validate args
78 # Validate interface name
79 if len(vif)>16:
80     print >>sys.stderr, "Interface name %s invalid"%(vif)
81     sys.exit(1)
82
83 if re.match(r'(tun|tap)%d-\d+' % sliceid, vif ) is None:
84     print >>sys.stderr, "Interface name %s does not match slice id %d."%(vif, sliceid)
85     sys.exit(1)
86
87
88
89 # Validate requested IP and convert to int format.
90 try:
91     vip_int = struct.unpack('!L',socket.inet_aton(vip))[0]
92 except socket.error:
93     print >>sys.stderr, "Invalid IP: %s" % vip
94     sys.exit(1)
95
96 # Check IP is in netblock
97 if (vip_int>>(32-mask)) != (base_int>>(32-mask)):
98     print >>sys.stderr, "Requested IP %s not in netblock %s/%d" % (vip,base,mask)
99     sys.exit(1)
100
101 # TODO. Check IP is not in use?
102
103 # Validate mask: Check requested mask is sane and within our netblock
104 if vmask>32 or vmask <8:
105     print >>sys.stderr, "Requested netmask /%d is invalid" %(vmask)
106     sys.exit(1)
107     
108 if vmask<mask:
109     print >>sys.stderr, "Requested netmask /%d larger than allocation /%d" %(vmask, mask)
110     sys.exit(1)
111
112
113
114 ### Process options
115
116 opt_txqueuelen = None
117 opt_rp_filter = None
118 opt_snat = None
119 opt_dropkern = None
120 opt_ovs_dp = None
121 opt_pointopoint = None
122 opt_gre = None
123 opt_gre_remote = None
124 opt_mtu = None
125
126 for optionline in options:
127     if len(optionline)==0: continue
128     try:
129         opt, val = optionline.split('=')
130     except:
131         print >>sys.stderr, "Bad option line: \"%s\"" % (optionline)
132         sys.exit(1)
133
134     if opt=="rp_filter":
135         if val=="0":
136             opt_rp_filter="0"
137         elif val=="1":
138             opt_rp_filter="1"
139         else:
140             print >>sys.stderr, "rp_filter value invalid: \"%s\"" % (val)
141             sys.exit(1)
142
143     elif opt=="txqueuelen":
144         intval = int(val)
145         if intval<1 or intval>10000:
146             print >>sys.stderr, "txqueuelen value %s out of range 1-10000" % (val)
147             sys.exit(1)
148         opt_txqueuelen = intval
149     elif opt=="snat":
150         intval = int(val)
151         if val=="1":
152             opt_snat = True
153     elif opt=="dropkern":
154         intval = int(val)
155         if val=="1":
156             opt_dropkern = True
157     elif opt=="pointopoint":
158         opt_pointopoint = val.strip()
159         try:
160             socket.inet_aton(opt_pointopoint)
161         except socket.error,e:
162             print >>sys.stderr, "could not parse pointopoint: %s" % (e,)
163             sys.exit(1)
164     elif opt=="vswitch":
165         opt_ovs_dp = val
166     # support several formats, parse later on
167     elif opt=="gre":
168         opt_gre = val
169     elif opt=="remote":
170         opt_gre_remote = val.strip()
171         try:
172             socket.inet_aton(opt_gre_remote)
173         except socket.error,e:
174             print >>sys.stderr, "could not parse remote: %s" % (e,)
175             sys.exit(1)
176     elif opt=="mtu":
177         intval = int(val)
178         if intval<1:
179             print >>sys.stderr, "MTU value %s out of range" % (val)
180             sys.exit(1)
181         opt_mtu = intval
182     else:
183         print >>sys.stderr, "Unknown option: \"%s\"" % (opt)
184         sys.exit(1)
185
186 # post processing for gre mode
187 if opt_gre:
188     if not opt_pointopoint:
189         print >>sys.stderr, "GRE tunnels need a pointopoint address"
190         sys.exit(1)
191     if not opt_gre_remote:
192         print >>sys.stderr, "GRE tunnels need a pointopoint address"
193         sys.exit(1)
194
195     if vif.startswith('tun'):
196         opt_gre_type = 'gre'
197     else:
198         opt_gre_type = 'gretap'
199
200     opt_gre=opt_gre.lower()
201     # helper - keys are expected between 0 and 2**32-1
202     def gre_hash (string):
203         return hash(string) & 0xffffffff
204     # compute gre_key according to option format
205     if opt_gre in ('yes','true','name'):
206         gre_key=gre_hash(slicename)
207     elif opt_gre in ('id','sliceid'):
208         # use slice_id as the key - this is the one from myplc - won't work across federation
209         gre_key = int(open("/etc/vservers/%s/slice_id" % (slicename,),"r").read().strip())
210     elif opt_gre.isdigit():
211         # use as-is
212         gre_key=int(opt_gre)
213     else:
214         # hash the provided string
215         gre_key=gre_hash(opt_gre)
216     ### temporary - debug 
217     ### use ip tunnel show to obtain the key, but it only works with IP over GRE
218     #open('/tmp/%s.key'%vif,'w').write('%s\n'%gre_key)
219
220
221 ### Configure interface
222
223 cmd_ifconfig = "/sbin/ifconfig %s %s" % (vif, vip)
224 if opt_pointopoint is None:
225     cmd_ifconfig += "/%d" % (vmask,)
226 else:
227     # point-to-point mask
228     cmd_ifconfig += " netmask 255.255.255.255"
229 if opt_txqueuelen is not None:
230     cmd_ifconfig += " txqueuelen %d" % (opt_txqueuelen,)
231 if opt_pointopoint is not None:
232     cmd_ifconfig += " pointopoint %s" % (opt_pointopoint,)
233 if opt_mtu is not None:
234     cmd_ifconfig += " mtu %d" % (opt_mtu,)
235
236 # Add iptables rules (Clearing old ones first, if they exist)
237 cmd_iptables_in = "/sbin/iptables -A INPUT -i %s -m mark -m state --state NEW ! --mark %d -j DROP" % (vif, sliceid)
238 cmd_iptables_del_in = "/sbin/iptables -D INPUT -i %s -m mark -m state --state NEW ! --mark %d -j DROP 2>/dev/null" % (vif, sliceid)
239 cmd_iptables_out = "/sbin/iptables -A OUTPUT -o %s -m state --state NEW -m mark ! --mark %d -j DROP" % (vif, sliceid)
240 cmd_iptables_del_out = "/sbin/iptables -D OUTPUT -o %s -m state --state NEW -m mark ! --mark %d -j DROP 2>/dev/null" % (vif, sliceid)
241
242 # this initial form should work again now, but for some reason on 2.6.27, 'ip route get ...' returns an empty line
243 #public_src = os.popen("ip route get 1.1.1.1 | head -1 | awk '{print $7;}'").read().rstrip();
244 public_src = os.popen("ifconfig | grep $(ip route | grep default | awk '{print $3}' | awk -F. '{print $1\"[.]\"$2}') | head -1 | awk '{print $2}' | awk -F : '{print $2}'").read().rstrip()
245 cmd_iptables_pr = "/sbin/iptables -t nat -A POSTROUTING -s %s/%d -j SNAT --to-source %s --random" % (vip, vmask, public_src)
246 cmd_iptables_del_pr = "/sbin/iptables -t nat -D POSTROUTING -s %s/%d -j SNAT --to-source %s --random > /dev/null 2>&1" % (vip, vmask, public_src)
247
248 cmd_iptables_dk = "/sbin/iptables -I OUTPUT -p tcp -s %s/%d --tcp-flags RST RST -j DROP"%(vip,vmask)
249 cmd_iptables_del_dk = "/sbin/iptables -D OUTPUT -p tcp -s %s/%d --tcp-flags RST RST -j DROP > /dev/null 2>&1"%(vip,vmask)
250
251 if opt_gre:
252     cmd_gre_setup = "modprobe ip_gre ; ip link add %s type %s remote %s local %s ttl 64 csum key %s" % (
253         vif, opt_gre_type, opt_gre_remote, public_src, gre_key )
254     cmd_iptables_gre_pr = "/sbin/iptables -t mangle -A INPUT -i %s -m mark --mark 0 -j MARK --set-mark %s 2>/dev/null" % (
255         vif, int(sliceid))
256
257     os.system(cmd_gre_setup)
258     os.system(cmd_iptables_gre_pr)
259
260 os.system(cmd_ifconfig)
261
262 os.system(cmd_iptables_del_in)
263 os.system(cmd_iptables_in)
264 os.system(cmd_iptables_del_out)
265 os.system(cmd_iptables_out)
266
267 # always remove snat rules
268 # in case there are leftovers from previous calls
269 os.system(cmd_iptables_del_pr)
270 if (opt_snat):
271     os.system(cmd_iptables_pr)
272
273 os.system(cmd_iptables_del_dk)
274 if (opt_snat):
275     os.system(cmd_iptables_dk)
276
277 # Process additional options
278 if opt_rp_filter is not None:
279     rp_cmd = "/sbin/sysctl net.ipv4.conf.%s.rp_filter=%s" % (vif, opt_rp_filter)
280     os.system(rp_cmd)
281
282 # OVS datapath
283 if opt_ovs_dp is not None:
284     cmd_ovs_addif = "ovs-dpctl add-if %s %s"%(opt_ovs_dp,vif)
285     os.system(cmd_ovs_addif)
286