├── .gitignore ├── AUTHORS ├── LICENSE ├── README.org ├── bess ├── bess-runner.py ├── bng.bess ├── fw.bess ├── l2fwd.bess ├── l3fwd.bess ├── mgw.bess └── portfwd.bess ├── doc ├── README.L2fwd.org ├── README.L3fwd.org ├── README.bng.org ├── README.config.org ├── README.fw.org ├── README.mgw.org ├── README.portfwd.org ├── README.workflow.org ├── benchmarks.odp ├── benchmarks.pdf ├── examples │ ├── dynamic-vs-static.json │ ├── l3fwd.json │ ├── multicore-joint.json │ ├── multicore.json │ ├── simple-visualize-2.json │ └── simple-visualize.json ├── fig │ ├── bng.pdf │ ├── bng.png │ ├── bng.xml │ ├── bng_pipeline.png │ ├── l3fwd_pipeline.png │ ├── mgw.pdf │ ├── mgw.png │ ├── mgw.svg │ ├── mgw.xml │ └── mgw_pipeline.png ├── workflow.graph-easy.png └── workflow.graph-easy.txt ├── lib ├── __init__.py ├── args_from_schema.py ├── find_mod.py ├── gen_conf.py ├── gen_conf_base.py ├── gen_pcap.py ├── gen_pcap_base.py ├── main-makefile.in ├── object_with_config.py ├── per-dir-makefile.in ├── pipeline.json.in ├── plot-makefile.in ├── plot.json.in ├── plot.py ├── plot_base.py ├── run_measurement.py ├── tester_base.py ├── validate.py └── wait_for_callback.py ├── module ├── erfs │ ├── RyuApp_erfs.py │ ├── SUT_erfs.py │ ├── SUT_erfs_bng.py │ ├── SUT_erfs_mgw.py │ ├── exp_ericsson.py │ ├── sut-erfs.json │ └── sw_conf_erfs.py ├── gwlb │ ├── GenConf_gwlb.py │ ├── GenPkt_gwlb.py │ ├── SUT_openflow_gwlb.py │ ├── bess-updater-gwlb.py │ ├── example-main.json │ ├── gwlb.bess │ └── pipeline-gwlb.json ├── lagopus │ ├── RyuApp_lagopus.py │ ├── SUT_lagopus.py │ ├── SUT_lagopus_bng.py │ ├── SUT_lagopus_mgw.py │ ├── lagopus.dsl │ ├── sut-install-lagopus.sh │ └── sut-lagopus.json ├── moongen │ ├── Tester_moongen.py │ ├── Tester_moongen_combined.py │ ├── Tester_moongen_flood.py │ ├── Tester_moongen_rfc2544.py │ ├── mg-flood.lua │ ├── mg-pcap.lua │ ├── mg-rfc2544.lua │ ├── mg-setRate.lua │ ├── mg-test-setRate.lua │ └── tester-moongen.json ├── nat │ ├── GenConf_nat.py │ ├── GenPkt_nat.py │ ├── SUT_bess_nat.py │ ├── SUT_ovs_nat.py │ ├── pipeline-nat.json │ └── test-nat.json ├── openflow │ ├── RyuApp_openflow.py │ ├── SUT_openflow.py │ ├── SUT_openflow_base.py │ ├── SUT_openflow_fw.py │ ├── SUT_openflow_l2fwd.py │ ├── SUT_openflow_l3fwd.py │ ├── color_log.py │ ├── log.cfg │ ├── ryu.conf │ ├── setup │ ├── start-ryu │ ├── sut-openflow.json │ ├── teardown │ ├── test-fw.json │ ├── test-l2fwd.json │ ├── test-l3fwd.json │ └── tipsy.py ├── openvswitch │ ├── RyuApp_ovs.py │ ├── SUT_ovs.py │ ├── SUT_ovs_bng.py │ ├── SUT_ovs_mgw.py │ ├── ip.py │ ├── sut-ovs.json │ ├── sw_conf_vsctl.py │ ├── test-bng.json │ └── test-mgw.json ├── plot │ ├── Plot_USL.py │ ├── Plot_contour.py │ ├── Plot_simple.py │ └── Plot_table.py ├── portfwd │ ├── GenConf_portfwd.py │ ├── GenPkt_portfwd.py │ ├── SUT_openflow_portfwd.py │ ├── pipeline-portfwd.json │ ├── test-openflow-portfwd.json │ └── test-ovs-portfwd.json ├── sut │ ├── SUT_base.py │ ├── SUT_bess.py │ ├── SUT_ofdpa.py │ ├── SUT_t4p4s.py │ ├── SUT_vpp.py │ └── sut-base.json └── trex │ ├── Tester_trex.py │ └── tester-trex.json ├── ofdpa └── tipsy.py ├── schema ├── .dir-locals.el ├── benchmark.json ├── definitions.json ├── main.json ├── pipeline-bng.json ├── pipeline-fw.json ├── pipeline-l2fwd.json ├── pipeline-l3fwd.json ├── pipeline-mgw.json ├── pipeline-vmgw.json └── traffic.json ├── t4p4s └── tipsy.py ├── tipsy ├── utils ├── extract ├── show-tables ├── sut-setup-script-example └── test-pcap │ ├── .gitignore │ └── test.sh ├── vagrant ├── .gitignore ├── Vagrantfile ├── run ├── sut-install.sh ├── sut-start-ovs.sh ├── tester-dpdk-init.sh ├── tester-install.sh ├── tester-run └── tests │ └── 0_default.json └── vpp └── vpp-runner.py /.gitignore: -------------------------------------------------------------------------------- 1 | schema/pipeline.json 2 | schema/plot.json 3 | schema/sut.json 4 | schema/tester.json 5 | *.pyc 6 | *~ 7 | \#*\# 8 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Tipsy is copyrighted by the following authors. 2 | (See LICENSE for licensing conditions.) 3 | 4 | Gábor Rétvári 5 | Tamás Lévai 6 | Felicián Németh 7 | Megyesi Péter 8 | Sándor Laki 9 | Péter Vörös 10 | Orsolya Kiss 11 | -------------------------------------------------------------------------------- /bess/fw.bess: -------------------------------------------------------------------------------- 1 | # -*- mode: python; -*- 2 | 3 | # TIPSY: Telco pIPeline benchmarking SYstem 4 | # 5 | # Copyright (C) 2018 by its authors (See AUTHORS) 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but 13 | # WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | # General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import json 21 | import binascii 22 | import sys 23 | 24 | def mac_from_str(s): 25 | return binascii.unhexlify(s.replace(':', '')) 26 | 27 | def coremask_to_corelist(coremask): 28 | cpum = int(coremask, 16) 29 | return [i for i in range(32) if (cpum >> i) & 1 == 1] 30 | 31 | class ObjectView(dict): 32 | def __init__(self, *args, **kwargs): 33 | tmp = {k.replace('-', '_'): v for k, v in kwargs.items()} 34 | self.update(*args, **tmp) 35 | 36 | def __getattr__(self, name): 37 | return self[name] 38 | 39 | def __setattr__(self, name, value): 40 | self[name.replace('-', '_')] = value 41 | 42 | 43 | def conv_fn(d): return ObjectView(**d) 44 | 45 | def remove_ipproto(rules): 46 | for r in rules: 47 | del r['ipproto'] 48 | 49 | pl_conf_file = $pl_config!'./fw.json' 50 | bm_conf_file = $bm_config!'./benchmark.json' 51 | with open(pl_conf_file, 'r') as f: 52 | conf = json.load(f, object_hook=conv_fn) 53 | with open(bm_conf_file, 'r') as f: 54 | bm_conf = json.load(f, object_hook=conv_fn) 55 | 56 | bess_dlport = int(bm_conf.sut.downlink_port) 57 | bess_ulport = int(bm_conf.sut.uplink_port) 58 | bess_workers = int(conf.core) 59 | corelist = coremask_to_corelist(bm_conf.sut.coremask) 60 | 61 | portDL = PMDPort(port_id=bess_dlport, 62 | num_inc_q=bess_workers, 63 | num_out_q=bess_workers) 64 | 65 | if bess_dlport == bess_ulport: 66 | portUL = portDL 67 | else: 68 | portUL = PMDPort(port_id=bess_ulport, 69 | num_inc_q=bess_workers, 70 | num_out_q=bess_workers) 71 | 72 | dl_rules = conf.dl_fw_rules 73 | ul_rules = conf.ul_fw_rules 74 | 75 | if bm_conf.pipeline.implementation_type == 'dpdk': 76 | module = DPDKACL 77 | else: 78 | module = ACL 79 | remove_ipproto(dl_rules) 80 | remove_ipproto(ul_rules) 81 | 82 | if bm_conf.pipeline.fakedrop: 83 | extra_rules = [{ 84 | "src_ip": '0.0.0.0/0', 85 | "dst_ip": '0.0.0.0/0', 86 | "drop": False 87 | }] 88 | else: 89 | extra_rules = [] 90 | 91 | in_u = QueueInc(name="ul_inport", port=portDL.name) 92 | out_u = QueueOut(name="ul_outport", port=portUL.name) 93 | in_u -> module(rules=ul_rules + extra_rules) -> out_u 94 | 95 | in_d = QueueInc(name="dl_inport", port=portUL.name) 96 | out_d = QueueOut(name="dl_outport", port=portDL.name) 97 | in_d -> module(rules=dl_rules + extra_rules) -> out_d 98 | 99 | 100 | try: 101 | bess.track_gate(False, '', '', bits=True) 102 | except AttributeError: 103 | bess.track_module('', False, bits=True) 104 | -------------------------------------------------------------------------------- /bess/l2fwd.bess: -------------------------------------------------------------------------------- 1 | # -*- mode: python; -*- 2 | 3 | # TIPSY: Telco pIPeline benchmarking SYstem 4 | # 5 | # Copyright (C) 2018 by its authors (See AUTHORS) 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but 13 | # WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | # General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import json 21 | import binascii 22 | import sys 23 | 24 | def mac_from_str(s): 25 | return binascii.unhexlify(s.replace(':', '')) 26 | 27 | def coremask_to_corelist(coremask): 28 | cpum = int(coremask, 16) 29 | return [i for i in range(32) if (cpum >> i) & 1 == 1] 30 | 31 | class ObjectView(object): 32 | def __init__(self, **kwargs): 33 | tmp = {k.replace('-', '_'): v for k, v in kwargs.items()} 34 | self.__dict__.update(**tmp) 35 | 36 | def __repr__(self): 37 | return self.__dict__.__repr__() 38 | 39 | def conv_fn(d): return ObjectView(**d) 40 | 41 | pl_conf_file = $pl_config!'./portfwd.json' 42 | bm_conf_file = $bm_config!'./benchmark.json' 43 | with open(pl_conf_file, 'r') as f: 44 | conf = json.load(f, object_hook=conv_fn) 45 | with open(bm_conf_file, 'r') as f: 46 | bm_conf = json.load(f, object_hook=conv_fn) 47 | 48 | bess_dlport = int(bm_conf.sut.downlink_port) 49 | bess_ulport = int(bm_conf.sut.uplink_port) 50 | bess_workers = int(conf.core) 51 | corelist = coremask_to_corelist(bm_conf.sut.coremask) 52 | 53 | portDL = PMDPort(port_id=bess_dlport, 54 | num_inc_q=bess_workers, 55 | num_out_q=bess_workers) 56 | 57 | if bess_dlport == bess_ulport: 58 | portUL = portDL 59 | else: 60 | portUL = PMDPort(port_id=bess_ulport, 61 | num_inc_q=bess_workers, 62 | num_out_q=bess_workers) 63 | 64 | for wid in range(bess_workers): 65 | wid2 = bess_workers + wid 66 | try: 67 | bess.add_worker(wid, corelist[wid]) 68 | bess.add_worker(wid2, corelist[wid2]) 69 | except IndexError: 70 | sys.exit('ERROR: Insufficient number of cores available.') 71 | 72 | # uplink 73 | in_u = QueueInc(name="ul_inport_%d" % wid, port=portUL.name, qid=wid) 74 | out_u = QueueOut(name="ul_outport_%d" % wid, port=portDL.name, qid=wid) 75 | buf_u = Buffer(name='out_buf_u_%d' % wid) 76 | if conf.fakedrop: 77 | drop = buf_u 78 | else: 79 | drop = Sink() 80 | mac_table_u = ExactMatch(name='mac_table_u_%d' % wid, 81 | fields=[{'offset': 0, 'num_bytes': 6}]) 82 | mac_table_u.set_default_gate(gate=0) 83 | for i, entry in enumerate(conf.upstream_table, start=1): 84 | gat = entry.out_port or i 85 | mac_table_u.add(fields=[{'value_bin': mac_from_str(entry.mac)}], 86 | gate=gat) 87 | mac_table_u:i -> buf_u 88 | 89 | in_u -> mac_table_u 90 | mac_table_u:0 -> drop 91 | buf_u -> out_u 92 | 93 | bess.attach_task(in_u.name, wid=wid) 94 | 95 | # downlink 96 | in_d = QueueInc(name="dl_inport_%d" % wid, port=portDL.name, qid=wid) 97 | out_d = QueueOut(name="dl_outport_%d" % wid, port=portUL.name, qid=wid) 98 | buf_d = Buffer(name='out_buf_d_%d' % wid) 99 | if conf.fakedrop: 100 | drop = buf_d 101 | else: 102 | drop = Sink() 103 | mac_table_d = ExactMatch(name='mac_table_d_%d' % wid, 104 | fields=[{'offset': 0, 'num_bytes': 6}]) 105 | mac_table_d.set_default_gate(gate=0) 106 | for i, entry in enumerate(conf.downstream_table, start=1): 107 | gat = entry.out_port or i 108 | mac_table_d.add(fields=[{'value_bin': mac_from_str(entry.mac)}], 109 | gate=gat) 110 | mac_table_d:i -> buf_d 111 | 112 | in_d -> mac_table_d 113 | mac_table_d:0 -> drop 114 | buf_d -> out_d 115 | 116 | bess.attach_task(in_d.name, wid=wid2) 117 | 118 | try: 119 | bess.track_gate(False, '', '', bits=True) 120 | except AttributeError: 121 | bess.track_module('', False, bits=True) 122 | -------------------------------------------------------------------------------- /bess/portfwd.bess: -------------------------------------------------------------------------------- 1 | # -*- mode: python; -*- 2 | 3 | # TIPSY: Telco pIPeline benchmarking SYstem 4 | # 5 | # Copyright (C) 2018 by its authors (See AUTHORS) 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but 13 | # WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | # General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import json 21 | import sys 22 | 23 | def mac_int_from_str(s): 24 | return int("0x%s" % ''.join(s.split(':')), 16) 25 | 26 | def coremask_to_corelist(coremask): 27 | cpum = int(coremask, 16) 28 | return [i for i in range(32) if (cpum >> i) & 1 == 1] 29 | 30 | class ObjectView(object): 31 | def __init__(self, **kwargs): 32 | tmp = {k.replace('-', '_'): v for k, v in kwargs.items()} 33 | self.__dict__.update(**tmp) 34 | 35 | def __repr__(self): 36 | return self.__dict__.__repr__() 37 | 38 | def conv_fn(d): return ObjectView(**d) 39 | 40 | pl_conf_file = $pl_config!'./portfwd.json' 41 | bm_conf_file = $bm_config!'./benchmark.json' 42 | with open(pl_conf_file, 'r') as f: 43 | conf = json.load(f, object_hook=conv_fn) 44 | with open(bm_conf_file, 'r') as f: 45 | bm_conf = json.load(f, object_hook=conv_fn) 46 | 47 | bess_dlport = int(bm_conf.sut.downlink_port) 48 | bess_ulport = int(bm_conf.sut.uplink_port) 49 | bess_workers = int(conf.core) 50 | corelist = coremask_to_corelist(bm_conf.sut.coremask) 51 | 52 | portDL = PMDPort(port_id=bess_dlport, 53 | num_inc_q=bess_workers, 54 | num_out_q=bess_workers) 55 | 56 | if bess_dlport == bess_ulport: 57 | portUL = portDL 58 | else: 59 | portUL = PMDPort(port_id=bess_ulport, 60 | num_inc_q=bess_workers, 61 | num_out_q=bess_workers) 62 | 63 | for wid in range(bess_workers): 64 | wid2 = bess_workers + wid 65 | try: 66 | bess.add_worker(wid, corelist[wid]) 67 | bess.add_worker(wid2, corelist[wid2]) 68 | except IndexError: 69 | sys.exit('ERROR: Insufficient number of cores available.') 70 | 71 | # uplink 72 | in_u = QueueInc(name="ul_inport_%d" % wid, port=portUL.name, qid=wid) 73 | out_u = QueueOut(name="ul_outport_%d" % wid, port=portDL.name, qid=wid) 74 | buf_u = Buffer(name='out_buf_u_%d' % wid) 75 | buf_u -> out_u 76 | mac_addr_up = conf.mac_swap_upstream 77 | if mac_addr_up: 78 | update = Update(name='update_mac_u_%d' % wid, 79 | fields=[{'offset': 6, 'size': 6, 80 | 'value': mac_int_from_str(mac_addr_up)}]) 81 | in_u -> update -> buf_u 82 | else: 83 | in_u -> buf_u 84 | bess.attach_task(in_u.name, wid=wid) 85 | 86 | # downlink 87 | in_d = QueueInc(name="dl_inport_%d" % wid, port=portDL.name, qid=wid) 88 | out_d = QueueOut(name="dl_outport_%d" % wid, port=portUL.name, qid=wid) 89 | buf_d = Buffer(name='out_buf_d_%d' % wid) 90 | buf_d -> out_d 91 | mac_addr_down = conf.mac_swap_downstream 92 | if mac_addr_down: 93 | update = Update(name='update_mac_d_%d' % wid, 94 | fields=[{'offset': 6, 'size': 6, 95 | 'value': mac_int_from_str(mac_addr_down)}]) 96 | in_d -> update -> buf_d 97 | else: 98 | in_d -> buf_d 99 | bess.attach_task(in_d.name, wid=wid2) 100 | 101 | try: 102 | bess.track_gate(False, '', '', bits=True) 103 | except AttributeError: 104 | bess.track_module('', False, bits=True) 105 | -------------------------------------------------------------------------------- /doc/README.L2fwd.org: -------------------------------------------------------------------------------- 1 | #+LaTeX_HEADER:\usepackage[margin=2cm]{geometry} 2 | #+LaTeX_HEADER:\usepackage{enumitem} 3 | #+LaTeX_HEADER:\usepackage{tikz} 4 | #+LATEX:\setitemize{noitemsep,topsep=0pt,parsep=0pt,partopsep=0pt} 5 | #+LATEX:\lstdefinelanguage{javascript}{basicstyle=\scriptsize\ttfamily,numbers=left,numberstyle=\scriptsize,stepnumber=1,showstringspaces=false,breaklines=true,frame=lines} 6 | #+OPTIONS: toc:nil ^:nil num:nil 7 | 8 | #+TITLE: L2 Packet Forwarding (L2fwd) 9 | 10 | The L2 Packet Forwarding pipeline (name: =L2fwd=) models a simple Ethernet 11 | switch using a static MAC lookup table. Note that this pipeline is rather 12 | basic and it will /not/ perform MAC learning or L3 routing. (Contributions 13 | are welcome!) 14 | 15 | * Static pipeline 16 | 17 | Upstream the L2fwd pipeline will receive packets from the downlink port, 18 | perform a lookup for the destination MAC address in a static MAC table, and 19 | if a match is found the packet will be forwarded to the uplink port or 20 | otherwise dropped (or likewise forwarded upstream if the =fakedrop= 21 | parameter is set to =true=). The downstream pipeline is just the other way 22 | around, but note that the upstream and downstream pipelines use separate 23 | MAC tables. 24 | 25 | * Dynamic scenarios 26 | 27 | The L2fwd pipeline defines a single dynamic scenario: 28 | 29 | - =table-update=: models a L2 MAC table update, whereby a MAC table entry 30 | is added to/removed from the upstream and downstream MAC tables 31 | 32 | * Pipeline configuration 33 | 34 | A sample TIPSY configuration for the L2fwd pipeline is shown below: 35 | 36 | #+BEGIN_SRC javascript 37 | { 38 | "pipeline": { 39 | "name": "L2fwd", 40 | "upstream-table-size": 100000, 41 | "downstream-table-size": 20, 42 | "fluct-table": 30, 43 | "fakedrop": false 44 | } 45 | } 46 | #+END_SRC 47 | 48 | The parameters specific to the L2fwd pipeline are as follows: 49 | 50 | - =name=: name of the pipeline, must be set to =l2fwd= for the L2fwd 51 | pipeline 52 | - =upstream-table-size=: number of entries in the upstream MAC table 53 | - =downstream-table-size=: number of entries in the downstream MAC table 54 | - =fluct-table=: number of MAC table entry update events (=table-update=) 55 | per sec 56 | - =fakedrop=: whether to actually drop unmatched packets (=false=) or send 57 | them immediately to the output port (=false=) for correct rate 58 | measurements 59 | 60 | * OVS Implementation: Caveats and considerations 61 | 62 | * BESS Implementation: Caveats and considerations 63 | 64 | 65 | -------------------------------------------------------------------------------- /doc/README.bng.org: -------------------------------------------------------------------------------- 1 | #+LaTeX_HEADER:\usepackage[margin=2cm]{geometry} 2 | #+LaTeX_HEADER:\usepackage{enumitem} 3 | #+LaTeX_HEADER:\usepackage{tikz} 4 | #+LATEX:\setitemize{noitemsep,topsep=0pt,parsep=0pt,partopsep=0pt} 5 | #+OPTIONS: toc:nil ^:nil num:nil 6 | 7 | #+TITLE: Broadband Nerwork Gateway (BNG) 8 | 9 | The broadband network gateway pipeline (name: =bng=) implements the 10 | following scenario: 11 | 12 | #+ATTR_LATEX: :centering :width 10cm :caption BNG setup 13 | [[./fig/bng.png]] 14 | 15 | * Static pipeline 16 | 17 | #+ATTR_LATEX: :centering :width 10cm :caption BNG pipeline 18 | [[./fig/bng_pipeline.png]] 19 | 20 | 21 | * Dynamic scenarios 22 | 23 | * Pipeline configuration 24 | 25 | * OVS Implementation: Caveats and considerations 26 | 27 | * BESS Implementation: Caveats and considerations 28 | -------------------------------------------------------------------------------- /doc/README.fw.org: -------------------------------------------------------------------------------- 1 | #+LaTeX_HEADER:\usepackage[margin=2cm]{geometry} 2 | #+LaTeX_HEADER:\usepackage{enumitem} 3 | #+LaTeX_HEADER:\usepackage{tikz} 4 | #+LATEX:\setitemize{noitemsep,topsep=0pt,parsep=0pt,partopsep=0pt} 5 | #+LATEX:\lstdefinelanguage{javascript}{basicstyle=\scriptsize\ttfamily,numbers=left,numberstyle=\scriptsize,stepnumber=1,showstringspaces=false,breaklines=true,frame=lines} 6 | #+OPTIONS: toc:nil ^:nil num:nil 7 | 8 | #+TITLE: Firewall (FW) 9 | 10 | The firewall pipeline (name: =fw=) is a basic Firewall setup that 11 | allows to micro-benchmark the ACL/firewall capabilities of switches. 12 | 13 | Config generation of the pipeline requires [[https://github.com/classbench-ng/classbench-ng][Classbench]]. 14 | 15 | * Static pipeline 16 | 17 | Both the upstream and the downstream direction consist of a single 18 | firewall module using separate (uplink/downlink) access control lists. 19 | The firewall rules contain L3/L4. 20 | 21 | The pipeline receives normal TCP/IP packets. The packet generator 22 | varies L2/L3 source and destination, L4 port number and port type (TCP 23 | or UDP) according to Classbench traces. 24 | 25 | * Dynamic scenarios 26 | 27 | The Firewall pipeline currently does not define dynamic scenarios. 28 | 29 | * Pipeline configuration 30 | 31 | The parameters specific to the Firewall pipeline are as follows: 32 | 33 | - =name=: name of the pipeline, must be set to =fw= for the Firewall pipeline 34 | - =implementation-type=: type of the internal implementation of the FW 35 | pipeline. In case of bess: 'acl' or 'dpdk'. Otherwise: 'default'. 36 | - =classbench-cmd=: absolute path of the classbench executable 37 | (https://github.com/classbench-ng/classbench-ng) 38 | - =seed-file=: seed file for Classbench (relative to 39 | classbench/vendor/parameter_files) 40 | - =rule-num=: number of firewall rules 41 | 42 | * OVS Implementation: Caveats and considerations 43 | 44 | * BESS Implementation: Caveats and considerations 45 | 46 | BESS implementation support two internal firewall implementations: 47 | - built-in =ACL= module 48 | - DPDK-based =DPDKACL= module 49 | -------------------------------------------------------------------------------- /doc/README.portfwd.org: -------------------------------------------------------------------------------- 1 | #+LaTeX_HEADER:\usepackage[margin=2cm]{geometry} 2 | #+LaTeX_HEADER:\usepackage{enumitem} 3 | #+LaTeX_HEADER:\usepackage{tikz} 4 | #+LATEX:\setitemize{noitemsep,topsep=0pt,parsep=0pt,partopsep=0pt} 5 | #+LATEX:\lstdefinelanguage{javascript}{basicstyle=\scriptsize\ttfamily,numbers=left,numberstyle=\scriptsize,stepnumber=1,showstringspaces=false,breaklines=true,frame=lines} 6 | #+OPTIONS: toc:nil ^:nil num:nil 7 | 8 | #+TITLE: L2 Port Forwarding (PORTfwd) 9 | 10 | The port-forwarder pipeline (name: =portfwd=) is a basic L2 setup that 11 | allows to exercise the raw speed of the underlying data plane 12 | technology. The port forwarding use case passes L2 packets between the 13 | uplink and the downlink port, optionally swapping MAC source addresses 14 | along the way. 15 | 16 | * Static pipeline 17 | 18 | In the upstream direction the pipeline will receive L2 packets from the 19 | downlink port of the SUT and forward them to the uplink port. Meanwhile, it 20 | may optionally rewrite the source MAC address in the L2 frame to the MAC 21 | address of the uplink port (must be specified by the pipeline config). The 22 | downstream direction is the same, but packets are received from the uplink 23 | port and forwarded to the downlink port after an optional MAC rewrite. 24 | 25 | * Dynamic scenarios 26 | 27 | The port forwarding pipeline does not define dynamic scenarios. 28 | 29 | * Pipeline configuration 30 | 31 | A sample TIPSY configuration for the PORTfwd pipeline is shown below: 32 | 33 | #+BEGIN_SRC javascript 34 | { 35 | "pipeline": { 36 | "name": "portfwd", 37 | "mac-swap-upstream": null, 38 | "mac-swap-downstream": "ac:dc:aa:bb:bb:aa" 39 | } 40 | } 41 | #+END_SRC 42 | 43 | The parameters specific to the PORTfwd pipeline are as follows: 44 | 45 | - =name=: name of the pipeline, must be set to =portfwd= for the Port 46 | Forward pipeline 47 | - =mac-swap-upstream=: if set, swap the source MAC address in the packets 48 | received from the downlink port before forwarding to the uplink port 49 | - =mac-swap-downstream=: if set, swap the source MAC address in the packets 50 | received from the uplink port before forwarding to the downlink port 51 | 52 | * OVS Implementation: Caveats and considerations 53 | 54 | * BESS Implementation: Caveats and considerations 55 | -------------------------------------------------------------------------------- /doc/README.workflow.org: -------------------------------------------------------------------------------- 1 | #+LaTeX_HEADER:\usepackage[margin=2cm]{geometry} 2 | #+LaTeX_HEADER:\usepackage{enumitem} 3 | #+LaTeX_HEADER:\usepackage{tikz} 4 | #+LATEX:\setitemize{noitemsep,topsep=0pt,parsep=0pt,partopsep=0pt} 5 | #+OPTIONS: toc:nil ^:nil num:nil 6 | 7 | #+TITLE: The TIPSY Workflow 8 | 9 | TIPSY was designed for those who would like to do *scalability studies*. It 10 | requires a pre-configured environment consisting of a *Tester* and a *SUT* 11 | (System Under Test). TIPSY already offers numerous pipeline implementations 12 | on various platforms. Due to its modular infrastructure it is easy to 13 | implement additional *pipelines* or to support new platforms. 14 | 15 | But, a typical TIPSY session involves only a small amount of user 16 | efforts. Namely, to provide a high-level description of the *benchmarks*: 17 | configuration of the environment, scaling parameters of the pipeline and 18 | configuration of the visualisation. The typical TIPSY workflow is the 19 | following. 20 | 21 | 0. Install TIPSY on the Tester and on the SUT. Configure the network 22 | interfaces, passwordless SSH access. The following steps are done on the 23 | Tester. 24 | 25 | 1. Create a directory for your scalability study. This folder will 26 | contain all of the files (configurations, traffic traces, figures) 27 | related to your study. 28 | 29 | #+BEGIN_SRC sh 30 | mkdir my_bng_study cd my_bng_study 31 | #+END_SRC 32 | 33 | 2. Initialise a new high-level TIPSY configuration or copy your old 34 | one. 35 | 36 | TIPSY can generate an initial configuration containing all of the 37 | available parameters -- in many cases it might contain more parameters 38 | than you would like to specify. 39 | 40 | #+BEGIN_SRC sh 41 | tipsy init mgw 42 | #+END_SRC 43 | 44 | 45 | 3. Edit the main TIPSY configuration file according to your needs 46 | (e.g.: adjust pipeline parameters or SUT config). For details check the 47 | [[./README.config.org][TIPSY configuration guide]]. 48 | 49 | It is a good practice to split your configuration file to multiple 50 | files. This way you can reuse e.g. Tester and SUT configuration among 51 | scalability studies. 52 | 53 | If your are unsure about the validity of your configuration file, TIPSY 54 | can check your configuration: 55 | 56 | #+BEGIN_SRC sh 57 | tipsy validate main.json 58 | #+END_SRC 59 | 60 | 4. Generate the configuration for the individual test cases that make up 61 | the benchmark, that is, a separate test for all settings benchmark 62 | parameters, with each test case configuration placed into a separate 63 | directory. Plus a main Makefile that will execute the measurements. 64 | 65 | #+BEGIN_SRC sh 66 | tipsy config 67 | #+END_SRC 68 | 69 | This call will set the benchmark configuration from your JSON files, 70 | setting each parameter that was not explicitly specified there to a sane 71 | default value. 72 | 73 | Optionally, you can force TIPSY to override existing measurement 74 | configurations and results too (!) with the following command. 75 | 76 | #+BEGIN_SRC sh 77 | tipsy config -f 78 | #+END_SRC 79 | 80 | 5. Let TIPSY do the cumbersome parts: 81 | - Generate sample traffic traces that will be fed to the SUT during 82 | the benchmark (this may take a while). 83 | - Run the benchmarks (this may take an even longer while). 84 | - Visualize benchmark results. 85 | 86 | #+BEGIN_SRC sh 87 | make 88 | #+END_SRC 89 | 90 | 6. Finally, clean up the benchmark directory by removing all temporary 91 | files (pcaps, logs, etc.). 92 | 93 | #+BEGIN_SRC sh 94 | tipsy clean 95 | #+END_SRC 96 | -------------------------------------------------------------------------------- /doc/benchmarks.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsnlab/tipsy/3e987f0f541222665b7046d1450aebe41a2aa0b3/doc/benchmarks.odp -------------------------------------------------------------------------------- /doc/benchmarks.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsnlab/tipsy/3e987f0f541222665b7046d1450aebe41a2aa0b3/doc/benchmarks.pdf -------------------------------------------------------------------------------- /doc/examples/l3fwd.json: -------------------------------------------------------------------------------- 1 | { 2 | "benchmark": [ 3 | { 4 | "pipeline": { 5 | "core": 1, 6 | "name": "l3fwd", 7 | "downstream-group-table-size": 20, 8 | "downstream-l3-table-size": [1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000, 100000], 9 | "upstream-group-table-size": 20, 10 | "upstream-l3-table-size": [1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000, 100000] 11 | }, 12 | "tester": { 13 | "type": "moongen-combined" 14 | } 15 | }, 16 | { 17 | "pipeline": { 18 | "core": 2, 19 | "name": "l3fwd", 20 | "downstream-group-table-size": 20, 21 | "downstream-l3-table-size": [1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000, 100000], 22 | "upstream-group-table-size": 20, 23 | "upstream-l3-table-size": [1, 3, 10, 30, 100, 300, 1000, 3000, 10000, 30000, 100000] 24 | }, 25 | "tester": { 26 | "type": "moongen" 27 | } 28 | } 29 | ], 30 | "visualize": [ 31 | { 32 | "type": "simple", 33 | "x-axis": "pipeline.upstream-l3-table-size", 34 | "y-axis": "out.throughput.RX.PacketRate", 35 | "group-by": ["sut.type", "tester.type"], 36 | "filter": {"pipeline.name": "l3fwd"}, 37 | "axis-type": "semilogx", 38 | "title": "{pipeline.name}" 39 | }, 40 | { 41 | "type": "simple", 42 | "x-axis": "pipeline.upstream-l3-table-size", 43 | "y-axis": "out.throughput.RX.PacketRate", 44 | "group-by": ["sut.type"], 45 | "filter": {"pipeline.name": "l3fwd", 46 | "tester.type": "moongen-combined"}, 47 | "axis-type": "semilogx", 48 | "title": "pl: {pipeline.name}" 49 | }, 50 | { 51 | "type": "simple", 52 | "x-axis": "pipeline.upstream-l3-table-size", 53 | "y-axis": "out.latency.3rd_Quartiles", 54 | "group-by": ["sut.type"], 55 | "filter": {"pipeline.name": "l3fwd", 56 | "tester.type": "moongen-combined"}, 57 | "axis-type": "semilogx", 58 | "title": "pl: {pipeline.name}" 59 | }, 60 | { 61 | "type": "simple", 62 | "x-axis": "pipeline.upstream-l3-table-size", 63 | "y-axis": "out.latency.3rd-Quartiles", 64 | "group-by": ["sut.type"], 65 | "filter": {"pipeline.name": "l3fwd", 66 | "sut.type": {"$not": "ovs"}, 67 | "tester.type": "moongen-combined", 68 | "pipeline.upstream-l3-table-size": {"$gt": 1}}, 69 | "axis-type": "semilogx", 70 | "title": "{tester.type}, {pipeline.name}, table-size $>1$" 71 | }, 72 | { 73 | "type": "simple", 74 | "x-axis": "pipeline.upstream-l3-table-size", 75 | "y-axis": "out.latency.Average", 76 | "group-by": ["sut.type"], 77 | "filter": {"pipeline.name": "l3fwd", 78 | "sut.type": {"$not": "ovs"}, 79 | "tester.type": "moongen", 80 | "pipeline.upstream-l3-table-size": {"$gt": 1}}, 81 | "axis-type": "semilogx", 82 | "title": "{tester.type}, {pipeline.name}, table-size $>1$ " 83 | } 84 | ] 85 | } 86 | -------------------------------------------------------------------------------- /doc/examples/multicore-joint.json: -------------------------------------------------------------------------------- 1 | { 2 | "benchmark": [ 3 | { 4 | "id": "all", 5 | "scale": "joint", 6 | "pipeline": { 7 | "name": "mgw", 8 | "core": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 9 | "bst": [40, 80, 120, 160, 200, 240, 280, 320, 360, 400, 440, 480], 10 | "nhop": [30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, 360], 11 | "rate-limit": 40000000000, 12 | "server": [400, 800, 1200, 1600, 2000, 2400, 2800, 3200, 3600, 4000, 4400, 4800], 13 | "user": [300, 600, 900, 1200, 1500, 1800, 2100, 2400, 2700, 3000, 3300, 3600] 14 | } 15 | }, 16 | { 17 | "id": "user", 18 | "scale": "joint", 19 | "pipeline": { 20 | "name": "mgw", 21 | "core": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 22 | "bst": 40, 23 | "nhop": 30, 24 | "rate-limit": 40000000000, 25 | "server": 400, 26 | "user": [300, 600, 900, 1200, 1500, 1800, 2100, 2400, 2700, 3000, 3300, 3600] 27 | } 28 | }, 29 | { 30 | "id": "all", 31 | "scale": "joint", 32 | "pipeline": { 33 | "name": "bng", 34 | "core": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 35 | "cpe": [40, 80, 120, 160, 200, 240, 280, 320, 360, 400, 440, 480], 36 | "nhop": [30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, 360], 37 | "rate-limit": 40000000000, 38 | "server": [400, 800, 1200, 1600, 2000, 2400, 2800, 3200, 3600, 4000, 4400, 4800], 39 | "user": [300, 600, 900, 1200, 1500, 1800, 2100, 2400, 2700, 3000, 3300, 3600], 40 | "user-conn": 3 41 | } 42 | }, 43 | { 44 | "id": "user", 45 | "scale": "joint", 46 | "pipeline": { 47 | "name": "bng", 48 | "core": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 49 | "cpe": 40, 50 | "nhop": 30, 51 | "rate-limit": 40000000000, 52 | "server": 400, 53 | "user": [300, 600, 900, 1200, 1500, 1800, 2100, 2400, 2700, 3000, 3300, 3600], 54 | "user-conn": 3 55 | } 56 | } 57 | ], 58 | "visualize": [ 59 | { 60 | "type": "USL", 61 | "x-axis": "pipeline.core", 62 | "y-axis": "out.throughput.RX.PacketRate", 63 | "group-by": ["sut.type", "tester.type", "traffic.pkt-size"], 64 | "filter": {"pipeline.name": "mgw", 65 | "id": "all"}, 66 | "title": "{pipeline.name} dir:{traffic.dir} {id} USL fit test" 67 | }, 68 | { 69 | "type": "USL", 70 | "x-axis": "pipeline.core", 71 | "y-axis": "out.throughput.RX.PacketRate", 72 | "group-by": ["sut.type", "tester.type", "traffic.pkt-size"], 73 | "filter": {"pipeline.name": "mgw", 74 | "id": "user"}, 75 | "title": "{pipeline.name} dir:{traffic.dir} {id} USL fit test" 76 | }, 77 | 78 | { 79 | "type": "USL", 80 | "x-axis": "pipeline.core", 81 | "y-axis": "out.throughput.RX.PacketRate", 82 | "group-by": ["sut.type", "tester.type", "traffic.pkt-size"], 83 | "filter": {"pipeline.name": "bng", 84 | "id": "all"}, 85 | "title": "{pipeline.name} dir:{traffic.dir} {id} USL fit test" 86 | }, 87 | { 88 | "type": "USL", 89 | "x-axis": "pipeline.core", 90 | "y-axis": "out.throughput.RX.PacketRate", 91 | "group-by": ["sut.type", "tester.type", "traffic.pkt-size"], 92 | "filter": {"pipeline.name": "bng", 93 | "id": "user"}, 94 | "title": "{pipeline.name} dir:{traffic.dir} {id} USL fit test" 95 | } 96 | 97 | ] 98 | } 99 | -------------------------------------------------------------------------------- /doc/examples/multicore.json: -------------------------------------------------------------------------------- 1 | { 2 | "benchmark": [ 3 | { 4 | "pipeline": { 5 | "name": "mgw", 6 | "core": [1,2,3,4,5,6,7,8,9,10,11,12], 7 | "bst": 40, 8 | "nhop": 30, 9 | "rate-limit": 40000000000, 10 | "server": 400, 11 | "user": 300 12 | } 13 | }, 14 | { 15 | "pipeline": { 16 | "name": "bng", 17 | "core": [1,2,3,4,5,6,7,8,9,10,11,12], 18 | "cpe": 40, 19 | "nhop": 30, 20 | "rate-limit": 40000000000, 21 | "server": 400, 22 | "user": 300, 23 | "user-conn": 3 24 | } 25 | } 26 | ], 27 | "visualize": [ 28 | { 29 | "title": "users:{pipeline.user}, {pipeline.name}, {tester.type}", 30 | "x-axis": "pipeline.core", 31 | "y-axis": "out.throughput.RX.PacketRate", 32 | "group-by": ["sut.type", "pipeline.user", "traffic.dir"], 33 | "filter": {"pipeline.name": "mgw", "pipeline.user": 300} 34 | }, 35 | { 36 | "title": "users:{pipeline.user}, {pipeline.name}, {tester.type}", 37 | "x-axis": "pipeline.core", 38 | "y-axis": "out.throughput.RX.PacketRate", 39 | "group-by": ["sut.type", "pipeline.user", "traffic.dir"], 40 | "filter": {"pipeline.name": "bng", "pipeline.user": 300} 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /doc/examples/simple-visualize-2.json: -------------------------------------------------------------------------------- 1 | { 2 | "benchmark": [{ 3 | "scale": "outer", 4 | "pipeline": { 5 | "name": "mgw", 6 | "user": [1, 1000] 7 | }, 8 | "traffic": { 9 | "pkt-num": 10, 10 | "pkt-size": [64, 128, 256] 11 | } 12 | }], 13 | "visualize": [{ 14 | "type": "simple", 15 | "title": "{pipeline.name} - OVS v{out.sut.version}", 16 | "group-by": "pipeline.user", 17 | "x-axis": "traffic.pkt-size", 18 | "y-axis": "out.throughput.RX.PacketRate" 19 | }] 20 | } 21 | -------------------------------------------------------------------------------- /doc/examples/simple-visualize.json: -------------------------------------------------------------------------------- 1 | { 2 | "benchmark": [ 3 | { 4 | "pipeline": { 5 | "name": "portfwd" 6 | }, 7 | "scale": "outer", 8 | "traffic": { 9 | "pkt-num": 100, 10 | "pkt-size": [64, 128, 256] 11 | } 12 | } 13 | ], 14 | "environment": { 15 | "sut": { 16 | "type": "ovs" 17 | } 18 | }, 19 | "visualize": [ 20 | { "x-axis": "traffic.pkt-size", 21 | "y-axis": [ 22 | "out.throughput.RX.PacketRate", 23 | "out.throughput.TX.PacketRate" 24 | ], 25 | "title": "{pipeline.name} - OVS v{out.sut.version}" 26 | }, 27 | { "x-axis": "traffic.pkt-size", 28 | "y-axis": "out.throughput.TX.Mbit", 29 | "title": "{pipeline.name} - OVS v{out.sut.version}" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /doc/fig/bng.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsnlab/tipsy/3e987f0f541222665b7046d1450aebe41a2aa0b3/doc/fig/bng.pdf -------------------------------------------------------------------------------- /doc/fig/bng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsnlab/tipsy/3e987f0f541222665b7046d1450aebe41a2aa0b3/doc/fig/bng.png -------------------------------------------------------------------------------- /doc/fig/bng.xml: -------------------------------------------------------------------------------- 1 | 7Vxtk5u6Ff41O/3mQbwI+JjdZNM7k9vZuem0t588GLQ2DUYekGNvf30lkDB6MWAbnO0WZ7IDQoDgeY70nHMkHpyn7fFrEe02v+MEZQ+2lRwfnM8Pth1aIf3LCt7qgiD064J1kSZ1ETgVfE//g3ihxUv3aYJKqSLBOCPpTi6McZ6jmEhlUVHgg1ztFWfyXXfRGmkF3+Mo00v/mSZkw0sBDE8H/orS9YbfOrD5862i+Me6wPuc3+/Bdl6rX314G4lr8QctN1GCD60i58uD81RgTOqt7fEJZezVitdWn/d85mjT7gLlZMgJIX+gn1G2R6LJVcPIm3gZ9Az63unOI23tjhXGGd7TCzweNilB33dRzAoPlAm0bEO2Gd0DdDOLVih7bN7IE85wQQ/lOK8uRgr8A4lC+qKen5/dJ4ceeU2zrFVO354dx6wc54RzxQ74fqueVf1oOX8kVBB0PPteQPO2KYkR3iJSvNEq/ATX8+pTOIGdeu9wIoMnuLpp8cCGkJOQE3DdXPkEAt3gOJzBJJgxMWEChdG8iV7mjqD4QMcgoX0G38UF2eA1zqPsy6n0sXrFiF3BklFAx5T8yYvZ9r/Y9sJjezltWHVoEXqBKKiPO8AWBS+oSOkzoIJfpCRRQT6xnq8NJi17Ttkj1TfKE6UGLWkd/zci5I1jGe0JpkWnx/qG8Y43XiMJjAO0em2OiB4zvIAeCr2SCAWvjF7rIkpSJJ3jowiiE6U0/lCM8L6IOUg+JwQDq5NkBcoikv6UBwATY/ipLziltz2RE1B8Wj+l+7C8BWz9fFe+PkVqjQi/pELKpo2DeCpGqJF5WnOTMxXIPPUCIPPU8maaXkhT6N2Hpp4nE1Gmqe3bEoltOBVNPV8b4/YlpQnr4h16AGb0XT2uaAFcs63fXtg73DA+qPSmAwqRWSsjz0nUho4XRVm6ztnQSZFhFH1kw1NKVeAnfmCbJkllIKZBVTaa7nH1slHSwBVt4Dw7SgIhHzim0LG1YRIIRdEeJgPr9lHSgx2wAh3Wl6cZzm44A1nz+JYueoBlED2joOldhmaJSOWo7ZgjhI8ztD16VrZTcE9kXQ3Zz9+/ffpdx7TpkW/H8uQLf1BAHVeG1HUDHVIx5I4OqXMZpLo/M0NqGE1dVxbyrmE0nQpS+zafE6hg/krtXf36tHfl3ypono1AeOyfWXuvqn+ylwLtxivhXgr0z7kpZ4kztTwPoFF/vzXifZAepxBGb61qO1ah7Lqr3G1BX2FpfUHzybYHjGc3d6/dnDGcBlMc7Hp7OPm2turc/j+aylnO1yTjTPEnsYNLCUv9VJlznMHPQ+v7Vmd94MCu+n0W4VvG1vVZhKEZ5qiR6A78Yd3BNZbmTGNps6Hdb3C52KhCmbaeF3YblddZv89IwpGMRG0GtBTlNZ5VDMjLCM37jQnYF1ymJMVM+64wIXjLVHK5qxOWr+mR2UrbTlTBzDzpMyJcZH22xzVLwC7ilBTpcXFAqyX10H9W+rtHROte9lDWj6CroSJtXF1Vg1BX1cAdQVYPSHnOMA6D0VfSpCL7cBccRcBkBnKEMGMoq3jg3BNJcbOL5IZBYIjt2sGzlXyqazUuYV3B9ZrE1btKVK0Cz/Wsm4RJvKekE1JMVSlBjOIzWSsUwVWlid6rSnFE9y0CMk63lPccq6t+j0pxbDmZ4or9S1UKbYbk2Ttyo4TeHl+zGOJ8s89ssphWR+HL3YTlnw0UfST974aykyyE9DlKOx7sqt9jWa4y4HgDw0aGZnQ6ydCDJsObwNAmCk59NDv7SCbjAcVk3G6TcRWnV6nfYzIeGMlktGb4k7nMQh1eZBWSeOk1CkvuvD0/lLtv6H5UkfeedV0vexsSXiylOqcwOa4/GZevkVLvJgE3aPJbJ93kFO3kU+GkvMP76O6hTGHYEfC8Zk6cKZ7Bk/cid09BiJJVlCe03t8QOeDiB936GhF0YI9S16Y3Wp2S/Qpl+7L8HdMdP3aq31ECybZpqpUp1Q/UiPNV0zemmV/eOVJK4+Sv7qAaKK/voAZ1LwLldvciysbrXwbDrucUTlPsnHnC5BVmbMudtC80esuMQ0Mgc5Q5WHpq4YSmq6P593/MaF62DsuE5mSzJMVw34KTB/PHm0H3kcHz1dirDp1tsEQ3GAE6uxM6PVYzQyfng9QwmiEdNBl2+kzWNnb61JQZOwm7UFkBYszlTQZeMIWOlRN7Sl7Pcm054uODJgT0rkI+/wsL0NqyOPxVqhjqC080VqXbag3/gLXNbc+Vn/U53a5pI7KU+snPcZbulpQCdDOvXeo0Z0f/wHtKnOVvMc6XwA6O9P9iR48Yl0kb8Lm/5XtAVkuNlbcsP5hILPk6RFMbvmMrC6RDB86Gf+UC6bbhg9H94avibaGqHy2oMPK2gJtvCrjN3UxvNwOhEikTH1i4Rzeji8OcyT/Nt95UyxBnN22Aj23JWh+IUeQeelH4GRqeVgPg7Kz12SPwZXsUnG8BaAuQxwbQ0Zd8a3j1zsIcOOlSDJysXhKVmwYKdfpldCidZFGieF9QUlTfiKruePYzNlpXm8AV9Jia0CaIynM2mg84sWbEeJvGfLsnaN3gbxjAX1+TsDtZe4EktOWpZq4hgCakm0QN53ZqAD0cOlPj/VBDndwopka2qSGmmEjUGGEYD/TQ6tc/vjAZuqeQZKU+nh9S2ibbYvH08i/VwFDXSJtPhHxu5T+b0nIePHo6iFCZQBEawrShMe3pjsADPU6rAabPrS9jvNjSv4ukzCKlk5CMX7Fpy4HsS4QGm26Uu9yb2C2Qz/ZQ5m5pTIBv+mxaM8qLEcDS1YFRrfsjwKur9Rnekb9UKELzzUI6Xb1PBu+AON0vXoFDfe76s1G3rb+ZGkUQyjLNC3UjBQDqMHojfNswGCDhO6y0RGRJgVmyT/7MttrZFcsYQ8OqRyswCK4xMNY/uDYQY6pcdyxCVS6jPFnuUJHuNqiIsnKxi2e8u60ayIEyaBmcLwPg6nzsqwAfsFrZDHhRBSTLxTpdR6uULEsqvePNMiLbJYnWy/rwjHz3qKzkYE0hUiBcc0lVq0vBrlrhfLXsmo39asgdEXVrPrzo383YQz2KeskITn7O2F4ksqGnh0rEF6JvxJbunj7/Xie1Tp/Yd778Fw== -------------------------------------------------------------------------------- /doc/fig/bng_pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsnlab/tipsy/3e987f0f541222665b7046d1450aebe41a2aa0b3/doc/fig/bng_pipeline.png -------------------------------------------------------------------------------- /doc/fig/l3fwd_pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsnlab/tipsy/3e987f0f541222665b7046d1450aebe41a2aa0b3/doc/fig/l3fwd_pipeline.png -------------------------------------------------------------------------------- /doc/fig/mgw.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsnlab/tipsy/3e987f0f541222665b7046d1450aebe41a2aa0b3/doc/fig/mgw.pdf -------------------------------------------------------------------------------- /doc/fig/mgw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsnlab/tipsy/3e987f0f541222665b7046d1450aebe41a2aa0b3/doc/fig/mgw.png -------------------------------------------------------------------------------- /doc/fig/mgw.xml: -------------------------------------------------------------------------------- 1 | 7VzZkps4FP0a1zzFBRLrY29OUpWp6pqkZnnqwiDbSjByYTl2z9ePBBJGEt7B7fTQfmi4iE3n6G66YgAf5puPebSY/U4SlA6AlWwG8HEAgG1Di/3jktdS4lthKZjmOBGNtoKv+F8khOK86QonaKk0pISkFC9UYUyyDMVUkUV5TtZqswlJ1bsuoikyBF/jKDWlf+GEzuR7eeH2wCeEpzNx6wD45YFxFP+Y5mSVifsNAJwUf+XheSSvJV50OYsSsq6J4NMAPuSE0HJrvnlAKe9b2W3leaMdR6vnzlFGjzkhAOUZP6N0heQjFw9GX2VnsDNYv7Ode/a0Cy6MU7JiF7hfzzBFXxdRzIVrRgUmm9F5yvZstplGY5TeVz3yQFKSs0MZyYqL0Zz8QFLIOmo0GjkPkB2Z4DStyVnvgTjmcpJRwRUQiP1aO6v4Y3LxSiinaLOzX+yqtxmLEZkjmr+yJuIEx3XLUwSBYbm33pLBlVyd1XgAPE+QUBBwWl15CwLbEDjswAT2mDRh4slBIzAJrSuC4tsmBgnTGWKX5HRGpiSL0qet9L7oYsSvYKkooA2mfwsx3/6Hbw9dvpexBysODUM3kILyOLSBFDyjHLN3QLm4yJJGOb3jmq8OJpONMH+l8kZZorVgktrx74jSV4FltKKEibav9YWQhXh4gyReHKDxpDoiNWZ4Aj00eiURCiacXtM8SjBSzvFR5KEtpQz+MIzIKo+RtDzCfET5FIlmYmhz/PbyLkdpRPFP1SZcQiJpPlomUUkcQSNbJZEb2CqJLLfn0Ikc8twb4pDrG9ZhtWQYcuUIocEvpm6pShu16wWK9b4ToijF04wbFtY1nCP3XHlj5iPdiQNznCQFQ5tMjsra/VbnNBvSAJZhVnbaENsOFBviBaYRsa0GIyLbXQSdtwc608b30CnQSQsskZMe2lWQc/cgZ3oGPXLqoAtV5GSwdBXkHAO5ccQcZ44XU8wka3H0bYO9dwokdDwFSccJTCSltWwdSTMsakKyldH47pG0XRVJ6JoRbmdIHpFzkH38hXfYM1niAmD4OCaUkjlHZbkoEz8TvOHdW4dXB4hyt7MZdBk9zzdTnskaxpjmeDOM2QO/LGZlmwOgmUq8hmMRsWk4VjF1Czh6qlF0ndCA0WnQrKHfAoqXRcS2PiTfMvgo/g4FH3ux1LWHy3/Nwce4+KlhmgeqsEyEaZ6/K07bSRsl7h0YIYsYZe2FLOLUZ4LZg9RUi8pJJwjVS5TPJM7a8o1BG73Wmi14g+Xu+wSaLfJ8jb3lBbdcrl7xOHo3JN3Op/c2Vgd6sP6rM7/GYl/jsL+Tw+cMl3N5b3dD/FP5qgcwrnCLR7vGEXT3tb+c4GayoBWCvzd+X5WqraeVzqNqqGbaXfcAVX1vX/uLqRrcvMO4RuOXJcp/FgHCTTuMQDObptsvw/W6w2g7Lfj9YQ9jWzD62vykZ/r93eEoJ9l6IC8HMghVr8CGV0WyD8VbQ9K2fNUIemZyrKtY3Dbz0z2O5+Kopcbc4Ho5FTn4T/LJG7xwuV3GYUArLHCsKm4rGzhuNUl8U5PC48B1XOsi7z1eMSMg4xXdlQ9iFO+YIUaRNy4Ch3Nd+dvw5KF0umSeF2r1YFp7F1r72l/syZuT1wbhpf7A86JI77AWOqjYdmibepK/uNmdVIBWkzYUz/M4o5RXId7xTgGjOMmsIY5JNsEsMM6HMbsjGCURjdg/LmfgjEhM5aZtcZd7xAlHPlCyRvkHGwTDRTZtGgCjUUNtw/VVItBTepapEn3fVIl+Gyqx1TRzn4e7Uh4OBqZKlOUpb6wTnRAobPas/ToRag6B1v5indgw+dwn4q6ZiGvkqnMTXHVtrerFOcDV0N/X/nKuHlGb3AcaR1ZTAK0W7YqTt9Asi+lxPBdHXxtzvllT2BWO8ron2Q4lJjpoOizVt3D9UPUuPOe9xo6dhYtNBufNCorhEbMrfUx2czGZ46terNOwHKarmMw5Jya7mWKfo1Ya7IVPLeq76rqD4DbcUq3uzLtovpeMv/NhDaxCKdRVUFX7WXsUwcVTHJUj/RJJBd4uiZazCmHdQ4nWS5gMMa8HzRD9GFG05m+rM+Hpgf+aOOg+8V+TRju6QnS/5Urx4pOkJ9v+c3CgdOx8vVOtrqtPWDmOqXhscLrm4Xq6JMeOmmBzJnJOxjjlVcFTAYuuqQ6VA+9ZUvS+a4Khq+Z+QUOWr7vq7m7WV+71ZBU/9q3NUoXk+WbpKKMiQVZ8T+utnE/XdD63C2uck8fu/2xhjV5S6APTB+xuZY1ZzrOFzu2hO6BsoQadc8XlbNJ7q0EnimX6BW3HgOfrs6QmdHLetQ6dXC11EXRgL3Rm6N5Dp9Zb+Y4CXVPhXGfYmcnzOnb94u0D2IWhmjBvrJXrDLygCwdVLdTR6nQsBygu6tC3q9zrTeVaf7WPN4Rv5e56R9TpNeRYj02WDniGdsoeIsVjngdlkf8LowDbzBBdk/wHzvjRP8iKEeflc0yyFxsEm8uTnV2PfNdWy52rUV4b+UFHzpJvQtT1wIdA+/JPCL1+4Lfw5R+79UD3rPRpqPuPlqcx8sIquyMK9Hs107A4xtMyYA25zc7UjOkcZtz9A17KfcAxcxK9aeENsuHYh2nHwOlYqq9v2w0xdlf+om9WWJR4WhWAfbB28OsGamVFxfkagMBqmGtoA0Bofq3JwOuNZp6WKF7ljBTFx0+LO+78PqOhahNv7LncmzCmntSSwurLpPwxYjLHsdg+kI2u8G8w4JNJEu6vkjjBJZRZFzn/Ldcy1ee/QQM14OXUsM3UZ0+N26GGXlgnFzHUqeE1lEa0saIuMFOrH789czd0xSBJl6Y9X2P2TMDiufPlb4VhKFtg2eDb0+dHKeQFLFK+7M3HARURqlGjYzVMVYcNE5q2tCmnTVVvP9tc+uzbb2PDp/8A -------------------------------------------------------------------------------- /doc/fig/mgw_pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsnlab/tipsy/3e987f0f541222665b7046d1450aebe41a2aa0b3/doc/fig/mgw_pipeline.png -------------------------------------------------------------------------------- /doc/workflow.graph-easy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsnlab/tipsy/3e987f0f541222665b7046d1450aebe41a2aa0b3/doc/workflow.graph-easy.png -------------------------------------------------------------------------------- /doc/workflow.graph-easy.txt: -------------------------------------------------------------------------------- 1 | # Tipsy workflow / dataflow 2 | # 3 | # TIPSY: Telco pIPeline benchmarking SYstem 4 | # 5 | # Copyright (C) 2018 by its authors (See AUTHORS) 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but 13 | # WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | # General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | # Box with dotted border: commands 21 | # Box with solid border: data files 22 | # line label: additonal data required as input 23 | # 24 | # Generate the output graph easily with graph-easy: 25 | # 26 | # $ graph-easy --dot | dot -Tsvg 27 | # 28 | graph { flow: south; } 29 | node.cmd { border: dotted; } 30 | 31 | [gen-conf] {class: cmd; label: tipsy config (gen-conf)} 32 | [gen-pcap] {class: cmd; label: tipsy traffic-gen (gen-pcap)} 33 | [meas] {class: cmd; label: tipsy run} 34 | [meas-moon] {class: cmd; label: moongen-script} 35 | [eval] {class: cmd; label: tispy evaulate} 36 | [show] {class: cmd; label: tipsy visualize} 37 | [ryu] {class: cmd; label: ryu/tipsy} 38 | [bess] {class: cmd; label: bess/update-agent} 39 | 40 | [main]{label: main.json} --> [gen-conf] 41 | 42 | [gen-conf] --> [pipeline.json] 43 | [gen-conf] --> [pipeline-in.json] 44 | [gen-conf] --> [traffic.json] 45 | 46 | [traffic.json], [pipeline.json] --> [gen-pcap] 47 | --> [traffic.pcap] 48 | --> [meas] 49 | 50 | [pipeline.json] --> [ryu] 51 | [pipeline.json] --> [bess] 52 | 53 | [meas] 54 | -- main/sut/type --> [pipeline-type]{label: type?; border-width: 0;} 55 | 56 | [meas] 57 | -- main/tester/backend --> [meas-moon] 58 | --> [result.json] 59 | --> [eval] 60 | --> [statistics.json] 61 | --> [show] 62 | --> [*.png] 63 | 64 | [pipeline-type] --> [ryu] 65 | [pipeline-type] --> [bess] 66 | 67 | # Local Variables: 68 | # compile-command: "graph-easy --dot workflow.graph-easy.txt | dot -Tpng -o workflow.graph-easy.png" 69 | # End: 70 | -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hsnlab/tipsy/3e987f0f541222665b7046d1450aebe41a2aa0b3/lib/__init__.py -------------------------------------------------------------------------------- /lib/find_mod.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # TIPSY: Telco pIPeline benchmarking SYstem 4 | # 5 | # Copyright (C) 2018 by its authors (See AUTHORS) 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but 13 | # WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | # General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | # 21 | # ~tipsy/module/unique_id/modName_variant.extension 22 | # 23 | 24 | import glob as Glob 25 | import importlib 26 | import inspect 27 | import os 28 | import re 29 | import sys 30 | 31 | class add_path(): 32 | def __init__(self, path): 33 | self.path = path 34 | 35 | def __enter__(self): 36 | sys.path.insert(0, self.path) 37 | 38 | def __exit__(self, exc_type, exc_value, traceback): 39 | sys.path.remove(self.path) 40 | 41 | def find_file(rel_filename): 42 | cdir = os.path.dirname(__file__) 43 | mdir = os.path.join(cdir, '..', 'module') 44 | for module in os.listdir(mdir): 45 | base_dir = os.path.abspath(os.path.join(mdir, module)) 46 | if not os.path.isdir(base_dir): 47 | continue 48 | path = os.path.join(base_dir, rel_filename) 49 | path = os.path.abspath(path) 50 | if os.path.exists(path): 51 | return path 52 | return None 53 | 54 | def glob(pattern): 55 | matches = [] 56 | cdir = os.path.dirname(__file__) 57 | mdir = os.path.join(cdir, '..', 'module') 58 | for module in os.listdir(mdir): 59 | base_dir = os.path.abspath(os.path.join(mdir, module)) 60 | if not os.path.isdir(base_dir): 61 | continue 62 | matches += Glob.glob(os.path.join(base_dir, pattern)) 63 | return matches 64 | 65 | pipelines = [] 66 | def list_pipelines(update=False): 67 | global pipelines 68 | if pipelines and not update: 69 | return pipelines 70 | 71 | pl = [] 72 | fdir = os.path.dirname(__file__) 73 | fn = os.path.join(fdir, '..', 'schema', 'pipeline.json') 74 | try: 75 | with open(fn, 'r') as f: 76 | for line in f: 77 | m = re.search('pipeline-(.*)\.json#', line) 78 | if m: 79 | pl.append(m.group(1)) 80 | except FileNotFoundError: 81 | pass 82 | 83 | pipelines = sorted(pl) 84 | return pipelines 85 | 86 | def find_class(class_name, variant): 87 | rel_filename = '%s_%s.py' % (class_name, variant) 88 | path = find_file(rel_filename) 89 | if path: 90 | with add_path(os.path.dirname(path)): 91 | cl = importlib.import_module('%s_%s' % (class_name, variant), class_name) 92 | #print('Found rel_filename(%s) as path(%s)' % (rel_filename, path)) 93 | if not inspect.isclass(cl): 94 | # cl should be the class, but it's the package (?) 95 | cl = getattr(cl, class_name) 96 | return cl 97 | cl = inspect.stack()[1][0].f_globals['%s_%s' % (class_name, variant)] 98 | return cl 99 | 100 | def new(class_name, variant, *args, **kw): 101 | klass = find_class(class_name, variant) 102 | return klass(*args, **kw) 103 | -------------------------------------------------------------------------------- /lib/gen_pcap_base.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from itertools import izip, chain, repeat 19 | import random 20 | import traceback 21 | 22 | from scapy.all import * 23 | 24 | def byte_seq(template, seq): 25 | return template % (int(seq / 254), (seq % 254) + 1) 26 | 27 | # https://stackoverflow.com/a/312644 28 | def grouper(n, iterable, padvalue=None): 29 | "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')" 30 | return izip(*[chain(iterable, repeat(padvalue, n-1))]*n) 31 | 32 | 33 | class PicklablePacket(object): 34 | """A container for scapy packets that can be pickled (in contrast 35 | to scapy packets themselves). https://stackoverflow.com/a/4312192""" 36 | 37 | __slots__ = ['contents', 'time'] 38 | 39 | def __init__(self, pkt): 40 | self.contents = bytes(pkt) 41 | self.time = pkt.time 42 | 43 | def __call__(self): 44 | """Get the original scapy packet.""" 45 | pkt = Ether(self.contents) 46 | pkt.time = self.time 47 | return pkt 48 | 49 | 50 | class GenPkt(object): 51 | def __init__(self, args, conf, in_que, out_que): 52 | self.args = args 53 | self.conf = conf 54 | self.in_que = in_que 55 | self.out_que = out_que 56 | 57 | def create_work_items(self, job_size): 58 | pkt_num = self.get_pkt_num() 59 | pkt_idx = list(range(pkt_num)) 60 | random.shuffle(pkt_idx) 61 | items = [] 62 | for job_idx, pkt_idxs in enumerate(grouper(job_size, pkt_idx)): 63 | pkt_idxs = [idx for idx in pkt_idxs if idx is not None] 64 | items.append({'job_idx': job_idx, 'pkt_idxs': pkt_idxs}) 65 | return items 66 | 67 | def do_work(self): 68 | try: 69 | while True: 70 | item = self.in_que.get() 71 | if item is None: 72 | break 73 | pkt_idxs = item['pkt_idxs'] 74 | pkts = [PicklablePacket(self.gen_pkt(idx)) for idx in pkt_idxs] 75 | item['pkts'] = pkts 76 | del item['pkt_idxs'] 77 | self.out_que.put(item) 78 | except Exception as e: 79 | item = {'exception': e, 'traceback': traceback.format_exc()} 80 | self.out_que.put(item) 81 | return True 82 | 83 | def gen_pkt(self, pkt_idx): 84 | raise NotImplementedError 85 | 86 | def get_pkt_num(self): 87 | "Return the number of packets to be generated" 88 | if self.args.pkt_num: 89 | self.args.auto_pkt_num = False 90 | return self.args.pkt_num 91 | self.args.auto_pkt_num = True 92 | return self.get_auto_pkt_num() 93 | 94 | def get_auto_pkt_num(self): 95 | raise NotImplementedError 96 | 97 | @staticmethod 98 | def add_payload(p, pkt_size): 99 | if len(p) < pkt_size: 100 | #"\x00" is a single zero byte 101 | s = "\x00" * (pkt_size - len(p)) 102 | p = p / Raw(s) 103 | return p 104 | 105 | def get_other_direction(self): 106 | return {'u': 'd', 'd': 'u'}[self.args.dir[0]] 107 | 108 | 109 | -------------------------------------------------------------------------------- /lib/main-makefile.in: -------------------------------------------------------------------------------- 1 | tipsy=@tipsy@ 2 | m_dir := $(sort $(wildcard measurements/[0-9][0-9][0-9])) 3 | results := $(foreach dir,$(m_dir),$(dir)/results.json) 4 | p_dir := $(sort $(wildcard plots/[0-9][0-9][0-9])) 5 | plots := $(foreach dir,$(p_dir),$(dir)/out.json) 6 | 7 | .PHONY: all plots 8 | 9 | all: measurements/result.json plots 10 | 11 | plots: $(plots) plots/fig.pdf 12 | 13 | $(plots): measurements/result.json 14 | $(MAKE) -C $(dir $@) 15 | 16 | .ONESHELL: 17 | plots/fig.pdf: $(plots) plots/fig.tex 18 | ifneq ($(plots),) 19 | cd plots 20 | pdflatex -shell-escape fig.tex 21 | else 22 | @echo No figs to generate 23 | endif 24 | 25 | plots/fig.tex: .tipsy.json 26 | 27 | .ONESHELL: 28 | measurements/result.json: $(results) 29 | @sep="" 30 | echo '[' > $@ 31 | for r in $(results); do 32 | echo $$sep >> $@ 33 | cat $$r >> $@ 34 | sep="," 35 | done 36 | echo ']' >> $@ 37 | 38 | $(results): .tipsy.json 39 | $(MAKE) -C $(dir $@) || exit 40 | 41 | .tipsy.json: *.json 42 | @echo '$@ is older than: $?' 43 | echo 'to regenerate the file, run:' 44 | echo ' $(tipsy) config $^' 45 | echo 'or' 46 | echo ' $(tipsy) config -f $^' 47 | echo 'to ignore this warning, run:' 48 | echo ' touch $@; make' 49 | false 50 | -------------------------------------------------------------------------------- /lib/object_with_config.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2017-2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import logging 19 | import json 20 | 21 | class ObjectView(object): 22 | def __init__(self, **kwargs): 23 | self.__dict__.update(kwargs) 24 | 25 | def __repr__(self): 26 | return self.__dict__.__repr__() 27 | 28 | def __getattr__(self, name): 29 | return self.__dict__[name.replace('_', '-')] 30 | 31 | def get (self, attr, default=None): 32 | return self.__dict__.get(attr, default) 33 | 34 | 35 | class ObjectWithConfig(object): 36 | def __init__(self): 37 | self.logger = logging.getLogger(__name__) 38 | 39 | def parse_conf(self, var_name, fname): 40 | self.logger.info("conf_file: %s" % fname) 41 | 42 | try: 43 | with open(fname, 'r') as f: 44 | conv_fn = lambda d: ObjectView(**d) 45 | config = json.load(f, object_hook=conv_fn) 46 | except IOError as e: 47 | self.logger.error('Failed to load cfg file (%s): %s' % 48 | (fname, e)) 49 | raise(e) 50 | except ValueError as e: 51 | self.logger.error('Failed to parse cfg file (%s): %s' % 52 | (fname, e)) 53 | raise(e) 54 | setattr(self, var_name, config) 55 | -------------------------------------------------------------------------------- /lib/per-dir-makefile.in: -------------------------------------------------------------------------------- 1 | tipsy=@tipsy@ 2 | tipsy_dir=$(dir $(tipsy)) 3 | gen_pcap=$(tipsy_dir)/lib/gen_pcap.py 4 | 5 | results.json: traffic.pcap benchmark.json 6 | $(tipsy_dir)/lib/run_measurement.py 7 | 8 | pipeline-in.json: benchmark.json 9 | $(tipsy_dir)/utils/extract $^ pipeline > $@ 10 | 11 | traffic.json: benchmark.json 12 | $(tipsy_dir)/utils/extract $^ traffic > $@ 13 | 14 | pipeline.json: pipeline-in.json 15 | $(tipsy_dir)/lib/gen_conf.py -j $^ -o $@ 16 | 17 | .DELETE_ON_ERROR: 18 | traffic.pcap: traffic.json pipeline.json 19 | $(gen_pcap) --json traffic.json --conf pipeline.json --output $@ 20 | -------------------------------------------------------------------------------- /lib/pipeline.json.in: -------------------------------------------------------------------------------- 1 | { 2 | "title": "High-level pipeline description. @auto@. Run 'tipsy update-mod' after the 'module' directory changes.", 3 | "oneOf": [ 4 | @pipelines@ 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /lib/plot-makefile.in: -------------------------------------------------------------------------------- 1 | tipsy=@tipsy@ 2 | tipsy_lib=$(dir $(tipsy))lib 3 | plot=$(tipsy_lib)/plot.py 4 | 5 | .DELETE_ON_ERROR: 6 | out.json: plot.json 7 | $(plot) 8 | 9 | -------------------------------------------------------------------------------- /lib/plot.json.in: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Definition of one plot", 3 | "description": "@auto@", 4 | "type": "object", 5 | "properties": { 6 | "type": { 7 | "type": "string", 8 | "enum": ["@plot-types@"], 9 | "default": "simple", 10 | "description": "Name of algorithm that generates the plot. (USL: fits Universal Scalability Law curve)" 11 | }, 12 | "x-axis": { 13 | "$ref": "definitions.json#/hierarchical-property-name", 14 | "description": "variable of the x-axis" 15 | }, 16 | "y-axis": { 17 | "anyOf": [ 18 | { "type": "string", 19 | "$ref": "definitions.json#/hierarchical-property-name" 20 | }, 21 | { "type": "array", 22 | "items": {"$ref": "definitions.json#/hierarchical-property-name"} 23 | } 24 | ], 25 | "description": "variable or a list of variables to plot" 26 | }, 27 | "z-axis": { 28 | "$ref": "definitions.json#/hierarchical-property-name", 29 | "description": "variable of the z-axis (in case of a contour plot)" 30 | }, 31 | "group-by": { 32 | "anyOf": [ 33 | { "type": "string", 34 | "$ref": "definitions.json#/hierarchical-property-name" 35 | }, 36 | { "type": "array", 37 | "items": {"$ref": "definitions.json#/hierarchical-property-name"} 38 | } 39 | ], 40 | "default": [], 41 | "description": "Group x-values by a variable" 42 | }, 43 | "filter": { 44 | "type": "object", 45 | "default" : {}, 46 | "description": "Filter values to plot by this mongodb query expression. If mongoquery is not installed, then only a subset of the query language can be used. See: https://pypi.python.org/pypi/mongoquery/" 47 | }, 48 | "aggregate": { 49 | "type": "array", 50 | "default" : [], 51 | "description": "Pipeline definition for pymongo. Requires running mongodb server." 52 | }, 53 | "title": { 54 | "type": "string", 55 | "default": "", 56 | "description": "The format string used for title, e.g., 'Throught of ovs version {sut.version}'" 57 | }, 58 | "axis-type": { 59 | "type": "string", 60 | "enum": ["normal", "semilogx", "semilogy", "loglog"], 61 | "default": "normal", 62 | "description": "Type of the axis. See 'The Axis Environment' in pgfplots.pdf" 63 | } 64 | }, 65 | "required": ["x-axis", "y-axis"], 66 | "additionalProperties": false 67 | } 68 | -------------------------------------------------------------------------------- /lib/plot_base.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | class Plot(object): 19 | def __init__(self, conf): 20 | self.conf = conf 21 | self.preamble = "" 22 | 23 | def write_preamble(self): 24 | with open('preamble.tex', 'w') as f: 25 | f.write(self.preamble) 26 | 27 | def plot(self, filtered_data): 28 | raise NotImplementedError 29 | -------------------------------------------------------------------------------- /lib/run_measurement.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # TIPSY: Telco pIPeline benchmarking SYstem 4 | # 5 | # Copyright (C) 2018 by its authors (See AUTHORS) 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but 13 | # WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | # General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import json 21 | import subprocess 22 | from pathlib import Path, PosixPath 23 | 24 | import find_mod 25 | 26 | __all__ = ["run"] 27 | 28 | class Config(dict): 29 | def __init__(self, *files, **kwargs): 30 | self.update(kwargs) 31 | for f in files: 32 | self.load(f) 33 | 34 | def __getattr__(self, name): 35 | return self[name.replace('_', '-')] 36 | 37 | def __setattr__(self, name, value): 38 | self[name.replace('_', '-')] = value 39 | 40 | def __delattr__(self, name): 41 | del self[name.replace('_', '-')] 42 | 43 | def load(self, file): 44 | """Update dict with json encode data from `file`. 45 | file is a filename or Path.""" 46 | 47 | oh = lambda x: Config(**x) 48 | try: 49 | if type(file) == PosixPath: 50 | with file.open('r') as f: 51 | data = json.load(f, object_hook=oh) 52 | else: 53 | with open(file, 'r') as f: 54 | data = json.load(f, object_hook=oh) 55 | except Exception as e: 56 | print(e) 57 | exit(-1) 58 | self.update(**data) 59 | 60 | 61 | def run(defaults=None): 62 | cwd = Path().cwd() 63 | conf = Config(cwd / 'benchmark.json') 64 | sut = find_mod.new('SUT', conf.sut.type, conf) 65 | sut.start() 66 | 67 | tester_type = conf.tester.type.replace('-','_') 68 | tester = find_mod.new('Tester', tester_type, conf) 69 | tester.run(cwd) 70 | 71 | sut.stop() 72 | 73 | result = conf 74 | result['out'] = {'sut': sut.result} 75 | result['out'].update(tester.result) 76 | with open('results.json', 'w') as f: 77 | json.dump(result, f, sort_keys=True, indent=4) 78 | 79 | 80 | if __name__ == "__main__": 81 | run() 82 | -------------------------------------------------------------------------------- /lib/tester_base.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import datetime 19 | import subprocess 20 | import time 21 | from pathlib import Path 22 | 23 | class Tester(object): 24 | def __init__(self, conf): 25 | self.conf = conf 26 | self.result = {} 27 | 28 | cwd = str(Path(__file__).parent) 29 | cmd = ['git', 'describe', '--dirty', '--always', '--tags'] 30 | try: 31 | v = subprocess.run(cmd, stdout=subprocess.PIPE, cwd=cwd, check=True) 32 | self.result['tipsy-version'] = v.stdout.decode().strip() 33 | except Exception as e: 34 | self.result['tipsy-version'] = 'n/a' 35 | self.result['tipsy-version-error-msg'] = str(e) 36 | 37 | def run(self, out_dir): 38 | self.result['timestamp'] = int(time.time()) 39 | self.result['iso-date'] = datetime.datetime.now().isoformat() 40 | self.result['test-id'] = out_dir.name 41 | self.run_setup_script() 42 | self._run(out_dir) 43 | self.run_teardown_script() 44 | self.collect_results() 45 | 46 | def _run(self, out_dir): 47 | raise NotImplementedError 48 | 49 | def collect_results(self): 50 | raise NotImplementedError 51 | 52 | def run_script(self, script): 53 | if Path(script).is_file(): 54 | subprocess.run([str(script)], check=True) 55 | 56 | def run_setup_script(self): 57 | self.run_script(self.conf.tester.setup_script) 58 | 59 | def run_teardown_script(self): 60 | self.run_script(self.conf.tester.teardown_script) 61 | 62 | 63 | -------------------------------------------------------------------------------- /lib/wait_for_callback.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # TIPSY: Telco pIPeline benchmarking SYstem 4 | # 5 | # Copyright (C) 2018 by its authors (See AUTHORS) 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but 13 | # WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | # General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import time 21 | from threading import Thread 22 | from http.server import BaseHTTPRequestHandler, HTTPServer 23 | 24 | class MyHandler(BaseHTTPRequestHandler): 25 | def do_GET(self): 26 | if self.path not in self.w_urls.keys(): 27 | self.send_response(404) 28 | return 29 | self.send_response(200) 30 | self.send_header("Content-type", "application/json") 31 | self.end_headers() 32 | self.wfile.write(bytes("\"%s\"\n" % self.path, 'utf-8')) 33 | 34 | def shutdown(server): 35 | server.shutdown() 36 | self.httpd.success = self.w_urls[self.path] 37 | Thread(target=shutdown, args=(self.httpd,)).start() 38 | 39 | def wait_for_request(host_name, port_number, urls): 40 | httpd = HTTPServer((host_name, port_number), MyHandler) 41 | httpd.RequestHandlerClass.w_urls = urls 42 | httpd.RequestHandlerClass.httpd = httpd 43 | interrupted = False 44 | try: 45 | httpd.serve_forever() 46 | except KeyboardInterrupt: 47 | interrupted = True 48 | finally: 49 | httpd.server_close() 50 | return not interrupted and httpd.success 51 | 52 | 53 | if __name__ == '__main__': 54 | urls = {'/configured': True, '/failed': False} 55 | params = ('127.0.0.1', 9000, urls) 56 | print('Waiting for %s:%s%s' % (params[0], params[1], ' or '.join(urls))) 57 | if wait_for_request(*params): 58 | print('Got it') 59 | else: 60 | print('Failed') 61 | exit(-1) 62 | -------------------------------------------------------------------------------- /module/erfs/RyuApp_erfs.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import os 19 | import re 20 | import subprocess 21 | import sys 22 | import time 23 | 24 | from ryu.lib import hub 25 | 26 | 27 | fdir = os.path.dirname(os.path.realpath(__file__)) 28 | sys.path.append(os.path.join(fdir, '..', '..', 'lib')) 29 | import find_mod 30 | 31 | RyuAppOpenflow = find_mod.find_class('RyuApp', 'openflow') 32 | 33 | class RyuApp(RyuAppOpenflow): 34 | def __init__(self, *args, **kwargs): 35 | if 'switch_type' not in kwargs: 36 | kwargs['switch_type'] = ['erfs', 'openflow'] 37 | self.core_idx = 0 # next core to allocate in the core_list 38 | with find.add_path(os.path.dirname(__file__)): 39 | import sw_conf_erfs as sw_conf 40 | self.sw_conf = sw_conf 41 | 42 | super(RyuApp, self).__init__(*args, **kwargs) 43 | 44 | def add_port(self, br_name, port_name, iface, core=1): 45 | self.sw_conf.add_port(br_name, port_name, iface, core) 46 | 47 | def get_cores(self, num_cores): 48 | coremask = self.bm_conf.sut.coremask 49 | cpum = int(coremask, 16) 50 | core_list = [i for i in range(32) if (cpum >> i) & 1 == 1] 51 | ## lcore 0 is reserved for the controller 52 | #core_list = [i for i in core_list if i != 0] 53 | cores = [] 54 | for i in range(num_cores): 55 | cores.append(core_list[self.core_idx]) 56 | self.core_idx = (self.core_idx + 1) % len(core_list) 57 | return cores 58 | 59 | def initialize_datapath(self): 60 | self.change_status('start_erfs') 61 | 62 | coremask = self.bm_conf.sut.coremask 63 | os.system('pkill dof') 64 | #cmd = ['./dof', '-c', coremask, '--socket-mem=1024,1024', '--', '-d', '10'] 65 | cmd = ['./dof', '-c', coremask, '--', '-d', '10'] 66 | cwd = self.bm_conf.sut.erfs_dir 67 | subprocess.Popen(cmd, cwd=cwd) 68 | time.sleep(15) 69 | 70 | self.change_status('initialize_datapath') 71 | self.sw_conf.init_sw() 72 | 73 | br_num = 1 # 'br-main' 74 | self.sw_conf.add_bridge(br_num) 75 | 76 | core = self.get_cores(self.bm_conf.pipeline.core) 77 | self.add_port(br_num, 1, self.ul_port_name, core=core) 78 | self.add_port(br_num, 2, self.dl_port_name, core=core) 79 | self.ul_port = 1 80 | self.dl_port = 2 81 | 82 | hub.spawn_after(1, self.sw_conf.set_controller, br_num, '127.0.0.1') 83 | 84 | def stop_datapath(self): 85 | self.sw_conf.del_bridge(1) 86 | 87 | def get_tun_port(self, tun_end): 88 | "Get SUT port to tun_end" 89 | return self.ports['tun-%s' % tun_end] 90 | -------------------------------------------------------------------------------- /module/erfs/SUT_erfs.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from pathlib import Path 19 | 20 | import find_mod 21 | Base = find_mod.find_class('SUT', 'openflow') 22 | 23 | class SUT(Base): 24 | pass 25 | -------------------------------------------------------------------------------- /module/erfs/sut-erfs.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "type": { 4 | "type": "string", 5 | "enum": ["erfs"], 6 | "description": "erfs: L. Molnar et al., 'Dataplane specialization for high performance OpenFlow software switching,' in ACM SIGCOMM, 2016, pp. 539-552." 7 | }, 8 | "erfs-dir": { 9 | "type": "string", 10 | "default": "/opt/erfs", 11 | "description": "A directory on SUT in which erfs is installed" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /module/erfs/sw_conf_erfs.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2017-2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import logging 19 | import os 20 | import subprocess 21 | import socket 22 | import time 23 | from subprocess import Popen, PIPE, STDOUT 24 | 25 | id2name = {} 26 | log = logging.getLogger(__name__) 27 | 28 | def call(cmd): 29 | log.warn(cmd) 30 | p = Popen(['nc', 'localhost', '16632'], stdout=PIPE, stdin=PIPE, stderr=STDOUT) 31 | r = p.communicate(input=b'%s\n' % cmd)[0] 32 | # if r.decode() != "OK": 33 | # pass 34 | ##p.terminate() 35 | 36 | def init_sw(): 37 | call('config demo_mode 1') 38 | pass 39 | 40 | def add_bridge(br_number, **kw): 41 | call('add-switch dpid=%s' % br_number) 42 | 43 | def del_bridge(br_num, **kw): 44 | call('remove-switch dpid=%s' % br_num) 45 | 46 | def set_controller(br_num, target, **kw): 47 | ryu_address = '127.0.0.1' 48 | ryu_ctrl = 6633 49 | sw_address = target 50 | sw_ctrl = 16632 + br_num 51 | args = ["socat", "TCP:{}:{}".format(ryu_address, ryu_ctrl), "TCP:{}:{}".format(sw_address, sw_ctrl)] 52 | 53 | subprocess.Popen(args) 54 | 55 | core_assignment = {} 56 | def add_port(dpid, portid, port_name, cores, **options): 57 | core_num = len(cores) 58 | call('add-port dpid=%s port-num=%s PCI:%s rx-queues=%s' % ( 59 | dpid, portid, port_name, core_num)) 60 | for que, core in enumerate(cores, start=0): 61 | current_assignment = core_assignment.get(core, []) 62 | current_assignment.append('PCI:%s/%s' % (port_name, que)) 63 | core_assignment[core] = current_assignment 64 | call('lcore %s %s' % (core, ' '.join(current_assignment))) 65 | 66 | def set_arp(bridge, ip, mac): 67 | cmd=['sudo', 'ovs-appctl', 'tnl/arp/set', bridge, ip, mac] 68 | if subprocess.call(cmd, stdout=open(os.devnull, 'w')): 69 | log.error('ovs-appctl failed (%s)' % ' '.join(cmd)) 70 | -------------------------------------------------------------------------------- /module/gwlb/GenConf_gwlb.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from gen_conf_base import GenConf as Base 19 | from gen_conf_base import byte_seq 20 | 21 | class GenConf (Base): 22 | "Cloud access-gateway & load-balancer" 23 | 24 | def __init__ (self, args): 25 | super().__init__(args) 26 | self.components += ['fakedrop', 'service'] 27 | 28 | def get_prefix_tree (self, new_elements, tree=None): 29 | import random 30 | from socket import inet_ntoa, inet_aton 31 | from struct import pack, unpack 32 | 33 | if new_elements == 0: 34 | return tree 35 | 36 | if tree is None: 37 | tree = [('0.0.0.0', 0)] 38 | return self.get_prefix_tree(new_elements - 1, tree) 39 | 40 | ok = False 41 | while not ok: 42 | subtree = random.choice(tree) 43 | prefix, prefix_len = subtree 44 | ok = prefix_len < 24 45 | idx = tree.index(subtree) 46 | del tree[idx] 47 | 48 | # split subtree 49 | prefix_len += 1 50 | prefix_b = unpack('!I', inet_aton(prefix))[0] 51 | prefix_2 = prefix_b | (1 << (32 - prefix_len)) 52 | prefix_2 = pack('!I', prefix_2) 53 | # add new subtrees 54 | tree += [(prefix, prefix_len), (inet_ntoa(prefix_2), prefix_len)] 55 | 56 | return self.get_prefix_tree(new_elements -1, tree) 57 | 58 | 59 | def add_service (self): 60 | from socket import inet_ntoa 61 | from struct import pack 62 | self.conf['service'] = [] 63 | self.conf['gw'] = {'mac': 'aa:cc:dd:cc:ac:dc'} 64 | for s in range(self.args.service_num): 65 | backends = [] 66 | prefixes = self.get_prefix_tree(self.args.backend_num) 67 | for (ip_src, prefix_len) in prefixes: 68 | backend = {'output': None, # send via the uplink port 69 | 'ip-src': ip_src, 70 | 'prefix-len': prefix_len} 71 | backends.append(backend) 72 | if s % 2 == 0: 73 | udp_dst = 319 # 319: Precision_Time_Protocol port 74 | else: 75 | udp_dst = 400 + s 76 | service = {'backend': backends, 77 | 'ip_dst': byte_seq('192.0.%d.%d', s), 78 | 'udp_dst': '%d' % udp_dst, 79 | } 80 | self.conf['service'].append(service) 81 | 82 | for idx in range(self.args.fluct_port): 83 | mod_port = {'action': 'mod_port', 84 | 'args': { 85 | 's_idx': idx % self.args.service_num 86 | }} 87 | self.conf['run_time'].append(mod_port) 88 | 89 | -------------------------------------------------------------------------------- /module/gwlb/GenPkt_gwlb.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import random 19 | from scapy.all import * 20 | 21 | from gen_pcap_base import GenPkt as Base 22 | 23 | class GenPkt(Base): 24 | 25 | def get_auto_pkt_num(self): 26 | services = len(self.conf.service) 27 | backends = len(self.conf.service[0].backend) 28 | # 10 pkts for each backend 29 | return 10 * services * backends 30 | 31 | def gen_pkt(self, pkt_idx): 32 | services = len(self.conf.service) 33 | backends = len(self.conf.service[0].backend) 34 | 35 | service_idx = (pkt_idx // backends) % services 36 | backend_idx = pkt_idx % backends 37 | service = self.conf.service[service_idx] 38 | backend = service.backend[backend_idx] 39 | 40 | if backend.prefix_len > 24: 41 | raise Exception('prefix (%d) > 24' % backend.prefix_len) 42 | ip_src = backend.ip_src.split('.') 43 | ip_src[-1] = str(random.randint(1, 254)) 44 | ip_src = '.'.join(ip_src) 45 | udp_src = 22 + (pkt_idx % 1000) 46 | 47 | pkt = ( 48 | Ether(dst=self.conf.gw.mac) / 49 | IP(src=ip_src, dst=service.ip_dst) / 50 | UDP(sport=udp_src, dport=int(service.udp_dst)) 51 | ) 52 | 53 | pkt = self.add_payload(pkt, self.args.pkt_size) 54 | return pkt 55 | 56 | -------------------------------------------------------------------------------- /module/gwlb/bess-updater-gwlb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # TIPSY: Telco pIPeline benchmarking SYstem 4 | # 5 | # Copyright (C) 2018 by its authors (See AUTHORS) 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but 13 | # WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | # General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import argparse 21 | import binascii 22 | import itertools 23 | import json 24 | import re 25 | import requests 26 | import signal 27 | import struct 28 | import socket 29 | import subprocess 30 | import sys 31 | import time 32 | from pathlib import Path 33 | 34 | 35 | class BessUpdaterGwlb(BessUpdater): 36 | def _run(self): 37 | try: 38 | i = self.bess.get_module_info('tbl_two_w0_s1') 39 | self.itype = 'goto' 40 | except Exception as e: 41 | self.itype = 'universal' 42 | while self._running: 43 | for task in self.conf.run_time: 44 | if not self._running: 45 | return 46 | getattr(self, 'do_%s' % task.action)(task.args) 47 | time.sleep(self.runtime_interval) 48 | 49 | def mod_port_universal(self, args, wid): 50 | name = 'tbl_one_%d' % wid 51 | ogate = 1 52 | s = self.conf.service[args.s_idx] 53 | for b in s.backend: 54 | src_ip_mask = 0xffffffff ^ (1 << 32 - b.prefix_len) - 1 55 | src_ip_mask = struct.pack("!I", src_ip_mask) 56 | values = [ 57 | {'value_bin': struct.pack("!H", 0x0800)}, # ethertype == IP 58 | {'value_bin': struct.pack("!B", 17)}, # UDP 59 | {'value_bin': aton(b.ip_src)}, 60 | {'value_bin': aton(s.ip_dst)}, 61 | {'value_bin': struct.pack("!H", int(s.udp_dst))}, 62 | ] 63 | masks = [ 64 | {'value_int': 0xffff}, 65 | {'value_int': 0xff}, 66 | {'value_bin': src_ip_mask}, 67 | {'value_int': 0xffFFffFF}, 68 | {'value_int': 0xffff}, 69 | ] 70 | argsD = {'values': values, 'masks': masks} 71 | argsA = {'values': values, 'masks': masks, 'gate': ogate} 72 | self.bess.pause_worker(wid) 73 | self.bess.run_module_command( 74 | name, 'delete', 'WildcardMatchCommandDeleteArg', argsD) 75 | self.bess.run_module_command( 76 | name, 'add', 'WildcardMatchCommandAddArg', argsA) 77 | 78 | def mod_port_goto(self, args, wid): 79 | name = 'tbl_one_%d' % wid 80 | ogate = int(args.s_idx) 81 | s = self.conf.service[args.s_idx] 82 | fields = [ 83 | {'value_bin': struct.pack("!H", 0x0800)}, 84 | {'value_bin': struct.pack("!B", 17)}, # UDP 85 | {'value_bin': aton(s.ip_dst)}, 86 | {'value_bin': struct.pack("!H", int(s.udp_dst))}, 87 | ] 88 | argsD = {'fields': fields} 89 | argsA = {'fields': fields, 'gate': ogate} 90 | self.bess.pause_worker(wid) 91 | self.bess.run_module_command( 92 | name, 'delete', 'ExactMatchCommandDeleteArg', argsD) 93 | self.bess.run_module_command( 94 | name, 'add', 'ExactMatchCommandAddArg', argsA) 95 | 96 | def do_mod_port(self, args): 97 | for wid in range(self.conf.core): 98 | try: 99 | attr = getattr(self, 'mod_port_%s' % self.itype) 100 | attr(args, wid) 101 | except BESS.Error: 102 | raise 103 | finally: 104 | self.bess.resume_worker(wid) 105 | -------------------------------------------------------------------------------- /module/gwlb/pipeline-gwlb.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Configuration of a Could access-gateway/load-balancer pipeline", 3 | "type": "object", 4 | "properties": { 5 | "name": { 6 | "type": "string", 7 | "enum": ["gwlb"], 8 | "description": 9 | "name of the pipeline, must be set to gwlb" 10 | }, 11 | "fakedrop": { 12 | "type": "boolean", 13 | "description": 14 | "whether to actually drop unmatched packets (False) or send them immediately to the output port (True) for correct rate measurements", 15 | "default": true 16 | }, 17 | "implementation-type": { 18 | "type": "string", 19 | "enum": ["universal", "metadata", "goto"], 20 | "description": "Type of the internal implementation of the pipeline.", 21 | "default": "universal" 22 | }, 23 | "service-num": { 24 | "$ref": "definitions.json#/positive-integer", 25 | "default": 3, 26 | "description": "Number of services" 27 | }, 28 | "backend-num": { 29 | "$ref": "definitions.json#/non-negative-integer", 30 | "default": 2, 31 | "description": "number of backends per service" 32 | }, 33 | "fluct-port": { 34 | "$ref": "definitions.json#/non-negative-integer", 35 | "description": "number of port change events per sec", 36 | "default": 0 37 | }, 38 | "core": { 39 | "$ref": "definitions.json#/positive-integer", 40 | "description": "number of CPU cores/workers running the pipeline", 41 | "default": 1 42 | } 43 | }, 44 | "required": ["name"], 45 | "additionalProperties": false 46 | } 47 | -------------------------------------------------------------------------------- /module/lagopus/RyuApp_lagopus.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import os 19 | import subprocess 20 | import sys 21 | import time 22 | 23 | from ryu.lib import hub 24 | 25 | 26 | fdir = os.path.dirname(os.path.realpath(__file__)) 27 | sys.path.append(os.path.join(fdir, '..', '..', 'lib')) 28 | import find_mod 29 | 30 | RyuAppOpenflow = find_mod.find_class('RyuApp', 'openflow') 31 | 32 | class RyuApp(RyuAppOpenflow): 33 | def __init__(self, *args, **kwargs): 34 | if 'switch_type' not in kwargs: 35 | kwargs['switch_type'] = ['lagopus', 'openflow'] 36 | self.core_idx = 0 # next core to allocate in the core_list 37 | 38 | super(RyuApp, self).__init__(*args, **kwargs) 39 | 40 | def get_cores(self, num_cores): 41 | coremask = self.bm_conf.sut.coremask 42 | cpum = int(coremask, 16) 43 | core_list = [i for i in range(32) if (cpum >> i) & 1 == 1] 44 | ## lcore 0 is reserved for the controller 45 | #core_list = [i for i in core_list if i != 0] 46 | cores = [] 47 | for i in range(num_cores): 48 | cores.append(core_list[self.core_idx]) 49 | self.core_idx = (self.core_idx + 1) % len(core_list) 50 | return cores 51 | 52 | def initialize_datapath(self): 53 | self.change_status('start_lagopus') 54 | 55 | num_cores = self.bm_conf.pipeline.core 56 | if num_cores == 1: 57 | num_cores += 1 58 | self.logger.warn('num_cores is increased to %d' % num_cores) 59 | 60 | corelist = [str(c) for c in self.get_cores(num_cores)] 61 | self.logger.warn('%s', corelist) 62 | config = os.path.join(fdir, 'lagopus.dsl') 63 | # If lagopus is restarted without any delay, it cannot grab the 64 | # hugepages. We should wait elsewhere, but this is the only 65 | # switch exhibiting this behavior. 66 | time.sleep(3) 67 | cmd = ['lagopus', '-C', config, '-d', 68 | '--', '-l%s' % ','.join(corelist), '-n2', 69 | '--', '-p3'] 70 | subprocess.Popen(cmd, cwd=fdir) 71 | 72 | self.ul_port = int(self.bm_conf.sut.uplink_port) + 1 73 | self.dl_port = int(self.bm_conf.sut.downlink_port) + 1 74 | 75 | self.change_status('initialize_datapath') 76 | 77 | def stop_datapath(self): 78 | os.system('echo stop|lagosh') 79 | 80 | def get_tun_port(self, tun_end): 81 | "Get SUT port to tun_end" 82 | return self.ports['tun-%s' % tun_end] 83 | 84 | def configure_1(self): 85 | self.change_status('configure_1') 86 | parser = self.dp.ofproto_parser 87 | 88 | self.insert_fakedrop_rules() 89 | self.pl.config_switch(parser) 90 | 91 | # Finally, send and wait for a barrier 92 | msg = parser.OFPBarrierRequest(self.dp) 93 | msgs = [] 94 | # doesn't work with ryu-lagopus 95 | #ofctl.send_stats_request(self.dp, msg, self.waiters, msgs, self.logger) 96 | 97 | self.handle_configured() 98 | -------------------------------------------------------------------------------- /module/lagopus/SUT_lagopus.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import subprocess 19 | from pathlib import Path 20 | 21 | import find_mod 22 | Base = find_mod.find_class('SUT', 'openflow') 23 | 24 | class SUT(Base): 25 | def __init__(self, conf): 26 | super().__init__(conf) 27 | self.virtualenv = self.conf.sut.lagopus_virtualenv 28 | 29 | def _query_version(self): 30 | v = self.run_ssh_cmd(['lagopus', '--version'], stdout=subprocess.PIPE) 31 | first_line = v.stdout.decode('utf8').split("\n")[0] 32 | self.result['version'] = first_line.split(' ')[-1] 33 | -------------------------------------------------------------------------------- /module/lagopus/SUT_lagopus_bng.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2017-2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import find_mod 19 | BaseLago = find_mod.find_class('SUT_lagopus', 'mgw') 20 | BaseErfs = find_mod.find_class('SUT_erfs', 'bng') 21 | 22 | class SUT_lagopus(BaseLago, BaseErfs): 23 | pass 24 | 25 | # TODO: this inheritance should be carefully tested 26 | -------------------------------------------------------------------------------- /module/lagopus/SUT_lagopus_mgw.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2017-2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from ryu.lib.packet import in_proto 19 | from ryu.lib.packet.ether_types import ETH_TYPE_IP 20 | 21 | import find_mod 22 | Base = find_mod.find_class('SUT_erfs', 'mgw') 23 | 24 | class SUT_lagopus(Base): 25 | 26 | def init_backend(self): 27 | # Backend specific initialization 28 | pass 29 | 30 | def get_vxlan_encap_actions(self, vxlan_vni, tun_ip_src, tun_ip_dst): 31 | # https://github.com/lagopus/lagopus/issues/92 32 | ofp = self.parent.dp.ofproto 33 | parser = self.parent.dp.ofproto_parser 34 | 35 | type_eth = (ofp.OFPHTN_ONF << 16) | ofp.OFPHTO_ETHERNET 36 | type_ip = (ofp.OFPHTN_ETHERTYPE << 16) | 0x0800 37 | type_udp = (ofp.OFPHTN_IP_PROTO << 16) | 17 38 | type_vxl = (ofp.OFPHTN_UDP_TCP_PORT << 16) | 4789 39 | type_next = (ofp.OFPHTN_ONF << 16) | ofp.OFPHTO_USE_NEXT_PROTO 40 | actions = [ 41 | parser.OFPActionEncap(type_vxl), 42 | parser.OFPActionSetField(vxlan_vni=vxlan_vni), 43 | parser.OFPActionEncap(type_udp), 44 | parser.OFPActionSetField(udp_src=5432), 45 | parser.OFPActionSetField(udp_dst=4789), 46 | parser.OFPActionEncap(type_ip), 47 | parser.OFPActionSetField(ipv4_dst=tun_ip_dst), 48 | parser.OFPActionSetField(ipv4_src=tun_ip_src), 49 | parser.OFPActionSetNwTtl(nw_ttl=64), 50 | parser.OFPActionEncap(type_eth), 51 | # parser.OFPActionSetField(eth_src='12:22:22:22:22:22'), 52 | # parser.OFPActionSetField(eth_dst='22:33:33:33:33:33'), 53 | ] 54 | return actions 55 | 56 | def get_vxlan_decap_actions(self): 57 | # https://github.com/lagopus/lagopus/issues/92 58 | ofp = self.parent.dp.ofproto 59 | parser = self.parent.dp.ofproto_parser 60 | 61 | type_eth = (ofp.OFPHTN_ONF << 16) | ofp.OFPHTO_ETHERNET 62 | type_ip = (ofp.OFPHTN_ETHERTYPE << 16) | 0x0800 63 | type_udp = (ofp.OFPHTN_IP_PROTO << 16) | 17 64 | type_vxl = (ofp.OFPHTN_UDP_TCP_PORT << 16) | 4789 65 | type_next = (ofp.OFPHTN_ONF << 16) | ofp.OFPHTO_USE_NEXT_PROTO 66 | 67 | actions = [parser.OFPActionDecap(type_eth, type_ip), 68 | parser.OFPActionDecap(type_ip, type_udp), 69 | parser.OFPActionDecap(type_udp, type_vxl), 70 | parser.OFPActionDecap(type_vxl, type_next), 71 | ] 72 | return actions 73 | 74 | def get_vxlan_decap_actions_before_match(self): 75 | # As opposed to erfs, we cannot decap vxlan header just now, 76 | # becasue that doesn't copy the tunnel-id to a metadata filed 77 | # actions = self.get_vxlan_decap_actions() 78 | return [] 79 | 80 | def get_vxlan_match_and_decap(self, vxlan_vni): 81 | # https://github.com/lagopus/lagopus/issues/92 82 | match = {'eth_type': ETH_TYPE_IP, 'ip_proto': in_proto.IPPROTO_UDP, 83 | 'vxlan_vni': vxlan_vni, 'udp_src': 4789} 84 | actions = self.get_vxlan_decap_actions() 85 | return match, actions 86 | -------------------------------------------------------------------------------- /module/lagopus/lagopus.dsl: -------------------------------------------------------------------------------- 1 | channel channel01 create -dst-addr 127.0.0.1 -protocol tcp 2 | 3 | controller controller01 create -channel channel01 -role equal -connection-type main 4 | 5 | interface interface01 create -type ethernet-dpdk-phy -port-number 0 6 | 7 | interface interface02 create -type ethernet-dpdk-phy -port-number 1 8 | 9 | port port01 create -interface interface01 10 | 11 | port port02 create -interface interface02 12 | 13 | bridge bridge01 create -controller controller01 -port port01 1 -port port02 2 -dpid 0x1 14 | bridge bridge01 enable 15 | -------------------------------------------------------------------------------- /module/lagopus/sut-install-lagopus.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # https://github.com/lagopus/lagopus/blob/master/QUICKSTART.md 4 | 5 | sudo apt-get install \ 6 | build-essential \ 7 | libgmp-dev \ 8 | libssl-dev \ 9 | libpcap-dev \ 10 | libnuma-dev \ 11 | byacc \ 12 | flex \ 13 | git \ 14 | python-dev \ 15 | linux-headers-$(uname -r) \ 16 | librte-acl2 17 | 18 | 19 | url=https://github.com/lagopus/lagopus.git 20 | target=/opt/lagopus 21 | git clone --depth=1 $url $target 22 | cd $target 23 | 24 | ./configure 25 | make -j `nproc` 26 | sudo make install 27 | 28 | cd /opt 29 | sudo apt-get install \ 30 | python-virtualenv 31 | sudo virtualenv lagpus-virtualenv 32 | source lagpus-virtualenv/bin/activate 33 | 34 | url=https://github.com/lagopus/ryu-lagopus-ext.git 35 | target=/opt/ryu-lagopus-ext 36 | branch=lagopus-general-tunnel-ext 37 | git clone --branch $branch --depth=1 $url $target 38 | cd $target 39 | 40 | cp ../tipsy/module/openflow/color_log.py ryu/contrib 41 | pip install . 42 | #sudo pip install msgpack-python 43 | 44 | deactivate 45 | -------------------------------------------------------------------------------- /module/lagopus/sut-lagopus.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "type": { 4 | "type": "string", 5 | "enum": ["lagopus"], 6 | "description": "Lagopus: A high-performance software OpenFlow 1.3 switch (http://www.lagopus.org)." 7 | }, 8 | "lagopus-virtualenv": { 9 | "type": "string", 10 | "default": "/opt/lagopus-virtualenv", 11 | "description": "Location of the python virtual environment directory that should be activated before starting the Ryu controller. (It should contain a lagopus specific version of Ryu: https://github.com/lagopus/ryu-lagopus-ext)" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /module/moongen/Tester_moongen.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import csv 19 | import subprocess 20 | from pathlib import Path 21 | 22 | from tester_base import Tester as Base 23 | 24 | class Tester(Base): 25 | def __init__(self, conf): 26 | super().__init__(conf) 27 | tester = conf.tester 28 | if conf.traffic.dir == 'uplink': 29 | self.txdev = tester.uplink_port 30 | self.rxdev = tester.downlink_port 31 | elif conf.traffic.dir == 'downlink': 32 | self.txdev = tester.downlink_port 33 | self.rxdev = tester.uplink_port 34 | else: 35 | raise Exception("unavailable traffic.dir: %s" % conf.traffic.dir) 36 | if tester.core != 1: 37 | self.txdev = "%s:%s" % (self.txdev, tester.core) 38 | self.mg_cmd = tester.moongen_cmd 39 | self.lua_dir = Path(__file__).parent 40 | self.script = self.lua_dir / 'mg-pcap.lua' 41 | self.runtime = tester.test_time 42 | self.rate_limit = tester.rate_limit 43 | self.loss_tolerance = tester.loss_tolerance 44 | 45 | def _run(self, out_dir): 46 | pcap = out_dir / 'traffic.pcap' 47 | pfix = out_dir / 'mg' 48 | hfile = out_dir / 'mg.histogram.csv' 49 | cmd = ['sudo', self.mg_cmd, self.script, self.txdev, self.rxdev, pcap, 50 | '-l', '-t', '-r', self.runtime, '-o', pfix, '--hfile', hfile] 51 | if self.rate_limit: 52 | cmd += ['--rate-limit', self.rate_limit] 53 | cmd = [ str(o) for o in cmd ] 54 | print(' '.join(cmd)) 55 | subprocess.call(cmd) 56 | 57 | def collect_results(self): 58 | with open('mg.latency.csv') as f: 59 | reader = csv.DictReader(f) 60 | for row in reader: 61 | latency = row 62 | latency['unit'] = 'ns' 63 | 64 | throughput = {} 65 | with open('mg.throughput.csv') as f: 66 | # The last rows describe for the overall performance 67 | reader = csv.DictReader(f) 68 | for row in reader: 69 | d = row.pop('Direction') 70 | throughput[d] = row 71 | self.result.update({ 72 | 'latency': latency, 73 | 'throughput': throughput 74 | }) 75 | -------------------------------------------------------------------------------- /module/moongen/Tester_moongen_combined.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from tester_base import Tester as Base 19 | from Tester_moongen import Tester as Tester_moongen 20 | from Tester_moongen_rfc2544 import Tester as Tester_moongen_rfc2544 21 | 22 | class Tester(Base): 23 | def __init__(self, conf): 24 | super().__init__(conf) 25 | self.rfc2544 = Tester_moongen_rfc2544(conf) 26 | self.latency = Tester_moongen(conf) 27 | 28 | def _run(self, out_dir): 29 | self.rfc2544._run(out_dir) 30 | self.rfc2544.collect_results() 31 | limit = self.rfc2544.result['rfc2544']['limit'] 32 | self.latency.rate_limit = limit 33 | self.latency._run(out_dir) 34 | 35 | def collect_results(self): 36 | self.latency.collect_results() 37 | self.result.update(self.rfc2544.result) 38 | self.result.update(self.latency.result) 39 | -------------------------------------------------------------------------------- /module/moongen/Tester_moongen_flood.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import csv 19 | import subprocess 20 | 21 | from Tester_moongen import Tester as Base 22 | 23 | class Tester(Base): 24 | def __init__(self, conf): 25 | super().__init__(conf) 26 | self.script = self.lua_dir / 'mg-flood.lua' 27 | 28 | def _run(self, out_dir): 29 | pcap = out_dir / 'traffic.pcap' 30 | ofile = out_dir / 'mg.flood.csv' 31 | cmd = ['sudo', self.mg_cmd, self.script, self.txdev, self.rxdev, 32 | pcap, '-l', '-r', self.runtime, '-o', ofile] 33 | cmd = [ str(o) for o in cmd ] 34 | print(' '.join(cmd)) 35 | subprocess.call(cmd) 36 | 37 | def collect_results(self): 38 | throughput = {} 39 | with open('mg.flood.csv') as f: 40 | reader = csv.DictReader(f) 41 | for row in reader: 42 | d = row.pop('Direction') 43 | throughput[d] = row 44 | self.result.update({'flood': throughput}) 45 | -------------------------------------------------------------------------------- /module/moongen/Tester_moongen_rfc2544.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import csv 19 | import subprocess 20 | 21 | from Tester_moongen import Tester as Base 22 | 23 | class Tester(Base): 24 | def __init__(self, conf): 25 | super().__init__(conf) 26 | self.script = self.lua_dir / 'mg-rfc2544.lua' 27 | 28 | def _run(self, out_dir): 29 | pcap = out_dir / 'traffic.pcap' 30 | ofile = out_dir / 'mg.rfc2544.csv' 31 | precision = 5 32 | cmd = ['sudo', self.mg_cmd, self.script, self.txdev, self.rxdev, pcap, 33 | '-r', self.runtime, '-p', precision, '-o', ofile, 34 | '--lossTolerance', self.loss_tolerance] 35 | cmd = [ str(o) for o in cmd ] 36 | print(' '.join(cmd)) 37 | subprocess.call(cmd) 38 | 39 | def collect_results(self): 40 | data = 'nan' 41 | with open('mg.rfc2544.csv') as f: 42 | reader = csv.DictReader(f) 43 | for row in reader: 44 | data = row 45 | self.result.update({'rfc2544': data }) 46 | -------------------------------------------------------------------------------- /module/moongen/mg-flood.lua: -------------------------------------------------------------------------------- 1 | --- Replay a pcap as fast as possible. 2 | 3 | -- TIPSY: Telco pIPeline benchmarking SYstem 4 | -- 5 | -- Copyright (C) 2018 by its authors (See AUTHORS) 6 | -- 7 | -- This program is free software: you can redistribute it and/or 8 | -- modify it under the terms of the GNU General Public License as 9 | -- published by the Free Software Foundation, either version 3 of the 10 | -- License, or (at your option) any later version. 11 | -- 12 | -- This program is distributed in the hope that it will be useful, but 13 | -- WITHOUT ANY WARRANTY; without even the implied warranty of 14 | -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | -- General Public License for more details. 16 | -- 17 | -- You should have received a copy of the GNU General Public License 18 | -- along with this program. If not, see . 19 | 20 | 21 | local mg = require "moongen" 22 | local device = require "device" 23 | local memory = require "memory" 24 | local stats = require "stats" 25 | local log = require "log" 26 | local pcap = require "pcap" 27 | local timer = require "timer" 28 | 29 | function configure(parser) 30 | parser:description("Replay a PCAP file and measure throughput.") 31 | parser:argument("txDev", "txport") 32 | :default(0) 33 | :convert(tonumber) 34 | parser:argument("rxDev", "rxport") 35 | :default(1) 36 | :convert(tonumber) 37 | parser:argument("file", "pcap file") 38 | :args(1) 39 | parser:option("-r --runtime", "running time in seconds.") 40 | :default(0) 41 | :convert(tonumber) 42 | parser:flag("-l --loop", "repeat pcap file") 43 | parser:option("-o --ofile", "file to use for saving the results") 44 | :default(nil) 45 | local args = parser:parse() 46 | return args 47 | end 48 | 49 | function master(args) 50 | local txDev, rxDev 51 | if args.txDev ~= args.rxDev then 52 | txDev = device.config({port = args.txDev, txQueues = 1, rxQueues = 1}) 53 | rxDev = device.config({port = args.rxDev, rxQueues = 1, txQueues = 1}) 54 | else 55 | txDev = device.config({port = args.txDev, txQueues = 1, rxQueues = 1}) 56 | rxDev = txDev 57 | end 58 | device.waitForLinks() 59 | mg.startTask("replay_pcap", txDev:getTxQueue(0), args.file, args.loop, 60 | rxDev:getRxQueue(0)) 61 | if args.ofile then 62 | stats.startStatsTask{txDevices = {txDev}, rxDevices = {rxDev}, 63 | format="csv", file=args.ofile} 64 | else 65 | stats.startStatsTask{txDevices = {txDev}, rxDevices = {rxDev}, 66 | format="plain"} 67 | end 68 | if args.runtime > 0 then 69 | mg.setRuntime(args.runtime) 70 | end 71 | mg.waitForTasks() 72 | end 73 | 74 | function replay_pcap(queue, file, loop, rxQueue) 75 | local rxMempool = memory.createMemPool() 76 | local rxBufs = rxMempool:bufArray() 77 | 78 | local mempool = memory:createMemPool(4096) 79 | local bufs = mempool:bufArray() 80 | local pcapFile = pcap:newReader(file) 81 | local n = pcapFile:read(bufs) 82 | pcapFile:reset() 83 | while mg.running() do 84 | local n = pcapFile:read(bufs) 85 | if n == 0 then 86 | if loop then 87 | pcapFile:reset() 88 | else 89 | break 90 | end 91 | end 92 | queue:sendN(bufs, n) 93 | 94 | rxQueue:tryRecvIdle(rxBufs, 5) 95 | rxBufs:freeAll() 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /module/moongen/mg-setRate.lua: -------------------------------------------------------------------------------- 1 | --- Replay a pcap file to measure RFC2544 like throughput 2 | 3 | -- TIPSY: Telco pIPeline benchmarking SYstem 4 | -- 5 | -- Copyright (C) 2018 by its authors (See AUTHORS) 6 | -- 7 | -- This program is free software: you can redistribute it and/or 8 | -- modify it under the terms of the GNU General Public License as 9 | -- published by the Free Software Foundation, either version 3 of the 10 | -- License, or (at your option) any later version. 11 | -- 12 | -- This program is distributed in the hope that it will be useful, but 13 | -- WITHOUT ANY WARRANTY; without even the implied warranty of 14 | -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | -- General Public License for more details. 16 | -- 17 | -- You should have received a copy of the GNU General Public License 18 | -- along with this program. If not, see . 19 | 20 | local mod = {} 21 | 22 | local device = require "device" 23 | 24 | function mod:setRate(dev, rate) 25 | local devices = device.getDevices() 26 | specname = 'Intel Corporation 82599ES' 27 | if devices[dev.id].name:sub(1, #specname) == specname then 28 | -- "82599 has per queue rate limit" 29 | -- https://blog.linuxplumbersconf.org/2012/wp-content/uploads/2012/09/2012-lpc-Hardware-Rate-Limiting-brandeburg.pdf 30 | for seq, que in pairs(dev.txQueues) do 31 | -- print('--> ' .. seq) 32 | que:setRate(rate) 33 | end 34 | else 35 | dev:setRate(rate) 36 | end 37 | end 38 | 39 | return mod 40 | -------------------------------------------------------------------------------- /module/moongen/mg-test-setRate.lua: -------------------------------------------------------------------------------- 1 | --- Test the precision of the hardware rate limiter 2 | 3 | -- TIPSY: Telco pIPeline benchmarking SYstem 4 | -- 5 | -- Copyright (C) 2018 by its authors (See AUTHORS) 6 | -- 7 | -- This program is free software: you can redistribute it and/or 8 | -- modify it under the terms of the GNU General Public License as 9 | -- published by the Free Software Foundation, either version 3 of the 10 | -- License, or (at your option) any later version. 11 | -- 12 | -- This program is distributed in the hope that it will be useful, but 13 | -- WITHOUT ANY WARRANTY; without even the implied warranty of 14 | -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | -- General Public License for more details. 16 | -- 17 | -- You should have received a copy of the GNU General Public License 18 | -- along with this program. If not, see . 19 | 20 | require "colors" 21 | 22 | local mg = require "moongen" 23 | local device = require "device" 24 | local memory = require "memory" 25 | local stats = require "stats" 26 | local log = require "log" 27 | local pcap = require "pcap" 28 | local limiter = require "software-ratecontrol" 29 | local timer = require "timer" 30 | 31 | function configure(parser) 32 | parser:description("Test precision of txDev:setRate().") 33 | parser:argument("txDev", "txport[:numcores]"):default(0) 34 | parser:argument("file", "pcap file"):args(1) 35 | parser:option("-r --runtime", "length of one measurement."):default(0):convert(tonumber) 36 | parser:option("-o --ofile", "file to write the result into."):default(nil) 37 | local args = parser:parse() 38 | return args 39 | end 40 | 41 | function print_res(file, rate, txWireMbit, txMbit) 42 | log:info(' result: tx:%5.0f [Mbit/s]', txMbit) 43 | if not file then 44 | return 45 | end 46 | file:write(tostring(rate), ",", txWireMbit, ",", txMbit, "\n") 47 | end 48 | 49 | function master(args) 50 | if args.runtime == 0 then 51 | log:error("Runtime cannot be 0, use the -r option") 52 | return 53 | end 54 | 55 | local txport, cores 56 | if args.txDev:find(":") then 57 | txport, cores = tonumberall(args.txDev:match("(%d+):(%d+)")) 58 | else 59 | txport, cores = tonumber(args.txDev), 1 60 | end 61 | local txDev 62 | 63 | txDev = device.config({port = txport, txQueues = cores, rxQueues = 1}) 64 | device.waitForLinks() 65 | 66 | a = {txDev=txDev, cores=cores, rate=rate, file=args.file, runtime=args.runtime} 67 | 68 | local file 69 | if args.ofile then 70 | file = io.open(args.ofile, "w") 71 | end 72 | 73 | local linkRate = txDev:getLinkStatus().speed 74 | local rateThreshold = linkRate * 0.01 75 | local finished = false 76 | local validRun, tx 77 | 78 | a.rate = linkRate 79 | 80 | while a.rate > 0 do 81 | log:info('Sending pcap with rate of %16s%s', 82 | green(tostring(a.rate)), 83 | white(' [Mbit/s] for %ds', a.runtime)) 84 | txWireMbit, txMbit = measure_with_rate(a) 85 | print_res(file, a.rate, txWireMbit, txMbit) 86 | 87 | a.rate = a.rate - linkRate * 0.01 88 | end 89 | if file then 90 | file:close() 91 | end 92 | end 93 | 94 | function measure_with_rate(...) 95 | a = ... 96 | local txCtr = stats:newDevTxCounter(a.txDev, "nil") 97 | txCtr:update() 98 | for i = 1, a.cores do 99 | mg.startTask("replay_pcap", a.txDev:getTxQueue(i-1), a.file, true) 100 | end 101 | a.txDev:setRate(a.rate) 102 | mg.setRuntime(a.runtime) 103 | 104 | mg.waitForTasks() 105 | txCtr:update() 106 | txCtr:finalize(0) 107 | local tx_stats = txCtr:getStats() 108 | log:info(' result: tx:%2.2f [Mpps]', txCtr.mpps[1]) 109 | return txCtr.wireMbit[1], txCtr.mbit[1] 110 | end 111 | 112 | function replay_pcap(queue, file, loop) 113 | local mempool = memory:createMemPool(4096) 114 | local bufs = mempool:bufArray() 115 | local pcapFile = pcap:newReader(file) 116 | local prev = 0 117 | local linkSpeed = queue.dev:getLinkStatus().speed 118 | while mg.running() do 119 | local n = pcapFile:read(bufs) 120 | if n <= 0 then 121 | if loop then 122 | pcapFile:reset() 123 | else 124 | break 125 | end 126 | end 127 | queue:sendN(bufs, n) 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /module/moongen/tester-moongen.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "TIPSY Tester description", 3 | "type": "object", 4 | "properties": { 5 | "type": { 6 | "type": "string", 7 | "enum": [ 8 | "moongen", 9 | "moongen-rfc2544", 10 | "moongen-combined", 11 | "moongen-flood" 12 | ], 13 | "default": "moongen", 14 | "description": "Type of the traffic generator/measurement tool. (moongen-combined first runs an rfc2544-style measurement then calls the latency measurement limiting the tx rate to the result of the rfc2544 limit)" 15 | }, 16 | "test-time": { 17 | "$ref": "definitions.json#/non-negative-integer", 18 | "description": "length of the measurement [s]", 19 | "default": 30 20 | }, 21 | "loss-tolerance": { 22 | "type": "number", 23 | "description": "Loss considered acceptable [Mpps] (only for moongen-rfc2544)", 24 | "default": 0.2 25 | }, 26 | "rate-limit": { 27 | "$ref": "definitions.json#/non-negative-integer", 28 | "description": "Config parameter of the hw rate limiter. It is relevant only when type is 'moongen'. The default (0) does not set the rate limiter. Due to the inaccuracies of the limiter, the actual TX rate is different from this parameter.", 29 | "default": 0 30 | }, 31 | "uplink-port": { 32 | "type": "string", 33 | "default": "0000:0c:00.1", 34 | "description": "Port name ('eth1') or pci addr for DPDK ('0000:0b:00.0') or DPDK port number (in case of moongen, e.g., '0') [TODO: we could calculate this from the pci_addr]" 35 | }, 36 | "downlink-port": { 37 | "type": "string", 38 | "default": "0000:0c:00.0", 39 | "description": "Port name ('eth1') or pci addr for DPDK ('0000:0b:00.0') or DPDK port number (in case of moongen, e.g., '0') [TODO: we could calculate this from the pci_addr]" 40 | }, 41 | "core": { 42 | "$ref": "definitions.json#/positive-integer", 43 | "description": "number of CPU cores to use", 44 | "default": 1 45 | }, 46 | "moongen-cmd": { 47 | "$ref": "definitions.json#/readable-file", 48 | "default": "/opt/MoonGen/build/MoonGen", 49 | "description": "Absolute path of the MoonGen executable" 50 | }, 51 | "setup-script": { 52 | "type": "string", 53 | "default": "", 54 | "description": "Absolute path of the custom Tester setup script" 55 | }, 56 | "teardown-script": { 57 | "type": "string", 58 | "default": "", 59 | "description": "Absolute path of the custom Tester teardown script" 60 | }, 61 | "default": {} 62 | }, 63 | "required": [], 64 | "additionalProperties": false 65 | } 66 | -------------------------------------------------------------------------------- /module/nat/GenConf_nat.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from gen_conf_base import GenConf as Base 19 | 20 | class GenConf (Base): 21 | "SNAT pipeline" 22 | def __init__ (self, args): 23 | super().__init__(args) 24 | self.components += ['nat'] 25 | 26 | def add_nat (self): 27 | for arg in ['downlink-dst-mac', 'uplink-dst-mac', 28 | 'range-ipv4-min', 'range-ipv4-max', 29 | 'range-port-min', 'range-port-max']: 30 | self.conf[arg] = self.get_arg(arg) 31 | -------------------------------------------------------------------------------- /module/nat/GenPkt_nat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # TIPSY: Telco pIPeline benchmarking SYstem 4 | # 5 | # Copyright (C) 2018 by its authors (See AUTHORS) 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but 13 | # WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | # General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import random 21 | from scapy.all import * 22 | 23 | from gen_pcap_base import GenPkt as Base 24 | from gen_pcap_base import byte_seq 25 | 26 | 27 | 28 | class GenPkt(Base): 29 | 30 | def get_auto_pkt_num(self): 31 | #return self.conf.num_flows 32 | return 10 33 | 34 | def gen_pkt(self, pkt_idx): 35 | smac = 'aa:bb:bb:aa:ab:ba' 36 | dmac = 'aa:cc:dd:cc:ac:dc' 37 | sip = byte_seq('2.2.%d.%d', (pkt_idx % 64516) + 1) 38 | dip = byte_seq('3.3.%d.%d', (pkt_idx % 64516) + 1) 39 | p = Ether(dst=dmac, src=smac) / IP(src=sip, dst=dip) 40 | p = self.add_payload(p, self.args.pkt_size) 41 | return p 42 | -------------------------------------------------------------------------------- /module/nat/SUT_bess_nat.py: -------------------------------------------------------------------------------- 1 | # https://github.com/NetSys/bess/blob/master/bessctl/conf/samples/nat.bess 2 | -------------------------------------------------------------------------------- /module/nat/SUT_ovs_nat.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2017-2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from ryu.ofproto import nicira_ext 19 | 20 | import os 21 | import sys 22 | fdir = os.path.dirname(os.path.realpath(__file__)) 23 | sys.path.append(os.path.join(fdir, '..', '..', 'lib')) 24 | import find_mod 25 | Base = find_mod.find_class('SUT_openflow', 'base') 26 | 27 | # http://ryu.readthedocs.io/en/latest/nicira_ext_ref.html 28 | # http://www.openvswitch.org/support/dist-docs/ovs-ofctl.8.txt 29 | # https://mail.openvswitch.org/pipermail/ovs-discuss/2016-July/041841.html 30 | 31 | def action_ct(parser, kw): 32 | "Create a connection track action" 33 | args = { 34 | 'flags': 0, 35 | 'zone_src': '', 36 | 'zone_ofs_nbits': 0, 37 | 'recirc_table': 255, 38 | 'alg': 0, 39 | 'actions': [], 40 | } 41 | args.update(**kw) 42 | return parser.NXActionCT(**args) 43 | 44 | def action_nat(parser, kw): 45 | "Create a NAT action" 46 | return parser.NXActionNAT(**kw) 47 | 48 | 49 | class SUT_ovs(Base): 50 | """SNAT pipeline 51 | """ 52 | def __init__(self, parent, conf): 53 | super(SUT_ovs, self).__init__(parent, conf) 54 | self.tables = { 55 | 'tbl' : 0, 56 | 'drop' : 9, 57 | } 58 | 59 | def config_switch(self, parser): 60 | mod_flow = self.parent.mod_flow 61 | ul_port = self.parent.ul_port 62 | dl_port = self.parent.dl_port 63 | flag_commit = 1 64 | flag_snat = 1 65 | flag_dnat = 2 66 | ct_state_minus_trk = (0, 32) 67 | ct_state_plus_trk = (32, 32) 68 | 69 | # dl -> ul (priv -> pubilc, orig -> translated) 70 | # 71 | # ip,in_port="ul_port" actions=ct(commit,zone=1,nat(dst=dst)),output:"dl_port" 72 | match = {'in_port': dl_port, 'eth_type': 0x0800} 73 | actions = [ 74 | action_ct(parser, { 75 | 'flags': flag_commit, 76 | 'zone_ofs_nbits': nicira_ext.ofs_nbits(0, 1), 77 | 'actions': [ 78 | action_nat(parser, { 79 | 'flags': flag_dnat, 80 | 'range_ipv4_min': self.conf.range_ipv4_min, 81 | 'range_ipv4_max': self.conf.range_ipv4_max, 82 | 'range_proto_min': self.conf.range_port_min, 83 | 'range_proto_max': self.conf.range_port_max, 84 | }) 85 | ] 86 | }), 87 | parser.OFPActionSetField(eth_dst=self.conf.uplink_dst_mac), 88 | ] 89 | mod_flow(match=match, actions=actions, output=ul_port) 90 | 91 | # ul -> dl (pubilc -> priv, translated -> orig) 92 | # 93 | # ct_state=-trk,ip,in_port="dl_port" actions=ct(table=0,zone=1,nat) 94 | match = { 95 | 'in_port': ul_port, 96 | 'eth_type': 0x0800, 97 | 'ct_state': ct_state_minus_trk, 98 | } 99 | actions = [ 100 | action_ct(parser, { 101 | 'flags': 0, 102 | 'zone_ofs_nbits': nicira_ext.ofs_nbits(0, 1), 103 | 'recirc_table': 0, 104 | 'actions': [ action_nat(parser, {'flags': 0}) ], 105 | }), 106 | parser.OFPActionSetField(eth_dst=self.conf.downlink_dst_mac), 107 | ] 108 | mod_flow(match=match, actions=actions, output=dl_port) 109 | 110 | # ct_state=+trk,ct_zone=1,in_port="dl_port" actions=output:"ul_port" 111 | match = { 112 | 'in_port': ul_port, 113 | 'eth_type': 0x0800, 114 | 'ct_state': ct_state_plus_trk, 115 | 'ct_zone': 1, 116 | } 117 | actions = [ 118 | parser.OFPActionSetField(eth_dst=self.conf.downlink_dst_mac), 119 | ] 120 | mod_flow(match=match, actions=actions, output=dl_port) 121 | 122 | -------------------------------------------------------------------------------- /module/nat/pipeline-nat.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Configuration of a NAT pipeline", 3 | "type": "object", 4 | "properties": { 5 | "name": { 6 | "type": "string", 7 | "enum": ["nat"], 8 | "description": 9 | "name of the pipeline, must be set to nat" 10 | }, 11 | "fakedrop": { 12 | "type": "boolean", 13 | "description": 14 | "whether to actually drop unmatched packets (False) or send them immediately to the output port (True) for correct rate measurements", 15 | "default": true 16 | }, 17 | "downlink-dst-mac": { 18 | "$ref": "definitions.json#/mac-address", 19 | "description": "mac address of the remote host on SUT's downlink port, i.e, the mac address of the Tester's uplink port.", 20 | "default": "00:00:00:00:00:02" 21 | }, 22 | "uplink-dst-mac": { 23 | "$ref": "definitions.json#/mac-address", 24 | "description": "mac address of the remote host on SUT's uplink port, i.e, the mac address of the Tester's downlink port.", 25 | "default": "00:00:00:00:00:04" 26 | }, 27 | "range-ipv4-min": { 28 | "$ref": "definitions.json#/ip-address", 29 | "default": "10.10.0.1", 30 | "description": "Lower end of the public IP range of the SNAT." 31 | }, 32 | "range-ipv4-max": { 33 | "$ref": "definitions.json#/ip-address", 34 | "default": "10.10.0.1", 35 | "description": "Upper end of the public IP range of the SNAT." 36 | }, 37 | "range-port-min": { 38 | "$ref": "definitions.json#/positive-integer", 39 | "default": 1000, 40 | "description": "Lower end of the port range from which the translated port should be selected." 41 | }, 42 | "range-port-max": { 43 | "$ref": "definitions.json#/positive-integer", 44 | "default": 2000, 45 | "description": "Upper end of the port range from which the translated port should be selected." 46 | }, 47 | "core": { 48 | "$ref": "definitions.json#/positive-integer", 49 | "description": "number of CPU cores/workers running the pipeline", 50 | "default": 1 51 | } 52 | }, 53 | "required": ["name"], 54 | "additionalProperties": false 55 | } 56 | -------------------------------------------------------------------------------- /module/nat/test-nat.json: -------------------------------------------------------------------------------- 1 | { 2 | "benchmark": [ 3 | { 4 | "pipeline": { 5 | "name": "nat", 6 | "core": 1, 7 | "fakedrop": false 8 | }, 9 | "tester": { 10 | "uplink-port": "00:08.0", 11 | "downlink-port": "00:09.0", 12 | "type": "trex", 13 | "trex-cli-args": { 14 | "f": "cap2/http_simple.yaml", 15 | "prom": true 16 | } 17 | } 18 | } 19 | ], 20 | "visualize": [ 21 | { "type": "table", 22 | "x-axis": "out.trex-info.Version", 23 | "y-axis": [ 24 | "out.trex.trex-global.data.m_rx_pps", 25 | "out.trex.trex-global.data.m_tx_pps", 26 | "out.trex.trex-global.data.m_total_rx_pkts", 27 | "out.trex.trex-global.data.m_total_tx_pkts" 28 | ], 29 | "filter": {"pipeline.name": "nat"}, 30 | "title": "{pipeline.name} - OVS v{out.sut.version}" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /module/openflow/SUT_openflow.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from pathlib import Path, PosixPath 19 | 20 | import find_mod 21 | Base = find_mod.find_class('SUT', 'base') 22 | 23 | class SUT(Base): 24 | def __init__(self, conf): 25 | super(SUT, self).__init__(conf) 26 | self.remote_dir = Path(self.conf.sut.tipsy_dir) / 'module' / 'openflow' 27 | self.virtualenv = None 28 | 29 | def _start(self): 30 | self.upload_conf_files('/tmp') 31 | 32 | cmd = ['sudo', str(self.remote_dir / 'start-ryu')] 33 | if self.virtualenv: 34 | cmd.append(self.virtualenv) 35 | self.run_async_ssh_cmd(cmd) 36 | self.wait_for_callback() 37 | -------------------------------------------------------------------------------- /module/openflow/SUT_openflow_base.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2017-2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from ryu.lib.packet import in_proto 19 | 20 | class SUT_openflow(object): 21 | def __init__(self, parent, conf): 22 | self.conf = conf 23 | self.parent = parent 24 | self.logger = self.parent.logger 25 | # The pipeline has tunnels if it has ports that take care of 26 | # adding and removing VXLAN, GRE, etc headers. OTOH, If flow 27 | # actions push and pop these headers (vlan_push, vlan_pop), then 28 | # has_tunnels should be False. 29 | self.has_tunnels = False 30 | self.tables = {'drop': 0} 31 | 32 | def get_tunnel_endpoints(self): 33 | raise NotImplementedError 34 | 35 | def do_unknown(self, action): 36 | self.logger.error('Unknown action: %s' % action.action) 37 | 38 | @staticmethod 39 | def get_proto_name (ip_proto_num): 40 | name = {in_proto.IPPROTO_TCP: 'tcp', 41 | in_proto.IPPROTO_UDP: 'udp'}.get(ip_proto_num) 42 | #TODO: handle None 43 | return name 44 | -------------------------------------------------------------------------------- /module/openflow/SUT_openflow_fw.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2017-2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from ryu.lib.packet.ether_types import ETH_TYPE_IP 19 | 20 | import os 21 | import sys 22 | fdir = os.path.dirname(os.path.realpath(__file__)) 23 | sys.path.append(os.path.join(fdir, '..', '..', 'lib')) 24 | import find_mod 25 | Base = find_mod.find_class('SUT_openflow', 'base') 26 | 27 | class SUT_openflow(Base): 28 | """Firewall (ACL) pipeline 29 | """ 30 | 31 | def __init__(self, parent, conf): 32 | super(SUT_openflow, self).__init__(parent, conf) 33 | self.tables = { 34 | 'selector' : 0, 35 | 'upstream' : 1, 36 | 'downstream' : 2, 37 | 'drop' : 3, 38 | } 39 | 40 | def config_switch(self, parser): 41 | ul_port = self.parent.ul_port 42 | dl_port = self.parent.dl_port 43 | 44 | table = 'selector' 45 | self.parent.mod_flow(table, match={'in_port': dl_port}, goto='upstream') 46 | self.parent.mod_flow(table, match={'in_port': ul_port}, goto='downstream') 47 | 48 | for d in ['u', 'd']: 49 | longname = {'u': 'upstream', 'd': 'downstream'}[d] 50 | for entry in self.conf.get('%sl_fw_rules' % d): 51 | self.mod_table('add', longname, entry) 52 | 53 | def mod_table(self, cmd, table, entry): 54 | mod_flow = self.parent.mod_flow 55 | out_port = {'upstream': self.parent.ul_port, 56 | 'downstream': self.parent.dl_port}[table] 57 | match = { 58 | 'eth_type': ETH_TYPE_IP, 59 | 'ipv4_src': entry.src_ip, 60 | 'ipv4_dst': entry.dst_ip, 61 | } 62 | pname = self.get_proto_name(entry.ipproto) 63 | 64 | if entry.ipproto > 0: 65 | match['ip_proto'] = entry.ipproto 66 | if entry.src_port > 0: 67 | match['%s_src' % pname] = entry.src_port 68 | if entry.dst_port > 0: 69 | match['%s_dst' % pname] = entry.dst_port 70 | 71 | mod_flow(table, match=match, output=out_port, cmd=cmd) 72 | 73 | def do_mod_table(self, args): 74 | self.mod_table(args.cmd, args.table, args.entry) 75 | -------------------------------------------------------------------------------- /module/openflow/SUT_openflow_l2fwd.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2017-2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import os 19 | import sys 20 | fdir = os.path.dirname(os.path.realpath(__file__)) 21 | sys.path.append(os.path.join(fdir, '..', '..', 'lib')) 22 | import find_mod 23 | Base = find_mod.find_class('SUT_openflow', 'base') 24 | 25 | class SUT_openflow(Base): 26 | """L2 Packet Forwarding 27 | 28 | Upstream the L2fwd pipeline will receive packets from the downlink 29 | port, perform a lookup for the destination MAC address in a static 30 | MAC table, and if a match is found the packet will be forwarded to 31 | the uplink port or otherwise dropped (or likewise forwarded upstream 32 | if the =fakedrop= parameter is set to =true=). The downstream 33 | pipeline is just the other way around, but note that the upstream 34 | and downstream pipelines use separate MAC tables. 35 | """ 36 | 37 | def __init__(self, parent, conf): 38 | super(SUT_openflow, self).__init__(parent, conf) 39 | self.tables = { 40 | 'selector' : 0, 41 | 'upstream' : 1, 42 | 'downstream' : 2, 43 | 'drop' : 3, 44 | } 45 | 46 | def config_switch(self, parser): 47 | ul_port = self.parent.ul_port 48 | dl_port = self.parent.dl_port 49 | 50 | table = 'selector' 51 | self.parent.mod_flow(table, match={'in_port': dl_port}, goto='upstream') 52 | self.parent.mod_flow(table, match={'in_port': ul_port}, goto='downstream') 53 | 54 | for d in ['upstream', 'downstream']: 55 | for entry in self.conf.get('%s-table' % d): 56 | self.mod_table('add', d, entry) 57 | 58 | def mod_table(self, cmd, table, entry): 59 | mod_flow = self.parent.mod_flow 60 | out_port = {'upstream': self.parent.ul_port, 61 | 'downstream': self.parent.dl_port}[table] 62 | out_port = entry.out_port or out_port 63 | 64 | mod_flow(table, match={'eth_dst': entry.mac}, output=out_port, cmd=cmd) 65 | 66 | def do_mod_table(self, args): 67 | self.mod_table(args.cmd, args.table, args.entry) 68 | 69 | -------------------------------------------------------------------------------- /module/openflow/color_log.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import logging 19 | 20 | # INSTALLATION 21 | # 1. Copy the file to ~ryu/ryu/contrib 22 | # 2. $ pip uninstall ryu 23 | # 3. ~ryu $ pip install . 24 | 25 | # https://en.wikipedia.org/wiki/ANSI_escape_code#Colors 26 | colors = {'DEBUG' : '39;49', 27 | 'INFO' : '32', 28 | 'WARNING' : '33', 29 | 'ERROR' : '31', 30 | 'CRITICAL' : '33;41', 31 | } 32 | 33 | class Formatter(logging.Formatter): 34 | 35 | def format(self, record): 36 | color = colors.get(record.levelname, colors['DEBUG']) 37 | f = "\033[%sm%%s\033[0m" % color 38 | record.msg = f % record.msg 39 | return super(Formatter, self).format(record) 40 | -------------------------------------------------------------------------------- /module/openflow/log.cfg: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root,Tipsy,Sw_conf 3 | 4 | [handlers] 5 | keys=stream_handler,stream_color 6 | 7 | [formatters] 8 | keys=formatter,color 9 | 10 | [logger_root] 11 | level=INFO 12 | handlers=stream_handler 13 | 14 | [logger_Tipsy] 15 | level=DEBUG 16 | qualname=Tipsy 17 | handlers=stream_color 18 | propagate=0 19 | 20 | [logger_Sw_conf] 21 | level=DEBUG 22 | qualname=Sw_conf 23 | handlers=stream_color 24 | propagate=0 25 | 26 | [handler_stream_handler] 27 | class=StreamHandler 28 | formatter=formatter 29 | args=(sys.stderr,) 30 | 31 | [handler_stream_color] 32 | class=StreamHandler 33 | formatter=color 34 | args=(sys.stderr,) 35 | 36 | [formatter_formatter] 37 | format=%(asctime)s %(levelname).1s:%(name)-15.15s %(message)s 38 | datefmt=%H:%M:%S 39 | 40 | [formatter_color] 41 | format=%(asctime)s %(message)s 42 | datefmt=%H:%M:%S 43 | class=ryu.contrib.color_log.Formatter 44 | -------------------------------------------------------------------------------- /module/openflow/ryu.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | log_config_file = log.cfg 3 | # wsapi_host=127.0.0.1 4 | # wsapi_port= 5 | app_lists = tipsy.py 6 | 7 | [TIPSY] 8 | pipeline_conf = /tmp/pipeline.json 9 | benchmark_conf = /tmp/benchmark.json 10 | webhook_configured=http://localhost:9000/configured 11 | webhook_failed=http://localhost:9000/failed 12 | 13 | -------------------------------------------------------------------------------- /module/openflow/setup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Setup script for test-openflow-portfwd. 4 | # Assumes the vagrant VM environment. 5 | 6 | ssh sut.local sudo ovs-vsctl \ 7 | --if-exists del-br br 8 | 9 | ssh sut.local sudo ovs-vsctl \ 10 | --if-exists del-br br-main 11 | 12 | ssh sut.local sudo ovs-vsctl \ 13 | add-br br -- \ 14 | set bridge br datapath_type=netdev -- \ 15 | set-controller br tcp:127.0.0.1 16 | 17 | ssh sut.local sudo ovs-vsctl \ 18 | add-port br downlink -- \ 19 | set Interface downlink type=dpdk options:dpdk-devargs=0000:00:08.0 20 | 21 | ssh sut.local sudo ovs-vsctl \ 22 | add-port br uplink -- \ 23 | set Interface uplink type=dpdk options:dpdk-devargs=0000:00:09.0 24 | -------------------------------------------------------------------------------- /module/openflow/start-ryu: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | 5 | if [ $# -gt 0 ]; then 6 | # Active the given python virtual environment 7 | source $1/bin/activate 8 | fi 9 | 10 | cd "$DIR" 11 | ryu-manager --config-dir . 12 | 13 | if [ $# -gt 0 ]; then 14 | deactivate 15 | fi 16 | -------------------------------------------------------------------------------- /module/openflow/sut-openflow.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "type": { 4 | "type": "string", 5 | "enum": ["openflow"], 6 | "description": "Openflow: General OpenFlow 1.3 controller that doesn't use any non-standard protocol extension." 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /module/openflow/teardown: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Teardown script for test-openflow-portfwd. 4 | # Assumes the vagrant VM environment. 5 | 6 | ssh sut.local sudo ovs-vsctl del-br br 7 | -------------------------------------------------------------------------------- /module/openflow/test-fw.json: -------------------------------------------------------------------------------- 1 | { 2 | "benchmark": [ 3 | { 4 | "scale": "outer", 5 | "pipeline": { 6 | "name": "fw", 7 | "fakedrop": false, 8 | "rule-num": [2, 20, 200] 9 | }, 10 | "traffic": { 11 | "trace-generator-pareto-a": [0.5, 0.7] 12 | } 13 | } 14 | ], 15 | "visualize": [ 16 | { "x-axis": "pipeline.rule-num", 17 | "y-axis": "out.flood.RX.PacketRate", 18 | "group-by": "traffic.trace-generator-pareto-a", 19 | "filter": {"pipeline.name": "fw"}, 20 | "title": "{pipeline.name} - Results grouped by pareto-a" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /module/openflow/test-l2fwd.json: -------------------------------------------------------------------------------- 1 | { 2 | "benchmark": [ 3 | { 4 | "pipeline": { 5 | "name": "l2fwd", 6 | "core": 1, 7 | "fakedrop": false, 8 | "downstream-table-size": 2, 9 | "upstream-table-size": [10, 100, 1000, 10000] 10 | }, 11 | "scale": "outer" 12 | } 13 | ], 14 | "visualize": [ 15 | { "x-axis": "pipeline.upstream-table-size", 16 | "y-axis": "out.flood.RX.PacketRate", 17 | "axis-type": "semilogx", 18 | "filter": {"pipeline.name": "l2fwd"}, 19 | "title": "{pipeline.name} - OVS v{out.sut.version}" 20 | }, 21 | { "x-axis": "pipeline.upstream-table-size", 22 | "y-axis": [ 23 | "out.flood.RX.PacketRate", 24 | "out.flood.TX.PacketRate" 25 | ], 26 | "axis-type": "semilogx", 27 | "filter": {"pipeline.name": "l2fwd"}, 28 | "title": "{pipeline.name} - OVS v{out.sut.version}" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /module/openflow/test-l3fwd.json: -------------------------------------------------------------------------------- 1 | { 2 | "benchmark": [ 3 | { 4 | "pipeline": { 5 | "name": "l3fwd", 6 | "core": 1, 7 | "fakedrop": true, 8 | "downlink-mac": "dd:dd:dd:dd:00:00", 9 | "downstream-group-table-size": 1, 10 | "downstream-l3-table-size": 2, 11 | "uplink-mac": "dd:dd:dd:dd:00:01", 12 | "upstream-group-table-size": 2, 13 | "upstream-l3-table-size": [10, 100, 1000, 10000] 14 | }, 15 | "scale": "outer" 16 | } 17 | ], 18 | "visualize": [ 19 | { "x-axis": "pipeline.upstream-l3-table-size", 20 | "y-axis": "out.flood.RX.PacketRate", 21 | "axis-type": "semilogx", 22 | "filter": {"pipeline.name": "l3fwd"}, 23 | "title": "{pipeline.name} - OVS v{out.sut.version}" 24 | }, 25 | { "x-axis": "pipeline.upstream-l3-table-size", 26 | "y-axis": [ 27 | "out.flood.RX.PacketRate", 28 | "out.flood.TX.PacketRate" 29 | ], 30 | "axis-type": "semilogx", 31 | "filter": {"pipeline.name": "l3fwd"}, 32 | "title": "{pipeline.name} - OVS v{out.sut.version}" 33 | }, 34 | { "type": "table", 35 | "x-axis": "pipeline.upstream-l3-table-size", 36 | "y-axis": [ 37 | "out.flood.RX.PacketRate", 38 | "out.flood.TX.PacketRate" 39 | ], 40 | "filter": {"pipeline.name": "l3fwd"}, 41 | "title": "{pipeline.name} - OVS v{out.sut.version}" 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /module/openflow/tipsy.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2017-2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | """ 18 | TIPSY: Telco pIPeline benchmarking SYstem 19 | 20 | Run as: 21 | $ cd path/to/tipsy.py 22 | $ ryu-manager --config-dir . 23 | """ 24 | from __future__ import print_function 25 | 26 | import json 27 | import os 28 | import sys 29 | 30 | from ryu import cfg 31 | 32 | fdir = os.path.dirname(os.path.realpath(__file__)) 33 | sys.path.append(os.path.join(fdir, '..', '..', 'lib')) 34 | import find_mod 35 | 36 | pipeline_conf = '/tmp/pipeline.json' 37 | benchmark_conf = '/tmp/benchmark.json' 38 | cfg.CONF.register_opts([ 39 | cfg.StrOpt('pipeline_conf', default=pipeline_conf, 40 | help='json formatted configuration file of the pipeline'), 41 | cfg.StrOpt('benchmark_conf', default=benchmark_conf, 42 | help='configuration of the whole benchmark (in json)'), 43 | cfg.StrOpt('webhook_configured', default='http://localhost:8888/configured', 44 | help='URL to request when the sw is configured'), 45 | cfg.StrOpt('webhook_failed', default='http://localhost:8888/failed', 46 | help='URL to request when the configuration is unsuccessful'), 47 | ], group='tipsy') 48 | CONF = cfg.CONF['tipsy'] 49 | 50 | 51 | def eprint(*args, **kw): 52 | print(*args, file=sys.stderr, **kw) 53 | 54 | try: 55 | with open(CONF['benchmark_conf'], 'r') as f: 56 | bm = json.load(f) 57 | except IOError as e: 58 | eprint('Failed to load cfg file (%s): %s' % (fname, e)) 59 | raise e 60 | except ValueError as e: 61 | eprint('Failed to parse cfg file (%s): %s' % (fname, e)) 62 | raise e 63 | 64 | App = find_mod.find_class('RyuApp', bm["sut"]["type"]) 65 | App.__module__ = 'tipsy' 66 | App.__name__ = 'Tipsy' 67 | -------------------------------------------------------------------------------- /module/openvswitch/SUT_ovs.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import subprocess 19 | from pathlib import Path 20 | 21 | import find_mod 22 | OpenFlow = find_mod.find_class('SUT', 'openflow') 23 | 24 | class SUT(OpenFlow): 25 | 26 | def _query_version(self): 27 | v = self.run_ssh_cmd(['ovs-vsctl', '--version'], stdout=subprocess.PIPE) 28 | first_line = v.stdout.decode('utf8').split("\n")[0] 29 | self.result['version'] = first_line.split(' ')[-1] 30 | -------------------------------------------------------------------------------- /module/openvswitch/SUT_ovs_bng.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2017-2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from ryu.lib.packet import in_proto 19 | from ryu.lib.packet.ether_types import ETH_TYPE_IP 20 | 21 | import os 22 | import sys 23 | fdir = os.path.dirname(os.path.realpath(__file__)) 24 | sys.path.append(os.path.join(fdir, '..', '..', 'lib')) 25 | import find_mod 26 | 27 | Base = find_mod.find_class('SUT_ovs', 'mgw') 28 | 29 | class SUT_ovs(Base): 30 | 31 | def __init__(self, parent, conf): 32 | super(SUT_ovs, self).__init__(parent, conf) 33 | self.tables = { 34 | 'ingress' : 0, 35 | 'dl_nat' : 1, 36 | 'dl_fw' : 2, 37 | 'downlink' : 3, 38 | 'uplink' : 4, 39 | 'ul_fw' : 5, 40 | 'ul_nat' : 6, 41 | 'l3_lookup' : 7, 42 | 'drop' : 250 43 | } 44 | 45 | def get_tunnel_endpoints(self): 46 | return self.conf.cpe 47 | 48 | def add_fw_rules(self, table_name, rules, next_table): 49 | if not rules: 50 | return 51 | 52 | mod_flow = self.parent.mod_flow 53 | parser = self.parent.dp.ofproto_parser 54 | 55 | for rule in rules: 56 | # TODO: ip_proto, ip mask, port mask (?) 57 | match = { 58 | 'eth_type': ETH_TYPE_IP, 59 | 'ip_proto': in_proto.IPPROTO_TCP, 60 | 'ipv4_src': (rule.src_ip, '255.255.255.0'), 61 | 'ipv4_dst': (rule.dst_ip, '255.255.255.0'), 62 | 'tcp_src': rule.src_port, 63 | 'tcp_dst': rule.dst_port, 64 | } 65 | mod_flow(table_name, match=match, goto='drop') 66 | mod_flow(table_name, priority=1, goto=next_table) 67 | 68 | def add_ul_nat_rules (self, table_name, next_table): 69 | mod_flow = self.parent.mod_flow 70 | parser = self.parent.dp.ofproto_parser 71 | 72 | for rule in self.conf.nat_table: 73 | proto_name = self.get_proto_name(rule.proto) 74 | match = {'eth_type': ETH_TYPE_IP, 75 | 'ipv4_src': (rule.priv_ip, '255.255.255.255'), 76 | 'ip_proto': rule.proto, 77 | proto_name + '_src': rule.priv_port} 78 | actions = [{'ipv4_src': rule.pub_ip}, 79 | {proto_name + '_src': rule.pub_port}] 80 | actions = [parser.OFPActionSetField(**a) for a in actions] 81 | mod_flow(table_name, match=match, actions=actions, goto=next_table) 82 | 83 | def add_dl_nat_rules (self, table_name, next_table): 84 | mod_flow = self.parent.mod_flow 85 | parser = self.parent.dp.ofproto_parser 86 | 87 | for rule in self.conf.nat_table: 88 | proto_name = self.get_proto_name(rule.proto) 89 | match = {'eth_type': ETH_TYPE_IP, 90 | 'ipv4_dst': (rule.pub_ip, '255.255.255.255'), 91 | 'ip_proto': rule.proto, 92 | proto_name + '_dst': rule.pub_port} 93 | actions = [{'ipv4_dst': rule.priv_ip}, 94 | {proto_name + '_dst': rule.priv_port}] 95 | actions = [parser.OFPActionSetField(**a) for a in actions] 96 | mod_flow(table_name, match=match, actions=actions, goto=next_table) 97 | 98 | def config_switch(self, parser): 99 | super(SUT_ovs, self).config_switch(parser) 100 | 101 | self.add_fw_rules('ul_fw', self.conf.ul_fw_rules, 'ul_nat') 102 | self.add_fw_rules('dl_fw', self.conf.dl_fw_rules, 'downlink') 103 | self.add_ul_nat_rules('ul_nat', 'l3_lookup') 104 | self.add_dl_nat_rules('dl_nat', 'dl_fw') 105 | 106 | 107 | -------------------------------------------------------------------------------- /module/openvswitch/ip.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2017-2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import subprocess 19 | import logging 20 | 21 | log = logging.getLogger() 22 | 23 | def call(*cmd): 24 | cmd = ['sudo', 'ip'] + list(cmd) 25 | if subprocess.call(cmd): 26 | log.error('ip command failed (%s)' % ' '.join(cmd)) 27 | 28 | def add_route(iface, net): 29 | call('route', 'add', net, 'dev', iface) 30 | 31 | def add_route_gw(net, gw): 32 | call('route', 'add', net, 'via', gw) 33 | 34 | def add_veth(name1, name2): 35 | call('link', 'add', 'name', name1, 'type', 'veth', 'peer', 'name', name2) 36 | 37 | def del_veth(name1, _name2=None): 38 | call('link', 'del', 'dev', name1) 39 | 40 | def set_up(iface, addr=None): 41 | call('link', 'set', 'dev', iface, 'up') 42 | if addr: 43 | call('addr', 'add', addr, 'dev', iface) 44 | 45 | def add_arp(iface, ip_addr, hw_addr): 46 | cmd = ['sudo', 'arp', '-i', iface, '-s', ip_addr, hw_addr] 47 | if subprocess.call(cmd): 48 | log.error('ip command failed (%s)' % ' '.join(cmd)) 49 | -------------------------------------------------------------------------------- /module/openvswitch/sut-ovs.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "type": { 4 | "type": "string", 5 | "enum": ["ovs"], 6 | "description": "ovs: (dpdk-enabled) Open vSwitch.", 7 | "default": "ovs" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /module/openvswitch/test-bng.json: -------------------------------------------------------------------------------- 1 | { 2 | "benchmark": [ 3 | { 4 | "scale": "joint", 5 | "pipeline": { 6 | "name": "bng", 7 | "fakedrop": false, 8 | "cpe": 2, 9 | "nhop": 2, 10 | "rate-limit": 40000000000, 11 | "server": 4, 12 | "user": [1, 5, 10], 13 | "user-conn": 2 14 | }, 15 | "traffic": { 16 | "dir": ["uplink", "downlink"] 17 | } 18 | } 19 | ], 20 | "visualize": [ 21 | { "x-axis": "pipeline.user", 22 | "y-axis": "out.flood.RX.PacketRate", 23 | "axis-type": "semilogx", 24 | "filter": {"pipeline.name": "bng"}, 25 | "group-by": "traffic.dir", 26 | "title": "{pipeline.name} - OVS v{out.sut.version}" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /module/openvswitch/test-mgw.json: -------------------------------------------------------------------------------- 1 | { 2 | "benchmark": [ 3 | { 4 | "scale": "joint", 5 | "pipeline": { 6 | "name": "mgw", 7 | "fakedrop": false, 8 | "bst": [1, 3], 9 | "nhop": [1, 3], 10 | "rate-limit": 40000000000, 11 | "server": [1, 3], 12 | "user": [1, 3] 13 | }, 14 | "traffic": { 15 | "dir": ["uplink", "downlink"] 16 | } 17 | } 18 | ], 19 | "visualize": [ 20 | { "x-axis": "pipeline.user", 21 | "y-axis": "out.flood.RX.PacketRate", 22 | "axis-type": "semilogx", 23 | "filter": {"pipeline.name": "mgw"}, 24 | "group-by": "traffic.dir", 25 | "title": "{pipeline.name} - OVS v{out.sut.version}" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /module/plot/Plot_USL.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import inspect 19 | import math 20 | 21 | from Plot_simple import Plot as Plot_simple 22 | 23 | class Plot(Plot_simple): 24 | def latex_extra_plot(self, name, points): 25 | import lmfit 26 | import numpy as np 27 | 28 | def usl(params, x, data): 29 | l = params['lambd'] 30 | s = params['sigma'] 31 | k = params['kappa'] 32 | 33 | model = l * x / (1 + s * (x - 1) + k * x * (x - 1)) 34 | 35 | return model - data 36 | 37 | 38 | x = np.array([x for x, y in points]) 39 | data = [y for x, y in points] 40 | params = lmfit.Parameters() 41 | try: 42 | params.add('lambd', value=points[0][1]/points[0][0]) 43 | except: 44 | params.add('lambd', value=1) 45 | params.add('sigma', value=0.1, min=0) 46 | params.add('kappa', value=0.01, min=0) 47 | # method: leastsq least_squares differential_evolution brute nelder 48 | method = 'leastsq' 49 | out = lmfit.minimize(usl, params, method=method, args=(x, data)) 50 | lmfit.report_fit(out) 51 | print(out.params) 52 | f = {key: param.value for key, param in out.params.items()} 53 | try: 54 | f['ymax'] = math.floor(math.sqrt((1-f['sigma'])/f['kappa'])) 55 | except: 56 | f['ymax'] = '\inf' 57 | f['legend'] = ('$\lambda = {lambd:.3f}, \sigma={sigma:.3f},' 58 | '\kappa={kappa:.3f}, n_{{max}}={ymax}$').format(**f) 59 | s = inspect.cleandoc(r""" 60 | \addplot [blue, domain=1:16] {{ 61 | {lambd} * x / (1 + {sigma}*(x-1) + {kappa} *x*(x-1)) 62 | }}; 63 | \addlegendentry{{{legend}}} 64 | """.format(**f)) 65 | 66 | return s 67 | -------------------------------------------------------------------------------- /module/plot/Plot_contour.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import collections 19 | import inspect 20 | 21 | from Plot_simple import Plot as Plot_simple 22 | 23 | class Plot(Plot_simple): 24 | 25 | def format_latex(self, data, title): 26 | addplot = "" 27 | for x, ys in sorted(data.items()): 28 | addplot += ' ' 29 | for y, z in sorted(ys.items()): 30 | addplot += "(%s, %s, %s) " % (x, y, z) 31 | addplot += "\n\n" 32 | f = { 33 | 'title': title, 34 | 'xlabel': self.conf.x_axis, 35 | 'ylabel': self.conf.y_axis, 36 | 'addplot': addplot, 37 | 'axis': self.get_latex_axis_type() 38 | } 39 | text = inspect.cleandoc(r""" 40 | \begin{{figure}} 41 | \centering 42 | \begin{{tikzpicture}} 43 | \begin{{{axis}}}[ 44 | view={{0}}{{90}}, 45 | xlabel={xlabel}, 46 | ylabel={ylabel}, 47 | ] 48 | \addplot3 [ contour gnuplot] coordinates {{ 49 | 50 | {addplot} 51 | 52 | }}; 53 | \end{{{axis}}} 54 | \end{{tikzpicture}} 55 | \caption{{{title}}} 56 | \end{{figure}} 57 | """ 58 | ).format(**f) 59 | with open('fig.tex', 'w') as f: 60 | f.write(text) 61 | f.write("\n") 62 | 63 | def plot(self, raw_data): 64 | self.xlabel = self.conf.x_axis 65 | self.ylabel = self.conf.y_axis 66 | 67 | title = '' 68 | data = collections.defaultdict(dict) 69 | for row in raw_data: 70 | x = float(row[self.conf.x_axis]) 71 | y = float(row[self.conf.y_axis]) 72 | z = float(row[self.conf.z_axis]) 73 | data[x][y] = z 74 | title = self.conf.title.format(**row) 75 | 76 | if data: 77 | self.format_latex(data, title) 78 | 79 | return data 80 | 81 | 82 | -------------------------------------------------------------------------------- /module/plot/Plot_table.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import inspect 19 | 20 | from Plot_simple import Plot as Plot_base 21 | from Plot_simple import str2tex, ensure_list 22 | 23 | class Plot(Plot_base): 24 | def __init__(self, conf): 25 | super().__init__(conf) 26 | self.preamble += inspect.cleandoc(r""" 27 | \usepackage{pgfplotstable} 28 | \usepackage{colortbl} 29 | """) + "\n" 30 | 31 | def format_matplotlib(self, series, title): 32 | pass 33 | 34 | def format_latex(self, series, title): 35 | f = {'title': title, 'plot_args': '', 'addplot': ''} 36 | 37 | names = [self.conf.x_axis] + [s[0] for s in series.items()] 38 | table = [names] 39 | 40 | x_vals = [] 41 | for s in series.items(): 42 | x_vals += list(zip(*s[1]))[0] 43 | x_vals = sorted(set(x_vals)) 44 | 45 | sdict = [{k: v for k, v in points} for points in series.values()] 46 | for x in x_vals: 47 | vals = [str(x)] 48 | for s in sdict: 49 | vals.append(str(s.get(x, 'nan'))) 50 | table.append(vals) 51 | 52 | if len(table) < len(table[0]): 53 | # more columns than rows -> transpose 54 | table = list(zip(*table)) 55 | if len(x_vals) == 1: 56 | table = [['variable', 'value']] + table 57 | header, table = table[0], table[1:] 58 | 59 | column_types = {} 60 | for colnum, col in enumerate(zip(*table)): 61 | for val in col: 62 | if val == 'nan': 63 | continue 64 | try: 65 | float(val) 66 | except ValueError: 67 | column_types[colnum] = 'string type, column type=l,' 68 | 69 | for colnum, name in enumerate(header): 70 | col_type = column_types.get(colnum, "") 71 | s = ",\n columns/%d/.style={%s column name={%s}}" 72 | f['plot_args'] += s % (colnum, col_type, str2tex(name)) 73 | 74 | for row in table: 75 | row = [str2tex(cell) for cell in row] 76 | f['addplot'] += ' & '.join(row) + "\\\\\n " 77 | 78 | text = inspect.cleandoc(r""" 79 | \begin{{table}} 80 | \tiny 81 | \centering 82 | \caption{{{title}}} 83 | \pgfplotstabletypeset[col sep=&,row sep=\\,header=false {plot_args}, 84 | every even row/.style={{before row={{\rowcolor[gray]{{0.9}}}}}}]{{ 85 | {addplot}}} 86 | \end{{table}} 87 | """ 88 | ).format(**f) 89 | with open('fig.tex', 'w') as f: 90 | f.write(text) 91 | f.write("\n") 92 | -------------------------------------------------------------------------------- /module/portfwd/GenConf_portfwd.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from gen_conf_base import GenConf as Base 19 | 20 | class GenConf (Base): 21 | "L2 Port Forwarding pipeline" 22 | def __init__ (self, args): 23 | super().__init__(args) 24 | self.components += ['portfwd'] 25 | 26 | def add_portfwd (self): 27 | for arg in ['mac_swap_upstream', 'mac_swap_downstream']: 28 | self.conf[arg] = self.get_arg(arg) 29 | 30 | -------------------------------------------------------------------------------- /module/portfwd/GenPkt_portfwd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # TIPSY: Telco pIPeline benchmarking SYstem 4 | # 5 | # Copyright (C) 2018 by its authors (See AUTHORS) 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but 13 | # WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | # General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import random 21 | from scapy.all import * 22 | 23 | from gen_pcap_base import GenPkt as Base 24 | from gen_pcap_base import byte_seq 25 | 26 | 27 | 28 | class GenPkt(Base): 29 | 30 | def get_auto_pkt_num(self): 31 | return 1024 32 | 33 | def gen_pkt(self, pkt_idx): 34 | smac = byte_seq('aa:bb:bb:aa:%02x:%02x', random.randrange(1, 65023)) 35 | dmac = byte_seq('aa:cc:dd:cc:%02x:%02x', random.randrange(1, 65023)) 36 | dip = byte_seq('3.3.%d.%d', random.randrange(1, 255)) 37 | p = Ether(dst=dmac, src=smac) / IP(dst=dip) 38 | p = self.add_payload(p, self.args.pkt_size) 39 | return p 40 | -------------------------------------------------------------------------------- /module/portfwd/SUT_openflow_portfwd.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2017-2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import os 19 | import sys 20 | 21 | fdir = os.path.dirname(os.path.realpath(__file__)) 22 | sys.path.append(os.path.join(fdir, '..', '..', 'lib')) 23 | import find_mod 24 | Base = find_mod.find_class('SUT_openflow', 'base') 25 | 26 | class SUT_openflow(Base): 27 | """L2 Port Forwarding 28 | 29 | In the upstream direction the pipeline will receive L2 packets from the 30 | downlink port of the SUT and forward them to the uplink port. Meanwhile, it 31 | may optionally rewrite the source MAC address in the L2 frame to the MAC 32 | address of the uplink port (must be specified by the pipeline config). The 33 | downstream direction is the same, but packets are received from the uplink 34 | port and forwarded to the downlink port after an optional MAC rewrite. 35 | """ 36 | def __init__(self, parent, conf): 37 | super(SUT_openflow, self).__init__(parent, conf) 38 | self.tables = { 39 | 'tbl' : 0, 40 | } 41 | 42 | def config_switch(self, parser): 43 | mod_flow = self.parent.mod_flow 44 | ul_port = self.parent.ul_port 45 | dl_port = self.parent.dl_port 46 | 47 | actions = [] 48 | mac = self.conf.mac_swap_downstream 49 | if mac: 50 | actions = [parser.OFPActionSetField(eth_src=mac)] 51 | match = {'in_port': dl_port} 52 | mod_flow(match=match, actions=actions, output=ul_port) 53 | 54 | actions = [] 55 | mac = self.conf.mac_swap_upstream 56 | if mac: 57 | actions = [parser.OFPActionSetField(eth_src=mac)] 58 | match = {'in_port': ul_port} 59 | mod_flow(match=match, actions=actions, output=dl_port) 60 | 61 | 62 | -------------------------------------------------------------------------------- /module/portfwd/pipeline-portfwd.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Configuration of a L2 Port Forwarding pipeline", 3 | "type": "object", 4 | "properties": { 5 | "name": { 6 | "type": "string", 7 | "enum": ["portfwd"], 8 | "description": 9 | "name of the pipeline, must be set to portfwd" 10 | }, 11 | "mac-swap-upstream": { 12 | "$ref": "definitions.json#/mac-address-or-null", 13 | "default": null, 14 | "description": "if set, swap the source MAC address in the packets received from the downlink port before forwarding to the uplink port" 15 | }, 16 | "mac-swap-downstream": { 17 | "$ref": "definitions.json#/mac-address-or-null", 18 | "default": null, 19 | "description": "if set, swap the source MAC address in the packets received from the uplink port before forwarding to the downlink port" 20 | }, 21 | "core": { 22 | "$ref": "definitions.json#/positive-integer", 23 | "description": "number of CPU cores/workers running the pipeline", 24 | "default": 1 25 | } 26 | }, 27 | "required": ["name"], 28 | "additionalProperties": false 29 | } 30 | -------------------------------------------------------------------------------- /module/portfwd/test-openflow-portfwd.json: -------------------------------------------------------------------------------- 1 | { 2 | "benchmark": [ 3 | { 4 | "scale": "outer", 5 | "pipeline": { 6 | "name": "portfwd", 7 | "core": 1 8 | }, 9 | "sut": { 10 | "type": "openflow", 11 | "downlink-port": "1", 12 | "uplink-port": "2", 13 | "setup-script": "/opt/tipsy/module/openflow/setup", 14 | "teardown-script": "/opt/tipsy/module/openflow/teardown" 15 | } 16 | } 17 | ], 18 | "visualize": [ 19 | { "type": "table", 20 | "x-axis": "sut.type", 21 | "y-axis": "out.flood.RX.PacketRate", 22 | "filter": {"pipeline.name": "portfwd"}, 23 | "title": "{pipeline.name}" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /module/portfwd/test-ovs-portfwd.json: -------------------------------------------------------------------------------- 1 | { 2 | "benchmark": [ 3 | { 4 | "scale": "outer", 5 | "pipeline": { 6 | "name": "portfwd", 7 | "core": 1 8 | } 9 | } 10 | ], 11 | "visualize": [ 12 | { "x-axis": "pipeline.core", 13 | "y-axis": "out.flood.RX.PacketRate", 14 | "filter": {"pipeline.name": "portfwd", 15 | "sut.type": "ovs"}, 16 | "title": "{pipeline.name} - OVS v{out.sut.version}" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /module/sut/SUT_bess.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import subprocess 19 | from pathlib import Path 20 | 21 | from sut_base import SUT as Base 22 | 23 | class SUT(Base): 24 | def _start(self): 25 | self.result['versions'] = {} 26 | cmd = [str(Path(self.conf.sut.bess_dir) / 'bin' / 'bessd'), '-t'] 27 | v = self.run_ssh_cmd(cmd, stdout=subprocess.PIPE) 28 | for line in v.stdout.decode('utf-8').split("\n"): 29 | if line.startswith(' '): 30 | break 31 | [var, val] = line.split(' ') 32 | self.result['versions'][var] = val 33 | self.result['version'] = self.result['versions'].get('bessd', 'n/a') 34 | 35 | remote_dir = Path('/tmp') 36 | self.upload_conf_files(remote_dir) 37 | cmd = [ 38 | Path(self.conf.sut.tipsy_dir) / 'bess' / 'bess-runner.py', 39 | '-d', self.conf.sut.bess_dir, 40 | '-p', remote_dir / 'pipeline.json', 41 | '-b', remote_dir / 'benchmark.json', 42 | ] 43 | self.run_async_ssh_cmd([str(c) for c in cmd]) 44 | self.wait_for_callback() 45 | 46 | 47 | -------------------------------------------------------------------------------- /module/sut/SUT_ofdpa.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from pathlib import Path 19 | 20 | from sut_base import SUT as Base 21 | 22 | class SUT(Base): 23 | def _start(self): 24 | remote_cmd = Path(self.conf.sut.tipsy_dir) / 'ofdpa' / 'tipsy.py' 25 | self.upload_conf_files('/tmp') 26 | self.run_async_ssh_cmd(['sudo', str(remote_cmd)]) 27 | self.wait_for_callback() 28 | -------------------------------------------------------------------------------- /module/sut/SUT_t4p4s.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from pathlib import Path 19 | 20 | from sut_base import SUT as Base 21 | 22 | class SUT(Base): 23 | def _start(self): 24 | self.upload_conf_files('/tmp') 25 | remote_cmd = Path(self.conf.sut.tipsy_dir) / 't4p4s' / 'tipsy.py' 26 | self.run_async_ssh_cmd([str(remote_cmd)]) 27 | self.wait_for_callback() 28 | -------------------------------------------------------------------------------- /module/sut/SUT_vpp.py: -------------------------------------------------------------------------------- 1 | # TIPSY: Telco pIPeline benchmarking SYstem 2 | # 3 | # Copyright (C) 2018 by its authors (See AUTHORS) 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import subprocess 19 | from pathlib import Path 20 | 21 | from sut_base import SUT as Base 22 | 23 | class SUT(Base): 24 | def _start(self): 25 | remote_dir = Path('/tmp') 26 | self.upload_conf_files(remote_dir) 27 | cmd = [ 28 | 'python3', 29 | Path(self.conf.sut.tipsy_dir) / 'vpp' / 'vpp-runner.py', 30 | '-p', remote_dir / 'pipeline.json', 31 | '-b', remote_dir / 'benchmark.json' 32 | ] 33 | self.run_async_ssh_cmd([str(c) for c in cmd]) 34 | self.wait_for_callback() 35 | 36 | cmd = ['sudo', 'vppctl', 'show', 'version'] 37 | v = self.run_ssh_cmd(cmd, stdout=subprocess.PIPE) 38 | self.result['version'] = v.stdout.decode('utf-8').split("\n")[0] 39 | dpdk_cmd = ['sudo', 'vppctl', 'show', 'dpdk', 'version'] 40 | d = self.run_ssh_cmd(dpdk_cmd, stdout=subprocess.PIPE) 41 | fline = d.stdout.decode('utf-8').split("\n")[0] 42 | dpdk_version = fline.split('DPDK')[-1].strip() 43 | self.result['versions'] = {} 44 | self.result['versions']['DPDK'] = dpdk_version 45 | -------------------------------------------------------------------------------- /module/sut/sut-base.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "TIPSY SUT description", 3 | "type": "object", 4 | "properties": { 5 | "type": { 6 | "type": "string", 7 | "enum": ["bess", "ofdpa", "t4p4s", "vpp"] 8 | }, 9 | "hostname": { 10 | "type": "string", 11 | "default": "sut.local", 12 | "description": "Hostname that can be used to ssh into SUT without a password as 'ssh hostname'. Edit ~/.ssh/config for more complicated scenarios. See man page: ssh_config (5)" 13 | }, 14 | "tipsy-dir": { 15 | "type": "string", 16 | "default": "/opt/tipsy", 17 | "description": "A directory on SUT in which TIPSY is installed" 18 | }, 19 | "bess-dir": { 20 | "type": "string", 21 | "default": "/opt/bess", 22 | "description": "A directory on SUT in which bess is installed" 23 | }, 24 | "t4p4s-dir": { 25 | "type": "string", 26 | "default": "/opt/t4p4s16/t4p4s-16", 27 | "description": "A directory on SUT in which t4p4s is installed" 28 | }, 29 | "uplink-port": { 30 | "type": "string", 31 | "default": "0000:0b:00.0", 32 | "description": "Port name ('eth1') or pci addr for DPDK ('0000:0b:00.0') or DPDK port number (in case of moongen, e.g., '0') [TODO: we could calculate this from the pci_addr]" 33 | }, 34 | "downlink-port": { 35 | "type": "string", 36 | "default": "0000:0b:00.1", 37 | "description": "Port name ('eth1') or pci addr for DPDK ('0000:0b:00.0') or DPDK port number (in case of moongen, e.g., '0') [TODO: we could calculate this from the pci_addr]" 38 | }, 39 | "uplink-vpp-interface": { 40 | "type": "string", 41 | "default": "FortyGigabitEthernet8/0/0", 42 | "description": "Uplink VPP Interface name. Required by VPP." 43 | }, 44 | "downlink-vpp-interface": { 45 | "type": "string", 46 | "default": "FortyGigabitEthernet8/0/1", 47 | "description": "Downlink VPP Interface name. Required by VPP." 48 | }, 49 | "coremask": { 50 | "$ref": "definitions.json#/hex-string", 51 | "default": "0x3f03f", 52 | "description": "Hexadecimal coremask as string. Supported by bess, ovs, t4p4s, vpp. Required by t4p4as. (This defines the availabilty of the cores. The actual number of cores is defined by pipeline.core)" 53 | }, 54 | "portmask": { 55 | "$ref": "definitions.json#/hex-string", 56 | "default": "0x3", 57 | "description": "Hexadecimal portmask as string. Supported by t4p4s. Required by t4p4as." 58 | }, 59 | "setup-script": { 60 | "type": "string", 61 | "default": "", 62 | "description": "Absolute path of the custom SUT setup script" 63 | }, 64 | "teardown-script": { 65 | "type": "string", 66 | "default": "", 67 | "description": "Absolute path of the custom SUT teardown script" 68 | }, 69 | "default": {} 70 | }, 71 | "required": [], 72 | "additionalProperties": false 73 | } 74 | -------------------------------------------------------------------------------- /module/trex/tester-trex.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "type": { 4 | "type": "string", 5 | "enum": [ 6 | "trex" 7 | ], 8 | "description": "Trex: https://trex-tgn.cisco.com/" 9 | }, 10 | "trex-dir": { 11 | "type": "string", 12 | "default": "/opt/trex", 13 | "description": "A directory on Tester in which TRex is installed" 14 | }, 15 | "trex-host": { 16 | "type": "string", 17 | "default": "localhost", 18 | "description": "Host where the trex daemon runs" 19 | }, 20 | "trex-client-args": { 21 | "type": "object", 22 | "default": {}, 23 | "description": "Arguments of the CTRexClient constructor. See: ~trex/trex_client/stf/trex_stf_lib/trex_client.py. Currently available properties: max_history_size, filtered_latency_amount, trex_daemon_port, master_daemon_port, trex_zmq_port, verbose, debug_image, trex_args, timeout. Can be empty if trex-host is localhost." 24 | }, 25 | "trex-cli-args": { 26 | "type": "object", 27 | "default": {"f": "cap2/http_simple.yaml"}, 28 | "description": "Trex command line arguments. See: start_trex() in ~trex/trex_client/stf/trex_stf_lib/trex_client.py, or Chapter 'Running TRex, understanding output' of http://trex-tgn.cisco.com/trex/doc/trex_book.pdf; or run './t-rex-64 --help'" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /schema/.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((js-mode 5 | (js-indent-level . 2))) 6 | -------------------------------------------------------------------------------- /schema/benchmark.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "TIPSY benchmark description", 3 | "type": "object", 4 | "properties": { 5 | "id": { 6 | "type": "string", 7 | "description": "The =id= parameter sets a name for the benchmark" 8 | }, 9 | "scale": { 10 | "type": "string", 11 | "enum": ["none", "outer", "joint"], 12 | "default": "none", 13 | "description": "=scale= describes the way the individual benchmark instances in the scalability benchmark are to be executed. TIPSY allows to easily request and perform scalability tests by repeating the benchmark multiple times, each time setting one or all parameters as controlled by the =scale= setting:\n =none=: do not perform scalability tests,\n =outer=: take the outer product of all settings specified for the benchmark and generate a separate test case for all,\n =joint=: scale the parameters jointly." 14 | }, 15 | "pipeline": { 16 | "$ref": "pipeline.json#/", 17 | "default": {"name": "mgw"} 18 | }, 19 | "traffic": { 20 | "$ref": "traffic.json#/", 21 | "default": {"conf": "pipeline.json"} 22 | }, 23 | "sut": { 24 | "$ref": "sut.json#/", 25 | "default": {} 26 | }, 27 | "tester": { 28 | "$ref": "tester.json#/", 29 | "default": {} 30 | } 31 | }, 32 | "required": [], 33 | "additionalProperties": false 34 | } 35 | -------------------------------------------------------------------------------- /schema/definitions.json: -------------------------------------------------------------------------------- 1 | { 2 | "positive-integer": { 3 | "type": "integer", 4 | "minimum": 1 5 | }, 6 | "non-negative-integer": { 7 | "type": "integer", 8 | "minimum": 0 9 | }, 10 | "hex-string": { 11 | "type": "string", 12 | "pattern": "^0[xX][0-9a-fA-F]*$" 13 | }, 14 | "readable-file": { 15 | "type": "string" 16 | }, 17 | "readable-file-or-null": { 18 | "anyOf": [ 19 | {"type": "string"}, 20 | {"type": "null"} 21 | ] 22 | }, 23 | "writable-file": { 24 | "type": "string" 25 | }, 26 | "ip-address": { 27 | "allOf": [ 28 | {"type": "string"}, 29 | {"title": "IP address", 30 | "pattern": "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"}, 31 | {"title": "addr shouldn't end with .0", 32 | "not": { "pattern": "\\.0?0?0$" } } 33 | ] 34 | }, 35 | "mac-address": { 36 | "title": "mac address", 37 | "type": "string", 38 | "pattern": "^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$" 39 | }, 40 | "mac-address-or-null": { 41 | "anyOf": [ 42 | {"$ref": "#/mac-address"}, 43 | {"type": "null"} 44 | ] 45 | }, 46 | "hierarchical-property-name": { 47 | "title": "hierarchical property name (e.g., mg.throughput.RX.Mbit)", 48 | "type": "string", 49 | "pattern": "^[-_a-zA-Z0-9.]*$" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /schema/main.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Main TIPSY configuration describing a series of measurements", 3 | "type": "object", 4 | "properties": { 5 | "benchmark": { 6 | "type": "array", 7 | "items": { "$ref": "benchmark.json#/" }, 8 | "default": [] 9 | }, 10 | "default": { 11 | "$ref": "benchmark.json#/", 12 | "default": {} 13 | }, 14 | "visualize": { 15 | "type": "array", 16 | "items": { "$ref": "plot.json#/" }, 17 | "default": [] 18 | } 19 | }, 20 | "required": [], 21 | "additionalProperties": false 22 | } 23 | -------------------------------------------------------------------------------- /schema/pipeline-bng.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Configuration of a Mobile Gateway pipeline", 3 | "type": "object", 4 | "properties": { 5 | "name": { 6 | "type": "string", 7 | "enum": ["bng"], 8 | "description": 9 | "name of the pipeline, must be set to bng for the BNG pipeline." 10 | }, 11 | "user": { 12 | "$ref": "definitions.json#/positive-integer", 13 | "description": "number of UEs", 14 | "default": 1 15 | }, 16 | "cpe": { 17 | "$ref": "definitions.json#/positive-integer", 18 | "description": "number of Customer Premises Equipments (max: 64516)", 19 | "default": 1 20 | }, 21 | "server": { 22 | "$ref": "definitions.json#/positive-integer", 23 | "description": "number of public servers", 24 | "default": 1 25 | }, 26 | "rate-limit": { 27 | "$ref": "definitions.json#/positive-integer", 28 | "description": "rate limit threshold [byte/sec]", 29 | "default": 10000 30 | }, 31 | "nhop": { 32 | "$ref": "definitions.json#/positive-integer", 33 | "description": 34 | "number of next-hops in the L3 table towards the public Internet", 35 | "default": 2 36 | }, 37 | "user-conn": { 38 | "$ref": "definitions.json#/positive-integer", 39 | "description": 40 | "number of connections for each user (max: 65023)", 41 | "default": 1 42 | }, 43 | "fakedrop": { 44 | "type": "boolean", 45 | "description": 46 | "whether to actually drop unmatched packets (False) or send them immediately to the output port (True) for correct rate measurements", 47 | "default": true 48 | }, 49 | "fw-rules": { 50 | "$ref": "definitions.json#/non-negative-integer", 51 | "description": "number of firewall rules", 52 | "default": 1 53 | }, 54 | "fluct-user": { 55 | "$ref": "definitions.json#/non-negative-integer", 56 | "description": "number of user arrival/departure events per sec", 57 | "default": 0 58 | }, 59 | "fluct-server": { 60 | "$ref": "definitions.json#/non-negative-integer", 61 | "description": "number of server update events per sec", 62 | "default": 0 63 | }, 64 | "gw-ip": { 65 | "$ref": "definitions.json#/ip-address", 66 | "description": "Gateway IP address", 67 | "default": "200.0.0.1" 68 | }, 69 | "gw-mac": { 70 | "$ref": "definitions.json#/mac-address", 71 | "description": "Gateway MAC address", 72 | "default": "aa:22:bb:44:cc:66" 73 | }, 74 | "downlink-default-gw-ip": { 75 | "$ref": "definitions.json#/ip-address", 76 | "description": "Default gateway IP address, downlink direction", 77 | "default": "200.0.0.222" 78 | }, 79 | "downlink-default-gw-mac": { 80 | "$ref": "definitions.json#/mac-address", 81 | "description": "Default gateway MAC address, downlink direction", 82 | "default": "aa:22:bb:44:cc:67" 83 | }, 84 | "core": { 85 | "$ref": "definitions.json#/positive-integer", 86 | "description": "number of CPU cores/workers running the pipeline", 87 | "default": 1 88 | } 89 | }, 90 | "required": ["name"], 91 | "additionalProperties": false 92 | } 93 | -------------------------------------------------------------------------------- /schema/pipeline-fw.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Configuration of a Firewall pipeline", 3 | "type": "object", 4 | "properties": { 5 | "name": { 6 | "type": "string", 7 | "enum": ["fw"], 8 | "description": 9 | "name of the pipeline, must be set to fw" 10 | }, 11 | "fakedrop": { 12 | "type": "boolean", 13 | "description": 14 | "whether to actually drop unmatched packets (False) or send them immediately to the output port (True) for correct rate measurements", 15 | "default": true 16 | }, 17 | "implementation-type": { 18 | "type": "string", 19 | "enum": ["acl", "dpdk", "default"], 20 | "description": "Type of the internal implementation of the FW pipeline. In case of bess: 'acl' or 'dpdk'. Otherwise: 'default'.", 21 | "default": "default" 22 | }, 23 | "seed-file": { 24 | "type": "string", 25 | "default": "acl1", 26 | "description": "Seed file for Classbench (relative to classbench/vendor/parameter_files)" 27 | }, 28 | "rule-num": { 29 | "$ref": "definitions.json#/positive-integer", 30 | "default": 2, 31 | "description": "Number of fw rules" 32 | }, 33 | "core": { 34 | "$ref": "definitions.json#/positive-integer", 35 | "description": "number of CPU cores/workers running the pipeline", 36 | "default": 1 37 | }, 38 | "classbench-cmd": { 39 | "type": "string", 40 | "default": "/opt/classbench-ng/classbench", 41 | "description": "Absolute path of the classbench executable (https://github.com/classbench-ng/classbench-ng)" 42 | } 43 | }, 44 | "required": ["name"], 45 | "additionalProperties": false 46 | } 47 | -------------------------------------------------------------------------------- /schema/pipeline-l2fwd.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Configuration of an L2 Packet Forwarding pipeline", 3 | "type": "object", 4 | "properties": { 5 | "name": { 6 | "type": "string", 7 | "enum": ["l2fwd"], 8 | "description": 9 | "name of the pipeline, must be set to l2fwd" 10 | }, 11 | "fakedrop": { 12 | "type": "boolean", 13 | "description": 14 | "whether to actually drop unmatched packets (False) or send them immediately to the output port (True) for correct rate measurements", 15 | "default": true 16 | }, 17 | "upstream-table-size": { 18 | "$ref": "definitions.json#/positive-integer", 19 | "default": 2, 20 | "description": "number of entries in the upstream MAC table" 21 | }, 22 | "downstream-table-size": { 23 | "$ref": "definitions.json#/positive-integer", 24 | "default": 2, 25 | "description": "number of entries in the downstream MAC table" 26 | }, 27 | "fluct-table": { 28 | "$ref": "definitions.json#/non-negative-integer", 29 | "default": 0, 30 | "description": "number of MAC table entry update events (table-update) per sec" 31 | }, 32 | "core": { 33 | "$ref": "definitions.json#/positive-integer", 34 | "description": "number of CPU cores/workers running the pipeline", 35 | "default": 1 36 | } 37 | }, 38 | "required": ["name"], 39 | "additionalProperties": false 40 | } 41 | -------------------------------------------------------------------------------- /schema/pipeline-l3fwd.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Configuration of an L3 Packet Forwarding pipeline", 3 | "type": "object", 4 | "properties": { 5 | "name": { 6 | "type": "string", 7 | "enum": ["l3fwd"], 8 | "description": 9 | "name of the pipeline, must be set to l3fwd" 10 | }, 11 | "fakedrop": { 12 | "type": "boolean", 13 | "description": 14 | "whether to actually drop unmatched packets (False) or send them immediately to the output port (True) for correct rate measurements", 15 | "default": true 16 | }, 17 | "upstream-l3-table-size": { 18 | "$ref": "definitions.json#/positive-integer", 19 | "default": 10, 20 | "description": "Number of destination entries (prefixes) in the L3FIB lookup table, upstream direction" 21 | }, 22 | "upstream-group-table-size": { 23 | "$ref": "definitions.json#/positive-integer", 24 | "default": 2, 25 | "description": "number of group table entries (next-hops), upstream direction" 26 | }, 27 | "downstream-l3-table-size": { 28 | "$ref": "definitions.json#/positive-integer", 29 | "default": 2, 30 | "description": "Number of destination entries (prefixes) in the L3FIB lookup table, upstream direction" 31 | }, 32 | "downstream-group-table-size": { 33 | "$ref": "definitions.json#/positive-integer", 34 | "default": 1, 35 | "description": "number of group table entries (next-hops), upstream direction" 36 | }, 37 | "uplink-mac": { 38 | "$ref": "definitions.json#/mac-address", 39 | "default": "dd:dd:dd:dd:00:01", 40 | "description": "mac address of the uplink port of SUT" 41 | }, 42 | "downlink-mac": { 43 | "$ref": "definitions.json#/mac-address", 44 | "default": "dd:dd:dd:dd:00:00", 45 | "description": "mac address of the downlink port of SUT" 46 | }, 47 | "fluct-l3-table": { 48 | "$ref": "definitions.json#/non-negative-integer", 49 | "default": 0, 50 | "description": "number of l3-table-update events in the L3FIB per sec" 51 | }, 52 | "fluct-group-table": { 53 | "$ref": "definitions.json#/non-negative-integer", 54 | "default": 0, 55 | "description": "number of group-table-update events in the Group Table per sec" 56 | }, 57 | "core": { 58 | "$ref": "definitions.json#/positive-integer", 59 | "description": "number of CPU cores/workers running the pipeline", 60 | "default": 1 61 | } 62 | }, 63 | "required": ["name"], 64 | "additionalProperties": false 65 | } 66 | -------------------------------------------------------------------------------- /schema/pipeline-mgw.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Configuration of a Mobile Gateway pipeline", 3 | "type": "object", 4 | "properties": { 5 | "name": { 6 | "type": "string", 7 | "enum": ["mgw"], 8 | "description": 9 | "name of the pipeline, must be set to mgw for the MGW pipeline." 10 | }, 11 | "user": { 12 | "$ref": "definitions.json#/positive-integer", 13 | "description": "number of UEs", 14 | "default": 1 15 | }, 16 | "bst": { 17 | "$ref": "definitions.json#/positive-integer", 18 | "description": "number of BSTs", 19 | "default": 1 20 | }, 21 | "server": { 22 | "$ref": "definitions.json#/positive-integer", 23 | "description": "number of public servers", 24 | "default": 1 25 | }, 26 | "rate-limit": { 27 | "$ref": "definitions.json#/positive-integer", 28 | "description": "rate limit threshold [byte/sec]", 29 | "default": 10000 30 | }, 31 | "nhop": { 32 | "$ref": "definitions.json#/positive-integer", 33 | "description": 34 | "number of next-hops in the L3 table towards the public Internet", 35 | "default": 2 36 | }, 37 | "fakedrop": { 38 | "type": "boolean", 39 | "description": 40 | "whether to actually drop unmatched packets (False) or send them immediately to the output port (True) for correct rate measurements", 41 | "default": true 42 | }, 43 | "fluct-user": { 44 | "$ref": "definitions.json#/non-negative-integer", 45 | "description": "number of user arrival/departure events per sec", 46 | "default": 0 47 | }, 48 | "handover": { 49 | "$ref": "definitions.json#/non-negative-integer", 50 | "description": "number of handover events per sec", 51 | "default": 0 52 | }, 53 | "fluct-server": { 54 | "$ref": "definitions.json#/non-negative-integer", 55 | "description": "number of server update events per sec", 56 | "default": 0 57 | }, 58 | "gw-ip": { 59 | "$ref": "definitions.json#/ip-address", 60 | "description": "Gateway IP address", 61 | "default": "200.0.0.1" 62 | }, 63 | "gw-mac": { 64 | "$ref": "definitions.json#/mac-address", 65 | "description": "Gateway MAC address", 66 | "default": "aa:22:bb:44:cc:66" 67 | }, 68 | "downlink-default-gw-ip": { 69 | "$ref": "definitions.json#/ip-address", 70 | "description": "Default gateway IP address, downlink direction", 71 | "default": "200.0.0.222" 72 | }, 73 | "downlink-default-gw-mac": { 74 | "$ref": "definitions.json#/mac-address", 75 | "description": "Default gateway MAC address, downlink direction", 76 | "default": "aa:22:bb:44:cc:67" 77 | }, 78 | "core": { 79 | "$ref": "definitions.json#/positive-integer", 80 | "description": "number of CPU cores/workers running the pipeline", 81 | "default": 1 82 | } 83 | }, 84 | "required": ["name"], 85 | "additionalProperties": false 86 | } 87 | -------------------------------------------------------------------------------- /schema/pipeline-vmgw.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Configuration of a Virtual Mobile Gateway pipeline", 3 | "type": "object", 4 | "properties": { 5 | "name": { 6 | "type": "string", 7 | "enum": ["vmgw"], 8 | "description": 9 | "name of the pipeline, must be set to mgw for the MGW pipeline." 10 | }, 11 | "user": { 12 | "$ref": "definitions.json#/positive-integer", 13 | "description": "number of UEs", 14 | "default": 1 15 | }, 16 | "bst": { 17 | "$ref": "definitions.json#/positive-integer", 18 | "description": "number of BSTs", 19 | "default": 1 20 | }, 21 | "server": { 22 | "$ref": "definitions.json#/positive-integer", 23 | "description": "number of public servers", 24 | "default": 1 25 | }, 26 | "rate-limit": { 27 | "$ref": "definitions.json#/positive-integer", 28 | "description": "rate limit threshold [byte/sec]", 29 | "default": 10000 30 | }, 31 | "nhop": { 32 | "$ref": "definitions.json#/positive-integer", 33 | "description": 34 | "number of next-hops in the L3 table towards the public Internet", 35 | "default": 2 36 | }, 37 | "fakedrop": { 38 | "type": "boolean", 39 | "description": 40 | "whether to actually drop unmatched packets (False) or send them immediately to the output port (True) for correct rate measurements", 41 | "default": true 42 | }, 43 | "fluct-user": { 44 | "$ref": "definitions.json#/non-negative-integer", 45 | "description": "number of user arrival/departure events per sec", 46 | "default": 0 47 | }, 48 | "handover": { 49 | "$ref": "definitions.json#/non-negative-integer", 50 | "description": "number of handover events per sec", 51 | "default": 0 52 | }, 53 | "fluct-server": { 54 | "$ref": "definitions.json#/non-negative-integer", 55 | "description": "number of server update events per sec", 56 | "default": 0 57 | }, 58 | "gw-ip": { 59 | "$ref": "definitions.json#/ip-address", 60 | "description": "Gateway IP address", 61 | "default": "200.0.0.1" 62 | }, 63 | "gw-mac": { 64 | "$ref": "definitions.json#/mac-address", 65 | "description": "Gateway MAC address", 66 | "default": "aa:22:bb:44:cc:66" 67 | }, 68 | "downlink-default-gw-ip": { 69 | "$ref": "definitions.json#/ip-address", 70 | "description": "Default gateway IP address, downlink direction", 71 | "default": "200.0.0.222" 72 | }, 73 | "downlink-default-gw-mac": { 74 | "$ref": "definitions.json#/mac-address", 75 | "description": "Default gateway MAC address, downlink direction", 76 | "default": "aa:22:bb:44:cc:67" 77 | }, 78 | "napps": { 79 | "$ref": "definitions.json#/positive-integer", 80 | "description": "number of apps", 81 | "default": 1, 82 | "enum": [1] 83 | }, 84 | "fw-rules": { 85 | "$ref": "definitions.json#/non-negative-integer", 86 | "description": "number of firewall rules", 87 | "default": 1 88 | }, 89 | "core": { 90 | "$ref": "definitions.json#/positive-integer", 91 | "description": "number of CPU cores/workers running the pipeline", 92 | "default": 1 93 | } 94 | }, 95 | "required": ["name"], 96 | "additionalProperties": false 97 | } 98 | -------------------------------------------------------------------------------- /schema/traffic.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "CLI arguments of the gen_pcap program", 3 | "type": "object", 4 | "properties": { 5 | "json": { 6 | "$ref": "definitions.json#/readable-file-or-null", 7 | "short_opt": "-j", 8 | "default": null, 9 | "description": 10 | "Input config file, command line arguments override settings" 11 | }, 12 | "conf": { 13 | "$ref": "definitions.json#/readable-file", 14 | "short_opt": "-c", 15 | "description": "Measurement setup (in JSON)", 16 | "default": "pipeline.json" 17 | }, 18 | "output": { 19 | "$ref": "definitions.json#/writable-file", 20 | "short_opt": "-o", 21 | "default": "/dev/stdout", 22 | "description": "Output file" 23 | }, 24 | "dir": { 25 | "type": "string", 26 | "enum": ["uplink", "downlink", "bidir"], 27 | "short_opt": "-d", 28 | "default": "uplink", 29 | "description": "Direction: uplink, downlink, or bidir" 30 | }, 31 | "pkt-num": { 32 | "$ref": "definitions.json#/non-negative-integer", 33 | "short_opt": "-n", 34 | "default": 10, 35 | "description": "Number of packets (0: determined by the pipeline 'size')" 36 | }, 37 | "pkt-size": { 38 | "$ref": "definitions.json#/positive-integer", 39 | "short_opt": "-s", 40 | "default": 64, 41 | "description": "Size of packets" 42 | }, 43 | "thread": { 44 | "$ref": "definitions.json#/non-negative-integer", 45 | "short_opt": "-t", 46 | "default": 0, 47 | "description": 48 | "Number of requested processing CPU threads. 0 means all of the available cores." 49 | }, 50 | "random-seed": { 51 | "$ref": "definitions.json#/positive-integer", 52 | "default": 1, 53 | "description": "Seed to initialize the random generator with. 0 means the current system time" 54 | }, 55 | "ascii": { 56 | "type": "boolean", 57 | "short_opt": "-a", 58 | "default": false, 59 | "description": "Dump generated packets in human readable ASCII form" 60 | }, 61 | "trace-generator-cmd": { 62 | "type": "string", 63 | "default": "/opt/trace_generator/trace_generator", 64 | "description": "Absolute path of the trace_generator executable, necessary for the FW pipeline (https://www.arl.wustl.edu/classbench/)" 65 | }, 66 | "trace-generator-pareto-a": { 67 | "type": "number", 68 | "default": 0.5, 69 | "description": "Pareto-a parameter of trace_generator" 70 | }, 71 | "trace-generator-pareto-b": { 72 | "type": "number", 73 | "default": 0.5, 74 | "description": "Pareto-b parameter of trace_generator" 75 | }, 76 | "trace-generator-scale": { 77 | "type": "number", 78 | "default": 1, 79 | "description": "Scale parameter of trace_generator" 80 | }, 81 | "tunneling-method": { 82 | "type": "string", 83 | "enum": ["vxlan", "gtp"], 84 | "default": "vxlan", 85 | "description": "Pipelines supporting gtp: mgw" 86 | } 87 | }, 88 | "required": [], 89 | "additionalProperties": false 90 | } 91 | -------------------------------------------------------------------------------- /utils/extract: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # TIPSY: Telco pIPeline benchmarking SYstem 4 | # 5 | # Copyright (C) 2018 by its authors (See AUTHORS) 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but 13 | # WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | # General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import sys 21 | import argparse 22 | import json 23 | 24 | def eprint(*args, **kwargs): 25 | print(*args, file=sys.stderr, **kwargs) 26 | 27 | parser = argparse.ArgumentParser() 28 | parser.description = "Extract a proprety from a json object" 29 | parser.add_argument('filename', type=argparse.FileType('r')) 30 | parser.add_argument('pname', metavar='property-name', type=str) 31 | args = parser.parse_args() 32 | 33 | obj = json.load(args.filename) 34 | try: 35 | obj = obj[args.pname] 36 | except KeyError as e: 37 | eprint(e) 38 | eprint('Available properties: %s', obj.keys()) 39 | exit(-1) 40 | 41 | print(json.dumps(obj, indent=2, sort_keys=True)) 42 | -------------------------------------------------------------------------------- /utils/show-tables: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # TIPSY: Telco pIPeline benchmarking SYstem 4 | # 5 | # Copyright (C) 2018 by its authors (See AUTHORS) 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, but 13 | # WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | # General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | br=br-main 21 | cmd="sudo ovs-ofctl --protocols=OpenFlow13 --color=always --stats --names" 22 | sort="" 23 | filter="sed -e s/cookie.*table=/tbl=/ -e s/n_bytes[^,]*,.//" 24 | 25 | $cmd dump-flows $sort $br | $filter 26 | $cmd dump-groups $br 27 | $cmd dump-meters $br 28 | $cmd dump-flows $sort br-phy | $filter 29 | -------------------------------------------------------------------------------- /utils/sut-setup-script-example: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # A sample script to be used as sut.setup-script. 4 | # It reboots SUT if current sut.type is different from the previous one. 5 | # 6 | # Copyright (C) 2018 by its authors (See AUTHORS) 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, but 14 | # WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | # General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import json 22 | import subprocess 23 | import time 24 | from pathlib import Path, PosixPath 25 | 26 | class Config(dict): 27 | def __init__(self, *files, **kwargs): 28 | self.update(kwargs) 29 | for f in files: 30 | self.load(f) 31 | 32 | def __getattr__(self, name): 33 | return self[name.replace('_', '-')] 34 | 35 | def __setattr__(self, name, value): 36 | self[name.replace('_', '-')] = value 37 | 38 | def __delattr__(self, name): 39 | del self[name.replace('_', '-')] 40 | 41 | def load(self, file): 42 | """Update dict with json encode data from `file`. 43 | file is a filename or Path.""" 44 | 45 | oh = lambda x: Config(**x) 46 | try: 47 | if type(file) == PosixPath: 48 | with file.open('r') as f: 49 | data = json.load(f, object_hook=oh) 50 | else: 51 | with open(file, 'r') as f: 52 | data = json.load(f, object_hook=oh) 53 | except Exception as e: 54 | print(e) 55 | exit(-1) 56 | self.update(**data) 57 | 58 | 59 | class Reboot(object): 60 | def __init__(self): 61 | fname = '/tmp/tipsy_sut_type' 62 | conf = Config(Path().cwd() / 'benchmark.json') 63 | self.hostname = conf.sut.hostname 64 | new_type = conf.sut.type 65 | old_type = self.get_remote_file_content(fname) 66 | print('new_type: \033[33m%s\033[0m' % new_type.upper()) 67 | 68 | if old_type and old_type != new_type: 69 | print('old_type: "%s", new_type: "%s"' % (old_type, new_type)) 70 | self.reboot_and_wait() 71 | self.ssh_cmd('/export/netsys/src/scripts/%s-start' % new_type) 72 | elif old_type is None: 73 | self.ssh_cmd('/export/netsys/src/scripts/%s-start' % new_type) 74 | 75 | self.write_remote_file(fname, new_type) 76 | 77 | def get_remote_file_content(self, fname): 78 | try: 79 | cmd = ['ssh', self.hostname, 'cat', fname] 80 | print(' '.join(cmd)) 81 | cnt = subprocess.check_output(cmd) 82 | except subprocess.CalledProcessError as e: 83 | #print('error', e) 84 | return None 85 | return cnt.decode('utf-8').strip() 86 | 87 | def ssh_cmd(self, cmd): 88 | if type(cmd) == str: 89 | cmd = [cmd] 90 | cmd = ['ssh', self.hostname] + cmd 91 | print(' '.join(cmd)) 92 | return subprocess.run(cmd, check=True) 93 | 94 | def write_remote_file(self, fname, content): 95 | lname = '/tmp/tipsy.reboot.tmp' 96 | with open(lname, 'w') as f: 97 | f.write(content) 98 | cmd = ['scp', lname, '%s:%s' % (self.hostname, fname)] 99 | print(' '.join(cmd)) 100 | subprocess.run(cmd, check=True) 101 | 102 | def reboot_and_wait(self): 103 | try: 104 | self.ssh_cmd(['sudo', 'reboot']) 105 | except subprocess.CalledProcessError as e: 106 | pass 107 | time.sleep(120) 108 | while self.get_remote_file_content('/etc/hostname') != self.hostname: 109 | time.sleep(15) 110 | print('!') 111 | 112 | 113 | if __name__ == '__main__': 114 | Reboot() 115 | -------------------------------------------------------------------------------- /utils/test-pcap/.gitignore: -------------------------------------------------------------------------------- 1 | *.pcap 2 | *.json 3 | fw_rules 4 | fw_rules_trace 5 | gmon.out 6 | -------------------------------------------------------------------------------- /utils/test-pcap/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | gen_conf=../../lib/gen_conf.py 4 | gen_pcap=../../lib/gen_pcap.py 5 | classbench=../../../classbench-ng/classbench 6 | tracegenerator=../../../trace_generator/trace_generator 7 | 8 | pipelines=$(ls -1 $(dirname $gen_pcap)/../schema/pipeline-*.json | \ 9 | sed -e 's/.*-\(.*\).json$/\1/g') 10 | 11 | for pl in $pipelines; do 12 | echo $pl 13 | e="" 14 | e2="" 15 | if [ $pl == fw ]; then 16 | e="$e --classbench=$classbench" 17 | e2="$e2 --trace-generator-cmd=$tracegenerator" 18 | #e2="$e2 --trace-generator-pareto-a=1" 19 | #e2="$e2 --trace-generator-pareto-b=1" 20 | fi 21 | $gen_conf -p $pl $e -o pipeline-$pl.json 22 | $gen_pcap $e2 -d uplink -c pipeline-$pl.json -o t-$pl-u.pcap 23 | $gen_pcap $e2 -d downlink -c pipeline-$pl.json -o t-$pl-d.pcap 24 | time $gen_pcap $e2 -t 0 -n 10000 -c pipeline-$pl.json -o t-$pl.pcap 25 | done 26 | -------------------------------------------------------------------------------- /vagrant/.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | ssh 3 | tests/.tipsy.json 4 | tests/Makefile 5 | tests/measurements 6 | tests/plots 7 | -------------------------------------------------------------------------------- /vagrant/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # https://www.vagrantup.com/docs/vagrantfile/tips.html#overwrite-host-locale-in-ssh-session 5 | ENV["LC_ALL"] = "en_US.UTF-8" 6 | 7 | system(" 8 | mkdir -p ssh 9 | if [ ! -e ssh/id_rsa ]; then 10 | ssh-keygen -f ssh/id_rsa -t rsa -N '' 11 | fi 12 | ") 13 | 14 | Vagrant.configure("2") do |config| 15 | config.vm.box = "bento/ubuntu-18.04" 16 | 17 | config.vm.define "SUT" do |s| 18 | s.vm.hostname = "SUT" 19 | s.vm.network "private_network", ip: "192.168.51.3", 20 | virtualbox__intnet: "tu2sd", adapter: 2 21 | s.vm.network "private_network", ip: "192.168.52.3", 22 | virtualbox__intnet: "td2su", adapter: 3 23 | s.vm.network "private_network", ip: "192.168.53.3", 24 | virtualbox__intnet: "mgmnt", adapter: 4 25 | s.vm.provider "virtualbox" do |vb| 26 | vb.name = "SUT" 27 | vb.memory = 3072 28 | vb.cpus = 2 29 | vb.customize ["modifyvm", :id, "--nic1", "nat"] 30 | vb.customize ["modifyvm", :id, "--nictype1", "virtio"] 31 | vb.customize ["modifyvm", :id, "--nictype2", "virtio"] 32 | vb.customize ["modifyvm", :id, "--nictype3", "virtio"] 33 | vb.customize ["modifyvm", :id, "--nictype4", "virtio"] 34 | vb.customize ["modifyvm", :id, "--nicpromisc2", "allow-all"] 35 | vb.customize ["modifyvm", :id, "--nicpromisc3", "allow-all"] 36 | vb.customize ["modifyvm", :id, "--nicpromisc4", "allow-all"] 37 | end 38 | config.vm.synced_folder ".", "/vagrant" 39 | config.vm.synced_folder "..", "/opt/tipsy" 40 | s.vm.provision "shell", path: "sut-install.sh" 41 | s.vm.provision "shell", path: "sut-start-ovs.sh", run: 'always' 42 | end 43 | 44 | config.vm.define "Tester" do |t| 45 | t.vm.hostname = "Tester" 46 | t.vm.network "private_network", ip: "192.168.51.2", 47 | virtualbox__intnet: "tu2sd", adapter: 2 48 | t.vm.network "private_network", ip: "192.168.52.2", 49 | virtualbox__intnet: "td2su", adapter: 3 50 | t.vm.network "private_network", ip: "192.168.53.2", 51 | virtualbox__intnet: "mgmnt", adapter: 4 52 | t.vm.provider "virtualbox" do |vb| 53 | vb.name = "Tester" 54 | vb.memory = 3072 55 | vb.cpus = 2 56 | vb.customize ["modifyvm", :id, "--nic1", "nat"] 57 | vb.customize ["modifyvm", :id, "--nictype1", "virtio"] 58 | vb.customize ["modifyvm", :id, "--nictype2", "virtio"] 59 | vb.customize ["modifyvm", :id, "--nictype3", "virtio"] 60 | vb.customize ["modifyvm", :id, "--nictype4", "virtio"] 61 | vb.customize ["modifyvm", :id, "--nicpromisc2", "allow-all"] 62 | vb.customize ["modifyvm", :id, "--nicpromisc3", "allow-all"] 63 | vb.customize ["modifyvm", :id, "--nicpromisc4", "allow-all"] 64 | end 65 | config.vm.synced_folder ".", "/vagrant" 66 | config.vm.synced_folder "..", "/opt/tipsy" 67 | t.vm.provision "file", 68 | source: "ssh/id_rsa", 69 | destination: "~/.ssh/id_rsa" 70 | t.vm.provision "shell", 71 | path: "tester-install.sh" 72 | t.vm.provision "shell", 73 | path: "tester-dpdk-init.sh", 74 | run: "always" 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /vagrant/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | vagrant up 4 | vagrant ssh Tester -- /vagrant/tester-run $* 5 | -------------------------------------------------------------------------------- /vagrant/sut-install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | export DEBIAN_FRONTEND="noninteractive" 6 | 7 | apt-get update 8 | apt-get install --yes \ 9 | ssh \ 10 | screen \ 11 | make \ 12 | g++-5 \ 13 | clang-5.0 \ 14 | libssl-dev \ 15 | libcap-ng-dev \ 16 | python \ 17 | python-dev \ 18 | python-pip \ 19 | python-requests \ 20 | python-scapy \ 21 | python-six \ 22 | linux-headers-`uname -r` \ 23 | git \ 24 | autoconf \ 25 | automake \ 26 | libtool \ 27 | python-pyftpdlib \ 28 | wget \ 29 | netcat \ 30 | curl \ 31 | python-tftpy \ 32 | libnuma-dev 33 | 34 | echo 'vm.nr_hugepages=256' > /etc/sysctl.d/hugepages.conf 35 | sysctl -p /etc/sysctl.d/hugepages.conf 36 | 37 | cat /vagrant/ssh/id_rsa.pub >> /home/vagrant/.ssh/authorized_keys 38 | 39 | cd /usr/src/ 40 | wget http://fast.dpdk.org/rel/dpdk-17.11.1.tar.xz 41 | tar xf dpdk-17.11.1.tar.xz 42 | export DPDK_DIR=/usr/src/dpdk-stable-17.11.1 43 | cd $DPDK_DIR 44 | 45 | export DPDK_TARGET=x86_64-native-linuxapp-gcc 46 | export DPDK_BUILD=$DPDK_DIR/$DPDK_TARGET 47 | make install T=$DPDK_TARGET DESTDIR=install 48 | 49 | git clone --branch v2.9.2 --depth=1 \ 50 | https://github.com/openvswitch/ovs.git /opt/ovs 51 | cd /opt/ovs 52 | ./boot.sh 53 | ./configure --with-dpdk=$DPDK_BUILD 54 | make -j 2 55 | make install 56 | 57 | mkdir -p /usr/local/etc/openvswitch 58 | mkdir -p /usr/local/var/run/openvswitch 59 | 60 | # install ryu 61 | git clone --depth=1 https://github.com/osrg/ryu.git /opt/ryu 62 | cd /opt/ryu 63 | cp /opt/tipsy/module/openflow/color_log.py ryu/contrib 64 | pip install . 65 | 66 | -------------------------------------------------------------------------------- /vagrant/sut-start-ovs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export DPDK_DIR=/usr/src/dpdk-stable-17.11.1 3 | modprobe uio_pci_generic 4 | modprobe openvswitch 5 | 6 | $DPDK_DIR/usertools/dpdk-devbind.py --force --bind=uio_pci_generic 0000:00:08.0 7 | $DPDK_DIR/usertools/dpdk-devbind.py --force --bind=uio_pci_generic 0000:00:09.0 8 | 9 | 10 | export PATH=$PATH:/usr/local/share/openvswitch/scripts 11 | export DB_SOCK=/usr/local/var/run/openvswitch/db.sock 12 | export DB_CONF=/usr/local/etc/openvswitch/conf.db 13 | cd /opt/ovs 14 | ovsdb/ovsdb-tool create ${DB_CONF} vswitchd/vswitch.ovsschema 15 | ovsdb/ovsdb-server --remote=punix:${DB_SOCK} \ 16 | --remote=db:Open_vSwitch,Open_vSwitch,manager_options \ 17 | --pidfile --detach 18 | utilities/ovs-vsctl --no-wait set Open_vSwitch . \ 19 | other_config:dpdk-init=true 20 | utilities/ovs-vsctl --no-wait set Open_vSwitch . \ 21 | other_config:dpdk-socket-mem="512" 22 | vswitchd/ovs-vswitchd unix:${DB_SOCK} --pidfile --detach 23 | utilities/ovs-ctl --no-ovsdb-server --db-sock="$DB_SOCK" start 24 | -------------------------------------------------------------------------------- /vagrant/tester-dpdk-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ip link set dev eth1 down 3 | ip link set dev eth2 down 4 | /opt/MoonGen/setup-hugetlbfs.sh 5 | /opt/MoonGen/bind-interfaces.sh 6 | -------------------------------------------------------------------------------- /vagrant/tester-install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | export DEBIAN_FRONTEND="noninteractive" 6 | 7 | apt-get update 8 | apt-get install --yes \ 9 | build-essential \ 10 | make \ 11 | cmake \ 12 | linux-headers-`uname -r` \ 13 | pciutils libnuma-dev \ 14 | git \ 15 | gcc \ 16 | ssh \ 17 | python-pip \ 18 | python-dev \ 19 | python3-jsonschema \ 20 | python3-matplotlib \ 21 | libffi-dev \ 22 | libssl-dev \ 23 | libtbb2 \ 24 | libxml2-dev \ 25 | libxslt1-dev \ 26 | zlib1g-dev \ 27 | texlive-latex-base \ 28 | texlive-pictures \ 29 | #scapy 30 | 31 | 32 | # scapy 2.2 in "Ubuntu 16.04.4 LTS, xenial" does not support VXLAN 33 | pip install scapy 34 | 35 | echo 192.168.53.3 sut.local >> /etc/hosts 36 | 37 | git clone --depth=1 https://github.com/emmericp/MoonGen.git /opt/MoonGen 38 | cd /opt/MoonGen 39 | ./build.sh 40 | 41 | 42 | # It's hard to patch a makefile downloaded during 'make', so: 43 | cat > /usr/local/bin/g++ <<'EOF' 44 | #!/bin/sh 45 | /usr/bin/g++ -fpermissive $* 46 | EOF 47 | chmod a+x /usr/local/bin/g++ 48 | 49 | # Classbench 50 | url=https://github.com/classbench-ng/classbench-ng.git 51 | target=/opt/classbench-ng 52 | git clone --depth=1 $url $target 53 | cd $target 54 | make 55 | gem install open4 ruby-ip docopt ipaddress 56 | 57 | url=https://www.arl.wustl.edu/classbench/trace_generator.tar.gz 58 | wget -qO- $url | tar xz -C /opt 59 | cd /opt/trace_generator 60 | make all 61 | 62 | # 63 | rm /usr/local/bin/g++ 64 | 65 | 66 | # T-Rex 67 | TREX_WEB_URL=http://trex-tgn.cisco.com/trex 68 | mkdir -p /opt/trex 69 | cd /opt 70 | wget --no-cache ${TREX_WEB_URL}/release/latest -O trex-latest 71 | tar --strip-components=1 -C /opt/trex -xzf trex-latest 72 | rm trex-latest 73 | cd /opt/trex 74 | tar axf trex_client_*.tar.gz 75 | -------------------------------------------------------------------------------- /vagrant/tester-run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | TIPSY=/opt/tipsy/tipsy 4 | 5 | # Permanently add 'sut.local' to the list of known hosts 6 | ssh -o StrictHostKeyChecking=no sut.local echo 7 | 8 | if [ $# -gt 0 ]; then 9 | tests=0_default.json 10 | for test in $*; do 11 | if [ -e ${test}.json ]; then 12 | tests="$tests ${test}.json" 13 | else 14 | f=$(find $(dirname $TIPSY)/module -name test-${test}.json) 15 | if [ $(echo $f|wc -w) -eq 1 ]; then 16 | tests="$tests $f" 17 | else 18 | echo Cannot find: $test >&2 19 | exit 1 20 | fi 21 | fi 22 | done 23 | else 24 | tests="*.json $($TIPSY list-module-tests)" 25 | fi 26 | cd /vagrant/tests 27 | $TIPSY config -f $tests 28 | make 29 | -------------------------------------------------------------------------------- /vagrant/tests/0_default.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": { 3 | "sut": { 4 | "bess-dir": "/opt/bess", 5 | "coremask": "0x3f03f", 6 | "downlink-port": "0000:00:08.0", 7 | "hostname": "sut.local", 8 | "portmask": "0x3", 9 | "tipsy-dir": "/opt/tipsy", 10 | "type": "ovs", 11 | "uplink-port": "0000:00:09.0" 12 | }, 13 | "tester": { 14 | "type": "moongen-flood", 15 | "core": 1, 16 | "downlink-port": "1", 17 | "uplink-port": "0", 18 | "moongen-cmd": "/opt/MoonGen/build/MoonGen", 19 | "test-time": 10 20 | }, 21 | "traffic": { 22 | "dir": "uplink", 23 | "pkt-num": 0, 24 | "pkt-size": 64, 25 | "random-seed": 1, 26 | "thread": 0 27 | } 28 | } 29 | } 30 | --------------------------------------------------------------------------------