Fixe ns3 routing
[nepi.git] / examples / Multicast / multicast_experiment.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 import getpass
5 import os
6 import os.path
7 import re
8 import sys
9 import shutil
10 import signal
11 import tempfile
12 import time
13 import struct
14 import socket
15 import operator
16 import ipaddr
17 import gzip
18 import random
19 import traceback
20 import math
21 import subprocess
22
23 sys.path.append(os.path.abspath("../../src"))
24
25 from nepi.core.design import ExperimentDescription, FactoriesProvider
26 from nepi.core.execute import ExperimentController
27 from nepi.util import proxy
28 from nepi.util.constants import DeploymentConfiguration as DC, ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP
29 from nepi.testbeds.planetlab import util as plutil
30 from optparse import OptionParser
31
32
33 class PlanetLabMulticastOverlay:
34     testbed_id = "planetlab"
35     slicename = "inria_nepi"
36     plchost = "www.planet-lab.eu"
37     plkey = os.environ.get(
38             "PL_SSH_KEY",
39             "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME'],) )
40     pluser = os.environ.get("PL_USER")
41     plpass = os.environ.get("PL_PASS")
42     vnet = "192.168.3.0"
43     user = os.getlogin()
44     
45     port_base = 2000 + (os.getpid() % 1000) * 13
46     
47     def setUp(self):
48         self.root_dir = tempfile.mkdtemp()
49         self.__class__.port_base = self.__class__.port_base + 100
50         
51         print "Using:"
52         print "\tDISPLAY:", os.environ['DISPLAY']
53         print "\tPLC:", self.plchost
54         print "\tUsername:", self.pluser
55         print "\tslice:", self.slicename
56
57         api = plutil.getAPI(self.pluser, self.plpass, hostname=self.plchost)
58         self.vnet = plutil.getVnet(api, self.slicename).split('/')[0].strip()
59
60         print "\tvnet:", self.vnet
61
62     def tearDown(self):
63         try:
64             shutil.rmtree(self.root_dir)
65         except:
66             # retry
67             time.sleep(0.1)
68             shutil.rmtree(self.root_dir)
69
70     def make_experiment_desc(self):
71         testbed_id = self.testbed_id
72         slicename = self.slicename
73         plchost = self.plchost
74         pl_ssh_key = self.plkey
75         pl_user = self.pluser
76         pl_pwd = self.plpass
77         
78         plroot_dir = os.path.join(self.root_dir,"pl")
79         if not os.path.exists(plroot_dir):
80             os.makedirs(plroot_dir)
81
82         exp_desc = ExperimentDescription()
83         pl_provider = FactoriesProvider(testbed_id)
84         pl_desc = exp_desc.add_testbed_description(pl_provider)
85         pl_desc.set_attribute_value(DC.ROOT_DIRECTORY, plroot_dir )
86         pl_desc.set_attribute_value("homeDirectory", self.root_dir)
87         pl_desc.set_attribute_value("slice", slicename)
88         pl_desc.set_attribute_value("sliceSSHKey", pl_ssh_key)
89         pl_desc.set_attribute_value("authUser", pl_user)
90         pl_desc.set_attribute_value("authPass", pl_pwd)
91         pl_desc.set_attribute_value("plcHost", plchost)
92         pl_desc.set_attribute_value("tapPortBase", self.port_base)
93         pl_desc.set_attribute_value("p2pDeployment", not self.no_p2p_deploy)
94         pl_desc.set_attribute_value("dedicatedSlice", True)
95         pl_desc.set_attribute_value("plLogLevel", "INFO")
96         
97         return pl_desc, exp_desc
98     
99     def make_pl_tapnode(self, pl, ip, inet = None, label = None, hostname = None, routes = None, mcast = False, mcastrouter = False, types = None):
100         if not isinstance(ip, list):
101             ips = [ip]
102         else:
103             ips = ip
104         node1 = pl.create("Node")
105         if label: 
106             node1.set_attribute_value("label", label)
107         if hostname: 
108             node1.set_attribute_value("hostname", hostname)
109         iface1 = pl.create("NodeInterface")
110         if label:
111             iface1.set_attribute_value("label", label+"iface")
112         if types is None:
113             types = ["TapInterface"] * len(ips)
114         tap1 = []
115         tap1ip = []
116         for i,(ip,devtype) in enumerate(zip(ips,types)):
117             _tap1 = pl.create(devtype)
118             _tap1.set_attribute_value("multicast", True)
119             _tap1.enable_trace("pcap") # for error output
120             if label:
121                 _tap1.set_attribute_value("label", label+"tap"+(str(i+1) if i else ""))
122         
123             _tap1ip = _tap1.add_address()
124             _tap1ip.set_attribute_value("Address", ip)
125             _tap1ip.set_attribute_value("NetPrefix", 32)
126             _tap1ip.set_attribute_value("Broadcast", False)
127         
128             node1.connector("devs").connect(_tap1.connector("node"))
129             
130             tap1.append(_tap1)
131             tap1ip.append(_tap1ip)
132             
133         inet = inet or pl.create("Internet")
134         node1.connector("devs").connect(iface1.connector("node"))
135         iface1.connector("inet").connect(inet.connector("devs"))
136         
137         for destip, destprefix, nexthop in routes:
138             r1 = node1.add_route()
139             r1.set_attribute_value("Destination", destip)
140             r1.set_attribute_value("NetPrefix", destprefix)
141             r1.set_attribute_value("NextHop", nexthop)
142         
143         if mcast:
144             if mcastrouter:
145                 fwd = pl.create("MulticastForwarder")
146                 fwd.enable_trace("stderr")
147                 fwd.connector("node").connect(node1.connector("apps"))
148                 mrt = pl.create("MulticastRouter")
149                 mrt.connector("fwd").connect(fwd.connector("router"))
150                 mrt.enable_trace("stderr")
151             else:
152                 ann = pl.create("MulticastAnnouncer")
153                 ann.enable_trace("stderr")
154                 ann.connector("node").connect(node1.connector("apps"))
155                 
156         return node1, iface1, tap1, tap1ip, inet
157     
158     def add_vlc_base(self, pl, node):
159         app = pl.create("Application")
160         app.set_attribute_value("rpmFusion", True)
161         app.set_attribute_value("depends", "vlc")
162         app.set_attribute_value("command", "sudo -S dbus-uuidgen --ensure ; vlc --version")
163         app.enable_trace("stdout")
164         app.enable_trace("stderr")
165         node.connector("apps").connect(app.connector("node"))
166         return app
167     
168     def add_vlc_restreamer(self, pl, node):
169         hostname = node.get_attribute_value("hostname")
170         app = self.add_vlc_base(pl, node)
171         app.set_attribute_value("label","vlc_restreamer_%d" % (node.guid,))
172         app.set_attribute_value("command",
173             "sudo -S dbus-uuidgen --ensure ; "
174             "while true ; do if "
175             "vlc -vvv -I dummy"
176             " udp/ts://@239.255.12.42"
177             " --sout '#std{access=http,mux=ts,dst="+hostname+":8080}'"
178             " ; then break ; else sleep 5 ; fi ; done ")
179         return app
180     
181     def add_vlc_dumper(self, pl, node, hostname=None, labelprefix = "vlc_dumper", precmd = "sleep 5 ; "):
182         app = self.add_vlc_base(pl, node)
183         mylabel = "%s_%d" % (labelprefix, node.guid,)
184         if hostname is None:
185             hostname = node.get_attribute_value("hostname")
186         app.set_attribute_value("label",mylabel)
187         app.set_attribute_value("command",
188             precmd+
189             "sudo -S dbus-uuidgen --ensure ; "
190             "while true ; do if "
191             "vlc -vvv -I dummy"
192             " http://"+hostname+":8080"
193             " --sout '#std{access=file,mux=ts,dst={#["+mylabel+"].trace[output].[name]#}}'"
194             " ; then break ; else sleep 5 ; fi ; done ")
195         app.enable_trace("output")
196         return app
197     
198     def add_vlc_source(self, pl, node, iflabels):
199         app = self.add_vlc_base(pl, node)
200         app.set_attribute_value("label","vlc_source_%d" % (node.guid,))
201         app.set_attribute_value("sources", self.movie_source)
202         app.set_attribute_value("command",
203             "sudo -S dbus-uuidgen --ensure ; "
204             "vlc -vvv -I dummy "
205             +os.path.basename(self.movie_source)
206             +" --sout '#duplicate{"
207             +','.join([
208                 "dst=std{access=udp,dst=239.255.12.42,mux=ts,ttl=64,miface-addr={#[%s].addr[0].[Address]#}}" % (iflabel,)
209                 for iflabel in iflabels
210             ])
211             +"}'")
212         return app
213     
214     def add_net_monitor(self, pl, node):
215         app = pl.create("Application")
216         app.set_attribute_value("label","network_monitor_%d" % (node.guid,))
217         app.set_attribute_value("command", 
218             r"""head -n 2 /proc/net/dev ; while true ; do cat /proc/net/dev | sed -r 's/.*/'"$(date -R)"': \0/' | grep eth0 ; sleep 1 ; done""")
219         app.enable_trace("stdout")
220         node.connector("apps").connect(app.connector("node"))
221         return app
222     
223     def add_ip_address(self, iface, address, netprefix):
224         ip = iface.add_address()
225         ip.set_attribute_value("Address", address)
226         ip.set_attribute_value("NetPrefix", netprefix)
227
228     def add_route(self, node, destination, netprefix, nexthop):
229         route = node.add_route()
230         route.set_attribute_value("Destination", destination)
231         route.set_attribute_value("NetPrefix", netprefix)
232         route.set_attribute_value("NextHop", nexthop)
233
234     def make_ns_in_pl(self, pl, exp, node1, iface1, root):
235         ns3_testbed_id = "ns3"
236         
237         # Add NS3 support in node1
238         plnepi = pl.create("NepiDependency")
239         plns3 = pl.create("NS3Dependency")
240         plnepi.connector("node").connect(node1.connector("deps"))
241         plns3.connector("node").connect(node1.connector("deps"))
242
243         # Create NS3 testbed running in node1
244         ns3_provider = FactoriesProvider(ns3_testbed_id)
245         ns_desc = exp.add_testbed_description(ns3_provider)
246         ns_desc.set_attribute_value("rootDirectory", root)
247         ns_desc.set_attribute_value("SimulatorImplementationType", "ns3::RealtimeSimulatorImpl")
248         ns_desc.set_attribute_value("ChecksumEnabled", True)
249         ns_desc.set_attribute_value(DC.DEPLOYMENT_HOST, "{#[%s].addr[0].[Address]#}" % (
250             iface1.get_attribute_value("label"),))
251         ns_desc.set_attribute_value(DC.DEPLOYMENT_USER, 
252             pl.get_attribute_value("slice"))
253         ns_desc.set_attribute_value(DC.DEPLOYMENT_KEY, 
254             pl.get_attribute_value("sliceSSHKey"))
255         ns_desc.set_attribute_value(DC.DEPLOYMENT_MODE, DC.MODE_DAEMON)
256         ns_desc.set_attribute_value(DC.DEPLOYMENT_COMMUNICATION, DC.ACCESS_SSH)
257         ns_desc.set_attribute_value(DC.DEPLOYMENT_ENVIRONMENT_SETUP,
258             "{#[%s].[%s]#}" % (
259                 node1.get_attribute_value("label"),
260                 ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,))
261         ns_desc.set_attribute_value(DC.LOG_LEVEL, DC.DEBUG_LEVEL)
262         
263         return ns_desc
264    
265     def add_ip_address(self, iface, address, netprefix, broadcast = False):
266         ip = iface.add_address()
267         ip.set_attribute_value("Address", address)
268         ip.set_attribute_value("NetPrefix", netprefix)
269         ip.set_attribute_value("Broadcast", broadcast)
270         return ip
271
272     def add_route(self, node, destination, netprefix, nexthop):
273         route = node.add_route()
274         route.set_attribute_value("Destination", destination)
275         route.set_attribute_value("NetPrefix", netprefix)
276         route.set_attribute_value("NextHop", nexthop)
277         return route
278
279     def add_ns_fdnd(self, ns_desc, node):
280         fdnd = ns_desc.create("ns3::FdNetDevice")
281         node.connector("devs").connect(fdnd.connector("node"))
282         #fdnd.enable_trace("FdPcapTrace")
283         return fdnd
284
285     def add_ns_node(self, ns_desc):
286         node = ns_desc.create("ns3::Node")
287         ipv4 = ns_desc.create("ns3::Ipv4L3Protocol")
288         arp  = ns_desc.create("ns3::ArpL3Protocol")
289         icmp = ns_desc.create("ns3::Icmpv4L4Protocol")
290         udp = ns_desc.create("ns3::UdpL4Protocol")
291         node.connector("protos").connect(ipv4.connector("node"))
292         node.connector("protos").connect(arp.connector("node"))
293         node.connector("protos").connect(icmp.connector("node"))
294         node.connector("protos").connect(udp.connector("node"))
295         return node
296
297     def add_ns_wifi_dev(self, ns_desc, node, access_point = False):
298         wifi = ns_desc.create("ns3::WifiNetDevice")
299         node.connector("devs").connect(wifi.connector("node"))
300
301         phy = ns_desc.create("ns3::YansWifiPhy")
302         error = ns_desc.create("ns3::NistErrorRateModel")
303         manager = ns_desc.create("ns3::ArfWifiManager")
304         if access_point:
305             mac = ns_desc.create("ns3::ApWifiMac")
306         else:
307             mac = ns_desc.create("ns3::StaWifiMac")
308
309         phy.set_attribute_value("Standard", "WIFI_PHY_STANDARD_80211a")
310         mac.set_attribute_value("Standard", "WIFI_PHY_STANDARD_80211a")
311         phy.connector("err").connect(error.connector("phy"))
312         wifi.connector("phy").connect(phy.connector("dev"))
313         wifi.connector("mac").connect(mac.connector("dev"))
314         wifi.connector("manager").connect(manager.connector("dev"))
315
316         #phy.enable_trace("YansWifiPhyPcapTrace")
317         return wifi, phy
318
319     def add_ns_constant_mobility(self, ns_desc, node, x, y, z):
320         mobility = ns_desc.create("ns3::ConstantPositionMobilityModel") 
321         position = "%d:%d:%d" % (x, y, z)
322         mobility.set_attribute_value("Position", position)
323         node.connector("mobility").connect(mobility.connector("node"))
324         return mobility
325
326     def add_ns_wifi_channel(self, ns_desc):
327         channel = ns_desc.create("ns3::YansWifiChannel")
328         delay = ns_desc.create("ns3::ConstantSpeedPropagationDelayModel")
329         loss  = ns_desc.create("ns3::LogDistancePropagationLossModel")
330         channel.connector("delay").connect(delay.connector("chan"))
331         channel.connector("loss").connect(loss.connector("prev"))
332         return channel
333
334     def make_netns_testbed(self, exp_desc):
335         netns_provider = FactoriesProvider("netns")
336         netns_desc = exp_desc.add_testbed_description(netns_provider)
337         netns_desc.set_attribute_value("homeDirectory", self.root_dir)
338         netns_desc.set_attribute_value(DC.DEPLOYMENT_MODE, DC.MODE_DAEMON)
339         netns_root_dir = os.path.join(self.root_dir, "netns")
340         os.mkdir(netns_root_dir)
341         netns_desc.set_attribute_value(DC.ROOT_DIRECTORY, netns_root_dir)
342         netns_desc.set_attribute_value(DC.LOG_LEVEL, DC.DEBUG_LEVEL)
343         netns_desc.set_attribute_value(DC.USE_SUDO, True)
344         return netns_desc
345
346     def add_netns_node(self, netns_desc, forwardX = True, label = None):
347         node = netns_desc.create("Node")
348         node.set_attribute_value("forward_X11", forwardX)
349         if label:
350             node.set_attribute_value("label", label)
351         return node
352     
353     def add_netns_app(self, netns_desc, command, node):
354         app = netns_desc.create("Application")
355         app.set_attribute_value("command", command)
356         app.set_attribute_value("user", self.user)
357         app.connector("node").connect(node.connector("apps"))
358         return app
359
360     def add_pl_netns_connection(self, 
361             pl_tap, 
362             netns_desc, netns_node, netns_addr, netns_prefix = 30,
363             taplabel = None):
364         pl_tap.set_attribute_value("tun_cipher", "PLAIN") 
365         pl_tap.set_attribute_value("multicast", True) 
366         #pl_tap.enable_trace("pcap")
367         #pl_tap.enable_trace("packets")
368         pl_tapip = pl_tap.addresses[0].get_attribute_value("Address")
369         netns_tap = netns_desc.create("TunNodeInterface")
370         netns_tap.set_attribute_value("up", True)
371         netns_tap.set_attribute_value("mtu", 1448)
372         self.add_ip_address(netns_tap, netns_addr, netns_prefix)
373         if taplabel:
374             netns_tap.set_attribute_value("label", taplabel)
375         netns_node.connector("devs").connect(netns_tap.connector("node"))
376         netns_tunchannel = netns_desc.create("TunChannel")
377         netns_tunchannel.set_attribute_value("tun_cipher", "PLAIN") 
378         netns_tunchannel.connector("->fd").connect(netns_tap.connector("fd->"))
379         pl_tap.connector("tcp").connect(netns_tunchannel.connector("tcp"))
380         
381         pl_tap.set_attribute_value("tun_cipher", "PLAIN") 
382         pl_tap.set_attribute_value("pointopoint", netns_addr)
383
384     def add_pl_ns_connection(self, pl_desc, pl_node, pl_addr,
385             ns, ns_node, ns_addr, prefix = 30,
386             fd = False, ptp = False):
387         pl_tap = pl_desc.create("TapInterface")
388         if fd:
389             pl_tap.set_attribute_value("tun_cipher", "PLAIN") 
390         self.add_ip_address(pl_tap, pl_addr, prefix)
391         pl_node.connector("devs").connect(pl_tap.connector("node"))
392         ns_fdnd = ns.create("ns3::FdNetDevice")
393         ns_node.connector("devs").connect(ns_fdnd.connector("node"))
394         self.add_ip_address(ns_fdnd, ns_addr, prefix)
395         
396         if fd:
397             pl_tap.connector("fd->").connect(ns_fdnd.connector("->fd"))
398         else:
399             tunchannel = ns.create("ns3::Nepi::TunChannel")
400             tunchannel.connector("fd->").connect(ns_fdnd.connector("->fd"))
401             pl_tap.connector("udp").connect(tunchannel.connector("udp"))
402         
403         if ptp:
404             pl_tap.set_attribute_value("pointopoint", ns_addr)
405
406     def make_pl_overlay(self, numnodes):
407         ns3_testbed_id = "ns3"
408         
409         pl, exp = self.make_experiment_desc()
410         
411         # We'll make a distribution spanning tree using prefix matching as a distance
412         api = plutil.getAPI(self.pluser, self.plpass, hostname=self.plchost)
413         nodes = plutil.getNodes(api, numnodes, operatingSystem = 'f12')
414         root = min(nodes, key=operator.attrgetter('hostname'))
415         links = list(plutil.getSpanningTree(nodes, root=root))
416         
417         for node in nodes:
418             node.vif_ips = set()
419             node.children = []
420             node.childips = set()
421         
422         # Build an explicit tree
423         for slave, master in links:
424             master.children.append(slave)
425         
426         # We have to assign IPs and routes.
427         # The IP will be assigned sequentially, depth-first.
428         # This will result in rather compact routing rules
429         nextip = [128-numnodes]
430         def traverse(traverse, node, parent=None, base=struct.unpack('!L',socket.inet_aton(self.vnet))[0]):
431             if nextip[0] >= 254:
432                 raise RuntimeError, "Too many IPs to assign!"
433             
434             node.vif_addr = base | (nextip[0])
435             nips = 1+len(node.children) # one vif per child, plus one for the parent
436             nextip[0] += nips
437             
438             for i in xrange(nips):
439                 node.vif_ips.add(node.vif_addr+i)
440
441             if parent:
442                 parent.childips.update(node.vif_ips)
443
444             for i,child in enumerate(node.children):
445                 traverse(traverse, child, node, base)
446                 
447             if parent:
448                 parent.childips.update(node.childips)
449                 
450         traverse(traverse, root)
451         
452         def printtree(printtree, node, indent=''):
453             print indent, '-', socket.inet_ntoa(struct.pack('!L',node.vif_addr)), '\t', node.country, node.city, node.site, '\t', node.hostname
454             for child in node.children:
455                 childips = map(ipaddr.IPAddress, child.childips)
456                 childnets = ipaddr.collapse_address_list(childips)
457                 cip = ipaddr.IPAddress(child.vif_addr)
458                 for cnet in childnets:
459                     print indent, '|- R', cnet, '->', cip
460                 printtree(printtree, child, indent+' | ')
461         printtree(printtree, root)
462
463         inet = pl.create("Internet")
464
465         ns_chosen = []
466         leaves = []
467
468         def maketree(maketree, node, parent=None, parentIp=None):
469             routes = []
470             ctaps = []
471             for i,child in enumerate(node.children):
472                 childips = map(ipaddr.IPAddress, child.childips)
473                 childnets = ipaddr.collapse_address_list(childips)
474                 cip = ipaddr.IPAddress(child.vif_addr)
475                 pip = ipaddr.IPAddress(node.vif_addr+1+i)
476                 for cnet in childnets:
477                     routes.append((cnet.ip.exploded, cnet.prefixlen, cip.exploded))
478                 ctaps.append( maketree(maketree, child, node, pip) )
479             if parentIp:
480                 routes.append((self.vnet,24,parentIp))
481             
482             if not parent:
483                 label = "root"
484             else:
485                 label = None
486                 
487             # NS node, first leaf
488             if not ns_chosen and not node.children:
489                 ns_chosen.append(True)
490                 label = "ns_root"
491                 
492             ips = [ ipaddr.IPAddress(node.vif_addr+i) for i in xrange(1+len(node.children)) ]
493             node1, iface1, tap1, tap1ip, _ = self.make_pl_tapnode(pl, ips, inet, 
494                 hostname = node.hostname,
495                 routes = routes,
496                 mcastrouter = bool(node.children),
497                 mcast = True,
498                 label = label,
499                 types = ( [ "TapInterface" ] * len(ips) if parent else [ "TunInterface" ] + [ "TapInterface" ] * (len(ips)-1) ) 
500                 )
501             
502             for tap, ctap in zip(tap1[1:], ctaps):
503                 tap.connector("udp").connect(ctap.connector("udp"))
504             
505             # Store leaves
506             if not node.children:
507                 leaves.append((node, node1))
508             
509             self.add_net_monitor(pl, node1)
510             self.add_vlc_dumper(pl, node1)
511             self.add_vlc_restreamer(pl, node1)
512             #if not parent:
513             #    taplabels = [
514             #        t.get_attribute_value("label")
515             #        for t in tap1[1:]
516             #    ]
517             #    self.add_vlc_source(pl, node1, taplabels)
518             
519             return tap1[0]
520         roottap = maketree(maketree, root)
521
522         vnet_i = int(ipaddr.IPAddress(self.vnet))
523
524         ## NS3 ##
525         pl_ns_root = exp.get_element_by_label("ns_root")
526         pl_ns_root_iface = exp.get_element_by_label("ns_rootiface")
527         ns = self.make_ns_in_pl(pl, exp, pl_ns_root, pl_ns_root_iface, "ns3")
528         wifi_chan = self.add_ns_wifi_channel(ns)
529
530         # AP node
531         ap_node = self.add_ns_node(ns)
532         self.add_ns_constant_mobility(ns, ap_node, 0, 0, 0)
533         ap_wifi, ap_phy = self.add_ns_wifi_dev(ns, ap_node, access_point = True)
534         ap_phy.connector("chan").connect(wifi_chan.connector("phys"))
535
536         # Net range free for WiFi
537         wifi_net_prefix = 32-int(math.floor(math.log(256-nextip[0]&0xff) / math.log(2)))
538         wifi_net = vnet_i | (256 - (1<<(32-wifi_net_prefix)))
539
540         # connect AP to PL
541         pl_addr = str(ipaddr.IPAddress(wifi_net | 1))
542         ns_addr = str(ipaddr.IPAddress(wifi_net | 2))
543         self.add_pl_ns_connection(
544             pl, pl_ns_root, pl_addr, 
545             ns, ap_node, ns_addr, 
546             fd = True, ptp = True, prefix=30)
547
548         
549         # AP ip
550         ap_addr = str(ipaddr.IPAddress(vnet_i | 254))
551         ap_addr_prefix = 32-int(math.ceil(math.log(self.nsta+3) / math.log(2)))
552         self.add_ip_address(ap_wifi, ap_addr, ap_addr_prefix)
553         
554         # route for PL->wifi
555         self.add_route(pl_ns_root, 
556             str(ipaddr.IPAddress(wifi_net)), wifi_net_prefix,
557             ns_addr)
558         
559         print "NS-3 AP\t%s/%s <--> PL AP %s" % (ns_addr, 30, pl_addr)
560         print " | (|) %s/%s" % (ap_addr, ap_addr_prefix)
561         print " |"
562         print " |                  R %s/%d --> %s" % (str(ipaddr.IPAddress(wifi_net)), wifi_net_prefix, ns_addr)
563        
564         nextpip = (vnet_i | 255) >> (32-ap_addr_prefix) << (32-ap_addr_prefix)
565         nextdip = vnet_i | 253
566         ap_net = nextpip - (1<<(32-ap_addr_prefix))
567         r = 50
568         # STA nodes
569         for i in xrange(self.nsta):
570             stai = self.add_ns_node(ns)
571             angi = (360/self.nsta)*i
572             xi = r*math.cos(angi)
573             yi = r*math.sin(angi)
574             self.add_ns_constant_mobility(ns, stai, xi, yi, 0)
575             wifi, phy = self.add_ns_wifi_dev(ns, stai, access_point = False)
576             phy.connector("chan").connect(wifi_chan.connector("phys"))
577             
578             wifi_addr = str(ipaddr.IPAddress(vnet_i | nextdip))
579             nextdip -= 1
580
581             nextpip -= 4
582             while nextpip & 3:
583                 nextpip -= 1
584             plns_net_i = nextpip
585             plns_net = str(ipaddr.IPAddress(plns_net_i))
586             pl_addr2 = str(ipaddr.IPAddress(plns_net_i | 1))
587             ns_addr2 = str(ipaddr.IPAddress(plns_net_i | 2))
588
589             # route from AP (after others)
590             print " | R %s/%s -> %s" % ( plns_net,30,ns_addr2 )
591             self.add_route(ap_node, plns_net, 30, wifi_addr)
592
593             print " +---\t(|) %16s/%s" % (wifi_addr,ap_addr_prefix)
594             print " |         %16s (ns3) <---> (pl) %16s/30" % (ns_addr2, pl_addr2)
595             print " |\t       \t\t                 <--  R %s/24" % (self.vnet, )
596             print " |\t       \t R %s/30 -> %s" % (plns_net, pl_addr2)
597             print " |\t       \t R %s <-- %s/24" % (ap_addr, plns_net)
598
599             self.add_ip_address(wifi, wifi_addr, ap_addr_prefix)
600             self.add_route(stai, plns_net, 30, pl_addr2)
601             self.add_route(stai, self.vnet, 24, ap_addr)
602             
603             pl_nodei, _, pl_ifacei, _, _ = self.make_pl_tapnode(pl, [], inet, 
604                 routes = [(self.vnet, 24, ns_addr2)],
605                 mcast = False,
606                 label = "ns_plnode_%d" % (i+1,)
607                 )
608  
609             self.add_pl_ns_connection(
610                 pl, pl_nodei, pl_addr2,
611                 ns, stai, ns_addr2,
612                 prefix = 30)
613             
614             self.add_vlc_dumper(pl, pl_nodei,
615                 hostname = pl_addr2,
616                 labelprefix = "vlc_dumper_ns",
617                 precmd = "sleep 15 ; ")
618
619             # Validate (post-fact to let the user see the diagram above)
620             if nextpip < wifi_net:
621                 raise RuntimeError, "Not enough IPs for wifi section"
622         
623         # route back to PL (after others)
624         print " | R %s/%s -> %s" % ( self.vnet,24,pl_addr )
625         self.add_route(ap_node, self.vnet, 24, pl_addr)
626
627
628         ## NETNS ##
629         netns_addr = str(ipaddr.IPAddress(vnet_i | 1))
630
631         root1 = exp.get_element_by_label("root")
632         netns = self.make_netns_testbed(exp)
633         netns_node = self.add_netns_node(netns)
634         netns_term = self.add_netns_app(netns, "xterm", netns_node)
635         if self.movie_source:
636             cmd = (
637                 "vlc -I dummy "
638                 +os.path.abspath(self.movie_source)
639                 +" --sout '#std{access=udp{ttl=64,miface-addr="+netns_addr+"},dst=239.255.12.42,mux=ts}'"
640             )
641         else:
642             cmd = self.movie_command % {
643                 "dst" : "std{access=udp{ttl=64,miface-addr="+netns_addr+"},dst=239.255.12.42,mux=ts}"
644             }
645         netns_vlc  = self.add_netns_app(netns, cmd, netns_node)
646         
647         # connection PL1/NETNS
648         self.add_pl_netns_connection(
649             roottap,
650             netns, netns_node, netns_addr,
651             24,
652             taplabel="netns_source")
653         self.add_route(netns_node, 
654             "0.0.0.0", 0, 
655             str(ipaddr.IPAddress(root.vif_addr)) )
656         
657         # pick random hostname to stream from
658         interactive_source_host = random.sample(leaves,1)[0][0].hostname
659
660         xml = exp.to_xml()
661         test_dir = "./results"
662         #sys.exit(1)
663
664         try:
665             controller = ExperimentController(xml, self.root_dir)
666             controller.start()
667             
668             # launch vlc client to monitor activity
669             time.sleep(5)
670             proc = subprocess.Popen([
671                 "vlc", "-I", "dummy", "http://%s:8080" % (interactive_source_host,)])
672             
673             print >>sys.stderr, "Close xterm to shut down or Ctrl+C"
674             try:
675                 while not controller.is_finished(netns_term.guid):
676                     time.sleep(5)
677             except KeyboardInterrupt:
678                 # ping netns
679                 try:
680                     controller.traces_info()
681                 except:
682                     pass
683                 try:
684                     controller.traces_info()
685                 except:
686                     pass
687             
688             # kill streamer
689             os.kill(proc.pid, signal.SIGTERM)
690             
691             # download results
692             traces_info = controller.traces_info()
693             for progress, (testbed_guid, guids) in enumerate(traces_info.iteritems()):
694                 for subprogress, (guid, traces) in enumerate(guids.iteritems()):
695                     for name, data in traces.iteritems():
696                         path = data["filepath"]
697                         elem = exp.get_element(guid)
698                         if elem is not None:
699                             label = elem.get_attribute_value("label")
700                             if label is not None:
701                                 path = "%s-%s" % (label,path)
702                         
703                         if not path:
704                             continue
705                         
706                         print >>sys.stderr, ("%.2f%% Downloading trace" % (progress + (subprogress * 1.0 / len(guids)) * 100.0 / len(traces_info))), path
707                         
708                         filepath = os.path.join(test_dir, path)
709                         
710                         try:
711                             trace = controller.trace(guid, name)
712                         except:
713                             traceback.print_exc(file=sys.stderr)
714                             continue
715                         try:
716                             if not os.path.exists(os.path.dirname(filepath)):
717                                 os.makedirs(os.path.dirname(filepath))
718                         except:
719                             traceback.print_exc(file=sys.stderr)
720                         
721                         try:
722                             if len(trace) >= 2**20:
723                                 # Bigger than 1M, compress
724                                 tracefile = gzip.GzipFile(filepath+".gz", "wb")
725                             else:
726                                 tracefile = open(filepath,"wb")
727                             try:
728                                 tracefile.write(trace)
729                             finally:
730                                 tracefile.close()
731                         except:
732                             traceback.print_exc(file=sys.stderr)
733         finally:
734             try:
735                 controller.stop()
736             except:
737                 traceback.print_exc()
738             try:
739                 controller.shutdown()
740             except:
741                 traceback.print_exc()
742
743
744 if __name__ == '__main__':
745     usage = "usage: %prog -m movie -u user"
746     parser = OptionParser(usage=usage)
747     parser.add_option("-u", "--user", dest="user", help="Valid linux system user (not root).", type="str")
748     parser.add_option("-U", "--pluser", dest="pluser", help="PlanetLab PLC username", type="str")
749     parser.add_option("-m", "--movie", dest="movie", help="Path to movie file to play", type="str")
750     parser.add_option("-n", "--nsta", dest="nsta", default=3, help="Number of wifi stations attached to the overlay", type="int")
751     parser.add_option("-N", "--nodes", dest="nodes", default=5, help="Number of overlay nodes", type="int")
752     parser.add_option("-s", "--slicename", dest="slicename", help="PlanetLab slice", type="str")
753     parser.add_option("-H", "--plchost", dest="plchost", help="PlanetLab's PLC hostname", type="str")
754     parser.add_option("-k", "--plkey", dest="plkey", help="Slice SSH key", type="str")
755     parser.add_option("-P", "--no-p2p", dest="nop2p", help="Disable peer-to-peer deployment. Not recommended for first deployment.", 
756         action="store_true", default=False)
757     (options, args) = parser.parse_args()
758     if options.user == 'root':
759         parser.error("Missing or invalid 'user' option.")
760
761     exp = PlanetLabMulticastOverlay()
762     if not options.movie or options.movie.startswith("/dev/"):
763         # use camera
764         if not options.movie:
765             options.movie = "/dev/video0"
766         exp.movie_source = None
767         exp.movie_command = (
768             "vlc -I dummy -vvv --color "
769             "v4l:///dev/video0:size=320x240:channel=0:adev=/dev/dsp:audio=0 "
770             "--sout '#transcode{vcodec=mpeg4,acodec=aac,vb=100,ab=16,venc=ffmpeg{keyint=80,hq=rd},deinterlace}:"
771             "%(dst)s'"
772         )
773     else:
774         exp.movie_source = options.movie
775     exp.no_p2p_deploy = options.nop2p
776     exp.nsta = options.nsta
777     if options.user:
778         exp.user = options.user
779     if options.plchost:
780         exp.plchost = options.plchost
781     if options.slicename:
782         exp.slicename = options.slicename
783     if options.plkey:
784         exp.plkey = options.plkey
785     if options.pluser:
786         exp.pluser = options.pluser
787     if not exp.plpass:
788         exp.plpass = getpass.getpass("Password for %s: " % (exp.pluser,))
789     
790     # Fix some distro's environment to work well with netns
791     if re.match(r"[^:]*:\d+$", os.environ['DISPLAY']):
792         os.environ['DISPLAY'] += '.0'
793     if not os.environ.get('XAUTHORITY'):
794         os.environ['XAUTHORITY'] = os.path.join(os.environ['HOME'], '.Xauthority')
795     
796     try:
797         exp.setUp()
798         exp.make_pl_overlay(options.nodes)
799     finally:
800         exp.tearDown()
801