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