1 import os
2 import subprocess
3 import pyroute2
4 from pyroute2 import IPRoute, NetNS, IPDB, NSPopen
5 
6 class Simulation(object):
7     """
8     Helper class for controlling multiple namespaces. Inherit from
9     this class and setup your namespaces.
10     """
11 
12     def __init__(self, ipdb):
13         self.ipdb = ipdb
14         self.ipdbs = {}
15         self.namespaces = []
16         self.processes = []
17         self.released = False
18 
19     # helper function to add additional ifc to namespace
20     # if called directly outside Simulation class, "ifc_base_name" should be
21     # different from "name", the "ifc_base_name" and "name" are the same for
22     # the first ifc created by namespace
23     def _ns_add_ifc(self, name, ns_ifc, ifc_base_name=None, in_ifc=None,
24                     out_ifc=None, ipaddr=None, macaddr=None, fn=None, cmd=None,
25                     action="ok", disable_ipv6=False):
26         if name in self.ipdbs:
27             ns_ipdb = self.ipdbs[name]
28         else:
29             try:
30                 nl=NetNS(name)
31                 self.namespaces.append(nl)
32             except KeyboardInterrupt:
33                 # remove the namespace if it has been created
34                 pyroute2.netns.remove(name)
35                 raise
36             ns_ipdb = IPDB(nl)
37             self.ipdbs[nl.netns] = ns_ipdb
38             if disable_ipv6:
39                 cmd1 = ["sysctl", "-q", "-w",
40                        "net.ipv6.conf.default.disable_ipv6=1"]
41                 nsp = NSPopen(ns_ipdb.nl.netns, cmd1)
42                 nsp.wait(); nsp.release()
43             try:
44                 ns_ipdb.interfaces.lo.up().commit()
45             except pyroute2.ipdb.exceptions.CommitException:
46                 print("Warning, commit for lo failed, operstate may be unknown")
47         if in_ifc:
48             in_ifname = in_ifc.ifname
49             with in_ifc as v:
50                 # move half of veth into namespace
51                 v.net_ns_fd = ns_ipdb.nl.netns
52         else:
53             # delete the potentially leaf-over veth interfaces
54             ipr = IPRoute()
55             for i in ipr.link_lookup(ifname='%sa' % ifc_base_name): ipr.link("del", index=i)
56             ipr.close()
57             try:
58                 out_ifc = self.ipdb.create(ifname="%sa" % ifc_base_name, kind="veth",
59                                            peer="%sb" % ifc_base_name).commit()
60                 in_ifc = self.ipdb.interfaces[out_ifc.peer]
61                 in_ifname = in_ifc.ifname
62                 with in_ifc as v:
63                     v.net_ns_fd = ns_ipdb.nl.netns
64             except KeyboardInterrupt:
65                 # explicitly remove the interface
66                 out_ifname = "%sa" % ifc_base_name
67                 if out_ifname in self.ipdb.interfaces: self.ipdb.interfaces[out_ifname].remove().commit()
68                 raise
69 
70         if out_ifc: out_ifc.up().commit()
71         try:
72             # this is a workaround for fc31 and possible other disto's.
73             # when interface 'lo' is already up, do another 'up().commit()'
74             # has issues in fc31.
75             # the workaround may become permanent if we upgrade pyroute2
76             # in all machines.
77             if 'state' in ns_ipdb.interfaces.lo.keys():
78                 if ns_ipdb.interfaces.lo['state'] != 'up':
79                     ns_ipdb.interfaces.lo.up().commit()
80             else:
81                 ns_ipdb.interfaces.lo.up().commit()
82         except pyroute2.ipdb.exceptions.CommitException:
83             print("Warning, commit for lo failed, operstate may be unknown")
84         ns_ipdb.initdb()
85         in_ifc = ns_ipdb.interfaces[in_ifname]
86         with in_ifc as v:
87             v.ifname = ns_ifc
88             if ipaddr: v.add_ip("%s" % ipaddr)
89             if macaddr: v.address = macaddr
90             v.up()
91         if disable_ipv6:
92             cmd1 = ["sysctl", "-q", "-w",
93                    "net.ipv6.conf.%s.disable_ipv6=1" % out_ifc.ifname]
94             subprocess.call(cmd1)
95         if fn and out_ifc:
96             self.ipdb.nl.tc("add", "ingress", out_ifc["index"], "ffff:")
97             self.ipdb.nl.tc("add-filter", "bpf", out_ifc["index"], ":1",
98                             fd=fn.fd, name=fn.name, parent="ffff:",
99                             action=action, classid=1)
100         if cmd:
101             self.processes.append(NSPopen(ns_ipdb.nl.netns, cmd))
102         return (ns_ipdb, out_ifc, in_ifc)
103 
104     # helper function to create a namespace and a veth connecting it
105     def _create_ns(self, name, in_ifc=None, out_ifc=None, ipaddr=None,
106                    macaddr=None, fn=None, cmd=None, action="ok", disable_ipv6=False):
107         (ns_ipdb, out_ifc, in_ifc) = self._ns_add_ifc(name, "eth0", name, in_ifc, out_ifc,
108                                                       ipaddr, macaddr, fn, cmd, action,
109                                                       disable_ipv6)
110         return (ns_ipdb, out_ifc, in_ifc)
111 
112     def release(self):
113         if self.released: return
114         self.released = True
115         for p in self.processes:
116             if p.released: continue
117             try:
118                 p.kill()
119                 p.wait()
120             except:
121                 pass
122             finally:
123                 p.release()
124         for name, db in self.ipdbs.items(): db.release()
125         for ns in self.namespaces: ns.remove()
126 
127