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