Commit 085379b7 authored by Brenden Blanco's avatar Brenden Blanco

Move shared example code into simulation.py

* Create a class for the examples to share, helps with IPDB cleanup and
  namespace creation.
Signed-off-by: default avatarBrenden Blanco <bblanco@plumgrid.com>
parent b00fd1b8
import atexit
from pyroute2 import IPRoute, NetNS, IPDB, NSPopen
class Simulation(object):
"""
Helper class for controlling multiple namespaces. Inherit from
this class and setup your namespaces.
"""
def __init__(self, ipdb):
self.ipdb = ipdb
self.ipdbs = {}
self.namespaces = []
self.processes = []
self.released = False
# helper function to create a namespace and a veth connecting it
def _create_ns(self, name, in_ifc=None, out_ifc=None, ipaddr=None,
macaddr=None, fn=None, cmd=None, action="ok"):
ns_ipdb = IPDB(nl=NetNS(name))
if in_ifc:
in_ifname = in_ifc.ifname
else:
out_ifc = self.ipdb.create(ifname="%sa" % name, kind="veth",
peer="%sb" % name).commit()
in_ifc = self.ipdb.interfaces[out_ifc.peer]
in_ifname = in_ifc.ifname
with in_ifc as v:
# move half of veth into namespace
v.net_ns_fd = ns_ipdb.nl.netns
in_ifc = ns_ipdb.interfaces[in_ifname]
if out_ifc: out_ifc.up().commit()
with in_ifc as v:
v.ifname = "eth0"
if ipaddr: v.add_ip("%s" % ipaddr)
if macaddr: v.address = macaddr
v.up()
if fn and out_ifc:
self.ipdb.nl.tc("add", "ingress", out_ifc["index"], "ffff:")
self.ipdb.nl.tc("add-filter", "bpf", out_ifc["index"], ":1",
fd=fn.fd, name=fn.name, parent="ffff:",
action=action, classid=1)
self.ipdbs[ns_ipdb.nl.netns] = ns_ipdb
self.namespaces.append(ns_ipdb.nl)
if cmd:
self.processes.append(NSPopen(ns_ipdb.nl.netns, cmd))
return (ns_ipdb, out_ifc, in_ifc)
def release(self):
if self.released: return
self.released = True
for p in self.processes:
if p.released: continue
p.kill(); p.wait(); p.release()
for name, db in self.ipdbs.items(): db.release()
for ns in self.namespaces: ns.remove()
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
from bpf import BPF from bpf import BPF
from pyroute2 import IPRoute, NetNS, IPDB, NSPopen from pyroute2 import IPRoute, NetNS, IPDB, NSPopen
from simulation import Simulation
import sys import sys
from time import sleep from time import sleep
...@@ -42,16 +43,14 @@ num_neighbors = 3 ...@@ -42,16 +43,14 @@ num_neighbors = 3
num_locals = 2 num_locals = 2
# class to build the simulation network # class to build the simulation network
class SharedNetSimulation(object): class SharedNetSimulation(Simulation):
def __init__(self): def __init__(self, ipdb):
self.ipdbs = [] super(SharedNetSimulation, self).__init__(ipdb)
self.namespaces = []
self.processes = []
# Create the wan namespace, and attach an ingress filter for throttling # Create the wan namespace, and attach an ingress filter for throttling
# inbound (download) traffic # inbound (download) traffic
(self.wan, wan_if) = self._create_ns("wan0", "172.16.1.5/24", None) wan_if = self._create_ns("wan0", ipaddr="172.16.1.5/24")[1]
ipr.tc("add", "ingress", wan_if["index"], "ffff:") ipr.tc("add", "ingress", wan_if["index"], "ffff:")
ipr.tc("add-filter", "bpf", wan_if["index"], ":1", fd=wan_fn.fd, ipr.tc("add-filter", "bpf", wan_if["index"], ":1", fd=wan_fn.fd,
prio=1, name=wan_fn.name, parent="ffff:", action="drop", prio=1, name=wan_fn.name, parent="ffff:", action="drop",
...@@ -61,39 +60,22 @@ class SharedNetSimulation(object): ...@@ -61,39 +60,22 @@ class SharedNetSimulation(object):
classid=2, rate="1024kbit", burst=1024 * 32, mtu=16 * 1024) classid=2, rate="1024kbit", burst=1024 * 32, mtu=16 * 1024)
self.wan_if = wan_if self.wan_if = wan_if
# helper function to create a namespace and a veth connecting it # start the namespaces that compose the network, interconnect them with the
def _create_ns(self, name, ipaddr, fn): # bridge, and attach the tc filters
ns_ipdb = IPDB(nl=NetNS(name))
ipdb.create(ifname="%sa" % name, kind="veth", peer="%sb" % name).commit()
with ipdb.interfaces["%sb" % name] as v:
# move half of veth into namespace
v.net_ns_fd = ns_ipdb.nl.netns
with ipdb.interfaces["%sa" % name] as v:
v.up()
with ns_ipdb.interfaces["%sb" % name] as v:
v.ifname = "eth0"
v.add_ip("%s" % ipaddr)
v.up()
ifc = ipdb.interfaces["%sa" % name]
if fn:
ipr.tc("add", "ingress", ifc["index"], "ffff:")
ipr.tc("add-filter", "bpf", ifc["index"], ":1", fd=fn.fd, name=fn.name,
parent="ffff:", action="ok", classid=1)
self.ipdbs.append(ns_ipdb)
self.namespaces.append(ns_ipdb.nl)
cmd = ["netserver", "-D"]
self.processes.append(NSPopen(ns_ipdb.nl.netns, cmd))
return (ns_ipdb, ifc)
# start the namespaces that compose the network, interconnect them with the bridge,
# and attach the tc filters
def start(self): def start(self):
neighbor_list = [] neighbor_list = []
local_list = [] local_list = []
cmd = ["netserver", "-D"]
for i in range(0, num_neighbors): for i in range(0, num_neighbors):
neighbor_list.append(self._create_ns("neighbor%d" % i, "172.16.1.%d/24" % (i + 100), neighbor_fn)) ipaddr = "172.16.1.%d/24" % (i + 100)
ret = self._create_ns("neighbor%d" % i, ipaddr=ipaddr,
fn=neighbor_fn, cmd=cmd)
neighbor_list.append(ret)
for i in range(0, num_locals): for i in range(0, num_locals):
local_list.append(self._create_ns("local%d" % i, "172.16.1.%d/24" % (i + 150), pass_fn)) ipaddr = "172.16.1.%d/24" % (i + 150)
ret = self._create_ns("local%d" % i, ipaddr=ipaddr,
fn=pass_fn, cmd=cmd)
local_list.append(ret)
with ipdb.create(ifname="br100", kind="bridge") as br100: with ipdb.create(ifname="br100", kind="bridge") as br100:
for x in neighbor_list: for x in neighbor_list:
...@@ -103,13 +85,8 @@ class SharedNetSimulation(object): ...@@ -103,13 +85,8 @@ class SharedNetSimulation(object):
br100.add_port(self.wan_if) br100.add_port(self.wan_if)
br100.up() br100.up()
def release(self):
for p in self.processes: p.kill(); p.release()
for db in self.ipdbs: db.release()
for ns in self.namespaces: ns.remove()
try: try:
sim = SharedNetSimulation() sim = SharedNetSimulation(ipdb)
sim.start() sim.start()
print("Network ready. Create a shell in the wan0 namespace and test with netperf") print("Network ready. Create a shell in the wan0 namespace and test with netperf")
print(" (Neighbors are 172.16.1.100-%d, and LAN clients are 172.16.1.150-%d)" print(" (Neighbors are 172.16.1.100-%d, and LAN clients are 172.16.1.150-%d)"
...@@ -119,7 +96,6 @@ try: ...@@ -119,7 +96,6 @@ try:
finally: finally:
if "sim" in locals(): sim.release() if "sim" in locals(): sim.release()
if "br100" in ipdb.interfaces: ipdb.interfaces.br100.remove().commit() if "br100" in ipdb.interfaces: ipdb.interfaces.br100.remove().commit()
sleep(2)
ipdb.release() ipdb.release()
...@@ -28,10 +28,10 @@ from ctypes import c_uint, c_int, c_ulonglong, Structure ...@@ -28,10 +28,10 @@ from ctypes import c_uint, c_int, c_ulonglong, Structure
from pyroute2 import IPRoute, NetNS, IPDB, NSPopen from pyroute2 import IPRoute, NetNS, IPDB, NSPopen
from random import shuffle from random import shuffle
from time import sleep from time import sleep
from simulation import Simulation
import sys import sys
ipr = IPRoute() ipr = IPRoute()
ipr.bind(async=True)
ipdb = IPDB(nl=ipr) ipdb = IPDB(nl=ipr)
num_workers = 3 num_workers = 3
...@@ -39,128 +39,96 @@ num_clients = 9 ...@@ -39,128 +39,96 @@ num_clients = 9
num_vlans = 16 num_vlans = 16
# load the bpf program # load the bpf program
b = BPF(src_file="examples/vlan_learning.c", debug=0) b = BPF(src_file="vlan_learning.c", debug=0)
phys_fn = b.load_func("handle_phys2virt", BPF.SCHED_CLS) phys_fn = b.load_func("handle_phys2virt", BPF.SCHED_CLS)
virt_fn = b.load_func("handle_virt2phys", BPF.SCHED_CLS) virt_fn = b.load_func("handle_virt2phys", BPF.SCHED_CLS)
ingress = b.get_table("ingress") ingress = b.get_table("ingress")
egress = b.get_table("egress") egress = b.get_table("egress")
ipdb_workers = [] class VlanSimulation(Simulation):
ipdb_clients = [] def __init__(self, ipdb):
ns_workers = [] super(VlanSimulation, self).__init__(ipdb)
ns_clients = []
worker_processes = [] def start(self):
client_processes = [] # start identical workers each in a namespace
for i in range(0, num_workers):
# start the worker namespaces: 1 veth pair, 1 http daemon httpmod = ("SimpleHTTPServer" if sys.version_info[0] < 3
for i in range(0, num_workers): else "http.server")
worker = IPDB(nl=NetNS("worker%d" % i)) cmd = ["python", "-m", httpmod, "80"]
ipdb.create(ifname="wrk%dp0" % i, kind="veth", peer="wrk%dp1" % i).commit() self._create_ns("worker%d" % i, cmd=cmd, fn=virt_fn, action="drop",
with ipdb.interfaces["wrk%dp0" % i] as v: ipaddr="172.16.1.5/24")
v.net_ns_fd = worker.nl.netns
with ipdb.interfaces["wrk%dp1" % i] as v: # simulate a physical eth vlan trunk
ipr.tc("add", "ingress", v["index"], "ffff:") with self.ipdb.create(ifname="eth0a", kind="veth", peer="eth0b") as v:
ipr.tc("add-filter", "bpf", v["index"], ":1", fd=virt_fn.fd, v.up()
name=virt_fn.name, parent="ffff:", action="drop", classid=1) self.ipdb.interfaces.eth0b.up().commit()
v.up()
# use the same ip address in each namespace, clients only need to know # connect the trunk to the bridge
# one destination IP! with self.ipdb.create(ifname="br100", kind="bridge") as br100:
with worker.interfaces["wrk%dp0" % i] as v: br100.add_port(self.ipdb.interfaces.eth0b)
v.ifname = "eth0" br100.up()
v.add_ip("172.16.1.5/24")
v.up() # for each vlan, create a subinterface on the eth...most of these will be
httpmod = "SimpleHTTPServer" if sys.version_info[0] < 3 else "http.server" # unused, but still listening and waiting for a client to send traffic on
worker_processes.append(NSPopen(worker.nl.netns, ["python", "-m", httpmod, "80"])) for i in range(2, 2 + num_vlans):
ipdb_workers.append(worker) with self.ipdb.create(ifname="eth0a.%d" % i, kind="vlan",
ns_workers.append(worker.nl) link=ipdb.interfaces.eth0a, vlan_id=i) as v:
v.up()
# simulate a physical eth vlan trunk v = self.ipdb.interfaces["eth0a.%d" % i]
with ipdb.create(ifname="eth0a", kind="veth", peer="eth0b") as v: # add the bpf program for demuxing phys2virt packets
v.up() ipr.tc("add", "ingress", v["index"], "ffff:")
ipdb.interfaces.eth0b.up().commit() ipr.tc("add-filter", "bpf", v["index"], ":1", fd=phys_fn.fd,
# connect the veth to the bridge name=phys_fn.name, parent="ffff:", action="drop", classid=1)
with ipdb.create(ifname="br100", kind="bridge") as br100:
br100.add_port(ipdb.interfaces.eth0b) # allocate vlans randomly
br100.up() available_vlans = [i for i in range(2, 2 + num_vlans)]
shuffle(available_vlans)
# for each vlan, create a subinterface on the eth...most of these will be available_ips = [[i for i in range(100, 105)] for i in range(0, num_workers)]
# unused, but still listening and waiting for a client to send traffic on
for i in range(2, 2 + num_vlans): # these are simulations of physical clients
with ipdb.create(ifname="eth0a.%d" % i, kind="vlan", for i in range(0, num_clients):
link=ipdb.interfaces.eth0a, vlan_id=i) as v: worker_i = i % num_workers
v.up() ipaddr = "172.16.1.%d" % available_ips[worker_i].pop(0)
v = ipdb.interfaces["eth0a.%d" % i] macaddr = ("02:00:00:%.2x:%.2x:%.2x" %
# add the bpf program for demuxing phys2virt packets ((i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff))
ipr.tc("add", "ingress", v["index"], "ffff:")
ipr.tc("add-filter", "bpf", v["index"], ":1", fd=phys_fn.fd, # program arp manually
name=phys_fn.name, parent="ffff:", action="drop", classid=1) p = NSPopen("worker%d" % worker_i, ["arp", "-s", ipaddr, macaddr])
p.communicate(); p.wait(); p.release()
# allocate vlans randomly
available_vlans = [i for i in range(2, 2 + num_vlans)] # assign this client to the given worker
shuffle(available_vlans) idx = self.ipdb.interfaces["worker%da" % worker_i]["index"]
available_ips = [[i for i in range(100, 105)] for i in range(0, num_workers)] mac = int(macaddr.replace(":", ""), 16)
ingress[ingress.Key(mac)] = ingress.Leaf(idx, 0, 0)
# these are simulations of physical clients
for i in range(0, num_clients): # test traffic with curl loop
worker_choice = i % num_workers cmd = ["bash", "-c",
client = IPDB(nl=NetNS("client%d" % i)) "for i in {1..8}; do curl 172.16.1.5 -o /dev/null; sleep 1; done"]
with ipdb.create(ifname="br100.%d" % i, kind="vlan", br_ifc = self.ipdb.create(ifname="br100.%d" % i, kind="vlan", link=br100,
link=br100, vlan_id=available_vlans.pop(0)) as v: vlan_id=available_vlans.pop(0)).commit()
v.net_ns_fd = client.nl.netns (out_ifc, in_ifc) = self._create_ns("client%d" % i, in_ifc=br_ifc,
ipaddr = "172.16.1.%d" % available_ips[worker_choice].pop(0) ipaddr=ipaddr + "/24", macaddr=macaddr,
with client.interfaces["br100.%d" % i] as v: cmd=cmd)[1:3]
v.add_ip("%s/24" % ipaddr)
v.ifname = "eth0" try:
v.address = "02:00:00:%.2x:%.2x:%.2x" % ((i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff) sim = VlanSimulation(ipdb)
v.up() sim.start()
macaddr = client.interfaces.eth0.address sleep(10)
# program arp manually input("Press enter to exit: ")
p = NSPopen(ipdb_workers[worker_choice].nl.netns, ["arp", "-s", ipaddr, macaddr])
p.communicate() stats_collect = {}
for key, leaf in ingress.items():
# assign this client to the given worker stats_collect[key.value] = [leaf.tx_pkts, leaf.tx_bytes, 0, 0]
idx = ipdb.interfaces["wrk%dp1" % worker_choice]["index"] for key, leaf in egress.items():
mac = int(macaddr.replace(":", ""), 16) x = stats_collect.get(key.value, [0, 0, 0, 0])
ingress[ingress.Key(mac)] = ingress.Leaf(idx, 0, 0) x[2] = leaf.tx_pkts
x[3] = leaf.tx_bytes
cmd = ["bash", "-c", "for i in {1..8}; do curl 172.16.1.5 -o /dev/null; sleep 1; done"] for k, v in stats_collect.items():
client_processes.append(NSPopen(client.nl.netns, cmd)) print("mac %.12x rx pkts = %u, rx bytes = %u" % (k, v[0], v[1]))
print(" tx pkts = %u, tx bytes = %u" % (v[2], v[3]))
ipdb_clients.append(client) finally:
ns_clients.append(client.nl) if "eth0a" in ipdb.interfaces: ipdb.interfaces.eth0a.remove().commit()
if "br100" in ipdb.interfaces: ipdb.interfaces.br100.remove().commit()
# IPDBs are no longer needed if "sim" in locals(): sim.release()
for db in ipdb_workers: db.release() ipdb.release()
for db in ipdb_clients: db.release()
sleep(10)
input("Press enter to exit: ")
stats_collect = {}
for key, leaf in ingress.items():
stats_collect[key.value] = [leaf.tx_pkts, leaf.tx_bytes, 0, 0]
for key, leaf in egress.items():
x = stats_collect.get(key.value, [0, 0, 0, 0])
x[2] = leaf.tx_pkts
x[3] = leaf.tx_bytes
for k, v in stats_collect.items():
print("mac %.12x rx pkts = %u, rx bytes = %u" % (k, v[0], v[1]))
print(" tx pkts = %u, tx bytes = %u" % (v[2], v[3]))
print("Killing worker processes")
for w in worker_processes:
w.kill()
w.release()
for c in client_processes:
c.kill()
c.release()
print("Removing namespaces and simulation interfaces")
for ns in ns_workers: ns.remove()
for ns in ns_clients: ns.remove()
ipdb.interfaces.br100.remove().commit()
ipdb.interfaces.eth0a.remove().commit()
sleep(2)
ipdb.release()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment