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