1import os 2import subprocess 3import pyroute2 4from pyroute2 import IPRoute, NetNS, IPDB, NSPopen 5 6class 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