├── docs
├── .nojekyll
├── objects.inv
├── _static
│ ├── file.png
│ ├── plus.png
│ ├── minus.png
│ ├── contents.png
│ ├── navigation.png
│ ├── documentation_options.js
│ ├── pygments.css
│ └── doctools.js
├── _images
│ ├── packet.png
│ ├── applayer.png
│ ├── srpyarch.png
│ └── applayer_detail.png
├── .buildinfo
├── _sources
│ ├── thanks.rst.txt
│ ├── index.rst.txt
│ ├── release_notes.rst.txt
│ ├── installation.rst.txt
│ └── intro.rst.txt
├── search.html
├── py-modindex.html
├── _modules
│ └── index.html
└── thanks.html
├── switchyard
├── __init__.py
├── lib
│ ├── __init__.py
│ ├── openflow
│ │ └── __init__.py
│ ├── .gitignore
│ ├── packet
│ │ ├── .gitignore
│ │ ├── __init__.py
│ │ ├── util.py
│ │ ├── null.py
│ │ ├── udp.py
│ │ └── arp.py
│ ├── socket
│ │ └── __init__.py
│ ├── topo
│ │ ├── __init__.py
│ │ └── util.py
│ ├── userlib.py
│ ├── debugging.py
│ ├── exceptions.py
│ ├── logging.py
│ └── interface.py
├── sim
│ ├── __init__.py
│ ├── linkem.py
│ └── nodeexec.py
├── outputfmt.py
├── importcode.py
├── textcolor.py
└── swyard.py
├── documentation
├── .gitignore
├── packet.pdf
├── packet.png
├── applayer.pdf
├── applayer.png
├── srpyarch.pdf
├── srpyarch.png
├── packet.graffle
├── applayer_detail.pdf
├── applayer_detail.png
├── code
│ ├── emptytestscenario.py
│ ├── failhub4.py
│ ├── run_example.py
│ ├── failhub1.py
│ ├── failhub2.py
│ ├── inout1.py
│ ├── failhub3.py
│ ├── failhub8.py
│ ├── failhub7.py
│ ├── failhub6.py
│ ├── badscenario1.py
│ ├── clientsocketapp.py
│ ├── inoutloop.py
│ ├── inout2.py
│ ├── enterdebugger.py
│ ├── testscenario1.py
│ ├── fullhub.py
│ ├── failhub5.py
│ ├── intfex.py
│ ├── fullhub2.py
│ ├── baaadhub.py
│ ├── inouttest.py
│ ├── protostackpattern.py
│ ├── testscenario2.py
│ ├── pkt_construction.py
│ ├── newheader.py
│ ├── udpappheader.py
│ └── hubtests.py
├── README.md
├── thanks.rst
├── index.rst
├── release_notes.rst
├── installation.rst
└── intro.rst
├── requirements.txt
├── examples
├── exercises
│ ├── applayer
│ │ ├── msgboarddiag.pdf
│ │ ├── msgboarddiag.png
│ │ ├── msgboarddiag.graffle
│ │ ├── msgboardmiddlebox.rst
│ │ └── applayer.rst
│ ├── router
│ │ ├── router1_pcap1.png
│ │ ├── router1_pcap2.png
│ │ ├── router2_pcap.png
│ │ ├── dvroute
│ │ │ ├── rippkt1.png
│ │ │ ├── dv_topology.pdf
│ │ │ ├── dv_topology.png
│ │ │ └── dv_topology.graffle
│ │ │ │ ├── data.plist
│ │ │ │ └── image1.pdf
│ │ ├── router2_topology.png
│ │ ├── router_topology.png
│ │ ├── lsroute
│ │ │ ├── ls_topology.pdf
│ │ │ ├── ls_topology.png
│ │ │ └── ls_topology.graffle
│ │ │ │ ├── data.plist
│ │ │ │ └── image1.pdf
│ │ ├── router2_topology.graffle
│ │ │ ├── data.plist
│ │ │ └── image1.pdf
│ │ ├── router_topology.graffle
│ │ │ ├── data.plist
│ │ │ └── image1.pdf
│ │ ├── forwarding_table.txt
│ │ ├── router4.rst
│ │ ├── myrouter.py
│ │ └── start_mininet.py
│ ├── firewall
│ │ ├── firewall_topology.png
│ │ ├── firewall_topology.graffle
│ │ │ ├── data.plist
│ │ │ ├── image1.pdf
│ │ │ └── image2.pdf
│ │ ├── www
│ │ │ └── start_webserver.sh
│ │ ├── firewall.py
│ │ ├── firewall_rules.txt
│ │ ├── start_mininet.py
│ │ └── impairmenttest.py
│ ├── learning_switch
│ │ ├── ls_diagram.png
│ │ ├── ls_flowchart.png
│ │ ├── ls_flowchart.graffle
│ │ ├── ls_diagram.graffle
│ │ │ ├── data.plist
│ │ │ └── image1.pdf
│ │ ├── myswitch.py
│ │ └── switchtopo.py
│ └── README.rst
├── server_udpstackex.py
├── clientapp_udpstackex.py
├── sniff.py
├── readpkt.py
├── sydump.py
├── udpstack_tests.py
├── sendpkt.py
├── myhub.py
├── udpstack.py
├── hubtests.py
└── README.rst
├── .gitignore
├── tests
├── test_rwimg.py
├── test_addr.py
├── test_null.py
├── test_udp.py
├── test_color.py
├── test_tcp.py
├── test_ethernet.py
├── test_importer.py
├── test_arp.py
├── test_ofswitch_ext.py
├── test_ripv2.py
├── test_pcapffi.py
└── test_hostfirewall.py
├── Dockerfile
├── runtests.sh
├── .travis.yml
├── setup.py
└── README.rst
/docs/.nojekyll:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/switchyard/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/switchyard/lib/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/switchyard/sim/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/switchyard/lib/openflow/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/switchyard/lib/.gitignore:
--------------------------------------------------------------------------------
1 | testpcap.py
2 |
--------------------------------------------------------------------------------
/documentation/.gitignore:
--------------------------------------------------------------------------------
1 | _build
2 | x.txt
3 |
--------------------------------------------------------------------------------
/switchyard/lib/packet/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | __pycache__
3 |
--------------------------------------------------------------------------------
/switchyard/lib/socket/__init__.py:
--------------------------------------------------------------------------------
1 | from .socketemu import *
2 |
--------------------------------------------------------------------------------
/docs/objects.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/docs/objects.inv
--------------------------------------------------------------------------------
/switchyard/lib/topo/__init__.py:
--------------------------------------------------------------------------------
1 | from .util import *
2 | from .topobuild import *
3 |
--------------------------------------------------------------------------------
/docs/_static/file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/docs/_static/file.png
--------------------------------------------------------------------------------
/docs/_static/plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/docs/_static/plus.png
--------------------------------------------------------------------------------
/docs/_images/packet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/docs/_images/packet.png
--------------------------------------------------------------------------------
/docs/_static/minus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/docs/_static/minus.png
--------------------------------------------------------------------------------
/documentation/packet.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/documentation/packet.pdf
--------------------------------------------------------------------------------
/documentation/packet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/documentation/packet.png
--------------------------------------------------------------------------------
/docs/_images/applayer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/docs/_images/applayer.png
--------------------------------------------------------------------------------
/docs/_images/srpyarch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/docs/_images/srpyarch.png
--------------------------------------------------------------------------------
/docs/_static/contents.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/docs/_static/contents.png
--------------------------------------------------------------------------------
/documentation/applayer.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/documentation/applayer.pdf
--------------------------------------------------------------------------------
/documentation/applayer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/documentation/applayer.png
--------------------------------------------------------------------------------
/documentation/srpyarch.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/documentation/srpyarch.pdf
--------------------------------------------------------------------------------
/documentation/srpyarch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/documentation/srpyarch.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | cffi>=1.14.0
2 | colorama>=0.4.3
3 | coverage>=5.1
4 | networkx>=2.4
5 | psutil>=5.7.0
6 |
--------------------------------------------------------------------------------
/docs/_static/navigation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/docs/_static/navigation.png
--------------------------------------------------------------------------------
/documentation/packet.graffle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/documentation/packet.graffle
--------------------------------------------------------------------------------
/docs/_images/applayer_detail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/docs/_images/applayer_detail.png
--------------------------------------------------------------------------------
/documentation/applayer_detail.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/documentation/applayer_detail.pdf
--------------------------------------------------------------------------------
/documentation/applayer_detail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/documentation/applayer_detail.png
--------------------------------------------------------------------------------
/documentation/code/emptytestscenario.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | scenario = TestScenario("test example")
4 |
--------------------------------------------------------------------------------
/examples/exercises/applayer/msgboarddiag.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/applayer/msgboarddiag.pdf
--------------------------------------------------------------------------------
/examples/exercises/applayer/msgboarddiag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/applayer/msgboarddiag.png
--------------------------------------------------------------------------------
/examples/exercises/router/router1_pcap1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/router/router1_pcap1.png
--------------------------------------------------------------------------------
/examples/exercises/router/router1_pcap2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/router/router1_pcap2.png
--------------------------------------------------------------------------------
/examples/exercises/router/router2_pcap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/router/router2_pcap.png
--------------------------------------------------------------------------------
/examples/exercises/router/dvroute/rippkt1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/router/dvroute/rippkt1.png
--------------------------------------------------------------------------------
/examples/exercises/router/router2_topology.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/router/router2_topology.png
--------------------------------------------------------------------------------
/examples/exercises/router/router_topology.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/router/router_topology.png
--------------------------------------------------------------------------------
/examples/exercises/applayer/msgboarddiag.graffle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/applayer/msgboarddiag.graffle
--------------------------------------------------------------------------------
/examples/exercises/firewall/firewall_topology.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/firewall/firewall_topology.png
--------------------------------------------------------------------------------
/examples/exercises/learning_switch/ls_diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/learning_switch/ls_diagram.png
--------------------------------------------------------------------------------
/examples/exercises/router/dvroute/dv_topology.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/router/dvroute/dv_topology.pdf
--------------------------------------------------------------------------------
/examples/exercises/router/dvroute/dv_topology.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/router/dvroute/dv_topology.png
--------------------------------------------------------------------------------
/examples/exercises/router/lsroute/ls_topology.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/router/lsroute/ls_topology.pdf
--------------------------------------------------------------------------------
/examples/exercises/router/lsroute/ls_topology.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/router/lsroute/ls_topology.png
--------------------------------------------------------------------------------
/examples/exercises/learning_switch/ls_flowchart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/learning_switch/ls_flowchart.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | xenv
2 | *.pyc
3 | __pycache__
4 | *.pcap
5 | .coverage
6 | htmlcov
7 | sandbox
8 | updatepypi.sh
9 | switchyard.egg-info
10 | build
11 | dist
12 |
--------------------------------------------------------------------------------
/examples/exercises/learning_switch/ls_flowchart.graffle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/learning_switch/ls_flowchart.graffle
--------------------------------------------------------------------------------
/examples/exercises/router/router2_topology.graffle/data.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/router/router2_topology.graffle/data.plist
--------------------------------------------------------------------------------
/examples/exercises/router/router2_topology.graffle/image1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/router/router2_topology.graffle/image1.pdf
--------------------------------------------------------------------------------
/examples/exercises/router/router_topology.graffle/data.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/router/router_topology.graffle/data.plist
--------------------------------------------------------------------------------
/examples/exercises/router/router_topology.graffle/image1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/router/router_topology.graffle/image1.pdf
--------------------------------------------------------------------------------
/examples/exercises/firewall/firewall_topology.graffle/data.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/firewall/firewall_topology.graffle/data.plist
--------------------------------------------------------------------------------
/examples/exercises/firewall/firewall_topology.graffle/image1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/firewall/firewall_topology.graffle/image1.pdf
--------------------------------------------------------------------------------
/examples/exercises/firewall/firewall_topology.graffle/image2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/firewall/firewall_topology.graffle/image2.pdf
--------------------------------------------------------------------------------
/examples/exercises/learning_switch/ls_diagram.graffle/data.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/learning_switch/ls_diagram.graffle/data.plist
--------------------------------------------------------------------------------
/examples/exercises/learning_switch/ls_diagram.graffle/image1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/learning_switch/ls_diagram.graffle/image1.pdf
--------------------------------------------------------------------------------
/examples/exercises/router/dvroute/dv_topology.graffle/data.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/router/dvroute/dv_topology.graffle/data.plist
--------------------------------------------------------------------------------
/examples/exercises/router/dvroute/dv_topology.graffle/image1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/router/dvroute/dv_topology.graffle/image1.pdf
--------------------------------------------------------------------------------
/examples/exercises/router/lsroute/ls_topology.graffle/data.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/router/lsroute/ls_topology.graffle/data.plist
--------------------------------------------------------------------------------
/examples/exercises/router/lsroute/ls_topology.graffle/image1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsommers/switchyard/HEAD/examples/exercises/router/lsroute/ls_topology.graffle/image1.pdf
--------------------------------------------------------------------------------
/documentation/code/failhub4.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | def main(net):
4 | timestamp,input_port,packet = net.recv_packet()
5 | print ("Received {} on {}".format(packet, input_port))
6 | net.clone_packet()
7 |
--------------------------------------------------------------------------------
/documentation/code/run_example.py:
--------------------------------------------------------------------------------
1 | from code import InteractiveConsole
2 | import sys
3 |
4 | console = InteractiveConsole()
5 | for line in sys.stdin:
6 | print(">>> {}".format(line.strip()))
7 | console.push(line.strip())
8 |
9 |
--------------------------------------------------------------------------------
/examples/exercises/router/forwarding_table.txt:
--------------------------------------------------------------------------------
1 | 172.16.0.0 255.255.0.0 192.168.1.2 router-eth0
2 | 172.16.128.0 255.255.192.0 10.10.0.254 router-eth1
3 | 172.16.64.0 255.255.192.0 10.10.1.254 router-eth1
4 | 10.100.0.0 255.255.0.0 172.16.42.2 router-eth2
--------------------------------------------------------------------------------
/documentation/code/failhub1.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | def main(net):
4 | timestamp,input_port,packet = net.recv_packet()
5 | print ("Received {} on {}".format(packet, input_port))
6 | net.send_packet('eth0', packet)
7 |
--------------------------------------------------------------------------------
/documentation/code/failhub2.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | def main(net):
4 | timestamp,input_port,packet = net.recv_packet()
5 | print ("Received {} on {}".format(packet, input_port))
6 | # net.send_packet('eth0', packet)
7 |
--------------------------------------------------------------------------------
/documentation/code/inout1.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | def main(net):
4 | timestamp,input_port,packet = net.recv_packet()
5 | print ("Received {} on {}".format(packet, input_port))
6 | net.send_packet(input_port, packet)
7 |
--------------------------------------------------------------------------------
/docs/.buildinfo:
--------------------------------------------------------------------------------
1 | # Sphinx build info version 1
2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
3 | config: 30eb21c01711bfe8ce05dc0c79cca030
4 | tags: 645f666f9bcd5a90fca523b33c5a78b7
5 |
--------------------------------------------------------------------------------
/documentation/code/failhub3.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | def main(net):
4 | timestamp,input_port,packet = net.recv_packet()
5 | print ("Received {} on {}".format(packet, input_port))
6 | timestamp,input_port,packet = net.recv_packet()
7 |
--------------------------------------------------------------------------------
/documentation/code/failhub8.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | def main(net):
4 | timestamp,input_port,packet = net.recv_packet()
5 | print ("Received {} on {}".format(packet, input_port))
6 | del packet[-1]
7 | net.send_packet("eth0", packet)
8 |
--------------------------------------------------------------------------------
/documentation/code/failhub7.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | def main(net):
4 | timestamp,input_port,packet = net.recv_packet()
5 | print ("Received {} on {}".format(packet, input_port))
6 | packet[-1] = TCP()
7 | net.send_packet("eth0", packet)
8 |
--------------------------------------------------------------------------------
/documentation/code/failhub6.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | def main(net):
4 | timestamp,input_port,packet = net.recv_packet()
5 | print ("Received {} on {}".format(packet, input_port))
6 | packet[Ethernet].src = "ba:aa:aa:ba:aa:aa"
7 | net.send_packet("eth0", packet)
8 |
--------------------------------------------------------------------------------
/documentation/code/badscenario1.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | scenario = TestScenario("test example")
4 | scenario.add_interface('eth0', 'ab:cd:ef:ab:cd:ef', '1.2.3.4', '255.255.0.0', iftype=InterfaceType.Wired)
5 |
6 | scenario.expect(PacketInputEvent('eth1', Packet()), "A packet knocks on the door...")
7 |
--------------------------------------------------------------------------------
/documentation/README.md:
--------------------------------------------------------------------------------
1 | # Switchyard documentation
2 |
3 | This directory contains Sphinx source (http://sphinx-doc.org/) for Switchyard documentation.
4 |
5 | ## License
6 |
7 | This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
8 | http://creativecommons.org/licenses/by-nc-sa/4.0/
9 |
--------------------------------------------------------------------------------
/switchyard/outputfmt.py:
--------------------------------------------------------------------------------
1 | class VerboseOutput(object):
2 | _on = False
3 |
4 | @staticmethod
5 | def enable():
6 | VerboseOutput._on = True
7 |
8 | @staticmethod
9 | def disable():
10 | VerboseOutput._on = False
11 |
12 | @staticmethod
13 | def enabled():
14 | return VerboseOutput._on
15 |
--------------------------------------------------------------------------------
/switchyard/lib/packet/__init__.py:
--------------------------------------------------------------------------------
1 | from .packet import *
2 | from .common import *
3 |
4 | from .ethernet import *
5 | from .arp import *
6 |
7 | from .ipv4 import *
8 | from .ipv6 import *
9 | from .icmpv6 import *
10 |
11 | from .udp import *
12 | from .tcp import *
13 | from .icmp import *
14 |
15 | from .ripv2 import *
16 |
17 | from .util import *
18 |
19 | from .null import *
20 |
21 |
--------------------------------------------------------------------------------
/docs/_static/documentation_options.js:
--------------------------------------------------------------------------------
1 | const DOCUMENTATION_OPTIONS = {
2 | VERSION: '1.0.1',
3 | LANGUAGE: 'en',
4 | COLLAPSE_INDEX: false,
5 | BUILDER: 'html',
6 | FILE_SUFFIX: '.html',
7 | LINK_SUFFIX: '.html',
8 | HAS_SOURCE: true,
9 | SOURCELINK_SUFFIX: '.txt',
10 | NAVIGATION_WITH_KEYS: false,
11 | SHOW_SEARCH_SUMMARY: true,
12 | ENABLE_SEARCH_SHORTCUTS: true,
13 | };
--------------------------------------------------------------------------------
/examples/server_udpstackex.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import socket
3 | HOST = '127.0.0.1'
4 | PORT = 10000
5 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
6 | s.bind((HOST, PORT))
7 | print("Server waiting on port {} for a message to echo back".format(PORT))
8 | data,addr = s.recvfrom(1024)
9 | print("Received {} from {}".format(repr(data), repr(addr)))
10 | x = s.sendto(data, (addr[0],addr[1]))
11 | s.close()
12 |
13 |
--------------------------------------------------------------------------------
/tests/test_rwimg.py:
--------------------------------------------------------------------------------
1 | import os
2 | from switchyard.lib.topo import *
3 |
4 | t = Topology()
5 | h1 = t.addHost()
6 | h2 = t.addHost()
7 | s1 = t.addSwitch()
8 | s2 = t.addSwitch()
9 | t.addLink(h1,s1,1000000,0.1)
10 | t.addLink(h2,s2,1000000,0.1)
11 | t.addLink(s1,s2,1000000,"1 microsec")
12 |
13 | save_to_file(t, 'xtopo.txt')
14 | save_graph(t, 'xtopo.png', showaddrs=False, showintfs=True)
15 |
16 | os.unlink('xtopo.txt')
17 | os.unlink('xtopo.png')
18 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:20.04
2 | RUN apt-get -y update && DEBIAN_FRONTEND=noninteractive TZ="America/New_York" apt-get -y install build-essential git-core vim make gcc clang libcurl3-gnutls-dev curl wget libxml2-dev libcurl4-gnutls-dev libssh2-1-dev libz-dev libssl-dev libreadline-dev automake libtool bison manpages-dev manpages-posix-dev net-tools man-db libffi-dev libpcap-dev python3-dev python3-pip python3-venv mininet
3 | RUN pip3 install switchyard
4 | WORKDIR swyard
5 | COPY ./examples examples
6 | CMD bash
7 |
--------------------------------------------------------------------------------
/examples/exercises/firewall/www/start_webserver.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # assume we're running this from parent directory (within mininet)
4 | cd www
5 |
6 | # create a couple files for testing
7 | echo '
Test file 1 sneaky crackers wuz here! ' > 1.html
8 | dd if=/dev/zero of=bigfile bs=1k count=100 2>&1 > /dev/null
9 |
10 | if (( $# == 0 )) ; then
11 | port=80
12 | else
13 | port=$1
14 | fi
15 |
16 | python3 -m http.server ${port} &
17 |
18 |
--------------------------------------------------------------------------------
/runtests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -x
2 |
3 | export PYTHONPATH=`pwd`/../..:`pwd`
4 | coverage erase
5 | rm -rf htmlcov
6 |
7 | PAT='switchyard/*','switchyard/switch*'
8 | EXCLPAT='*__init__.py','switchyard/sim/*','switchyard/lib/openflow/*'
9 |
10 | for f in tests/*.py
11 | do
12 | # python3 $f
13 | # coverage run --source '.,switchyard' --omit '*xenv*' --include ${PAT} -a $f
14 | coverage run --source '.,switchyard' --omit '*xenv*' -a $f
15 | done
16 |
17 | coverage html --include ${PAT} --omit ${EXCLPAT}
18 | coverage report --include ${PAT} --omit ${EXCLPAT}
19 |
20 |
--------------------------------------------------------------------------------
/examples/clientapp_udpstackex.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # import socket
4 | import switchyard.lib.socket as socket
5 |
6 | HOST = '127.0.0.1'
7 | PORT = 10000
8 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
9 | s.settimeout(2.0)
10 |
11 | print("Sending message to server at {},{}".format(HOST,PORT))
12 | s.sendto(b'Hello, stack', (HOST,PORT))
13 | try:
14 | data,addr = s.recvfrom(1024)
15 | print('Client socket application received message from {}: {}'.format(repr(addr),data.decode('utf8')))
16 | except:
17 | print("Timeout")
18 |
19 | s.close()
20 |
--------------------------------------------------------------------------------
/documentation/code/clientsocketapp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # import socket
4 | import switchyard.lib.socket as socket
5 |
6 | HOST = '127.0.0.1'
7 | PORT = 10000
8 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
9 | s.settimeout(2.0)
10 |
11 | print("Sending message to server at {},{}".format(HOST,PORT))
12 | s.sendto(b'Hello, stack', (HOST,PORT))
13 | try:
14 | data,addr = s.recvfrom(1024)
15 | print('Client socket application received message from {}: {}'.format(repr(addr),data.decode('utf8')))
16 | except:
17 | print("Timeout")
18 |
19 | s.close()
20 |
--------------------------------------------------------------------------------
/documentation/code/inoutloop.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | def main(net):
4 | while True:
5 | try:
6 | timestamp,input_port,packet = net.recv_packet()
7 | except Shutdown:
8 | log_info ("Got shutdown signal; exiting")
9 | break
10 | except NoPackets:
11 | log_info ("No packets were available.")
12 | continue
13 |
14 | # if we get here, we must have received a packet
15 | log_info ("Received {} on {}".format(packet, input_port))
16 | net.send_packet(input_port, packet)
17 |
--------------------------------------------------------------------------------
/switchyard/lib/userlib.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a wrapper module to facilitate easy import of the various modules, functions, classes, and other items needed from the perspective of a user program in Switchyard.
3 | '''
4 | from .packet import *
5 | from .address import *
6 | from .exceptions import *
7 | from .logging import log_debug, log_info, log_failure, log_warn
8 | from .interface import Interface, InterfaceType
9 | from .testing import PacketInputEvent, PacketOutputEvent, PacketInputTimeoutEvent, TestScenario
10 | from .debugging import debugger
11 | from .socket.socketemu import ApplicationLayer
12 |
--------------------------------------------------------------------------------
/documentation/code/inout2.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | def main(net):
4 | # below, recvdata is a namedtuple
5 | recvdata = net.recv_packet()
6 | print ("At {}, received {} on {}".format(
7 | recvdata.timestamp, recvdata.packet, recvdata.input_port))
8 |
9 | # alternatively, the above line could use indexing, although
10 | # readability suffers:
11 | # recvdata[0], recvdata[2], recvdata[1]))
12 |
13 | net.send_packet(recvdata.input_port, recvdata.packet)
14 |
15 | # likewise, the above line could be written using indexing
16 | # but, again, readability suffers:
17 | # net.send_packet(recvdata[1], recvdata[2])
18 |
--------------------------------------------------------------------------------
/examples/sniff.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | '''
4 | Packet sniffer in Python
5 | '''
6 |
7 | from switchyard.lib.userlib import *
8 |
9 | def main(net):
10 | my_interfaces = net.interfaces()
11 | log_info ("My interfaces: {}".format([intf.name for intf in my_interfaces]))
12 | count = 0
13 | while True:
14 | try:
15 | timestamp,dev,packet = net.recv_packet(timeout=1.0)
16 | except NoPackets:
17 | continue
18 | except Shutdown:
19 | return
20 |
21 | log_info("{:.3f}: {} {}".format(timestamp,dev,packet))
22 | count += 1
23 |
24 | net.shutdown()
25 | print ("Got {} packets.".format(count))
26 |
--------------------------------------------------------------------------------
/documentation/code/enterdebugger.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | def main(net):
4 | while True:
5 | try:
6 | timestamp,input_port,packet = net.recv_packet(timeout=1.0)
7 | except NoPackets:
8 | # timeout waiting for packet arrival
9 | continue
10 | except Shutdown:
11 | # we're done; bail out of while loop
12 | break
13 |
14 | # invoke the debugger every time we get here, which
15 | # should be for every packet we receive!
16 | debugger()
17 | hdrs = packet.num_headers()
18 |
19 | # before exiting our main function,
20 | # perform shutdown on network
21 | net.shutdown()
22 |
--------------------------------------------------------------------------------
/examples/readpkt.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | '''
3 | Simple program that reads contents of a tcpdump tracefile
4 | and prints packets to stdout.
5 | '''
6 |
7 | import sys
8 | from switchyard.pcapffi import *
9 | from switchyard.lib.packet import *
10 |
11 | files = ['sydump.pcap']
12 | if len(sys.argv) > 1:
13 | files = sys.argv[1:]
14 |
15 | for infile in files:
16 | print("Opening {}.".format(infile))
17 | reader = PcapReader(infile)
18 | count = 0
19 | while True:
20 | pkt = reader.recv_packet()
21 | if pkt is None:
22 | break
23 | p = Packet(raw=pkt.raw)
24 | print (p)
25 | count += 1
26 | print ("Got {} packets from {}.".format(count, infile))
27 | reader.close()
28 |
--------------------------------------------------------------------------------
/documentation/code/testscenario1.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | scenario = TestScenario("in/out test scenario example")
4 |
5 | # only one interface on this imaginary device
6 | scenario.add_interface('eth0', 'ab:cd:ef:ab:cd:ef', '1.2.3.4/16',
7 | iftype=InterfaceType.Wired)
8 |
9 | # construct a packet to be received
10 | p = Ethernet(src="00:11:22:33:44:55", dst="66:55:44:33:22:11") + \
11 | IPv4(src="1.1.1.1", dst="2.2.2.2", protocol=IPProtocol.UDP) + \
12 | UDP(src=5555, dst=8888) + b'some payload'
13 |
14 | # expect that the packet is received
15 | scenario.expect(PacketInputEvent('eth0', p),
16 | "A udp packet should arrive on eth0")
17 |
18 | # and expect that the packet is sent right back out
19 | scenario.expect(PacketOutputEvent('eth0', p, exact=True),
20 | "The udp packet should be emitted back out eth0")
21 |
--------------------------------------------------------------------------------
/switchyard/lib/debugging.py:
--------------------------------------------------------------------------------
1 | # global: use in any timer callbacks
2 | # to decide whether to handle the timer or not.
3 | # if we're in the debugger, just drop it.
4 |
5 | from functools import wraps
6 | import pdb
7 |
8 | in_debugger = False
9 | def disable_timer():
10 | global in_debugger
11 | in_debugger = True
12 |
13 |
14 | # decorate the "real" debugger entrypoint by
15 | # disabling any SIGALRM invocations -- just ignore
16 | # them if we're going into the debugger
17 | def setup_debugger(f):
18 | @wraps(f)
19 | def wrapper(*args, **kwargs):
20 | disable_timer()
21 | return f(*args, **kwargs)
22 | return wrapper
23 |
24 | @setup_debugger
25 | def debugger():
26 | '''Invoke the interactive debugger. Can be used anywhere
27 | within a Switchyard program.'''
28 | pdb.Pdb(skip=['switchyard.lib.debugging']).set_trace()
29 |
--------------------------------------------------------------------------------
/documentation/code/fullhub.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | def main(net):
4 | # add some informational text about ports on this device
5 | log_info ("Hub is starting up with these ports:")
6 | for port in net.ports():
7 | log_info ("{}: ethernet address {}".format(port.name, port.ethaddr))
8 |
9 | while True:
10 | try:
11 | timestamp,input_port,packet = net.recv_packet()
12 | except Shutdown:
13 | # got shutdown signal
14 | break
15 | except NoPackets:
16 | # try again...
17 | continue
18 |
19 | # send the packet out all ports *except*
20 | # the one on which it arrived
21 | for port in net.ports():
22 | if port.name != input_port:
23 | net.send_packet(port.name, packet)
24 |
25 | # shutdown is the last thing we should do
26 | net.shutdown()
27 |
--------------------------------------------------------------------------------
/documentation/code/failhub5.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | def main(net):
4 | # add some informational text about ports on this device
5 | log_info ("Hub is starting up with these ports:")
6 | for port in net.ports():
7 | log_info ("{}: ethernet address {}".format(port.name, port.ethaddr))
8 |
9 | while True:
10 | try:
11 | timestamp,input_port,packet = net.recv_packet()
12 | except Shutdown:
13 | # got shutdown signal
14 | break
15 | except NoPackets:
16 | # try again...
17 | continue
18 |
19 | # send the packet out all ports *except*
20 | # the one on which it arrived
21 | for port in net.ports():
22 | if port.name != input_port:
23 | net.send_packet(port.name, packet)
24 |
25 | # shutdown is the last thing we should do
26 | net.shutdown()
27 |
--------------------------------------------------------------------------------
/examples/exercises/firewall/firewall.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 | import time
3 |
4 | def main(net):
5 | # assumes that there are exactly 2 ports
6 | portnames = [ p.name for p in net.ports() ]
7 | portpair = dict(zip(portnames, portnames[::-1]))
8 |
9 | while True:
10 | pkt = None
11 | try:
12 | timestamp,input_port,pkt = net.recv_packet(timeout=0.5)
13 | except NoPackets:
14 | pass
15 | except Shutdown:
16 | break
17 |
18 | if pkt is not None:
19 |
20 | # This is logically where you'd include some firewall
21 | # rule tests. It currently just forwards the packet
22 | # out the other port, but depending on the firewall rules
23 | # the packet may be dropped or mutilated.
24 | net.send_packet(portpair[input_port], pkt)
25 |
26 |
27 | net.shutdown()
28 |
--------------------------------------------------------------------------------
/examples/sydump.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | '''
3 | Simple tcpdump-like program that use Switchyard libraries for
4 | reading packets from a local device and dumping them to a file.
5 | '''
6 |
7 | import sys
8 | from switchyard.pcapffi import *
9 | from switchyard.lib.packet import *
10 |
11 | interface = 'en0'
12 | if len(sys.argv) > 1:
13 | interface = sys.argv[1]
14 |
15 | reader = PcapLiveDevice(interface)
16 | writer = PcapDumper("sydump.pcap")
17 | print("Reading from {}".format(interface))
18 | count = 0
19 | while True:
20 | pkt = reader.recv_packet(10.0)
21 | if pkt is None:
22 | break
23 | try:
24 | p = Packet(raw=pkt.raw)
25 | print (p)
26 | except Exception as e:
27 | print ("Failed to parse packet: {}".format(e))
28 |
29 | writer.write_packet(pkt.raw)
30 | count += 1
31 |
32 | print ("Got {} packets".format(count))
33 | reader.close()
34 | writer.close()
35 |
--------------------------------------------------------------------------------
/documentation/code/intfex.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | def main(net):
4 | for intf in net.interfaces():
5 | addrs = ','.join([str(a) for a in intf.ipaddrs])
6 | log_info("{} has ethaddr {} and ipaddrs {} and is of type {}".format(
7 | intf.name, intf.ethaddr, addrs, intf.iftype.name))
8 |
9 | # below, recvdata is a namedtuple
10 | recvdata = net.recv_packet()
11 | print ("At {}, received {} on {}".format(
12 | recvdata.timestamp, recvdata.packet, recvdata.input_port))
13 |
14 | # alternatively, the above line could use indexing, although
15 | # readability suffers:
16 | # recvdata[0], recvdata[2], recvdata[1]))
17 |
18 | net.send_packet(recvdata.input_port, recvdata.packet)
19 |
20 | # likewise, the above line could be written using indexing
21 | # but, again, readability suffers:
22 | # net.send_packet(recvdata[1], recvdata[2])
23 |
--------------------------------------------------------------------------------
/examples/udpstack_tests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from copy import deepcopy
4 | from switchyard.lib.userlib import *
5 |
6 | def udp_stack_tests():
7 | s = TestScenario("UDP stack test (with pretend localhost)")
8 | s.add_interface('lo0', '00:00:00:00:00:00', '127.0.0.1', iftype=InterfaceType.Loopback)
9 |
10 | p = Null() + \
11 | IPv4(src='127.0.0.1',dst='127.0.0.1',protocol=IPProtocol.UDP) + \
12 | UDP(src=65535, dst=10000) + b'Hello stack'
13 |
14 | s.expect(PacketOutputEvent("lo0", p, exact=False, wildcards=[(UDP, 'src')]), "Emit UDP packet")
15 |
16 | reply = deepcopy(p)
17 | reply[1].src,reply[1].dst = reply[1].dst,reply[1].src
18 | reply[2].src,reply[2].dst = reply[2].dst,reply[2].src
19 |
20 | s.expect(PacketInputEvent('lo0', reply,
21 | copyfromlastout=('lo0',UDP,'src',UDP,'dst')),
22 | "Receive UDP packet")
23 |
24 | return s
25 |
26 | scenario = udp_stack_tests()
27 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "3.6"
4 | before_install:
5 | - sudo apt-get -qq update
6 | - sudo apt-get install -y libffi-dev libpcap-dev
7 | install:
8 | - python setup.py install
9 | script:
10 | - python tests/test_addr.py
11 | - python tests/test_ipv6.py
12 | - python tests/test_ripv2.py
13 | - python tests/test_addr.py
14 | - python tests/test_llnetdev.py
15 | - python tests/test_arp.py
16 | - python tests/test_matcher.py
17 | - python tests/test_scenarios.py
18 | - python tests/test_null.py
19 | - python tests/test_socket.py
20 | - python tests/test_ethernet.py
21 | - python tests/test_srpy.py
22 | - python tests/test_hostfirewall.py
23 | - python tests/test_tcp.py
24 | - python tests/test_icmp.py
25 | # temporarily remove uncritical apis
26 | # - python tests/test_topo.py
27 | - python tests/test_importer.py
28 | - python tests/test_packet.py
29 | - python tests/test_udp.py
30 | - python tests/test_ipv4.py
31 |
--------------------------------------------------------------------------------
/switchyard/lib/packet/util.py:
--------------------------------------------------------------------------------
1 | from . import *
2 |
3 | def create_ip_arp_reply(srchw, dsthw, srcip, targetip):
4 | '''
5 | Create an ARP reply (just change what needs to be changed
6 | from a request)
7 | '''
8 | pkt = create_ip_arp_request(srchw, srcip, targetip)
9 | pkt[0].dst = dsthw
10 | pkt[1].operation = ArpOperation.Reply
11 | pkt[1].targethwaddr = dsthw
12 | return pkt
13 |
14 | def create_ip_arp_request(srchw, srcip, targetip):
15 | '''
16 | Create and return a packet containing an Ethernet header
17 | and ARP header.
18 | '''
19 | ether = Ethernet()
20 | ether.src = srchw
21 | ether.dst = SpecialEthAddr.ETHER_BROADCAST.value
22 | ether.ethertype = EtherType.ARP
23 | arp = Arp()
24 | arp.operation = ArpOperation.Request
25 | arp.senderhwaddr = srchw
26 | arp.senderprotoaddr = srcip
27 | arp.targethwaddr = SpecialEthAddr.ETHER_BROADCAST.value
28 | arp.targetprotoaddr = targetip
29 | return ether + arp
30 |
--------------------------------------------------------------------------------
/examples/sendpkt.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | '''
3 | Simple program that uses Switchyard libraries to emit a packet
4 | on every interface that can be opened.
5 | '''
6 |
7 | from switchyard.lib.userlib import *
8 |
9 | def main(net):
10 | my_interfaces = net.interfaces()
11 |
12 | eth = Ethernet(dst='ff:ff:ff:ff:ff:ff')
13 | ip = IPv4(dst='192.168.100.100', ttl=16, protocol=IPProtocol.ICMP)
14 | icmp = ICMP(icmptype=ICMPType.EchoRequest)
15 | icmp.icmpdata.sequence = 1
16 | icmp.icmpdata.identifier = 13
17 | pkt = eth+ip+icmp
18 | for intf in my_interfaces:
19 | eth.src = intf.ethaddr
20 | # assumes at least one v4 address assigned
21 | v4addrs = [a for a in intf.ipaddrs if a.version == 4]
22 | ip.src = v4addrs[0].src.ip
23 | print("Sending {} out {}".format(pkt, intf.name))
24 | try:
25 | net.send_packet(intf.name, pkt)
26 | except Exception as e:
27 | log_failure("Can't send packet: {}".format(str(e)))
28 | net.shutdown()
29 |
--------------------------------------------------------------------------------
/examples/exercises/router/router4.rst:
--------------------------------------------------------------------------------
1 | Router extensions
2 | -----------------
3 |
4 | Here are three possible extensions to the router with exercise descriptions:
5 |
6 | * For a firewall extension:,see the ``firewall`` exercise folder.
7 |
8 | * For a RIPv2-like distance vector dynamic routing extension, see the ``dvroute`` folder.
9 |
10 | * For a link state (OSPF-like) dynamic routing extension, see the ``lsroute`` folder.
11 |
12 | Another idea, but without an exercise description:
13 |
14 | * Add a load balancing capability among 2 or more interfaces that are assumed to be connected to links leading to the same far-end destination (either to a single remote router or to multiple co-located routers). You could create a hash function that uses packet header attributes as input to make a decision about which interface on which to forward the packet.
15 |
16 |
17 | License
18 | -------
19 |
20 | This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
21 | http://creativecommons.org/licenses/by-nc-sa/4.0/
22 |
--------------------------------------------------------------------------------
/examples/myhub.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | '''
4 | Ethernet hub in Switchyard.
5 | '''
6 | from switchyard.lib.userlib import *
7 |
8 | def main(net):
9 | my_interfaces = net.interfaces()
10 | mymacs = [intf.ethaddr for intf in my_interfaces]
11 |
12 | while True:
13 | try:
14 | timestamp,dev,packet = net.recv_packet()
15 | except NoPackets:
16 | continue
17 | except Shutdown:
18 | return
19 |
20 | log_debug ("In {} received packet {} on {}".format(net.name, packet, dev))
21 | eth = packet.get_header(Ethernet)
22 | if eth is None:
23 | log_info("Received a non-Ethernet packet?!")
24 | continue
25 |
26 | if eth.dst in mymacs:
27 | log_info ("Received a packet intended for me")
28 | else:
29 | for intf in my_interfaces:
30 | if dev != intf.name:
31 | log_info ("Flooding packet {} to {}".format(packet, intf.name))
32 | net.send_packet(intf, packet)
33 | net.shutdown()
34 |
--------------------------------------------------------------------------------
/documentation/code/fullhub2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | '''
4 | Ethernet hub in Switchyard.
5 | '''
6 | from switchyard.lib.userlib import *
7 |
8 | def main(net):
9 | my_interfaces = net.interfaces()
10 | mymacs = [intf.ethaddr for intf in my_interfaces]
11 |
12 | while True:
13 | try:
14 | timestamp,dev,packet = net.recv_packet()
15 | except NoPackets:
16 | continue
17 | except Shutdown:
18 | return
19 |
20 | log_debug ("In {} received packet {} on {}".format(net.name, packet, dev))
21 | eth = packet.get_header(Ethernet)
22 | if eth is None:
23 | log_info("Received a non-Ethernet packet?!")
24 | continue
25 |
26 | if eth.dst in mymacs:
27 | log_info ("Received a packet intended for me")
28 | else:
29 | for intf in my_interfaces:
30 | if dev != intf.name:
31 | log_info ("Flooding packet {} to {}".format(packet, intf.name))
32 | net.send_packet(intf, packet)
33 | net.shutdown()
34 |
--------------------------------------------------------------------------------
/documentation/thanks.rst:
--------------------------------------------------------------------------------
1 | Acknowledgments and thanks
2 | **************************
3 |
4 | Thanks to Colgate COSC465 students from Spring 2014 and Spring 2015 for being guinea pigs and giving feedback for the very first versions of Switchyard. Thanks also to Prof. Paul Barford and CS640 students at the University of Wisconsin for using and providing feedback on Switchyard.
5 |
6 | Thanks to those students who have contributed fixes and made suggestions for improvements. In particular:
7 |
8 | * Thanks to Saul Shanabrook for several specific suggestions and bug reports that have led to improvements in Switchyard.
9 | * Thanks to Xuyi Ruan for identifying and suggesting a fix to bugs on one of the documentation diagrams.
10 | * Thanks to Sean Wilson for a bug fix on an infinitely recursive property setter. Oops, but this dumb bug motivated me to significantly improve test coverage, so there's that.
11 | * Thanks to Leon Yang for identifying a problem with kwarg processing for ICMP.
12 | * Thanks to Jordan Ansell at Victoria University-Wellington NZ for initial code for ICMPv6 neighbor discovery
13 |
--------------------------------------------------------------------------------
/docs/_sources/thanks.rst.txt:
--------------------------------------------------------------------------------
1 | Acknowledgments and thanks
2 | **************************
3 |
4 | Thanks to Colgate COSC465 students from Spring 2014 and Spring 2015 for being guinea pigs and giving feedback for the very first versions of Switchyard. Thanks also to Prof. Paul Barford and CS640 students at the University of Wisconsin for using and providing feedback on Switchyard.
5 |
6 | Thanks to those students who have contributed fixes and made suggestions for improvements. In particular:
7 |
8 | * Thanks to Saul Shanabrook for several specific suggestions and bug reports that have led to improvements in Switchyard.
9 | * Thanks to Xuyi Ruan for identifying and suggesting a fix to bugs on one of the documentation diagrams.
10 | * Thanks to Sean Wilson for a bug fix on an infinitely recursive property setter. Oops, but this dumb bug motivated me to significantly improve test coverage, so there's that.
11 | * Thanks to Leon Yang for identifying a problem with kwarg processing for ICMP.
12 | * Thanks to Jordan Ansell at Victoria University-Wellington NZ for initial code for ICMPv6 neighbor discovery
13 |
--------------------------------------------------------------------------------
/examples/exercises/learning_switch/myswitch.py:
--------------------------------------------------------------------------------
1 | '''
2 | Ethernet learning switch in Python.
3 |
4 | Note that this file currently has the code to implement a "hub"
5 | in it, not a learning switch. (I.e., it's currently a switch
6 | that doesn't learn.)
7 | '''
8 | from switchyard.lib.userlib import *
9 |
10 | def main(net):
11 | my_interfaces = net.interfaces()
12 | mymacs = [intf.ethaddr for intf in my_interfaces]
13 |
14 | while True:
15 | try:
16 | timestamp,input_port,packet = net.recv_packet()
17 | except NoPackets:
18 | continue
19 | except Shutdown:
20 | return
21 |
22 | log_debug ("In {} received packet {} on {}".format(net.name, packet, input_port))
23 | if packet[0].dst in mymacs:
24 | log_debug ("Packet intended for me")
25 | else:
26 | for intf in my_interfaces:
27 | if input_port != intf.name:
28 | log_debug ("Flooding packet {} to {}".format(packet, intf.name))
29 | net.send_packet(intf.name, packet)
30 | net.shutdown()
31 |
--------------------------------------------------------------------------------
/switchyard/lib/exceptions.py:
--------------------------------------------------------------------------------
1 | class SwitchyardException(Exception):
2 | def __init__(self, message):
3 | self.message = message
4 |
5 | def __str__(self):
6 | return self.message
7 |
8 | def __repr__(self):
9 | return self.message
10 |
11 | class Shutdown(SwitchyardException):
12 | '''Exception that is raised in user Switchyard program when the
13 | framework is being shut down.'''
14 | def __init__(self, *args):
15 | SwitchyardException.__init__(self, "Framework shutdown")
16 |
17 |
18 | class NoPackets(SwitchyardException):
19 | '''Exception that is raised in user Switchyard program when
20 | the recv_packet() method is called on the net object and there
21 | are no packets available.'''
22 | def __init__(self, *args):
23 | SwitchyardException.__init__(self, "No packets available")
24 |
25 | class NotEnoughDataError(SwitchyardException):
26 | '''Exception that is raised when attempting to build a packet
27 | header object from a bytes object, but there aren't enough bytes
28 | to perform the reconstruction.'''
29 | pass
30 |
--------------------------------------------------------------------------------
/switchyard/sim/linkem.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import heapq
3 | from queue import Queue,Empty
4 | import time
5 |
6 | class LinkEmulator(object):
7 | def __init__(self, inqueue):
8 | self.expiryheap = []
9 | self.inqueue = inqueue
10 | self.__shutdown = False
11 |
12 | def shutdown(self):
13 | self.__shutdown = True
14 |
15 | def run(self):
16 | while not self.__shutdown:
17 |
18 | now = time.time()
19 | while len(self.expiryheap) and self.expiryheap[0][0] <= now:
20 | expiretime,item,outqueue = heapq.heappop(self.expiryheap)
21 | outqueue.put(item)
22 |
23 | if len(self.expiryheap):
24 | expiretime,item,outqueue = self.expiryheap[0]
25 | timeout = expiretime - time.time()
26 | else:
27 | timeout = 0.1
28 |
29 | try:
30 | expiretime,item,outqueue = self.inqueue.get(timeout=timeout)
31 | except Empty:
32 | pass
33 | else:
34 | heapq.heappush(self.expiryheap, (expiretime, item, outqueue))
35 |
36 |
--------------------------------------------------------------------------------
/documentation/code/baaadhub.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | '''
4 | Ethernet hub in Switchyard.
5 | '''
6 | from switchyard.lib.userlib import *
7 |
8 | def main(net):
9 | my_interfaces = net.interfaces()
10 | mymacs = [intf.ethaddr for intf in my_interfaces]
11 |
12 | while True:
13 | try:
14 | timestamp,dev,packet = net.recv_packet()
15 | except NoPackets:
16 | continue
17 | except Shutdown:
18 | return
19 |
20 | log_debug ("In {} received packet {} on {}".format(net.name, packet, dev))
21 | eth = packet.get_header(Ethernet)
22 | if eth is None:
23 | log_info("Received a non-Ethernet packet?!")
24 | continue
25 |
26 | if eth.dst in mymacs:
27 | log_info ("Received a packet intended for me")
28 | else:
29 | for intf in my_interfaces:
30 | if dev != intf.name:
31 | log_info ("Flooding packet {} to {}".format(packet, intf.name))
32 | eth.src = 'ba:ba:ba:ba:ba:ba' # sheep!
33 | net.send_packet(intf, packet)
34 | net.shutdown()
35 |
--------------------------------------------------------------------------------
/switchyard/lib/logging.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from ..textcolor import *
4 | from .debugging import debugger
5 |
6 | def setup_logging(debug, logfile=None):
7 | '''
8 | Setup logging format and log level.
9 | '''
10 | if debug:
11 | level = logging.DEBUG
12 | else:
13 | level = logging.INFO
14 | if logfile is not None:
15 | logging.basicConfig(format="%(asctime)s %(levelname)8s %(message)s", datefmt="%H:%M:%S %Y/%m/%d", level=level, filename=logfile)
16 | else:
17 | logging.basicConfig(format="%(asctime)s %(levelname)8s %(message)s", datefmt="%H:%M:%S %Y/%m/%d", level=level)
18 |
19 | def log_failure(s):
20 | '''Convenience function for failure message.'''
21 | with red():
22 | logging.fatal("{}".format(s))
23 |
24 | def log_debug(s):
25 | '''Convenience function for debugging message.'''
26 | logging.debug("{}".format(s))
27 |
28 | def log_warn(s):
29 | '''Convenience function for warning message.'''
30 | with magenta():
31 | logging.warning("{}".format(s))
32 |
33 | def log_info(s):
34 | '''Convenience function for info message.'''
35 | logging.info("{}".format(s))
36 |
--------------------------------------------------------------------------------
/examples/exercises/router/myrouter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | '''
4 | Basic IPv4 router (static routing) in Python.
5 | '''
6 |
7 | import sys
8 | import os
9 | import time
10 | from switchyard.lib.userlib import *
11 |
12 | class Router(object):
13 | def __init__(self, net):
14 | self.net = net
15 | # other initialization stuff here
16 |
17 |
18 | def router_main(self):
19 | '''
20 | Main method for router; we stay in a loop in this method, receiving
21 | packets until the end of time.
22 | '''
23 | while True:
24 | gotpkt = True
25 | try:
26 | timestamp,dev,pkt = self.net.recv_packet(timeout=1.0)
27 | except NoPackets:
28 | log_debug("No packets available in recv_packet")
29 | gotpkt = False
30 | except Shutdown:
31 | log_debug("Got shutdown signal")
32 | break
33 |
34 | if gotpkt:
35 | log_debug("Got a packet: {}".format(str(pkt)))
36 |
37 |
38 |
39 | def main(net):
40 | '''
41 | Main entry point for router. Just create Router
42 | object and get it going.
43 | '''
44 | r = Router(net)
45 | r.router_main()
46 | net.shutdown()
47 |
--------------------------------------------------------------------------------
/documentation/code/inouttest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from switchyard.lib.userlib import *
4 |
5 | def mk_pkt(hwsrc, hwdst, ipsrc, ipdst, reply=False):
6 | ether = Ethernet(src=hwsrc, dst=hwdst, ethertype=EtherType.IP)
7 | ippkt = IPv4(src=ipsrc, dst=ipdst, protocol=IPProtocol.ICMP, ttl=32)
8 | icmppkt = ICMP()
9 | if reply:
10 | icmppkt.icmptype = ICMPType.EchoReply
11 | else:
12 | icmppkt.icmptype = ICMPType.EchoRequest
13 | return ether + ippkt + icmppkt
14 |
15 | def inouttests():
16 | s = TestScenario("in/out basic tests")
17 | s.add_interface('eth0', '10:00:00:00:00:01', '172.16.42.1', '255.255.255.252')
18 | s.add_interface('eth1', '10:00:00:00:00:02', '10.10.0.1', '255.255.0.0')
19 | s.add_interface('eth2', '10:00:00:00:00:03', '192.168.1.1', '255.255.255.0')
20 |
21 | # test case 1: a frame with broadcast destination should get sent out
22 | # all ports except ingress
23 | testpkt = mk_pkt("30:00:00:00:00:02", "ff:ff:ff:ff:ff:ff", "172.16.42.2", "255.255.255.255")
24 | s.expect(PacketInputEvent("eth1", testpkt, display=Ethernet), "An Ethernet frame with a broadcast destination address should arrive on eth1")
25 | s.expect(PacketOutputEvent("eth1", testpkt, display=Ethernet), "The Ethernet frame should be forwarded back out eth1.")
26 | return s
27 |
28 | scenario = inouttests()
29 |
--------------------------------------------------------------------------------
/documentation/code/protostackpattern.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | class ProtocolStack(object):
4 | def __init__(self, net):
5 | self._net = net
6 |
7 | def handle_app_data(self, appdata):
8 | # do something to handle application data here, likely
9 | # resulting in an eventual call to self._net.send_packet()
10 |
11 | def handle_network_data(self, netdata):
12 | # do something with network data here, likely resulting
13 | # in an eventual call to ApplicationLayer.send_to_app()
14 |
15 | def main_loop(self):
16 | while True:
17 | appdata = None
18 | try:
19 | appdata = ApplicationLayer.recv_from_app(timeout=0.1)
20 | except NoPackets:
21 | pass
22 | except Shutdown:
23 | break
24 | if appdata is not None:
25 | handle_app_data(net, intf, appdata)
26 |
27 | netdata = None
28 | try:
29 | netdata = net.recv_packet(timeout=0.1)
30 | except NoPackets:
31 | pass
32 | except Shutdown:
33 | break
34 | if netdata is not None:
35 | handle_network_data(netdata)
36 |
37 |
38 | def main(net):
39 | stack = ProtocolStack(net)
40 | stack.main_loop()
41 | net.shutdown()
42 |
--------------------------------------------------------------------------------
/tests/test_addr.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.packet import *
2 | from switchyard.lib.address import *
3 | from ipaddress import AddressValueError
4 | import unittest
5 |
6 | class AddressTests(unittest.TestCase):
7 | def testEthAddr(self):
8 | e = EthAddr()
9 | self.assertEqual(e, SpecialEthAddr.ETHER_ANY.value)
10 | e1 = EthAddr("01-80-C2-00-00-0e")
11 | self.assertTrue(e1.is_bridge_filtered)
12 | e2 = EthAddr("e2-00-00-00-00-00")
13 | self.assertTrue(e2.is_local)
14 | self.assertFalse(e2.is_global)
15 | self.assertEqual(e2.raw, b'\xe2\x00\x00\x00\x00\x00')
16 | self.assertEqual(e2.toRaw(), b'\xe2\x00\x00\x00\x00\x00')
17 | self.assertEqual(e2.packed, b'\xe2\x00\x00\x00\x00\x00')
18 | self.assertEqual(e2.toStr('-'), "e2-00-00-00-00-00")
19 | self.assertEqual(str(e2), "e2:00:00:00:00:00")
20 | self.assertEqual(repr(e2), "EthAddr('e2:00:00:00:00:00')")
21 | self.assertEqual(e2.toTuple(), (0xe2, 0x0, 0x0, 0x0, 0x0, 0x0))
22 | self.assertTrue(e1 < e2)
23 |
24 | def testSpecialEth(self):
25 | self.assertEqual(SpecialEthAddr.ETHER_ANY.value.raw, b'\x00'*6)
26 | self.assertTrue(SpecialEthAddr.LLDP_MULTICAST.value.is_multicast)
27 | self.assertEqual(str(SpecialEthAddr.PAE_MULTICAST.value), "01:80:c2:00:00:03")
28 |
29 |
30 | if __name__ == '__main__':
31 | unittest.main()
32 |
--------------------------------------------------------------------------------
/tests/test_null.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from socket import AF_INET, AF_INET6, AF_DECnet
3 |
4 | from switchyard.lib.packet import *
5 |
6 | class NullPacketTests(unittest.TestCase):
7 | def testNullInstance(self):
8 | n = Null()
9 | b = n.to_bytes()
10 | self.assertEqual(len(b), 4)
11 | self.assertEqual(len(b), n.size())
12 | self.assertEqual(b, b'\x02\x00\x00\x00')
13 |
14 | n2 = Null()
15 | self.assertEqual(n, n2)
16 |
17 | self.assertEqual(str(n), "Null: AF_INET")
18 | self.assertIsNone(n2.pre_serialize(None, None, None))
19 |
20 | def testAf(self):
21 | n = Null()
22 | self.assertEqual(n.af, AF_INET)
23 | n.af = AF_INET6
24 | self.assertEqual(n.af, AF_INET6)
25 | self.assertEqual(n.next_header_class(), IPv6)
26 | n.af = AF_INET
27 | self.assertEqual(n.next_header_class(), IPv4)
28 |
29 | n.af = AF_DECnet
30 | with self.assertRaises(Exception):
31 | n.next_header_class()
32 |
33 | def testFromBytes(self):
34 | n = Null(AF_INET6)
35 | b = n.to_bytes()
36 |
37 | with self.assertRaises(Exception):
38 | n.from_bytes(b'\x00')
39 |
40 | x = n.from_bytes(b'\x02\x00\x00\x00')
41 | n2 = Null(AF_INET)
42 | self.assertEqual(n, n2)
43 |
44 | self.assertEqual(x, b'')
45 |
46 |
47 | if __name__ == '__main__':
48 | unittest.main()
49 |
--------------------------------------------------------------------------------
/examples/exercises/applayer/msgboardmiddlebox.rst:
--------------------------------------------------------------------------------
1 | Message board application middlebox
2 | -----------------------------------
3 |
4 | The goal of the third and final part of the exercise is to create a simple middlebox device to introduce packet loss in the network. This part can either be done with "hard-coded" Ethernet address/IPv4 address mappings, or can implement ARP to operate in a more flexible manner.
5 |
6 | You'll implement this device using the Switchyard framework. It will only have two ports, with one port handling traffic to/from a MBclient, and the other port handling traffic to/from a MBserver. When ever a packet arrives on one port, it should be forwarded out the other port, and vice versa. There is no need for any explicit routing.
7 |
8 | Here's the "fun" part: although there's a pretty dumb forwarding mechanism used by the device, it will also be in charge of probabilistically dropping packets to simulate the evil sorts of things that can happen in a real network. Packet drops should only happen in the MBserver to MBclient direction, not the other way around.
9 |
10 | The ``main`` function of your middlebox device should accept, in addition to the ``net`` object, a floating point number between 0 and 1 which represents the probability of dropping a given packet.
11 |
12 | That's it!
13 |
14 | License
15 | -------
16 |
17 | This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
18 | http://creativecommons.org/licenses/by-nc-sa/4.0/
19 |
--------------------------------------------------------------------------------
/tests/test_udp.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.packet import *
2 | import unittest
3 |
4 | class UDPPacketTests(unittest.TestCase):
5 | def testSerialize(self):
6 | ether = Ethernet()
7 | ether.src = '00:00:00:11:22:33'
8 | ether.dst = '11:22:33:00:00:00'
9 | ether.ethertype = EtherType.IP
10 | ippkt = IPv4()
11 | ippkt.src = '1.2.3.4'
12 | ippkt.dst = '4.5.6.7'
13 | ippkt.protocol = IPProtocol.UDP
14 | ippkt.ttl = 37
15 | ippkt.ipid = 0
16 | udppkt = UDP()
17 | udppkt.src = 10000
18 | udppkt.dst = 9999
19 | pkt = ether + ippkt + udppkt + RawPacketContents('hello, world')
20 | b = pkt.to_bytes()
21 |
22 | newpkt = Packet(raw=b)
23 | self.assertEqual(b, newpkt.to_bytes())
24 |
25 | b = b[:-15] # slice into the udp header
26 | with self.assertRaises(Exception):
27 | newpkt = Packet(raw=b)
28 |
29 | def testChecksum(self):
30 | u = UDP()
31 | self.assertEqual(u.checksum, 0)
32 |
33 | p = IPv4() + u
34 | p[0].protocol = IPProtocol.UDP
35 | b = p.to_bytes()
36 | self.assertEqual(u.checksum, 65502)
37 | u = UDP(src=1234,dst=4567)
38 | u.pre_serialize(b'', Packet(), 0)
39 | self.assertEqual(u.checksum, 0)
40 |
41 | x = b'\x00\x00\xfe\xff\x00\x00'
42 | c = checksum(x, start=0, skip_word=1)
43 | self.assertEqual(c, 65535)
44 |
45 |
46 | if __name__ == '__main__':
47 | unittest.main()
48 |
--------------------------------------------------------------------------------
/documentation/code/testscenario2.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 |
3 | scenario = TestScenario("packet forwarding example")
4 |
5 | # three interfaces on this device
6 | scenario.add_interface('eth0', 'ab:cd:ef:ab:cd:ef', '1.2.3.4/16')
7 | scenario.add_interface('eth1', '00:11:22:ab:cd:ef', '5.6.7.8/16')
8 | scenario.add_interface('eth2', 'ab:cd:ef:00:11:22', '9.10.11.12/24')
9 |
10 | # add a forwarding table file to be written out when the test
11 | # scenario is executed
12 | scenario.add_file('forwarding_table.txt', '''
13 | # network subnet-mask next-hop port
14 | 2.0.0.0 255.0.0.0 9.10.11.13 eth2
15 | 3.0.0.0 255.255.0.0 5.6.100.200 eth1
16 | ''')
17 |
18 |
19 | # construct a packet to be received
20 | p = Ethernet(src="00:11:22:33:44:55", dst="66:55:44:33:22:11") + \
21 | IPv4(src="1.1.1.1", dst="2.2.2.2", protocol=IPProtocol.UDP, ttl=61) + \
22 | UDP(src=5555, dst=8888) + b'some payload'
23 |
24 | # expect that the packet is received
25 | scenario.expect(PacketInputEvent('eth0', p),
26 | "A udp packet destined to 2.2.2.2 arrives on port eth0")
27 |
28 | # and subsequently forwarded out the correct port; employ
29 | # subset (exact=False) matching, along with a check that the
30 | # IPv4 TTL was decremented exactly by 1.
31 | scenario.expect(PacketOutputEvent('eth2', p, exact=False,
32 | predicate='''lambda pkt: pkt.has_header(IPv4) and pkt[IPv4].ttl == 60'''),
33 | "The udp packet destined to 2.2.2.2 should be forwarded out port eth2, with an appropriately decremented TTL.")
34 |
--------------------------------------------------------------------------------
/examples/exercises/applayer/applayer.rst:
--------------------------------------------------------------------------------
1 | Application layer
2 | -----------------
3 |
4 | Note: these exercises are a work in progress and some details may not be clear or correctly included yet.
5 |
6 | This exercise has three parts. In the first part, the goal is to create a Python socket-based client for a "message board" application. The server with this the client should communicate is already written; only the client needs to be created. This part of the project does *not* require any use of Switchyard --- only the built-in Python ``socket`` module is used. UDP is used as as the transport protocol.
7 |
8 | The goal of the second part of the exercise is to create a UDP-based network stack, which can be used by the client created in part 1 (and also by the server). The network stack will implement a static window-based form of reliable transport.
9 |
10 | The goal of the third and final part of the exercise is to create a simple middlebox device to introduce packet loss in the network. This part can either be done with "hard-coded" Ethernet address/IPv4 address mappings, or can implement ARP to operate in a more flexible manner.
11 |
12 | The three parts of this exercise are described in detail in three separate documents found in this folder:
13 |
14 | * ``msgboardapp.rst``
15 |
16 | * ``msgboardstack.rst``
17 |
18 | * ``msgboardmiddlebox.rst``
19 |
20 |
21 |
22 | License
23 | -------
24 |
25 | This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
26 | http://creativecommons.org/licenses/by-nc-sa/4.0/
27 |
--------------------------------------------------------------------------------
/examples/exercises/firewall/firewall_rules.txt:
--------------------------------------------------------------------------------
1 |
2 | # drop everything from an internal subnet which shouldn't be allowed
3 | # to communicate with rest of internet
4 | # rule 1
5 | deny ip src 192.168.42.0/24 dst any
6 | # rule 2
7 | deny ip src any dst 192.168.42.0/24
8 |
9 | # allow traffic to/from an internal web server that should
10 | # be accessible to external hosts
11 | # rule 3
12 | permit tcp src 192.168.13.13 srcport 80 dst any dstport any
13 | # rule 4
14 | permit tcp src any srcport any dst 192.168.13.13 dstport 80
15 |
16 | # allow DNS (udp port 53) traffic in/out of network
17 | # rule 5
18 | permit udp src 192.168.0.0/16 srcport any dst any dstport 53
19 | # rule 6
20 | permit udp src any srcport 53 dst 192.168.0.0/16 dstport any
21 |
22 | # allow internal hosts access to web (tcp ports 80 and 443)
23 | # rate limit http traffic to 100 kB/s (12500 bytes/sec), but
24 | # don't rate limit any encrypted HTTP traffic.
25 | # rule 7
26 | permit tcp src 192.168.0.0/16 srcport any dst any dstport 80 ratelimit 12500
27 | # rule 8
28 | permit tcp src any srcport any dst 172.16.42.0/24 dstport 80 ratelimit 12500
29 | # rule 9
30 | permit tcp src 192.168.0.0/16 srcport any dst any dstport 443
31 | # rule 10
32 | permit tcp src any srcport any dst 172.16.42.0/24 dstport 443
33 |
34 | # permit, but impair certain traffic flows
35 | # rule 11
36 | permit tcp src 192.168.0.0/24 srcport any dst any dstport 8000 impair
37 |
38 | # permit, but rate limit icmp to 100 bytes/sec
39 | # rule 12
40 | permit icmp src any dst any ratelimit 100
41 |
42 | # block everything else
43 | # rule 13
44 | deny ip src any dst any
45 |
--------------------------------------------------------------------------------
/documentation/index.rst:
--------------------------------------------------------------------------------
1 | .. Switchyard documentation master file, created by
2 | sphinx-quickstart on Wed Dec 3 22:08:55 2014.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Switchyard documentation
7 | ========================
8 |
9 | .. Contents:
10 |
11 | .. toctree::
12 | :maxdepth: 2
13 |
14 | intro
15 | writing_a_program
16 | test_execution
17 | test_scenario_creation
18 | live_execution
19 | advanced_api
20 | installation
21 | reference
22 | release_notes
23 | thanks
24 |
25 |
26 | Indices and tables
27 | ==================
28 |
29 | * :ref:`genindex`
30 | * :ref:`modindex`
31 | * :ref:`search`
32 |
33 | I gratefully acknowledge support from the NSF. The materials here are based upon work supported by the National Science Foundation under grants CNS-1814537 ("NeTS: Small: RUI: Automating Active Measurement Metadata Collection and Analysis"; 2018-2021) and CNS-1054985 ("CAREER: Expanding the functionality of Internet routers"; 2011-2017).
34 |
35 | Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author and do not necessarily reflect the views of the National Science Foundation.
36 |
37 | .. todolist::
38 |
39 | License
40 | =======
41 |
42 | The Switchyard software is distributed under terms of the GNU General Public License, version 3.
43 |
44 | Switchyard's documentation is distributed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License: http://creativecommons.org/licenses/by-nc-sa/4.0/.
45 |
--------------------------------------------------------------------------------
/docs/_sources/index.rst.txt:
--------------------------------------------------------------------------------
1 | .. Switchyard documentation master file, created by
2 | sphinx-quickstart on Wed Dec 3 22:08:55 2014.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Switchyard documentation
7 | ========================
8 |
9 | .. Contents:
10 |
11 | .. toctree::
12 | :maxdepth: 2
13 |
14 | intro
15 | writing_a_program
16 | test_execution
17 | test_scenario_creation
18 | live_execution
19 | advanced_api
20 | installation
21 | reference
22 | release_notes
23 | thanks
24 |
25 |
26 | Indices and tables
27 | ==================
28 |
29 | * :ref:`genindex`
30 | * :ref:`modindex`
31 | * :ref:`search`
32 |
33 | I gratefully acknowledge support from the NSF. The materials here are based upon work supported by the National Science Foundation under grants CNS-1814537 ("NeTS: Small: RUI: Automating Active Measurement Metadata Collection and Analysis"; 2018-2021) and CNS-1054985 ("CAREER: Expanding the functionality of Internet routers"; 2011-2017).
34 |
35 | Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author and do not necessarily reflect the views of the National Science Foundation.
36 |
37 | .. todolist::
38 |
39 | License
40 | =======
41 |
42 | The Switchyard software is distributed under terms of the GNU General Public License, version 3.
43 |
44 | Switchyard's documentation is distributed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License: http://creativecommons.org/licenses/by-nc-sa/4.0/.
45 |
--------------------------------------------------------------------------------
/tests/test_color.py:
--------------------------------------------------------------------------------
1 | from switchyard.textcolor import green, red, blue, \
2 | cyan, magenta, yellow, TextColor
3 |
4 | import unittest
5 |
6 | class ColorlibTests(unittest.TestCase):
7 | def testContext(self):
8 | print ("These tests need eyes...")
9 | with green():
10 | print ("This should be green!")
11 | with red():
12 | print ("This should be red!")
13 | with blue():
14 | print ("This should be blue!")
15 | with cyan():
16 | print ("This should be cyan!")
17 | with magenta():
18 | print ("This should be magenta!")
19 | with yellow():
20 | print ("This should be yellow!")
21 | print ("This should not be colored")
22 |
23 | def testOther(self):
24 | TextColor.setup(False)
25 | self.assertTrue(TextColor._SETUP)
26 | with self.assertRaises(Exception):
27 | TextColor()
28 |
29 | def testNoColor(self):
30 | TextColor.setup(True)
31 | print ("These tests need eyes NONE SHOULD BE COLORED ...")
32 | with green():
33 | print ("This should NOT be green!")
34 | with red():
35 | print ("This should NOT be red!")
36 | with blue():
37 | print ("This should NOT be blue!")
38 | with cyan():
39 | print ("This should NOT be cyan!")
40 | with magenta():
41 | print ("This should NOT be magenta!")
42 | with yellow():
43 | print ("This should NOT be yellow!")
44 |
45 |
46 | if __name__ == '__main__':
47 | unittest.main()
48 |
--------------------------------------------------------------------------------
/documentation/code/pkt_construction.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.packet import *
2 | p = Packet() # construct a packet object
3 | e = Ethernet() # construct Ethernet header
4 | ip = IPv4() # construct IPv4 header
5 | icmp = ICMP() # construct ICMP header
6 | p += e # add eth header to packet
7 | p += ip # add ip header to packet
8 | p += icmp # add icmp header to packet
9 | print (p)
10 | p = Ethernet() + IPv4() + ICMP()
11 | p.num_headers()
12 | len(p)
13 | p.size()
14 |
15 | p[0]
16 | p[0].src
17 | p[0].dst
18 | p[0].dst = "ab:cd:ef:00:11:22"
19 | str(p[0])
20 | p[0].dst = EthAddr("00:11:22:33:44:55")
21 | str(p[0])
22 | p[0].ethertype
23 | p[0].ethertype = EtherType.ARP
24 | print (p)
25 | p[0].ethertype = EtherType.IPv4 # set it back to sensible value
26 |
27 | p.has_header(IPv4)
28 | p.get_header_index(IPv4)
29 | str(p[1]) # access by index
30 | str(p[IPv4]) # access by header type
31 | p[IPv4].protocol
32 | p[IPv4].src
33 | p[IPv4].dst
34 | p[IPv4].dst = '149.43.80.13'
35 |
36 | p.has_header(ICMP)
37 | p.get_header_index(ICMP)
38 | p[2] # access by index; notice no conversion to string
39 | p[ICMP] # access by header type
40 | p[ICMP].icmptype
41 | p[ICMP].icmpcode
42 | p[ICMP].icmpdata
43 | icmp.icmpdata.sequence
44 | icmp.icmpdata.identifier
45 | icmp.icmpdata.identifier = 42
46 | icmp.icmpdata.sequence = 13
47 | print (p)
48 |
49 | icmp.icmpdata.data = "hello, world"
50 | print (p)
51 |
52 | p.to_bytes()
53 |
54 | e = Ethernet(src="11:22:33:44:55:66", dst="66:55:44:33:22:11", ethertype=EtherType.IP)
55 | ip = IPv4(src="1.2.3.4", dst="4.3.2.1", protocol=IPProtocol.UDP, ttl=32)
56 | udp = UDP(src=1234, dst=4321)
57 | p = e + ip + udp + b"this is some application payload!"
58 | print(p)
59 |
60 |
61 |
--------------------------------------------------------------------------------
/documentation/code/newheader.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 | import struct
3 |
4 | class SpanningTreeMessage(PacketHeaderBase):
5 | _PACKFMT = "6sxB"
6 |
7 | def __init__(self, root="00:00:00:00:00:00", **kwargs):
8 | self._root = EthAddr(root)
9 | self._hops_to_root = 0
10 | PacketHeaderBase.__init__(self, **kwargs)
11 |
12 | def to_bytes(self):
13 | raw = struct.pack(self._PACKFMT, self._root.raw, self._hops_to_root)
14 | return raw
15 |
16 | def from_bytes(self, raw):
17 | packsize = struct.calcsize(self._PACKFMT)
18 | if len(raw) < packsize:
19 | raise ValueError("Not enough bytes to unpack SpanningTreeMessage")
20 | xroot,xhops = struct.unpack(self._PACKFMT, raw[:packsize])
21 | self._root = EthAddr(xroot)
22 | self.hops_to_root = xhops
23 | return raw[packsize:]
24 |
25 | @property
26 | def hops_to_root(self):
27 | return self._hops_to_root
28 |
29 | @hops_to_root.setter
30 | def hops_to_root(self, value):
31 | self._hops_to_root = int(value)
32 |
33 | @property
34 | def root(self):
35 | return self._root
36 |
37 | def __str__(self):
38 | return "{} (root: {}, hops-to-root: {})".format(
39 | self.__class__.__name__, self.root, self.hops_to_root)
40 |
41 |
42 | if __name__ == '__main__':
43 | spm = SpanningTreeMessage("00:11:22:33:44:55", hops_to_root=1)
44 | print(spm)
45 |
46 | Ethernet.add_next_header_class(EtherType.SLOW, SpanningTreeMessage)
47 | pkt = Ethernet(src="11:22:11:22:11:22",
48 | dst="22:33:22:33:22:33",
49 | ethertype=EtherType.SLOW) + spm
50 | print(pkt)
51 | xbytes = pkt.to_bytes()
52 | p = Packet(raw=xbytes)
53 | print(p)
54 |
--------------------------------------------------------------------------------
/examples/exercises/README.rst:
--------------------------------------------------------------------------------
1 | Sample Exercises
2 | ****************
3 |
4 | This folder contains sources for sample exercises. Each subfolder includes
5 | a project description and various support files. Note: if you're viewing this source through Github, it will render individual ``.rst`` files if you click on them. If you don't like ReStructuredText, use pandoc to convert to a format you like better.
6 |
7 | **Instructors**: if you'd like the Switchyard test files (and test source code), please email me. Any tests referred to in the project/exercise descriptions are intentionally excluded from this repo (except for the firewall, currently).
8 |
9 | An overview of existing and in-the-works exercises is as follows:
10 |
11 | Learning switch
12 | Build a simple Ethernet learning switch. This is a nice starter exercise for getting accustomed to the APIs and workflow in Switchyard. Some extensions and variants to this exercise are included in the description, such as a spanning-tree protocol-like capability.
13 |
14 | IP router
15 | This is really a set of 3 projects designed to gradually built up capabilities to implement an IPv4 router that uses a static forwarding table. Descriptions of extensions and variants such as dynamic routing are included.
16 |
17 | An exercise to develop an IPv6 router is in progress.
18 |
19 | Firewall
20 | In this exercise, build a stand-alone firewall device with token bucket rate-limiting capability.
21 |
22 | UDP network stack + application
23 | This exercise is an introduction to using Switchyard's socket API emulation capabilities. The goal is to build the Ethernet/IP/UDP layers to support a UDP-based application, along with a basic windowed form of transport reliability.
24 |
25 | License
26 | -------
27 |
28 | This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
29 | http://creativecommons.org/licenses/by-nc-sa/4.0/
30 |
--------------------------------------------------------------------------------
/switchyard/lib/packet/null.py:
--------------------------------------------------------------------------------
1 | import struct
2 | import socket
3 |
4 | from .packet import PacketHeaderBase,Packet
5 | from .ipv4 import IPv4
6 | from .ipv6 import IPv6
7 | from ..exceptions import *
8 |
9 | AFTypeClasses = {
10 | socket.AF_INET: IPv4,
11 | socket.AF_INET6: IPv6
12 | }
13 |
14 | AFTypeNames = {
15 | socket.AF_INET: "AF_INET",
16 | socket.AF_INET6: "AF_INET6"
17 | }
18 |
19 | class Null(PacketHeaderBase):
20 | __slots__ = ['_af']
21 |
22 | def __init__(self, af=socket.AF_INET):
23 | self._af = int(af)
24 | super().__init__()
25 |
26 | def size(self):
27 | return 4
28 |
29 | @property
30 | def af(self):
31 | return self._af
32 |
33 | @af.setter
34 | def af(self,value):
35 | self._af = int(value)
36 |
37 | def to_bytes(self):
38 | '''
39 | Return packed byte representation of the Ethernet header.
40 | '''
41 | return struct.pack('=I', self._af)
42 |
43 | def from_bytes(self, raw):
44 | '''Return a Null header object reconstructed from raw bytes, or an
45 | Exception if we can't resurrect the packet.'''
46 | if len(raw) < 4:
47 | raise NotEnoughDataError("Not enough bytes ({}) to reconstruct a Null object".format(len(raw)))
48 | fields = struct.unpack('=I', raw[:4])
49 | self._af = fields[0]
50 | return raw[4:]
51 |
52 | def next_header_class(self):
53 | cls = AFTypeClasses.get(self.af, None)
54 | if cls is None:
55 | raise Exception("No mapping from address family {} to a packet header class".format(self.af))
56 | return cls
57 |
58 | def pre_serialize(self, raw, pkt, i):
59 | pass
60 |
61 | def __eq__(self, other):
62 | return self.af == other.af
63 |
64 | def __str__(self):
65 | return '{}: {}'.format(self.__class__.__name__, AFTypeNames.get(self.af, "?"))
66 |
67 |
--------------------------------------------------------------------------------
/tests/test_tcp.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.packet import *
2 | from switchyard.lib.address import *
3 | import unittest
4 |
5 | class TCPPacketTests(unittest.TestCase):
6 | def setUp(self):
7 | self.t = TCP()
8 |
9 | def testReconstruct(self):
10 | self.t.ack = 1234
11 | self.t.ack = 5678
12 | self.t.PSH = True
13 | b = self.t.to_bytes()
14 | t2 = TCP()
15 | t2.from_bytes(b)
16 | self.assertEqual(self.t, t2)
17 | self.assertEqual(t2.size(), 20)
18 | self.assertIsNone(self.t.next_header_class())
19 |
20 | def testFlags(self):
21 | self.assertEqual(self.t.flags, 0)
22 | self.t.SYN = 1
23 | self.t.FIN = True
24 | self.assertTrue(self.t.SYN)
25 | self.assertTrue(self.t.FIN)
26 |
27 | t2 = TCP(src=40, dst=80, seq=19, ack=47, SYN=1, ACK=1)
28 | self.assertIn("SA", str(t2))
29 | t2.ACK = 0
30 | self.assertNotIn("SA", str(t2))
31 | self.assertEqual(t2.ACK, 0)
32 |
33 | for f in TCPFlags:
34 | setattr(t2, f.name, 1)
35 | self.assertEqual(getattr(t2, f.name), 1)
36 | setattr(t2, f.name, 0)
37 | self.assertEqual(getattr(t2, f.name), 0)
38 |
39 | for f in TCPFlags:
40 | setattr(t2, f.name, 1)
41 | self.assertEqual(getattr(t2, f.name), 1)
42 | self.assertEqual(len(t2.flagstr), 9)
43 |
44 | def testBadSet(self):
45 | with self.assertRaises(Exception):
46 | self.t.srcport = 55
47 |
48 | def testChecksum(self):
49 | ip = IPv4(protocol=IPProtocol.TCP)
50 | t = TCP(src=40, dst=80, seq=19, ack=47, SYN=1)
51 | self.assertEqual(t.checksum, 0)
52 | p = Ethernet() + ip + t
53 | b = p.to_bytes()
54 | self.assertEqual("TCP 40->80 (S 19:47)", str(t))
55 | self.assertEqual(t.checksum, 44841)
56 |
57 | with self.assertRaises(Exception):
58 | x = Packet(raw=b[:-2])
59 |
60 |
61 | if __name__ == '__main__':
62 | unittest.main()
63 |
--------------------------------------------------------------------------------
/switchyard/importcode.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import importlib
3 | import os
4 |
5 | from .lib.logging import log_failure, log_debug
6 |
7 | def import_or_die(module_name, entrypoint_names):
8 | '''
9 | Import user code; return reference to usercode function.
10 |
11 | (str) -> function reference
12 | '''
13 | log_debug("Importing {}".format(module_name))
14 | module_name = os.path.abspath(module_name)
15 | if module_name.endswith('.py'):
16 | module_name,ext = os.path.splitext(module_name)
17 | modname = os.path.basename(module_name)
18 | dirname = os.path.dirname(module_name)
19 | if dirname and dirname not in sys.path:
20 | sys.path.append(dirname)
21 |
22 | # first, try to reload code
23 | if modname in sys.modules:
24 | user_module = sys.modules.get(modname)
25 | user_module = importlib.reload(user_module)
26 | # if it isn't in sys.modules, load it for the first time, or
27 | # try to.
28 | else:
29 | try:
30 | mypaths = [ x for x in sys.path if ("Cellar" not in x and "packages" not in x)]
31 | # print("Loading {} from {} ({})".format(modname, dirname, mypaths))
32 | # user_module = importlib.import_module(modname)
33 | user_module = importlib.__import__(modname)
34 | except ImportError as e:
35 | log_failure("Fatal error: couldn't import module (error: {}) while executing {}".format(str(e), modname))
36 | raise ImportError(e)
37 |
38 | # if there aren't any functions to call into, then the caller
39 | # just wanted the module/code to be imported, and that's it.
40 | if not entrypoint_names:
41 | return
42 |
43 | existing_names = dir(user_module)
44 | for method in entrypoint_names:
45 | if method in existing_names:
46 | return getattr(user_module, method)
47 |
48 | if len(entrypoint_names) > 1:
49 | entrypoints = "one of {}".format(', '.join(entrypoint_names))
50 | else:
51 | entrypoints = entrypoint_names[0]
52 | raise ImportError("Required entrypoint function or symbol ({}) not found in your code".format(entrypoints))
53 |
--------------------------------------------------------------------------------
/switchyard/textcolor.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import atexit
3 | from contextlib import contextmanager
4 | import colorama
5 | from colorama import Fore, Back, Style
6 |
7 |
8 | class TextColor(object):
9 | _SETUP=False
10 | _NoColor=False
11 |
12 | def __init__(self):
13 | raise Exception("Don't instantiate me.")
14 |
15 | @staticmethod
16 | def setup(nocolor):
17 | if TextColor._SETUP:
18 | return
19 | TextColor._NoColor=nocolor
20 | if sys.platform == 'win32':
21 | colorama.init(autoreset=True,strip=True,convert=True,wrap=True)
22 | else:
23 | colorama.init(autoreset=True,)
24 | atexit.register(TextColor.reset)
25 | TextColor._SETUP=True
26 |
27 | @staticmethod
28 | def reset():
29 | print(Fore.RESET + Back.RESET + Style.RESET_ALL)
30 |
31 | @staticmethod
32 | def green():
33 | if not TextColor._NoColor:
34 | print(Fore.GREEN,end='')
35 |
36 | @staticmethod
37 | def red():
38 | if not TextColor._NoColor:
39 | print(Fore.RED,end='')
40 |
41 | @staticmethod
42 | def blue():
43 | if not TextColor._NoColor:
44 | print(Fore.BLUE,end='')
45 |
46 | @staticmethod
47 | def cyan():
48 | if not TextColor._NoColor:
49 | print(Fore.CYAN,end='')
50 |
51 | @staticmethod
52 | def magenta():
53 | if not TextColor._NoColor:
54 | print(Fore.MAGENTA,end='')
55 |
56 | @staticmethod
57 | def yellow():
58 | if not TextColor._NoColor:
59 | print(Fore.YELLOW,end='')
60 |
61 | @contextmanager
62 | def red():
63 | TextColor.red()
64 | yield
65 | TextColor.reset()
66 |
67 | @contextmanager
68 | def green():
69 | TextColor.green()
70 | yield
71 | TextColor.reset()
72 |
73 | @contextmanager
74 | def blue():
75 | TextColor.blue()
76 | yield
77 | TextColor.reset()
78 |
79 | @contextmanager
80 | def cyan():
81 | TextColor.cyan()
82 | yield
83 | TextColor.reset()
84 |
85 | @contextmanager
86 | def magenta():
87 | TextColor.magenta()
88 | yield
89 | TextColor.reset()
90 |
91 | @contextmanager
92 | def yellow():
93 | TextColor.yellow()
94 | yield
95 | TextColor.reset()
96 |
97 |
--------------------------------------------------------------------------------
/examples/udpstack.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from switchyard.lib.userlib import *
4 |
5 | def main(net):
6 | intf = None
7 |
8 | for i in net.interfaces():
9 | if i.iftype == InterfaceType.Loopback:
10 | intf = i
11 | break
12 | if intf is None:
13 | raise Exception("This example is designed to use the loopback interface but I didn't find one")
14 |
15 | while True:
16 | appdata = None
17 | try:
18 | appdata = ApplicationLayer.recv_from_app(timeout=0.1)
19 | except NoPackets:
20 | pass
21 | except Shutdown:
22 | break
23 | if appdata is not None:
24 | handle_app_data(net, intf, appdata)
25 |
26 | netdata = None
27 | try:
28 | netdata = net.recv_packet(timeout=0.1)
29 | except NoPackets:
30 | pass
31 | except Shutdown:
32 | break
33 | if netdata is not None:
34 | handle_network_data(netdata)
35 |
36 | net.shutdown()
37 |
38 | def handle_app_data(net, intf, appdata):
39 | flowaddr,message = appdata
40 | log_debug("Received data from app layer: <{}>".format(message))
41 | log_debug("flowaddr: {}".format(flowaddr))
42 |
43 | proto,srcip,srcport,dstip,dstport = flowaddr
44 | srcaddr = [a for a in intf.ipaddrs if a.version == 4][0]
45 | p = Null() + IPv4(protocol=proto, src=srcaddr.ip, dst=dstip, ipid=0xabcd, ttl=64, flags=IPFragmentFlag.DontFragment) + UDP(src=srcport, dst=dstport) + message
46 |
47 | log_debug("Sending {} to {}".format(p, intf.name))
48 | net.send_packet(intf, p)
49 |
50 | def handle_network_data(netdata):
51 | timestamp, ingress, pkt = netdata
52 | log_debug("On {} received {}".format(ingress, pkt))
53 | if pkt.has_header(IPv4):
54 | ipidx = pkt.get_header_index(IPv4)
55 | ip = pkt[ipidx]
56 | if pkt[ipidx].protocol == IPProtocol.UDP:
57 | udp = pkt.get_header(UDP)
58 | ApplicationLayer.send_to_app(IPProtocol.UDP, (ip.dst, udp.dst),
59 | (ip.src, udp.src), pkt[-1].data)
60 | elif pkt[ipidx].protocol == IPProtocol.ICMP:
61 | log_info("Received ICMP message: {}".format(pkt[ipidx+1]))
62 | else:
63 | log_info("Received an unexpected packet: {}".format(pkt[1:]))
64 |
65 |
--------------------------------------------------------------------------------
/documentation/code/udpappheader.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.userlib import *
2 | import struct
3 |
4 | class UDPPing(PacketHeaderBase):
5 | _PACKFMT = "!H"
6 |
7 | def __init__(self, seq=0, **kwargs):
8 | self._sequence = int(seq)
9 | PacketHeaderBase.__init__(self, **kwargs)
10 |
11 | def to_bytes(self):
12 | raw = struct.pack(self._PACKFMT, self._sequence)
13 | return raw
14 |
15 | def from_bytes(self, raw):
16 | packsize = struct.calcsize(self._PACKFMT)
17 | if len(raw) < packsize:
18 | raise ValueError("Not enough bytes to unpack UDPPing")
19 | attrs = struct.unpack(self._PACKFMT, raw[:packsize])
20 | self.sequence = attrs[0]
21 | return raw[packsize:]
22 |
23 | @property
24 | def sequence(self):
25 | return self._sequence
26 |
27 | @sequence.setter
28 | def sequence(self, value):
29 | self._sequence = int(value)
30 |
31 | def __str__(self):
32 | return "{} seq: {}".format(self.__class__.__name__, self.sequence)
33 |
34 |
35 | if __name__ == '__main__':
36 | up1 = UDPPing()
37 | print(up1)
38 |
39 | up2 = UDPPing()
40 | up2.sequence = 13
41 | print(up2)
42 |
43 | up3 = UDPPing(sequence=42)
44 | print(up3)
45 |
46 | UDP_PING_PORT = 12345
47 | pkt = Ethernet(src="11:22:11:22:11:22",
48 | dst="22:33:22:33:22:33") + \
49 | IPv4(src="1.2.3.4", dst="5.6.7.8",
50 | protocol=IPProtocol.UDP, ttl=64) + \
51 | UDP(src=55555, dst=UDP_PING_PORT) + \
52 | UDPPing(42)
53 | print("Before serialize/deserialize:", pkt)
54 | xbytes = pkt.to_bytes()
55 | reanimated_pkt = Packet(raw=xbytes)
56 | print("After deserialization:", reanimated_pkt)
57 |
58 | print("*" * 40)
59 |
60 | UDP.add_next_header_class(UDP_PING_PORT, UDPPing)
61 | UDP.set_next_header_class_key('dst')
62 | pkt = Ethernet(src="11:22:11:22:11:22",
63 | dst="22:33:22:33:22:33") + \
64 | IPv4(src="1.2.3.4", dst="5.6.7.8",
65 | protocol=IPProtocol.UDP, ttl=64) + \
66 | UDP(src=55555, dst=UDP_PING_PORT) + \
67 | UDPPing(sequence=13)
68 | print("Before serialize/deserialize:", pkt)
69 | xbytes = pkt.to_bytes()
70 | reanimated_pkt = Packet(raw=xbytes)
71 | print("After deserialization:", reanimated_pkt)
72 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | thisversion = '1.0.1'
4 |
5 | setup(name="switchyard",
6 | version=thisversion,
7 | description="Switchyard is a framework for creating networked systems",
8 | author="Joel Sommers",
9 | author_email="jsommers@colgate.edu",
10 | url="https://github.com/jsommers/switchyard",
11 | keywords=['education', 'networked systems',],
12 | zip_safe=True,
13 | packages=find_packages(),
14 | python_requires='>=3.6',
15 | package_data={ '': ['*.txt', '*.rst'], },
16 | exclude_package_data={'': ['README.rst','README.md']},
17 | install_requires=["cffi >=1.14.0","colorama >=0.4.3","networkx >=2.4", "psutil >=5.7.0"],
18 | tests_require=['coverage >=5.1'],
19 | entry_points= {
20 | 'console_scripts': [ 'swyard = switchyard.swyard:main' ],
21 | },
22 | license="This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. http://creativecommons.org/licenses/by-nc-sa/4.0/",
23 | classifiers=[
24 | "Programming Language :: Python :: 3 :: Only",
25 | "Development Status :: 4 - Beta",
26 | "Topic :: Scientific/Engineering",
27 | "Topic :: Education",
28 | "Topic :: Software Development :: Libraries",
29 | "Topic :: System :: Networking",
30 | "Environment :: Console",
31 | "Intended Audience :: Education",
32 | "Intended Audience :: Science/Research",
33 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
34 | ],
35 | long_description='''
36 | Switchyard is a library and framework for creating networked systems in Python. It is primarily intended for educational use and supports creating devices from layer 2 (Ethernet) all the way through the application layer.
37 |
38 | Documentation is available at http://jsommers.github.io/switchyard
39 | Documentation is written using the Python Sphinx package; doc sources are
40 | available in the documentation directory.
41 |
42 | The Switchyard software is distributed under terms of the GNU General Public License, version 3.
43 |
44 | Switchyard's documentation is distributed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License: http://creativecommons.org/licenses/by-nc-sa/4.0/.
45 | '''
46 | )
47 |
--------------------------------------------------------------------------------
/tests/test_ethernet.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.packet import *
2 | from switchyard.lib.address import EthAddr
3 | import unittest
4 |
5 | class EthernetPacketTests(unittest.TestCase):
6 | def setUp(self):
7 | self.e = Ethernet()
8 |
9 | def testBlankAddrs(self):
10 | self.assertEqual(self.e.src, EthAddr())
11 | self.assertEqual(self.e.dst, EthAddr())
12 |
13 | def testSetSrc(self):
14 | astr = '00:00:00:00:00:01'
15 | self.e.src = astr
16 | self.assertEqual(self.e.src, EthAddr(astr))
17 |
18 | def testSetDst(self):
19 | astr = '00:00:00:00:00:01'
20 | self.e.dst = astr
21 | self.assertEqual(self.e.dst, EthAddr(astr))
22 |
23 | def testBadSet(self):
24 | with self.assertRaises(Exception):
25 | self.e.xdst = EthAddr()
26 |
27 | def testBadEType(self):
28 | # try to set an invalid ethertype
29 | with self.assertRaises(ValueError):
30 | self.e.ethertype = 0x01
31 |
32 | def testBadAddr(self):
33 | with self.assertRaises(RuntimeError):
34 | x = EthAddr("a")
35 |
36 | def testParse(self):
37 | raw = b'\x01\x02\x03\x04\x05\x06\x06\x05\x04\x03\x02\x01\x08\x00'
38 | astr = '01:02:03:04:05:06'
39 | e = Ethernet()
40 | e.from_bytes(raw)
41 | self.assertEqual(e.dst, EthAddr(astr))
42 | self.assertEqual(e.src, EthAddr(':'.join(astr.split(':')[::-1])))
43 | self.assertEqual(e.ethertype, EtherType.IP)
44 |
45 | def testBadParse(self):
46 | raw = b'x\01'
47 | e = Ethernet()
48 | with self.assertRaises(Exception):
49 | e.from_bytes(raw)
50 |
51 | def testVlan(self):
52 | e = Ethernet()
53 | e.src = "00:11:22:33:44:55"
54 | e.dst = "aa:bb:cc:dd:ee:ff"
55 | e.ethertype = EtherType.x8021Q
56 |
57 | v = Vlan()
58 | v.vlanid = 42
59 | v.pcp = 2
60 | v.ethertype = EtherType.IP
61 |
62 | ip = IPv4()
63 | ip.src = "1.2.3.4"
64 | ip.dst = "5.6.7.8"
65 | icmp = ICMP()
66 |
67 | packet = e + v + ip + icmp
68 | self.assertEqual(packet.num_headers(), 4)
69 | self.assertEqual(packet[1].vlanid, 42)
70 | self.assertEqual(packet[1].pcp, 2)
71 | self.assertEqual(len(packet), 46)
72 | serialized = packet.to_bytes()
73 |
74 | other = Packet(raw=serialized)
75 | self.assertEqual(packet, other)
76 |
77 | if __name__ == '__main__':
78 | unittest.main()
79 |
--------------------------------------------------------------------------------
/documentation/code/hubtests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from switchyard.lib.userlib import *
4 |
5 | def mk_pkt(hwsrc, hwdst, ipsrc, ipdst, reply=False):
6 | ether = Ethernet(src=hwsrc, dst=hwdst, ethertype=EtherType.IP)
7 | ippkt = IPv4(src=ipsrc, dst=ipdst, protocol=IPProtocol.ICMP, ttl=32)
8 | icmppkt = ICMP()
9 | if reply:
10 | icmppkt.icmptype = ICMPType.EchoReply
11 | else:
12 | icmppkt.icmptype = ICMPType.EchoRequest
13 | return ether + ippkt + icmppkt
14 |
15 | def hub_tests():
16 | s = TestScenario("hub tests")
17 | s.add_interface('eth0', '10:00:00:00:00:01')
18 | s.add_interface('eth1', '10:00:00:00:00:02')
19 | s.add_interface('eth2', '10:00:00:00:00:03')
20 |
21 | # test case 1: a frame with broadcast destination should get sent out
22 | # all ports except ingress
23 | testpkt = mk_pkt("30:00:00:00:00:02", "ff:ff:ff:ff:ff:ff", "172.16.42.2", "255.255.255.255")
24 | s.expect(PacketInputEvent("eth1", testpkt), "An Ethernet frame with a broadcast destination address should arrive on eth1")
25 | s.expect(PacketOutputEvent("eth0", testpkt, "eth2", testpkt), "The Ethernet frame with a broadcast destination address should be forwarded out ports eth0 and eth2")
26 |
27 | # test case 2: a frame with any unicast address except one assigned to hub
28 | # interface should be sent out all ports except ingress
29 | reqpkt = mk_pkt("20:00:00:00:00:01", "30:00:00:00:00:02", '192.168.1.100','172.16.42.2')
30 | s.expect(PacketInputEvent("eth0", reqpkt), "An Ethernet frame from 20:00:00:00:00:01 to 30:00:00:00:00:02 should arrive on eth0")
31 | s.expect(PacketOutputEvent("eth1", reqpkt, "eth2", reqpkt), "Ethernet frame destined for 30:00:00:00:00:02 should be flooded out eth1 and eth2")
32 |
33 | resppkt = mk_pkt("30:00:00:00:00:02", "20:00:00:00:00:01", '172.16.42.2', '192.168.1.100', reply=True)
34 | s.expect(PacketInputEvent("eth1", resppkt), "An Ethernet frame from 30:00:00:00:00:02 to 20:00:00:00:00:01 should arrive on eth1")
35 | s.expect(PacketOutputEvent("eth0", resppkt, "eth2", resppkt), "Ethernet frame destined to 20:00:00:00:00:01 should be flooded out eth0 and eth2")
36 |
37 | # test case 3: a frame with dest address of one of the interfaces should
38 | # result in nothing happening
39 | reqpkt = mk_pkt("20:00:00:00:00:01", "10:00:00:00:00:03", '192.168.1.100','172.16.42.2')
40 | s.expect(PacketInputEvent("eth2", reqpkt), "An Ethernet frame should arrive on eth2 with destination address the same as eth2's MAC address")
41 | s.expect(PacketInputTimeoutEvent(1.0), "The hub should not do anything in response to a frame arriving with a destination address referring to the hub itself.")
42 | return s
43 |
44 | scenario = hub_tests()
45 |
--------------------------------------------------------------------------------
/tests/test_importer.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import sys
3 | import os
4 | import tempfile
5 | import importlib
6 |
7 | import switchyard.importcode as imp
8 |
9 | class TestImporter(unittest.TestCase):
10 | def _writeFile(self, name):
11 | with open(name, "w") as outf:
12 | print("x = 1", file=outf)
13 | print("def fn():", file=outf)
14 | print(" print(x)", file=outf)
15 |
16 | def setUp(self):
17 | importlib.invalidate_caches()
18 |
19 | def testImporter1(self):
20 | name = "firsttest.py"
21 | self._writeFile(name)
22 | mod = imp.import_or_die(name, None)
23 | self.assertIsNone(mod)
24 | self.assertIn(name[:-3], sys.modules)
25 | os.unlink(name)
26 |
27 | def testImporter1b(self):
28 | name = "firsttwo_partdau.py"
29 | self._writeFile(name)
30 | mod = imp.import_or_die(name[:-3], None)
31 | self.assertIsNone(mod)
32 | self.assertIn(name[:-3], sys.modules)
33 | os.unlink(name)
34 |
35 | def testImporter2(self):
36 | name = "testimp2.py"
37 | self._writeFile(name)
38 | xfn = imp.import_or_die(name, ["fn"])
39 | self.assertIsNotNone(xfn)
40 | self.assertEqual(xfn.__name__, "fn")
41 | self.assertIn(name[:-3], sys.modules)
42 | os.unlink(name)
43 |
44 | def testImporter3(self):
45 | name = "testimp3.py"
46 | self._writeFile(name)
47 | with self.assertRaises(ImportError):
48 | imp.import_or_die(name, ["ugh"])
49 | os.unlink(name)
50 |
51 | def testImporter4(self):
52 | name = "testimp4.py"
53 | xfile = os.path.join(tempfile.gettempdir(), name)
54 | self._writeFile(xfile)
55 | xfn = imp.import_or_die(xfile, ["main","blob","fn"])
56 | self.assertIsNotNone(xfn)
57 | self.assertEqual(xfn.__name__, "fn")
58 | self.assertIn(name[:-3], sys.modules)
59 | os.unlink(xfile)
60 |
61 | def testImporter5(self):
62 | with self.assertLogs() as cm:
63 | with self.assertRaises(ImportError):
64 | imp.import_or_die("/tmp/notafile.py", None)
65 | self.assertIn("couldn't import module", cm.output[0])
66 | with self.assertLogs() as cm:
67 | with self.assertRaises(ImportError):
68 | imp.import_or_die("nothinghere.py", None)
69 | self.assertIn("couldn't import module", cm.output[0])
70 | with self.assertLogs() as cm:
71 | with self.assertRaises(ImportError):
72 | imp.import_or_die("nothinghere", None)
73 | self.assertIn("couldn't import module", cm.output[0])
74 |
75 |
76 | if __name__ == '__main__':
77 | unittest.main()
78 |
--------------------------------------------------------------------------------
/tests/test_arp.py:
--------------------------------------------------------------------------------
1 | from switchyard.lib.packet import *
2 | from switchyard.lib.address import EthAddr, ip_address
3 | import unittest
4 |
5 | class ArpTests(unittest.TestCase):
6 | def testArpRequest(self):
7 | p = create_ip_arp_request("00:00:00:11:22:33", "1.2.3.4", "10.11.12.13")
8 | self.assertEqual(p.num_headers(), 2)
9 | ether = p[0]
10 | arp = p[1]
11 | self.assertEqual(ether.src, "00:00:00:11:22:33")
12 | self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
13 | self.assertEqual(ether.ethertype, EtherType.ARP)
14 | self.assertEqual(len(ether), 14)
15 | self.assertEqual(arp.operation, ArpOperation.Request)
16 | self.assertEqual(arp.hardwaretype, ArpHwType.Ethernet)
17 | self.assertEqual(arp.protocoltype, EtherType.IP)
18 | self.assertEqual(arp.senderhwaddr, EthAddr("00:00:00:11:22:33"))
19 | self.assertEqual(arp.targethwaddr, EthAddr("ff:ff:ff:ff:ff:ff"))
20 | self.assertEqual(arp.senderprotoaddr, IPv4Address("1.2.3.4"))
21 | self.assertEqual(arp.targetprotoaddr, IPv4Address("10.11.12.13"))
22 | serialized = arp.to_bytes()
23 | other = Arp()
24 | other.from_bytes(serialized)
25 | self.assertEqual(arp, other)
26 | self.assertEqual(len(arp), 28)
27 | with self.assertRaises(Exception):
28 | other.from_bytes(serialized[:-3])
29 | xbytes = arp.to_bytes()
30 | # inject an invalid arp operation
31 | xbytes = xbytes[:6] + b'\xff\xff' + xbytes[8:]
32 | a = Arp()
33 | with self.assertRaises(Exception):
34 | a.from_bytes(xbytes)
35 |
36 | def testArpReply(self):
37 | p = create_ip_arp_reply("aa:bb:cc:dd:ee:ff", "00:00:00:11:22:33", "10.11.12.13", "1.2.3.4")
38 | self.assertEqual(p.num_headers(), 2)
39 | ether = p[0]
40 | arp = p[1]
41 | self.assertEqual(ether.dst, "00:00:00:11:22:33")
42 | self.assertEqual(ether.src, "aa:bb:cc:dd:ee:ff")
43 | self.assertEqual(ether.ethertype, EtherType.ARP)
44 | self.assertEqual(len(ether), 14)
45 | self.assertEqual(arp.operation, ArpOperation.Reply)
46 | self.assertEqual(arp.hardwaretype, ArpHwType.Ethernet)
47 | self.assertEqual(arp.protocoltype, EtherType.IP)
48 | self.assertEqual(arp.targethwaddr, EthAddr("00:00:00:11:22:33"))
49 | self.assertEqual(arp.senderhwaddr, EthAddr("aa:bb:cc:dd:ee:ff"))
50 | self.assertEqual(arp.targetprotoaddr, IPv4Address("1.2.3.4"))
51 | self.assertEqual(arp.senderprotoaddr, IPv4Address("10.11.12.13"))
52 | serialized = arp.to_bytes()
53 | other = Arp()
54 | other.from_bytes(serialized)
55 | self.assertEqual(arp, other)
56 | self.assertEqual(len(arp), 28)
57 |
58 | if __name__ == '__main__':
59 | unittest.main()
60 |
--------------------------------------------------------------------------------
/examples/hubtests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from switchyard.lib.userlib import *
4 |
5 | def mk_pkt(hwsrc, hwdst, ipsrc, ipdst, reply=False):
6 | ether = Ethernet(src=hwsrc, dst=hwdst, ethertype=EtherType.IP)
7 | ippkt = IPv4(src=ipsrc, dst=ipdst, protocol=IPProtocol.ICMP, ttl=32)
8 | icmppkt = ICMP()
9 | if reply:
10 | icmppkt.icmptype = ICMPType.EchoReply
11 | else:
12 | icmppkt.icmptype = ICMPType.EchoRequest
13 | return ether + ippkt + icmppkt
14 |
15 | def hub_tests():
16 | s = TestScenario("hub tests")
17 | s.add_interface('eth0', '10:00:00:00:00:01')
18 | s.add_interface('eth1', '10:00:00:00:00:02')
19 | s.add_interface('eth2', '10:00:00:00:00:03')
20 |
21 | # test case 1: a frame with broadcast destination should get sent out
22 | # all ports except ingress
23 | testpkt = mk_pkt("30:00:00:00:00:02", "ff:ff:ff:ff:ff:ff", "172.16.42.2", "255.255.255.255")
24 | s.expect(PacketInputEvent("eth1", testpkt, display=Ethernet), "An Ethernet frame with a broadcast destination address should arrive on eth1")
25 | s.expect(PacketOutputEvent("eth0", testpkt, "eth2", testpkt, display=Ethernet), "The Ethernet frame with a broadcast destination address should be forwarded out ports eth0 and eth2")
26 |
27 | # test case 2: a frame with any unicast address except one assigned to hub
28 | # interface should be sent out all ports except ingress
29 | reqpkt = mk_pkt("20:00:00:00:00:01", "30:00:00:00:00:02", '192.168.1.100','172.16.42.2')
30 | s.expect(PacketInputEvent("eth0", reqpkt, display=Ethernet), "An Ethernet frame from 20:00:00:00:00:01 to 30:00:00:00:00:02 should arrive on eth0")
31 | s.expect(PacketOutputEvent("eth1", reqpkt, "eth2", reqpkt, display=Ethernet), "Ethernet frame destined for 30:00:00:00:00:02 should be flooded out eth1 and eth2")
32 |
33 | resppkt = mk_pkt("30:00:00:00:00:02", "20:00:00:00:00:01", '172.16.42.2', '192.168.1.100', reply=True)
34 | s.expect(PacketInputEvent("eth1", resppkt, display=Ethernet), "An Ethernet frame from 30:00:00:00:00:02 to 20:00:00:00:00:01 should arrive on eth1")
35 | s.expect(PacketOutputEvent("eth0", resppkt, "eth2", resppkt, display=Ethernet), "Ethernet frame destined to 20:00:00:00:00:01 should be flooded out eth0 and eth2")
36 |
37 | # test case 3: a frame with dest address of one of the interfaces should
38 | # result in nothing happening
39 | reqpkt = mk_pkt("20:00:00:00:00:01", "10:00:00:00:00:03", '192.168.1.100','172.16.42.2')
40 | s.expect(PacketInputEvent("eth2", reqpkt, display=Ethernet), "An Ethernet frame should arrive on eth2 with destination address the same as eth2's MAC address")
41 | s.expect(PacketInputTimeoutEvent(1.0), "The hub should not do anything in response to a frame arriving with a destination address referring to the hub itself.")
42 | return s
43 |
44 | scenario = hub_tests()
45 |
--------------------------------------------------------------------------------
/switchyard/lib/packet/udp.py:
--------------------------------------------------------------------------------
1 | import struct
2 |
3 | from .packet import PacketHeaderBase
4 | from .common import checksum
5 | from ..exceptions import *
6 |
7 | '''
8 | References:
9 | IETF RFC 768
10 | '''
11 |
12 | # FIXME: checksum is broken for ip6
13 |
14 | class UDP(PacketHeaderBase):
15 | __slots__ = ['_src','_dst','_len','_checksum']
16 | _PACKFMT = '!HHHH'
17 | _MINLEN = struct.calcsize(_PACKFMT)
18 | _next_header_map = {}
19 | _next_header_class_key = ''
20 |
21 | def __init__(self, **kwargs):
22 | self.src = self.dst = 0
23 | self._len = self.size()
24 | self._checksum = 0
25 | super().__init__(**kwargs)
26 |
27 | def size(self):
28 | return struct.calcsize(UDP._PACKFMT)
29 |
30 | def to_bytes(self):
31 | '''
32 | Return packed byte representation of the UDP header.
33 | '''
34 | return struct.pack(UDP._PACKFMT, self._src, self._dst,
35 | self._len, self._checksum)
36 |
37 | def from_bytes(self, raw):
38 | '''Return an Ethernet object reconstructed from raw bytes, or an
39 | Exception if we can't resurrect the packet.'''
40 | if len(raw) < UDP._MINLEN:
41 | raise NotEnoughDataError("Not enough bytes ({}) to reconstruct an UDP object".format(len(raw)))
42 | fields = struct.unpack(UDP._PACKFMT, raw[:UDP._MINLEN])
43 | self._src = fields[0]
44 | self._dst = fields[1]
45 | self._len = fields[2]
46 | self._checksum = fields[3]
47 | return raw[UDP._MINLEN:]
48 |
49 | def __eq__(self, other):
50 | return self.src == other.src and \
51 | self.dst == other.dst
52 |
53 | @property
54 | def src(self):
55 | return self._src
56 |
57 | @property
58 | def dst(self):
59 | return self._dst
60 |
61 | @src.setter
62 | def src(self,value):
63 | self._src = value
64 |
65 | @dst.setter
66 | def dst(self,value):
67 | self._dst = value
68 |
69 | @property
70 | def checksum(self):
71 | return self._checksum
72 |
73 | @property
74 | def length(self):
75 | return self._len
76 |
77 | def __str__(self):
78 | return '{} {}->{}'.format(self.__class__.__name__, self.src, self.dst)
79 |
80 | def _compute_checksum_ipv4(self, ip4, xdata):
81 | if ip4 is None:
82 | return 0
83 | xhdr = struct.pack('!IIxBHHHHH', int(ip4.src), int(ip4.dst),
84 | ip4.protocol.value, self._len,
85 | self.src, self.dst, self._len, 0)
86 | return checksum(xhdr + xdata)
87 |
88 | def pre_serialize(self, raw, pkt, i):
89 | self._len = self.size() + len(raw)
90 | # checksum calc currently assumes we're only dealing with ipv4.
91 | # will need to be modified for ipv6 support...
92 | self._checksum = self._compute_checksum_ipv4(pkt.get_header_by_name('IPv4'), raw)
93 |
--------------------------------------------------------------------------------
/examples/exercises/learning_switch/switchtopo.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import sys
4 |
5 | from mininet.topo import Topo
6 | from mininet.net import Mininet
7 | from mininet.log import lg
8 | from mininet.node import CPULimitedHost
9 | from mininet.link import TCLink
10 | from mininet.util import irange, custom, quietRun, dumpNetConnections
11 | from mininet.cli import CLI
12 |
13 | from time import sleep, time
14 | from subprocess import Popen, PIPE
15 | import subprocess
16 | import argparse
17 | import os
18 |
19 | parser = argparse.ArgumentParser(description="Mininet pyswitch topology")
20 | # no arguments needed as yet :-)
21 | args = parser.parse_args()
22 | lg.setLogLevel('info')
23 |
24 | class PySwitchTopo(Topo):
25 |
26 | def __init__(self, args):
27 | # Add default members to class.
28 | super(PySwitchTopo, self).__init__()
29 |
30 | # Host and link configuration
31 | #
32 | #
33 | # server1
34 | # \
35 | # switch----client
36 | # /
37 | # server2
38 | #
39 |
40 | nodeconfig = {'cpu':-1}
41 | self.addHost('server1', **nodeconfig)
42 | self.addHost('server2', **nodeconfig)
43 | self.addHost('switch', **nodeconfig)
44 | self.addHost('client', **nodeconfig)
45 |
46 | for node in ['server1','server2','client']:
47 | # all links are 10Mb/s, 100 millisecond prop delay
48 | self.addLink(node, 'switch', bw=10, delay='100ms')
49 |
50 | def set_ip(net, node1, node2, ip):
51 | node1 = net.get(node1)
52 | ilist = node1.connectionsTo(net.get(node2)) # returns list of tuples
53 | intf = ilist[0]
54 | intf[0].setIP(ip)
55 |
56 | def reset_macs(net, node, macbase):
57 | ifnum = 1
58 | node_object = net.get(node)
59 | for intf in node_object.intfList():
60 | node_object.setMAC(macbase.format(ifnum), intf)
61 | ifnum += 1
62 |
63 | for intf in node_object.intfList():
64 | print node,intf,node_object.MAC(intf)
65 |
66 | def set_route(net, fromnode, prefix, nextnode):
67 | node_object = net.get(fromnode)
68 | ilist = node_object.connectionsTo(net.get(nextnode))
69 | node_object.setDefaultRoute(ilist[0][0])
70 |
71 | def setup_addressing(net):
72 | reset_macs(net, 'server1', '10:00:00:00:00:{:02x}')
73 | reset_macs(net, 'server2', '20:00:00:00:00:{:02x}')
74 | reset_macs(net, 'client', '30:00:00:00:00:{:02x}')
75 | reset_macs(net, 'switch', '40:00:00:00:00:{:02x}')
76 | set_ip(net, 'server1','switch','192.168.100.1/24')
77 | set_ip(net, 'server2','switch','192.168.100.2/24')
78 | set_ip(net, 'client','switch','192.168.100.3/24')
79 |
80 | def main():
81 | topo = PySwitchTopo(args)
82 | net = Mininet(controller=None, topo=topo, link=TCLink, cleanup=True)
83 | setup_addressing(net)
84 | net.interact()
85 |
86 | if __name__ == '__main__':
87 | main()
88 |
--------------------------------------------------------------------------------
/documentation/release_notes.rst:
--------------------------------------------------------------------------------
1 | Release notes
2 | *************
3 |
4 | 1.0.0
5 | -----
6 | Major revision, with fixes and updates to IPv6 classes and to generalize the ``Interface`` class so that any number of IP addresses may be assigned to it. ICMPv6 classes are basically complete for neighbor discovery and router solicitation and advertisement. These changes are **backward incompatible**.
7 |
8 |
9 | The headings below refer either to branches on Switchyard's github repo (v1 and v2) or tags (2017.01.1).
10 |
11 | 2017.01.4
12 | ---------
13 | More bugfixes.
14 |
15 | 2017.01.3
16 | ---------
17 | Minor bugfixes.
18 |
19 | 2017.01.2
20 | ---------
21 |
22 | Add the capability to pass arguments to a Switchyard program via ``-g`` option to ``swyard``.
23 | Switchyard parses and assembles ``*args`` and ``**kwargs`` to pass into the user code, being careful to only pass them if the code can accept them.
24 |
25 | 2017.01.1
26 | ---------
27 |
28 | Major revision; expansion of types of exercises supported (notably application-layer programs via socket emulation) and several non-backward compatible API changes. Simplified user code import (single import of switchyard.lib.userlib). Installation via standard setuptools, so easily installed via easy_install or pip. Major revision of documentation. Lots of new tests were written, bringing test coverage above 90%. Expansion of exercises is still in progress.
29 |
30 | Some key API changes to be aware of:
31 |
32 | * the Scenario class is renamed TestScenario. The PacketOutputEvent previously allowed Openflow 1.0-like wildcard strings to specify wildcards for matching packets; these strings are no longer supported. To specify wildcards, a tuple of (classname,attribute) must be used; refer to :ref:`test-scenario-creation`, above.
33 | * ``recv_packet`` *always* returns a timestamp now; it returns a 3-tuple (named tuple) of timestamp, input_port and packet.
34 | * The only import required by user code is switchyard.lib.userlib, although individual imports are still fine (just more verbose).
35 | * Instead of invoking ``srpy.py``, a ``swyard`` program is installed during the new install process. ``swyard`` has a few command-line changes compared with ``srpy.py``. In particular, the ``-s`` option has gone away; to run Switchyard with a test, just use the ``-t`` option with the scenario file as the argument.
36 |
37 |
38 | alphav2
39 | -------
40 |
41 | Complete rewrite of v1. Moved to Python 3 and created packet parsing libraries, new libpcap interface library (pcapffi). Redesigned test scenario modules and an expanded of publicly available exercises. Used at Colgate twice and University of Wisconsin-Madison twice. Available on the ``v2`` branch on github.
42 |
43 | alphav1
44 | -------
45 |
46 | First version, which used the POX packet parsing libraries and had a variety of limitations. Implemented in Python 2 and used at Colgate once. Available on the ``v1`` branch on github, but very much obsolete.
47 |
--------------------------------------------------------------------------------
/docs/_sources/release_notes.rst.txt:
--------------------------------------------------------------------------------
1 | Release notes
2 | *************
3 |
4 | 1.0.0
5 | -----
6 | Major revision, with fixes and updates to IPv6 classes and to generalize the ``Interface`` class so that any number of IP addresses may be assigned to it. ICMPv6 classes are basically complete for neighbor discovery and router solicitation and advertisement. These changes are **backward incompatible**.
7 |
8 |
9 | The headings below refer either to branches on Switchyard's github repo (v1 and v2) or tags (2017.01.1).
10 |
11 | 2017.01.4
12 | ---------
13 | More bugfixes.
14 |
15 | 2017.01.3
16 | ---------
17 | Minor bugfixes.
18 |
19 | 2017.01.2
20 | ---------
21 |
22 | Add the capability to pass arguments to a Switchyard program via ``-g`` option to ``swyard``.
23 | Switchyard parses and assembles ``*args`` and ``**kwargs`` to pass into the user code, being careful to only pass them if the code can accept them.
24 |
25 | 2017.01.1
26 | ---------
27 |
28 | Major revision; expansion of types of exercises supported (notably application-layer programs via socket emulation) and several non-backward compatible API changes. Simplified user code import (single import of switchyard.lib.userlib). Installation via standard setuptools, so easily installed via easy_install or pip. Major revision of documentation. Lots of new tests were written, bringing test coverage above 90%. Expansion of exercises is still in progress.
29 |
30 | Some key API changes to be aware of:
31 |
32 | * the Scenario class is renamed TestScenario. The PacketOutputEvent previously allowed Openflow 1.0-like wildcard strings to specify wildcards for matching packets; these strings are no longer supported. To specify wildcards, a tuple of (classname,attribute) must be used; refer to :ref:`test-scenario-creation`, above.
33 | * ``recv_packet`` *always* returns a timestamp now; it returns a 3-tuple (named tuple) of timestamp, input_port and packet.
34 | * The only import required by user code is switchyard.lib.userlib, although individual imports are still fine (just more verbose).
35 | * Instead of invoking ``srpy.py``, a ``swyard`` program is installed during the new install process. ``swyard`` has a few command-line changes compared with ``srpy.py``. In particular, the ``-s`` option has gone away; to run Switchyard with a test, just use the ``-t`` option with the scenario file as the argument.
36 |
37 |
38 | alphav2
39 | -------
40 |
41 | Complete rewrite of v1. Moved to Python 3 and created packet parsing libraries, new libpcap interface library (pcapffi). Redesigned test scenario modules and an expanded of publicly available exercises. Used at Colgate twice and University of Wisconsin-Madison twice. Available on the ``v2`` branch on github.
42 |
43 | alphav1
44 | -------
45 |
46 | First version, which used the POX packet parsing libraries and had a variety of limitations. Implemented in Python 2 and used at Colgate once. Available on the ``v1`` branch on github, but very much obsolete.
47 |
--------------------------------------------------------------------------------
/docs/_sources/installation.rst.txt:
--------------------------------------------------------------------------------
1 | .. _install:
2 |
3 | Installing Switchyard
4 | *********************
5 |
6 | Switchyard has been tested and developed on the following operating systems:
7 |
8 | * macOS 10.10 and later
9 | * Ubuntu LTS releases from 14.04 and later
10 | * Fedora 21
11 |
12 | Note that these are all Unix-based systems. Switchyard may be enhanced in the future to support Windows-based systems. Ubuntu (current LTS) and macOS receive the most testing of Unix-based operating systems.
13 |
14 | ---
15 |
16 | The steps for getting Switchyard up and running are as follows:
17 |
18 | 0. Install Python 3.4 or later, if you don't already have it.
19 | 1. Install any necessary libraries for your operating system.
20 | 2. Create an Python "virtual environment" for installing Python modules (or install the modules to your system Python)
21 | 3. Install Switchyard.
22 |
23 | For step 0, you're on your own. Go to https://www.python.org/downloads/, or install packages via your OS'es package system, or use homebrew if you're on a Mac. Have fun.
24 |
25 | The specific libraries necessary for different OSes (step 1) are described below, but steps 2 and 3 are the same for all operating systems and are covered next.
26 |
27 | The recommended install procedure is to create a Python virtual environment for installing Switchyard and other required Python modules. One way to create a new virtual environment is to execute the following at a command line (in the folder in which you want to create the virtual environment)::
28 |
29 | $ python3 -m venv syenv
30 |
31 | This command will create a new virtual environment called ``syenv``. Once that's done, you can "activate" that environment and install Switchyard as follows::
32 |
33 | $ source ./syenv/bin/activate
34 | (syenv)$ python3 -m pip install switchyard
35 |
36 | That's it. Once you've done that, the ``swyard`` program should be on your ``PATH`` (you can check by typing ``which swyard``). If you no longer want to use the Python virtual environment you've created, you can just type ``deactivate``.
37 |
38 | Operating system-specific instructions
39 | ======================================
40 |
41 | MacOS X
42 | -------
43 |
44 | The easiest way to get Switchyard running in macOS is to install homebrew. You can use ``brew`` to install Python 3. You should also ``brew`` to install the ``libpcap`` package. That should be all that is necessary.
45 |
46 | Ubuntu
47 | ------
48 |
49 | For Ubuntu systems, you'll need to use ``apt-get`` or something similar to install the following packages::
50 |
51 | libffi-dev libpcap-dev python3-dev python3-pip python3-venv
52 |
53 | Fedora/RedHat
54 | -------------
55 |
56 | For Fedora and RedHat-based systems, you'll need to use ``yum`` or something similar to install a similar set of packages as with Ubuntu (but with the right name changes for the way packages are identified on Fedora)::
57 |
58 | libffi-devel libpcap-devel python3-devel python3-pip python3-virtualenv
59 |
60 |
--------------------------------------------------------------------------------
/documentation/installation.rst:
--------------------------------------------------------------------------------
1 | .. _install:
2 |
3 | Installing Switchyard
4 | *********************
5 |
6 | Switchyard has been tested and developed on the following operating systems:
7 |
8 | * macOS 10.10 and later
9 | * Ubuntu LTS releases from 14.04 and later
10 | * Fedora 21
11 |
12 | Note that these are all Unix-based systems. Switchyard may be enhanced in the future to support Windows-based systems. Ubuntu (current LTS) and macOS receive the most testing of Unix-based operating systems.
13 |
14 | ---
15 |
16 | The steps for getting Switchyard up and running are as follows:
17 |
18 | 0. Install Python 3.4 or later, if you don't already have it.
19 | 1. Install any necessary libraries for your operating system.
20 | 2. Create an Python "virtual environment" for installing Python modules (or install the modules to your system Python)
21 | 3. Install Switchyard.
22 |
23 | For step 0, you're on your own. Go to https://www.python.org/downloads/, or install packages via your OS'es package system, or use homebrew if you're on a Mac. Have fun.
24 |
25 | The specific libraries necessary for different OSes (step 1) are described below, but steps 2 and 3 are the same for all operating systems and are covered next.
26 |
27 | The recommended install procedure is to create a Python virtual environment for installing Switchyard and other required Python modules. One way to create a new virtual environment is to execute the following at a command line (in the folder in which you want to create the virtual environment)::
28 |
29 | $ python3 -m venv syenv
30 |
31 | This command will create a new virtual environment called ``syenv``. Once that's done, you can "activate" that environment and install Switchyard as follows::
32 |
33 | $ source ./syenv/bin/activate
34 | (syenv)$ python3 -m pip install switchyard
35 |
36 | That's it. Once you've done that, the ``swyard`` program should be on your ``PATH`` (you can check by typing ``which swyard``). If you no longer want to use the Python virtual environment you've created, you can just type ``deactivate``.
37 |
38 | Operating system-specific instructions
39 | ======================================
40 |
41 | MacOS X
42 | -------
43 |
44 | The easiest way to get Switchyard running in macOS is to install homebrew. You can use ``brew`` to install Python 3. You should also ``brew`` to install the ``libpcap`` package. That should be all that is necessary.
45 |
46 | Ubuntu
47 | ------
48 |
49 | For Ubuntu systems, you'll need to use ``apt-get`` or something similar to install the following packages::
50 |
51 | libffi-dev libpcap-dev python3-dev python3-pip python3-venv
52 |
53 | Fedora/RedHat
54 | -------------
55 |
56 | For Fedora and RedHat-based systems, you'll need to use ``yum`` or something similar to install a similar set of packages as with Ubuntu (but with the right name changes for the way packages are identified on Fedora)::
57 |
58 | libffi-devel libpcap-devel python3-devel python3-pip python3-virtualenv
59 |
60 |
--------------------------------------------------------------------------------
/examples/exercises/firewall/start_mininet.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import sys
4 |
5 | from mininet.topo import Topo
6 | from mininet.net import Mininet
7 | from mininet.log import lg
8 | from mininet.node import CPULimitedHost
9 | from mininet.link import TCLink
10 | from mininet.util import irange, custom, quietRun, dumpNetConnections
11 | from mininet.cli import CLI
12 |
13 | from time import sleep, time
14 | from subprocess import Popen, PIPE
15 | import subprocess
16 | import argparse
17 | import os
18 |
19 | parser = argparse.ArgumentParser(description="Mininet portion of pyrouter")
20 | # no arguments needed as yet :-)
21 | args = parser.parse_args()
22 | lg.setLogLevel('info')
23 |
24 | class PyRouterTopo(Topo):
25 |
26 | def __init__(self, args):
27 | # Add default members to class.
28 | super(PyRouterTopo, self).__init__()
29 |
30 | # Host and link configuration
31 | #
32 | # external----firewall----internal
33 | #
34 | # external refers to the internet outside a given
35 | # enterprise's network. internal refers to the
36 | # enterprise's network.
37 |
38 | self.addHost('external')
39 | self.addHost('internal')
40 | self.addHost('firewall')
41 |
42 | for node in ['internal','external']:
43 | self.addLink(node, 'firewall', bw=1000, delay="10ms")
44 |
45 | def set_ip_pair(net, node1, node2, ip1, ip2):
46 | node1 = net.get(node1)
47 | ilist = node1.connectionsTo(net.get(node2)) # returns list of tuples
48 | intf = ilist[0]
49 | intf[0].setIP(ip1)
50 | intf[1].setIP(ip2)
51 |
52 | def set_ip(net, node, ifname, addr):
53 | node_object = net.get(node)
54 | intf = node_object.intf(ifname)
55 | intf.setIP(addr)
56 |
57 | def reset_macs(net, node, macbase):
58 | ifnum = 1
59 | node_object = net.get(node)
60 | for intf in node_object.intfList():
61 | if node not in str(intf):
62 | continue # don't set lo or other interfaces
63 | node_object.setMAC(macbase.format(ifnum), intf)
64 | ifnum += 1
65 |
66 | for intf in node_object.intfList():
67 | print node,intf,node_object.MAC(intf)
68 |
69 | def set_def_route(net, fromnode, gw):
70 | node_object = net.get(fromnode)
71 | node_object.cmdPrint("route add default gw {}".format(gw))
72 |
73 | def setup_addressing(net):
74 | reset_macs(net, 'internal', '00:00:00:00:01:{:02x}')
75 | reset_macs(net, 'external', '00:00:00:00:10:{:02x}')
76 | reset_macs(net, 'firewall', '00:00:00:00:0b:{:02x}')
77 |
78 | set_ip(net,'internal','internal-eth0','192.168.0.1/24')
79 | set_ip(net,'external','external-eth0','192.168.0.2/24')
80 |
81 | def stop_nodegrams(net):
82 | for nname in ['external','internal','firewall']:
83 | n = net.get(nname)
84 | n.cmd("killall python3")
85 |
86 | def main():
87 | topo = PyRouterTopo(args)
88 | net = Mininet(topo=topo, link=TCLink, cleanup=True, autoSetMacs=True, controller=None)
89 | setup_addressing(net)
90 | net.staticArp()
91 | net.interact()
92 |
93 | if __name__ == '__main__':
94 | main()
95 |
--------------------------------------------------------------------------------
/examples/exercises/firewall/impairmenttest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from copy import deepcopy
4 | import random
5 |
6 | from switchyard.lib.userlib import *
7 |
8 | firewall_rules = '''
9 | # drop everything from an internal subnet which shouldn't be allowed
10 | # to communicate with rest of internet
11 | # rule 1
12 | deny ip src 192.168.42.0/24 dst any
13 | # rule 2
14 | deny ip src any dst 192.168.42.0/24
15 |
16 | # allow traffic to/from an internal web server that should
17 | # be accessible to external hosts
18 | # rule 3
19 | permit tcp src 192.168.13.13 srcport 80 dst any dstport any
20 | # rule 4
21 | permit tcp src any srcport any dst 192.168.13.13 dstport 80
22 |
23 | # allow DNS (udp port 53) traffic in/out of network
24 | # rule 5
25 | permit udp src 192.168.0.0/16 srcport any dst any dstport 53
26 | # rule 6
27 | permit udp src any srcport 53 dst 192.168.0.0/16 dstport any
28 |
29 | # allow internal hosts access to web (tcp ports 80 and 443)
30 | # rate limit http traffic to 100 kB/s (12500 bytes/sec), but
31 | # don't rate limit any encrypted HTTP traffic.
32 | # rule 7
33 | permit tcp src 192.168.0.0/16 srcport any dst any dstport 80 ratelimit 12500
34 | # rule 8
35 | permit tcp src any srcport any dst 172.16.42.0/24 dstport 80 ratelimit 12500
36 | # rule 9
37 | permit tcp src 192.168.0.0/16 srcport any dst any dstport 443
38 | # rule 10
39 | permit tcp src any srcport any dst 172.16.42.0/24 dstport 443
40 |
41 | # permit, but impair certain traffic flows
42 | # rule 11
43 | permit tcp src 192.168.0.0/24 srcport any dst any dstport 8000 impair
44 |
45 | # permit, but rate limit icmp to 100 bytes/sec
46 | # rule 12
47 | permit icmp src any dst any ratelimit 100
48 |
49 | # block everything else
50 | # rule 13
51 | deny ip src any dst any
52 | '''
53 |
54 | def firewall_tests():
55 | s = TestScenario("Firewall tests")
56 | s.add_file('firewall_rules.txt', firewall_rules)
57 |
58 | # two ethernet ports; no IP addresses assigned to
59 | # them. eth0 is internal network-facing, and eth1
60 | # is external network-facing.
61 | s.add_interface('eth0', '00:00:00:00:0b:01')
62 | s.add_interface('eth1', '00:00:00:00:0b:02')
63 |
64 | t = TCP()
65 | t.ACK = 1
66 | t.ack = random.randrange(0,2**32)
67 | t.seq = random.randrange(0,2**32)
68 | t.src = random.randrange(2**12,2**16)
69 | t.dst = 8000
70 | ip = IPv4()
71 | ip.src = '192.168.0.13'
72 | ip.dst = IPv4Address(random.randrange(2**16, 2**32))
73 | ip.protocol = IPProtocol.TCP
74 | pkt = Ethernet() + ip + t + "This is some TCP data!".encode()
75 | # fill in any other packet headers or data to the constructed packet
76 | s.expect(PacketInputEvent('eth0',pkt),
77 | 'Packet that should be impaired arrives on eth0')
78 |
79 | # Modify the packet in the same way that you expect the firewall
80 | # to modify the packet.
81 | pkt = deepcopy(pkt) # make a full copy of the packet before modifying
82 |
83 | s.expect(PacketOutputEvent('eth1',pkt),
84 | 'Test description for a packet departure --- what should happen?')
85 |
86 | return s
87 |
88 | scenario = firewall_tests()
89 |
--------------------------------------------------------------------------------
/tests/test_ofswitch_ext.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | '''
4 | OF switch tests for use with an external controller.
5 | '''
6 |
7 | import sys
8 | from switchyard.lib.address import *
9 | from switchyard.lib.packet import *
10 | from switchyard.lib.testing import *
11 | from switchyard.lib.openflow import *
12 |
13 |
14 | def mk_pkt(hwsrc, hwdst, ipsrc, ipdst, reply=False):
15 | ether = Ethernet()
16 | ether.src = EthAddr(hwsrc)
17 | ether.dst = EthAddr(hwdst)
18 | ether.ethertype = EtherType.IP
19 |
20 | ippkt = IPv4()
21 | ippkt.src = ip_address(ipsrc)
22 | ippkt.dst = ip_address(ipdst)
23 | ippkt.protocol = IPProtocol.ICMP
24 | ippkt.ttl = 32
25 |
26 | icmppkt = ICMP()
27 | if reply:
28 | icmppkt.icmptype = ICMPType.EchoReply
29 | else:
30 | icmppkt.icmptype = ICMPType.EchoRequest
31 | return ether + ippkt + icmppkt
32 |
33 |
34 | def ofswitch_tests():
35 | s = TestScenario("Openflow Switch Tests")
36 | s.add_interface('eth0', '10:00:00:00:00:01')
37 | s.add_interface('eth1', '10:00:00:00:00:02')
38 | s.add_interface('eth2', '10:00:00:00:00:03')
39 |
40 | # test case 1: a frame with broadcast destination should get sent out
41 | # all ports except ingress
42 | testpkt = mk_pkt(
43 | "30:00:00:00:00:02", "ff:ff:ff:ff:ff:ff", "172.16.42.2", "255.255.255.255")
44 | s.expect(PacketInputEvent("eth1", testpkt, display=Ethernet),
45 | "An Ethernet frame with a broadcast destination address should arrive on eth1")
46 | s.expect(PacketInputTimeoutEvent(timeout=5), description="Wait for events to complete")
47 | s.expect(PacketOutputEvent("eth2", testpkt, "eth0", testpkt, display=Ethernet),
48 | "The Ethernet frame with a broadcast destination address should be forwarded out ports eth0 and eth2")
49 | # test case 2: frame with dest 30:...:02 should be sent out eth 1 (from where bcast frame arrived)
50 | testpkt2 = mk_pkt(
51 | "30:00:00:00:00:03", "30:00:00:00:00:02", "10.0.42.200", "172.16.42.2")
52 | s.expect(PacketInputEvent("eth2", testpkt2, display=Ethernet),
53 | "An Ethernet frame with a destination address 172.16.42.2 should arrive on eth2")
54 | s.expect(PacketInputTimeoutEvent(timeout=5), description="Wait for events to complete")
55 | s.expect(PacketOutputEvent("eth1", testpkt2, display=Ethernet),
56 | "An Ethernet frame with a destination address 172.16.42.2 should be forwarded out port eth1")
57 | s.expect(PacketInputTimeoutEvent(timeout=5), description="Wait for events to complete")
58 |
59 | # test case 3: dest port for 30::03 should have been learned from previous exchange
60 | testpkt3 = mk_pkt(
61 | "30:00:00:00:00:01", "30:00:00:00:00:03", "172.16.42.2", "10.1.13.13")
62 | s.expect(PacketInputEvent("eth0", testpkt3, display=Ethernet),
63 | "An Ethernet frame with a destination address 10.1.13.13 should arrive on eth0")
64 | s.expect(PacketInputTimeoutEvent(timeout=5), description="Wait for events to complete")
65 | s.expect(PacketOutputEvent("eth2", testpkt3, display=Ethernet),
66 | "An Ethernet frame with a destination address 10.1.13.13 should be forwarded out port eth2")
67 |
68 | return s
69 |
70 |
71 | scenario = ofswitch_tests()
72 |
--------------------------------------------------------------------------------
/tests/test_ripv2.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from switchyard.lib.packet import *
4 | from switchyard.pcapffi import PcapDumper
5 |
6 | class RIPv2PacketTests(unittest.TestCase):
7 | def testRequest(self):
8 | p = Ethernet() + IPv4() + UDP() + RIPv2()
9 | p[0].src = '00:11:22:33:44:55'
10 | p[0].dst = '55:44:33:22:11:00'
11 | p[1].protocol = IPProtocol.UDP
12 | p[1].src = '192.168.100.42'
13 | p[1].dst = '192.168.100.255'
14 | p[2].src = 5000
15 | p[2].dst = 520
16 | xraw = p.to_bytes()
17 | pkt = Packet(raw=xraw)
18 | pkt[-1] = RIPv2(pkt[-1])
19 | self.assertEqual(pkt, p)
20 | self.assertEqual(pkt[-1].size(), 4)
21 |
22 | self.assertIn("RIPv2 Request", str(pkt[-1]))
23 |
24 | def testReply(self):
25 | p = Ethernet() + IPv4() + UDP() + RIPv2()
26 | p[0].src = '00:11:22:33:44:55'
27 | p[0].dst = '55:44:33:22:11:00'
28 | p[1].protocol = IPProtocol.UDP
29 | p[1].src = '192.168.100.42'
30 | p[1].dst = '192.168.100.255'
31 | p[2].src = 5000
32 | p[2].dst = 520
33 | p[3].command = RIPCommand.Reply
34 | p[3].append(RIPRouteEntry('192.168.200.0','255.255.255.0','192.168.200.254',4))
35 | p[3].append(RIPRouteEntry('192.168.100.0','255.255.252.0','192.168.100.254',3))
36 | xraw = p.to_bytes()
37 | pkt = Packet(raw=xraw)
38 | pkt[-1] = RIPv2(pkt[-1])
39 | self.assertEqual(pkt, p)
40 | s = str(pkt)
41 | self.assertIn('192.168.200.0/24', s)
42 | self.assertIn('192.168.100.0/22', s)
43 | self.assertEqual(pkt[-1][0].address, IPv4Address('192.168.200.0'))
44 | self.assertEqual(pkt[-1][0].netmask, IPv4Address('255.255.255.0'))
45 | self.assertEqual(pkt.size(), 44)
46 | self.assertEqual(pkt[3].size(), 44)
47 |
48 | pkt[-1][-1] = RIPRouteEntry('192.168.0.0','255.255.0.0','192.168.42.5',15)
49 | s = str(pkt)
50 | self.assertIn('192.168.0.0/16', s)
51 | self.assertNotIn('192.168.100.0/22', s)
52 | with self.assertRaises(ValueError):
53 | pkt[-1].append(1)
54 | with self.assertRaises(ValueError):
55 | pkt[-1][0] = 0
56 | with self.assertRaises(IndexError):
57 | pkt[-1][2] = RIPRouteEntry()
58 | with self.assertRaises(IndexError):
59 | x = pkt[-1][2]
60 |
61 | with self.assertRaises(TypeError):
62 | x = pkt[-1]["a"]
63 | lastentry = pkt[-1][-1]
64 | self.assertEqual(lastentry.address, IPv4Address("192.168.0.0"))
65 | self.assertEqual(lastentry.netmask, IPv4Address("255.255.0.0"))
66 | self.assertEqual(lastentry.nexthop, IPv4Address("192.168.42.5"))
67 | self.assertEqual(lastentry.metric, RIP_INFINITY)
68 |
69 | re = RIPRouteEntry('192.168.100.0','255.255.252.0','192.168.100.254',3)
70 | re2 = RIPRouteEntry.from_bytes(re.to_bytes())
71 | self.assertEqual(re, re2)
72 | with self.assertRaises(Exception):
73 | re2.from_bytes(re.to_bytes()[:-1])
74 |
75 | r1 = pkt[-1]
76 | b = r1.to_bytes()
77 | r2 = RIPv2()
78 | with self.assertRaises(Exception):
79 | r2.from_bytes(b[:3])
80 |
81 | with self.assertLogs() as cm:
82 | b += b'\x11\x22\x33'
83 | r2.from_bytes(b)
84 | self.assertIn("payload isn't of expected size", cm.output[0])
85 |
86 | with self.assertLogs() as cm:
87 | r2.from_bytes(b[:-9])
88 | self.assertIn("payload isn't of expected size", cm.output[0])
89 |
90 | self.assertIsNone(r1.next_header_class())
91 |
92 | self.assertIn("RIPv2 Reply (2 routes", str(r1))
93 |
94 |
95 |
96 | if __name__ == '__main__':
97 | unittest.main()
98 |
99 |
--------------------------------------------------------------------------------
/docs/search.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Search — Switchyard 1.0.1 documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
Search
42 |
43 |
44 |
45 |
46 | Please activate JavaScript to enable the search
47 | functionality.
48 |
49 |
50 |
51 |
52 |
53 |
54 | Searching for multiple words only shows matches that contain
55 | all words.
56 |
57 |
58 |
59 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
80 |
81 |
82 |
95 |
99 |
100 |
--------------------------------------------------------------------------------
/examples/exercises/router/start_mininet.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import sys
4 |
5 | from mininet.topo import Topo
6 | from mininet.net import Mininet
7 | from mininet.log import lg
8 | from mininet.node import CPULimitedHost
9 | from mininet.link import TCLink
10 | from mininet.util import irange, custom, quietRun, dumpNetConnections
11 | from mininet.cli import CLI
12 |
13 | from time import sleep, time
14 | from subprocess import Popen, PIPE
15 | import subprocess
16 | import argparse
17 | import os
18 |
19 | parser = argparse.ArgumentParser(description="Mininet portion of pyrouter")
20 | # no arguments needed as yet :-)
21 | args = parser.parse_args()
22 | lg.setLogLevel('info')
23 |
24 | class PyRouterTopo(Topo):
25 |
26 | def __init__(self, args):
27 | # Add default members to class.
28 | super(PyRouterTopo, self).__init__()
29 |
30 | # Host and link configuration
31 | #
32 | #
33 | # server1
34 | # \
35 | # router----client
36 | # /
37 | # server2
38 | #
39 |
40 | nodeconfig = {'cpu':-1}
41 | self.addHost('server1', **nodeconfig)
42 | self.addHost('server2', **nodeconfig)
43 | self.addHost('router', **nodeconfig)
44 | self.addHost('client', **nodeconfig)
45 |
46 | linkconfig = {
47 | 'bw': 10,
48 | 'delay': 0.010,
49 | 'loss': 0.0
50 | }
51 |
52 | for node in ['server1','server2','client']:
53 | self.addLink(node, 'router', **linkconfig)
54 |
55 | def set_ip_pair(net, node1, node2, ip1, ip2):
56 | node1 = net.get(node1)
57 | ilist = node1.connectionsTo(net.get(node2)) # returns list of tuples
58 | intf = ilist[0]
59 | intf[0].setIP(ip1)
60 | intf[1].setIP(ip2)
61 |
62 | def reset_macs(net, node, macbase):
63 | ifnum = 1
64 | node_object = net.get(node)
65 | for intf in node_object.intfList():
66 | node_object.setMAC(macbase.format(ifnum), intf)
67 | ifnum += 1
68 |
69 | for intf in node_object.intfList():
70 | print node,intf,node_object.MAC(intf)
71 |
72 | def set_route(net, fromnode, prefix, gw):
73 | node_object = net.get(fromnode)
74 | node_object.cmdPrint("route add -net {} gw {}".format(prefix, gw))
75 |
76 | def setup_addressing(net):
77 | reset_macs(net, 'server1', '10:00:00:00:00:{:02x}')
78 | reset_macs(net, 'server2', '20:00:00:00:00:{:02x}')
79 | reset_macs(net, 'client', '30:00:00:00:00:{:02x}')
80 | reset_macs(net, 'router', '40:00:00:00:00:{:02x}')
81 | set_ip_pair(net, 'server1','router','192.168.100.1/30','192.168.100.2/30')
82 | set_ip_pair(net, 'server2','router','192.168.200.1/30','192.168.200.2/30')
83 | set_ip_pair(net, 'client','router','10.1.1.1/30','10.1.1.2/30')
84 | set_route(net, 'server1', '10.1.0.0/16', '192.168.100.2')
85 | set_route(net, 'server1', '192.168.200.0/24', '192.168.100.2')
86 | set_route(net, 'server2', '10.1.0.0/16', '192.168.200.2')
87 | set_route(net, 'server2', '192.168.100.0/24', '192.168.200.2')
88 | set_route(net, 'client', '192.168.100.0/24', '10.1.1.2')
89 | set_route(net, 'client', '192.168.200.0/24', '10.1.1.2')
90 | set_route(net, 'client', '172.16.0.0/16', '10.1.1.2')
91 |
92 | forwarding_table = open('forwarding_table.txt', 'w')
93 | table = '''192.168.100.0 255.255.255.0 192.168.100.1 router-eth0
94 | 192.168.200.0 255.255.255.0 192.168.200.1 router-eth1
95 | 10.1.0.0 255.255.0.0 10.1.1.1 router-eth2
96 | '''
97 | forwarding_table.write(table)
98 | forwarding_table.close()
99 |
100 |
101 | def main():
102 | topo = PyRouterTopo(args)
103 | net = Mininet(topo=topo, link=TCLink, cleanup=True, controller=None)
104 | setup_addressing(net)
105 | net.interact()
106 |
107 | if __name__ == '__main__':
108 | main()
109 |
--------------------------------------------------------------------------------
/tests/test_pcapffi.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import os
3 | import sys
4 | from unittest.mock import Mock
5 |
6 | from switchyard.lib.packet import *
7 | import switchyard.pcapffi as pf
8 |
9 | class PcapFfiTests(unittest.TestCase):
10 | def testWriteRead1(self):
11 | dump = pf.PcapDumper("testXX.pcap")
12 | pkt = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00\x1c\x00\x00\x00\x00\x00\x01\xba\xe2\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\xf7\xff\x00\x00\x00\x00'
13 | dump.write_packet(pkt)
14 | with self.assertRaises(pf.PcapException):
15 | dump.write_packet("hello, world")
16 | dump.close()
17 |
18 | reader = pf.PcapReader("testXX.pcap")
19 | reader.set_filter("icmp")
20 | pkts = []
21 | while True:
22 | p = reader.recv_packet()
23 | if p is None:
24 | break
25 | pkts.append(p)
26 | reader.close()
27 | self.assertEqual(len(pkts), 1)
28 | self.assertEqual(pkts[0].capture_length, len(pkt))
29 | self.assertEqual(pkts[0].length, len(pkt))
30 | self.assertEqual(pkts[0].raw, pkt)
31 | os.unlink("testXX.pcap")
32 |
33 | def testWriteRead2(self):
34 | dump = pf.PcapDumper("testXX.pcap")
35 | pkt = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00\x1c\x00\x00\x00\x00\x00\x01\xba\xe2\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\xf7\xff\x00\x00\x00\x00'
36 | dump.write_packet(pkt)
37 | with self.assertRaises(pf.PcapException):
38 | dump.write_packet("hello, world")
39 | dump.close()
40 |
41 | reader = pf.PcapReader("testXX.pcap")
42 | reader.set_filter("tcp")
43 | pkts = []
44 | while True:
45 | p = reader.recv_packet()
46 | if p is None:
47 | break
48 | pkts.append(p)
49 | reader.close()
50 | self.assertEqual(len(pkts), 0)
51 | os.unlink("testXX.pcap")
52 |
53 | def testAnotherInstance(self):
54 | with self.assertRaises(Exception):
55 | pf._PcapFfi()
56 |
57 | with self.assertRaises(Exception):
58 | pf._PcapFfi._instance.discoverdevs()
59 |
60 | def testCreate(self):
61 | devs = pf.pcap_devices()
62 | xname = devs[0].name
63 | px = pf.PcapLiveDevice.create(xname)
64 | px.snaplen = 80
65 | px.set_promiscuous(True)
66 | px.set_timeout(0)
67 | px.set_immediate_mode(True)
68 | px.set_buffer_size(4096)
69 | try:
70 | px.set_tstamp_type(PcapTstampType.Host)
71 | except:
72 | pass
73 | self.assertEqual(px.tstamp_precision, pf.PcapTstampPrecision.Micro)
74 | xlist = px.list_tstamp_types()
75 | self.assertIsInstance(xlist, list)
76 | try:
77 | px.tstamp_precision = PcapTstampPrecision.Nano
78 | self.assertEqual(px.tstamp_precision, pf.PcapTstampPrecision.Nano)
79 | except:
80 | self.assertEqual(px.tstamp_precision, pf.PcapTstampPrecision.Micro)
81 | with self.assertRaises(pf.PcapException):
82 | px.blocking
83 | px.blocking = True
84 | with self.assertRaises(pf.PcapException):
85 | px.blocking
86 | with self.assertRaises(pf.PcapException):
87 | x = px.dlt # exc because not activated
88 | self.assertEqual(px.fd, -1)
89 | with self.assertRaises(pf.PcapException):
90 | px.send_packet("hello, world") # not bytes
91 | self.assertIsNone(px.recv_packet(0))
92 | self.assertIsNone(px.recv_packet_or_none())
93 | with self.assertRaises(pf.PcapException):
94 | px.set_direction(pf.PcapDirection.InOut) # not active
95 | with self.assertRaises(pf.PcapException):
96 | px.set_filter("icmp") # not active
97 | px.close()
98 |
99 | if __name__ == '__main__':
100 | unittest.main(verbosity=1)
101 |
--------------------------------------------------------------------------------
/examples/README.rst:
--------------------------------------------------------------------------------
1 | Examples
2 | ********
3 |
4 | This folder contains a few different examples of Switchyard user code, as described below. Sample exercises intended for classroom use can be found in the ``exercises`` folder. For further details and explanation of various API calls in the examples below, please refer to the documentation. For additional instructor materials (including exercises, full tests, etc.), please email jsommers@colgate.edu.
5 |
6 | Basic send/receive examples
7 | ---------------------------
8 |
9 | There are a few simple examples to illustrate sending and receiving packets using Switchyard.
10 |
11 | ``sendpkt.py``
12 | This program is designed to send one packet (an ICMP echo request) out each available interface. It should be invoked as a Switchyard program (not as a regular Python program). For example: ``swyard sendpkt.py``. You will likely need to run this program as root in order to successfully send packets.
13 |
14 | ``sniff.py``
15 | This program is a simple "packet sniffer", which just prints out a string representation of each packet as it is received. It should be invoked as a Switchyard program, e.g., ``swyard sniff.py``. It may need to be run as root in order to successfully capture and print packets.
16 |
17 | ``sydump.py``
18 | This program *uses* the Switchyard libraries, but is just executed as a regular Python program. It captures packets from one interface, prints out each packet, and also stores the packet in a file named ``sydump.pcap``.
19 |
20 | This program can just be invoked as ``python3 sydump.py``, assuming Switchyard and its dependencies have been installed.
21 |
22 | ``readpkt.py``
23 | This program also just *uses* the Switchyard libraries but is just executed as a regular Python program. I reads packet data stored in a libpcap file and prints a string representation of each packet to the console.
24 |
25 | This program can just be invoked as ``python3 readpkt.py``, assuming Switchyard and its dependencies have been installed.
26 |
27 | Network hub example
28 | -------------------
29 |
30 | The network hub example is discussed quite extensively in the documentation. There are two files here related to that example: ``hubtests.py`` and ``myhub.py``. It can be executed as ``swyard -t hubtests.py myhub.py``
31 |
32 | Application layer code + stack example
33 | --------------------------------------
34 |
35 | Switchyard contains a *socket emulation capability* which can be used to run a (mostly) standard UDP-based Python socket program and have that program use a Switchyard-based networking stack. There are four example files related to this capability:
36 |
37 | ``clientapp_udpstackex.py``
38 | A client socket-based program that sends a UDP message to 127.0.0.1:10000 and waits for one response back from a server.
39 |
40 | ``server_udpstackex.py``
41 | A server socket-based program that binds to UDP port 10000 and echos back whatever gets sent to it.
42 |
43 | ``udpstack_tests.py``
44 | A test file that waits for a message emitted from a client program and mimics a server's response back to the client.
45 |
46 | ``udpstack.py``
47 | A Switchyard program that implements the very basics of a UDP networking stack. It presently assumes that only the localhost interface is used (thus ARP and a forwarding decision are not required). To run this program in *real* mode currently requires use of macos (Linux and other OSes are not yet supported).
48 |
49 | To run this example in test mode, you can use the following command line:
50 | ``swyard -t udpstack_tests.py -a clientapp_udpstackex.py udpstack.py``
51 |
52 | To run this example in real mode, you might want to start the server first, but you don't actually need to::
53 |
54 | $ python3 server_udpstackex.py
55 |
56 | The Switchyard component(s) then can be executed as:``swyard -i lo0 -a clientapp_udpstackex.py udpstack.py``. Note that if the server isn't started, you should see an ICMP destination unreachable (port unreachable) error message returned to the stack.
57 |
58 | Finally, note that to execute the client and server *without* any Switchyard involvement requires a single line edit in the client file to import the Python socket library instead of Switchyard's socket emulation library.
59 |
--------------------------------------------------------------------------------
/docs/py-modindex.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Python Module Index — Switchyard 1.0.1 documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
Python Module Index
40 |
41 |
44 |
45 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
81 |
82 |
83 |
96 |
100 |
101 |
--------------------------------------------------------------------------------
/documentation/intro.rst:
--------------------------------------------------------------------------------
1 | Introduction and Overview
2 | *************************
3 |
4 | Switchyard is a framework for creating, testing, and experimenting with software implementations of networked systems such as Ethernet switches, IP routers, firewalls and middleboxes, and end-host protocol stacks. Switchyard can be used for system-building projects targeting layers of the network protocol stack from layer 2 (link layer) and above. It is intended primarily for educational use and has purpose-built testing and debugging features. Although its design favors understandability over speed, it can work quite nicely as a prototyping environment for new kinds of networked devices.
5 |
6 | The Switchyard framework is implemented in Python and consists of two components: a program (``swyard``) which creates a runtime environment for the code that implements some networked system or device, and a collection of library modules that can be used for a variety of tasks such as packet creation and parsing. The networked system code is implemented in one or more Python files (which you write!) and that use the Switchyard libraries and conform to certain conventions. The ``swyard`` runtime environment creator and orchestrator seamlessly handles running your code either in a test setting where no actual network traffic is generated or in a real or "live" setting in which your code can interact with other networked systems.
7 |
8 | The Switchyard runtime environment (depicted below) provides a given networked system with 1 or more *interfaces* or *ports*. A port may represent a wired connection to another device, or may represent a wireless interface, or may represent a *loopback* [#loopback]_ interface. In any case, it is through these ports that packets are sent and received. Each port has, at minimum, a name (e.g., ``en0``) and an Ethernet address. A port may also have an IPv4 address and subnet mask associated with it.
9 |
10 |
11 | .. figure:: srpyarch.*
12 | :align: center
13 | :figwidth: 80%
14 |
15 |
16 | The typical goal of a Switchyard-based program is to receive a packet on one port, possibly modify it, then either forward it out one or more ports or to drop the packet. The rest of this documentation is organized around how to perform these tasks in various settings. In particular:
17 |
18 | * The next section, :ref:`writing a Switchyard program `, describes how to develop a basic Switchyard program, including what APIs are available for parsing and constructing packets and sending/receiving packets on network interfaces.
19 | * Following that, the next section, :ref:`running in the test environment `, provides details on running a Switchyard program in the test environment. The section after that gives details on :ref:`how to write test scenarios`.
20 | * The next section describes :ref:`running Switchyard in a live environment `, such as on a standard Linux host or within the Mininet emulation environment or some other kind of virtual environment.
21 | * :ref:`Advanced API topics ` are addressed next, such as creating new packet header types, and implementing network protocol stacks that can interoperate with a Python socket-based program.
22 | * An :ref:`installation guide ` appears next.
23 | * Finally, you can find an :ref:`apiref` at the end of this documentation along with and an index.
24 |
25 | **A note to the pedantic**: In this documentation we use the term *packet* in a generic sense to refer to what may more traditionally be a link layer *frame*, a network layer *packet*, a transport layer *segment*, or an application layer *message*. Where appropriate, we use the appropriate specific term, but often resort to using *packet* in a more general sense.
26 |
27 | **And one more (genuinely important) note**: Switchyard is Python 3-only! You'll get an error (or maybe even more than one error!) if you try to use Switchyard with Python 2. Python 3.4 is required, at minimum. An installation guide (see :ref:`install`) is provided in this documentation to help with getting any necessary libraries installed on your platform to make Switchyard work right.
28 |
29 |
30 | .. rubric:: Footnotes
31 |
32 | .. [#loopback] The loopback interface is a *virtual* interface that connects a host to itself. It is typically used to facilitate network communication among processes on the same host.
33 |
34 |
35 |
--------------------------------------------------------------------------------
/docs/_sources/intro.rst.txt:
--------------------------------------------------------------------------------
1 | Introduction and Overview
2 | *************************
3 |
4 | Switchyard is a framework for creating, testing, and experimenting with software implementations of networked systems such as Ethernet switches, IP routers, firewalls and middleboxes, and end-host protocol stacks. Switchyard can be used for system-building projects targeting layers of the network protocol stack from layer 2 (link layer) and above. It is intended primarily for educational use and has purpose-built testing and debugging features. Although its design favors understandability over speed, it can work quite nicely as a prototyping environment for new kinds of networked devices.
5 |
6 | The Switchyard framework is implemented in Python and consists of two components: a program (``swyard``) which creates a runtime environment for the code that implements some networked system or device, and a collection of library modules that can be used for a variety of tasks such as packet creation and parsing. The networked system code is implemented in one or more Python files (which you write!) and that use the Switchyard libraries and conform to certain conventions. The ``swyard`` runtime environment creator and orchestrator seamlessly handles running your code either in a test setting where no actual network traffic is generated or in a real or "live" setting in which your code can interact with other networked systems.
7 |
8 | The Switchyard runtime environment (depicted below) provides a given networked system with 1 or more *interfaces* or *ports*. A port may represent a wired connection to another device, or may represent a wireless interface, or may represent a *loopback* [#loopback]_ interface. In any case, it is through these ports that packets are sent and received. Each port has, at minimum, a name (e.g., ``en0``) and an Ethernet address. A port may also have an IPv4 address and subnet mask associated with it.
9 |
10 |
11 | .. figure:: srpyarch.*
12 | :align: center
13 | :figwidth: 80%
14 |
15 |
16 | The typical goal of a Switchyard-based program is to receive a packet on one port, possibly modify it, then either forward it out one or more ports or to drop the packet. The rest of this documentation is organized around how to perform these tasks in various settings. In particular:
17 |
18 | * The next section, :ref:`writing a Switchyard program `, describes how to develop a basic Switchyard program, including what APIs are available for parsing and constructing packets and sending/receiving packets on network interfaces.
19 | * Following that, the next section, :ref:`running in the test environment `, provides details on running a Switchyard program in the test environment. The section after that gives details on :ref:`how to write test scenarios`.
20 | * The next section describes :ref:`running Switchyard in a live environment `, such as on a standard Linux host or within the Mininet emulation environment or some other kind of virtual environment.
21 | * :ref:`Advanced API topics ` are addressed next, such as creating new packet header types, and implementing network protocol stacks that can interoperate with a Python socket-based program.
22 | * An :ref:`installation guide ` appears next.
23 | * Finally, you can find an :ref:`apiref` at the end of this documentation along with and an index.
24 |
25 | **A note to the pedantic**: In this documentation we use the term *packet* in a generic sense to refer to what may more traditionally be a link layer *frame*, a network layer *packet*, a transport layer *segment*, or an application layer *message*. Where appropriate, we use the appropriate specific term, but often resort to using *packet* in a more general sense.
26 |
27 | **And one more (genuinely important) note**: Switchyard is Python 3-only! You'll get an error (or maybe even more than one error!) if you try to use Switchyard with Python 2. Python 3.4 is required, at minimum. An installation guide (see :ref:`install`) is provided in this documentation to help with getting any necessary libraries installed on your platform to make Switchyard work right.
28 |
29 |
30 | .. rubric:: Footnotes
31 |
32 | .. [#loopback] The loopback interface is a *virtual* interface that connects a host to itself. It is typically used to facilitate network communication among processes on the same host.
33 |
34 |
35 |
--------------------------------------------------------------------------------
/switchyard/swyard.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | import os
5 | sys.path.append(os.getcwd())
6 | import argparse
7 |
8 | from switchyard.syinit import start_framework
9 | import switchyard.textcolor as tcolor
10 |
11 | def version_check():
12 | required = (3,6)
13 | this = (sys.version_info.major, sys.version_info.minor)
14 | if this < required:
15 | log_failure("Invalid Python version for using Switchyard: need at least 3.6")
16 | sys.exit(-1)
17 |
18 | def main():
19 | version_check()
20 |
21 | progname = "swyard"
22 | parser = argparse.ArgumentParser(prog=progname)
23 | parser.add_argument("-i", "--include", metavar='INCLUDE_INTF',
24 | help="Specify interface names to include/use for data plane traffic "
25 | "(default: all non-loopback interfaces).",
26 | dest="intf", action='append')
27 | parser.add_argument("-x", "--exclude", metavar='EXCLUDE_INTF',
28 | help="Specify interface names to exclude in {}. "
29 | "All other non-loopback interfaces will be used.".format(progname),
30 | dest="exclude", action='append')
31 | parser.add_argument('usercode', metavar="YOURCODE", type=str, nargs='?',
32 | help='User switch/router code to execute.')
33 | parser.add_argument("-g", "--codearg", metavar="YOURCODE_ARGS",
34 | type=str, default='',
35 | help='Arguments to pass to your code (if multiple args, they need to be '
36 | ' quoted in the shell).')
37 | parser.add_argument("-c", "--compile",
38 | help="Compile test scenario to binary format for distribution.",
39 | dest="compile", action="append", metavar="TEST_SCENARIO")
40 | parser.add_argument("-t", "--test",
41 | help="Run {} in testing mode, using the given test "
42 | "scenario file.".format(progname), metavar="TESTSCENARIO",
43 | dest="tests", action="append")
44 | parser.add_argument("--dryrun",
45 | help="Get everything ready to go, but don't actually do anything.",
46 | action='store_true', dest='dryrun', default=False)
47 | parser.add_argument("-v", "--verbose",
48 | help="Turn on verbose output, including full packet dumps in test "
49 | "results. Can be specified multiple times to increase verbosity.",
50 | dest="verbose", action="count", default=0)
51 | parser.add_argument("-d", "--debug", help="Turn on debug logging output.",
52 | dest="debug", action="store_true", default=False)
53 | parser.add_argument("-l", "--logfile", help="Specify the name of a file to send"
54 | " log entries to (default is to send log to stdout/stderr).",
55 | dest="logfile", default=None, type=str)
56 | parser.add_argument('--nocolor', default=False, action='store_true',
57 | help="Don't show colored output; use terminal defaults.")
58 | parser.add_argument("--nopdb", help="Don't enter pdb on crash.",
59 | dest="nopdb", action="store_true", default=False)
60 | parser.add_argument("-f", "--firewall",
61 | help="Specify host firewall rules (for real/live mode only).",
62 | dest="fwconfig", action="append")
63 | parser.add_argument("-a", "--app",
64 | help="Specify application layer (socket-based) program to start.",
65 | dest="app", default=None, metavar="SOCKET_APP")
66 | parser.add_argument("-e", "--nohandle",
67 | help="Don't trap exceptions. Use of this option is helpful if you want"
68 | " to use Switchyard with a different symbolic debugger than pdb.",
69 | dest="nohandle", action="store_true", default=False)
70 | parser.add_argument("--cli", help="Enter switchyard simulation command-line (EXPERIMENTAL!)",
71 | dest="cli", action="store_true", default=False)
72 | parser.add_argument("--topology", help="Specify topology to use for simulation"
73 | " (only used if --cli is specified).",
74 | dest="topology", type=str, default=None)
75 | parser.add_argument("--listif", help="List available interfaces (then exit)",
76 | dest="listif", action="store_true", default=False)
77 | args = parser.parse_args()
78 |
79 | if (args.usercode is None and not args.compile) and not args.listif:
80 | parser.print_usage()
81 | return -1
82 | tcolor.TextColor.setup(args.nocolor)
83 | start_framework(args)
84 |
85 | if __name__ == '__main__':
86 | main()
87 |
--------------------------------------------------------------------------------
/docs/_modules/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Overview: module code — Switchyard 1.0.1 documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
All modules for which code is available
36 |
54 |
55 |
56 |
57 |
58 |
59 |
73 |
74 |
75 |
88 |
92 |
93 |
--------------------------------------------------------------------------------
/switchyard/sim/nodeexec.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import heapq
3 | from collections import namedtuple, defaultdict
4 | import threading
5 | from queue import Queue,Empty
6 | import time
7 | from importlib import import_module
8 | from cmd import Cmd
9 | import re
10 | from abc import ABCMeta,abstractmethod
11 |
12 | from ..llnetbase import LLNetBase
13 | from ..lib.exceptions import NoPackets,Shutdown
14 | from ..lib.logging import log_debug
15 | from .monitor import *
16 | from ..lib.topo import *
17 | from .linkem import LinkEmulator
18 |
19 |
20 | EgressPipe = namedtuple('EgressPipe', ['queue','delay','capacity','remote_devname'])
21 | class NodeExecutor(LLNetBase):
22 | __slots__ = ['__done', '__ingress_queue', '__egress_pipes', '__name','__interfaces','__symod', '__linkem', '__tolinkem','__recv_monitors','__t']
23 | def __init__(self, name, ingress_queue, symod=None):
24 | LLNetBase.__init__(self)
25 | self.__ingress_queue = ingress_queue
26 | self.__egress_pipes = {}
27 | self.__name = name
28 | self.__interfaces = {}
29 | self.__symod = symod
30 | self.__done = False
31 | self.__linkem = None
32 | self.__tolinkem = None
33 | self.__recv_monitors = {'host': NullMonitor()}
34 | self.__t = None
35 |
36 | def sendHostPacket(self, pkt):
37 | self.__ingress_queue.put( ('host', pkt) )
38 |
39 | def addEgressInterface(self, devname, intf, queue, capacity, delay, remote_devname):
40 | # print ("Adding egress interface on {} {}".format(self.name, devname))
41 | self.__egress_pipes[devname] = EgressPipe(queue, delay, capacity, remote_devname)
42 | self.__interfaces[devname] = intf
43 | self.__recv_monitors[devname] = NullMonitor()
44 |
45 | @property
46 | def name(self):
47 | return self.__name
48 |
49 | def interfaces(self):
50 | return self.__interfaces.values()
51 |
52 | def set_devupdown_callback(self, callback):
53 | pass
54 |
55 | def interface_by_name(self, name):
56 | return self.__interfaces[name]
57 |
58 | def interface_by_ipaddr(self, ipaddr):
59 | pass
60 |
61 | def interface_by_macaddr(self, macaddr):
62 | pass
63 |
64 | def attach_recv_monitor(self, interface, monitorobject):
65 | self.__recv_monitors[interface] = monitorobject
66 |
67 | def remove_recv_monitor(self, interface):
68 | self.__recv_monitors[interface] = NullMonitor()
69 |
70 | def recv_packet(self, timeout=0.0, timestamp=False):
71 | #
72 | # FIXME: not sure about how best to handle...
73 | #
74 | giveup_time = time.time() + timeout
75 | inner_timeout = 0.05
76 |
77 | while timeout == 0.0 or time.time() < giveup_time:
78 | try:
79 | devname,packet = self.__ingress_queue.get(block=True, timeout=inner_timeout)
80 | now = time.time()
81 | self.__recv_monitors[devname](devname,now,packet)
82 | if timestamp:
83 | return devname,now,packet
84 | return devname,packet
85 | except Empty:
86 | pass
87 |
88 | if self.__done:
89 | raise Shutdown()
90 |
91 | raise NoPackets()
92 |
93 | def send_packet(self, dev, packet):
94 | egress_pipe = self.__egress_pipes[dev]
95 | now = time.time()
96 | delay = now + len(packet) / float(egress_pipe.capacity) + egress_pipe.delay
97 | self.__tolinkem.put( (delay, (egress_pipe.remote_devname, packet), egress_pipe.queue) )
98 |
99 | def shutdown(self):
100 | self.__linkem.shutdown()
101 | self.__done = True
102 | log_debug("Joining node codeexec thread {} node {}".format(self.__t.name, self.__name))
103 | self.__t.join()
104 |
105 | def __idleloop(self):
106 | while not self.__done:
107 | try:
108 | devname,ts,packet = self.recv_packet(timestamp=True, timeout=0.1)
109 | except Shutdown:
110 | break
111 | except NoPackets:
112 | pass
113 |
114 | def run(self):
115 | self.__tolinkem = Queue()
116 | self.__linkem = LinkEmulator(self.__tolinkem)
117 | self.__t = threading.Thread(target=self.__linkem.run)
118 | self.__t.start()
119 | self.startcode()
120 |
121 | def resetcode(self, mod=None):
122 | self.__symod = mod
123 | self.startcode()
124 |
125 | def startcode(self):
126 | if self.__symod:
127 | self.__symod(self)
128 | else:
129 | self.__idleloop()
130 | self.__t.join()
131 | del self.__t
132 | del self.__linkem
133 | del self.__tolinkem
134 | del self.__egress_pipes
135 | del self.__recv_monitors
136 |
137 |
--------------------------------------------------------------------------------
/tests/test_hostfirewall.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import unittest
3 |
4 | import switchyard.hostfirewall as hf
5 |
6 | class HostFirewallTests(unittest.TestCase):
7 | def _collectcmd(self, cmd, stdin=None):
8 | self.cmds.append( (cmd,stdin) )
9 | rv = ""
10 | if cmd == "/sbin/pfctl -E":
11 | rv = "Token: 0"
12 | return True,rv
13 |
14 | def setUp(self):
15 | self.cmds = []
16 | setattr(hf, "_runcmd", self._collectcmd)
17 | hf.Firewall._instance = None
18 |
19 | def testErrorInit(self):
20 | hf.Firewall(("eth0",), ("icmp:*","tcp:80"))
21 | with self.assertRaises(Exception):
22 | hf.Firewall(("eth0",), ("icmp:*","tcp:80"))
23 | setattr(sys, "platform", "vax/vms")
24 | hf.Firewall._instance = None
25 | with self.assertRaises(Exception):
26 | hf.Firewall(("eth0",), ("icmp:*","tcp:80"))
27 |
28 | def testLinux(self):
29 | setattr(sys, "platform", "linux")
30 | fw = hf.Firewall(("eth0",), ("icmp:*","tcp:80"))
31 | fw.__enter__()
32 | cmds = ['/sbin/iptables-save',
33 | '/sbin/iptables -F',
34 | '/sbin/iptables -t raw -F',
35 | '/sbin/iptables -t raw -A PREROUTING -j DROP --protocol icmp -i eth0',
36 | '/sbin/iptables -t raw -A PREROUTING -j DROP --protocol tcp -i eth0 --dport 80']
37 |
38 | xcmds = [ c for c,inp in self.cmds]
39 | self.assertEqual(cmds, xcmds)
40 | fw.add_rule("udp:123")
41 | cmds = ['/sbin/iptables-save',
42 | '/sbin/iptables -F',
43 | '/sbin/iptables -t raw -F',
44 | '/sbin/iptables -t raw -A PREROUTING -j DROP --protocol icmp -i eth0',
45 | '/sbin/iptables -t raw -A PREROUTING -j DROP --protocol tcp -i eth0 --dport 80',
46 | '/sbin/iptables -t raw -A PREROUTING -j DROP --protocol udp -i eth0 --dport 123']
47 | xcmds = [ c for c,inp in self.cmds]
48 | self.assertEqual(cmds, xcmds)
49 | fw.__exit__(0,0,0)
50 |
51 | def testLinux2(self):
52 | setattr(sys, "platform", "linux")
53 | fw = hf.Firewall(("eth0","eth1"), ("all",))
54 | fw.__enter__()
55 | cmds = ['/sbin/iptables-save',
56 | '/sbin/sysctl net.ipv4.conf.eth0.arp_ignore',
57 | '/sbin/sysctl net.ipv4.conf.eth1.arp_ignore',
58 | '/sbin/iptables -F',
59 | '/sbin/iptables -t raw -F',
60 | '/sbin/iptables -t raw -A PREROUTING -j DROP -i eth0',
61 | '/sbin/iptables -t raw -A PREROUTING -j DROP -i eth1',]
62 | xcmds = [ c for c,inp in self.cmds]
63 | self.assertEqual(cmds, xcmds)
64 | fw.__exit__(0,0,0)
65 |
66 | def testLinux3(self):
67 | setattr(sys, "platform", "linux")
68 | fw = hf.Firewall(("eth0","eth1"), ("none",))
69 | fw.__enter__()
70 | cmds = ['/sbin/iptables-save',
71 | '/sbin/iptables -F',
72 | '/sbin/iptables -t raw -F' ]
73 | xcmds = [ c for c,inp in self.cmds]
74 | self.assertEqual(cmds, xcmds)
75 | fw.__exit__(0,0,0)
76 |
77 | def testMacos(self):
78 | setattr(sys, "platform", "darwin")
79 | fw = hf.Firewall(("eth0",), ("icmp:*","tcp:80"))
80 | fw.__enter__()
81 | rules = self.cmds[1][1]
82 | self.assertEqual(rules[0], 'block drop on eth0 proto icmp from any to any')
83 | self.assertEqual(rules[1], 'block drop on eth0 proto tcp from any port 80 to any port 80')
84 | fw.add_rule("udp:123")
85 | rules = self.cmds[1][1]
86 | self.assertEqual(rules[0], 'block drop on eth0 proto icmp from any to any')
87 | self.assertEqual(rules[1], 'block drop on eth0 proto tcp from any port 80 to any port 80')
88 | self.assertEqual(rules[2], 'block drop on eth0 proto udp from any port 123 to any port 123')
89 | fw.__exit__(0,0,0)
90 |
91 | def testBadRule1(self):
92 | setattr(sys, "platform", "test")
93 | with self.assertRaises(ValueError):
94 | fw = hf.Firewall(("eth0",), ("mpxyz",))
95 |
96 | def testBadRule2(self):
97 | setattr(sys, "platform", "test")
98 | with self.assertRaises(ValueError):
99 | fw = hf.Firewall(("eth0",), ("xicmp:asdf",))
100 |
101 | def testTest(self):
102 | setattr(sys, "platform", "test")
103 | fw = hf.Firewall(("eth0",), ("all", "none", "icmp:*","tcp:80"))
104 | fw.__enter__()
105 | self.assertEqual(self.cmds, [])
106 | fw.add_rule("udp:123")
107 | self.assertEqual(self.cmds, [])
108 | self.assertEqual(
109 | [('all', None), ('none', None), ('icmp', None), ('tcp', '80'), ('udp', '123')],
110 | hf.Firewall._instance._firewall_delegate._rules)
111 | fw.__exit__(0,0,0)
112 |
113 |
114 | if __name__ == '__main__':
115 | unittest.main()
116 |
117 |
--------------------------------------------------------------------------------
/switchyard/lib/topo/util.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | def humanize_bandwidth(bits):
4 | '''
5 | Accept some number of bits/sec (i.e., a link capacity) as an
6 | integer, and return a string representing a 'human'(-like)
7 | representation of the capacity, e.g., 10 Mb/s, 1.5 Mb/s,
8 | 900 Gb/s.
9 |
10 | As is the standard in networking, capacity values are assumed
11 | to be base-10 values (not base 2), so 1000 is 1 Kb/s.
12 | '''
13 | unit = ''
14 | divisor = 1
15 | if bits < 1000:
16 | unit = 'bits'
17 | divisor = 1
18 | elif bits < 1000000:
19 | unit = 'Kb'
20 | divisor = 1000
21 | elif bits < 1000000000:
22 | unit = 'Mb'
23 | divisor = 1000000
24 | elif bits < 1000000000000:
25 | unit = 'Gb'
26 | divisor = 1000000000
27 | elif bits < 1000000000000000:
28 | unit = 'Tb'
29 | divisor = 1000000000000
30 | else:
31 | raise Exception("Can't humanize that many bits.")
32 |
33 | if bits % divisor == 0:
34 | value = int(bits/divisor)
35 | else:
36 | value = bits/divisor
37 |
38 | return "{} {}/s".format(value, unit)
39 |
40 | def unhumanize_bandwidth(bitsstr):
41 | '''
42 | Take a string representing a link capacity, e.g., 10 Mb/s, and
43 | return an integer representing the number of bits/sec.
44 | Recognizes:
45 | - 'bits/sec' or 'b/s' are treated as plain bits per second
46 | - 'Kb' or 'kb' as thousand bits/sec
47 | - 'Mb' or 'mb' as million bits/sec
48 | - 'Gb' or 'gb' as billion bits/sec
49 | - 'Tb' or 'tb' as trillion bits/sec
50 | - if second character is 'B', quantity is interpreted as bytes/sec
51 | - any subsequent characters after the first two are ignored, so
52 | Kb/s Kb/sec Kbps are interpreted identically.
53 |
54 | Returns None if the string doesn't contain anything parseable.
55 | '''
56 | if isinstance(bitsstr, int):
57 | return bitsstr
58 |
59 | mobj = re.match('^\s*([\d\.]+)\s*(.*)\s*$', bitsstr)
60 | if not mobj:
61 | return None
62 | value, units = mobj.groups()
63 | value = float(value)
64 | multipliers = { 'b':1, 'k':1e3, 'm':1e6, 'g':1e9, 't':1e12 }
65 | if not units:
66 | units = 'bits'
67 | mult = multipliers.get(units[0].lower(), 0)
68 | bits = 1
69 | if len(units) > 1:
70 | if units[1] == 'B': bits = 8
71 | # print (bitsstr, value, mult, bits)
72 | return int(value * mult * bits)
73 |
74 | # a couple aliases
75 | humanize_capacity = humanize_bandwidth
76 | unhumanize_capacity = unhumanize_bandwidth
77 |
78 | def humanize_delay(delay):
79 | '''
80 | Accept a floating point number presenting link propagation delay
81 | in seconds (e.g., 0.1 for 100 milliseconds delay), and return
82 | a human(-like) string like '100 milliseconds'. Handles values as
83 | small as 1 microsecond, but no smaller.
84 |
85 | Because of imprecision in floating point numbers, a relatively easy
86 | way to handle this is to convert to string, then slice out sections.
87 | '''
88 | delaystr = '{:1.06f}'.format(delay)
89 | decimal = delaystr.find('.')
90 | seconds = int(delaystr[:decimal])
91 | millis = int(delaystr[-6:-3])
92 | micros = int(delaystr[-3:])
93 | # print (delay,delaystr,seconds,millis,micros)
94 | units = ''
95 | microsecs = micros + 1e3 * millis + 1e6 * seconds
96 | if micros > 0:
97 | units = ' \u00B5sec'
98 | value = int(microsecs)
99 | elif millis > 0:
100 | units = ' msec'
101 | value = int(microsecs / 1000)
102 | elif seconds > 0:
103 | units = ' sec'
104 | value = int(microsecs / 1000000)
105 | else:
106 | units = ' sec'
107 | value = delay
108 | if value > 1:
109 | units += 's'
110 | return '{}{}'.format(value, units)
111 |
112 | def unhumanize_delay(delaystr):
113 | '''
114 | Accept a string representing link propagation delay (e.g.,
115 | '100 milliseconds' or '100 msec' or 100 millisec') and return
116 | a floating point number representing the delay in seconds.
117 | Recognizes:
118 | - us, usec, micros* all as microseconds
119 | - ms, msec, millisec* all as milliseconds
120 | - s, sec* as seconds
121 |
122 | returns None on parse failure.
123 | '''
124 | if isinstance(delaystr, float):
125 | return delaystr
126 |
127 | mobj = re.match('^\s*([\d\.]+)\s*(\w*)', delaystr)
128 | if not mobj:
129 | return None
130 | value, units = mobj.groups()
131 | value = float(value)
132 | if not units:
133 | divisor = 1.0
134 | elif units == 'us' or units == 'usec' or units.startswith('micros'):
135 | divisor = 1e6
136 | elif units == 'ms' or units == 'msec' or units.startswith('millis'):
137 | divisor = 1e3
138 | elif units == 's' or units.startswith('sec'):
139 | divisor = 1.0
140 | else:
141 | return None
142 | return value / divisor
143 |
--------------------------------------------------------------------------------
/docs/_static/pygments.css:
--------------------------------------------------------------------------------
1 | pre { line-height: 125%; }
2 | td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
3 | span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
4 | td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
5 | span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
6 | .highlight .hll { background-color: #ffffcc }
7 | .highlight { background: #eeffcc; }
8 | .highlight .c { color: #408090; font-style: italic } /* Comment */
9 | .highlight .err { border: 1px solid #FF0000 } /* Error */
10 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */
11 | .highlight .o { color: #666666 } /* Operator */
12 | .highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */
13 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */
14 | .highlight .cp { color: #007020 } /* Comment.Preproc */
15 | .highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */
16 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */
17 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */
18 | .highlight .gd { color: #A00000 } /* Generic.Deleted */
19 | .highlight .ge { font-style: italic } /* Generic.Emph */
20 | .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
21 | .highlight .gr { color: #FF0000 } /* Generic.Error */
22 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
23 | .highlight .gi { color: #00A000 } /* Generic.Inserted */
24 | .highlight .go { color: #333333 } /* Generic.Output */
25 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
26 | .highlight .gs { font-weight: bold } /* Generic.Strong */
27 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
28 | .highlight .gt { color: #0044DD } /* Generic.Traceback */
29 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
30 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
31 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
32 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */
33 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
34 | .highlight .kt { color: #902000 } /* Keyword.Type */
35 | .highlight .m { color: #208050 } /* Literal.Number */
36 | .highlight .s { color: #4070a0 } /* Literal.String */
37 | .highlight .na { color: #4070a0 } /* Name.Attribute */
38 | .highlight .nb { color: #007020 } /* Name.Builtin */
39 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
40 | .highlight .no { color: #60add5 } /* Name.Constant */
41 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */
42 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */
43 | .highlight .ne { color: #007020 } /* Name.Exception */
44 | .highlight .nf { color: #06287e } /* Name.Function */
45 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */
46 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
47 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */
48 | .highlight .nv { color: #bb60d5 } /* Name.Variable */
49 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */
50 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */
51 | .highlight .mb { color: #208050 } /* Literal.Number.Bin */
52 | .highlight .mf { color: #208050 } /* Literal.Number.Float */
53 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */
54 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */
55 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */
56 | .highlight .sa { color: #4070a0 } /* Literal.String.Affix */
57 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */
58 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */
59 | .highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */
60 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
61 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */
62 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
63 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */
64 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
65 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */
66 | .highlight .sr { color: #235388 } /* Literal.String.Regex */
67 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */
68 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */
69 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */
70 | .highlight .fm { color: #06287e } /* Name.Function.Magic */
71 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */
72 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */
73 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */
74 | .highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */
75 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */
--------------------------------------------------------------------------------
/switchyard/lib/packet/arp.py:
--------------------------------------------------------------------------------
1 | from .packet import PacketHeaderBase,Packet
2 | from ..address import EthAddr,ip_address,SpecialIPv4Addr,SpecialEthAddr
3 | import struct
4 | from .common import EtherType, ArpHwType, ArpOperation
5 | from ..exceptions import *
6 |
7 | '''
8 | References:
9 | Plummer.
10 | "RFC826", An Ethernet Address Resolution Protocol.
11 | Finlayson, Mann, Mogul, and Theimer.
12 | "RFC903", A Reverse Address Resolution Protocol.
13 | http://en.wikipedia.org/wiki/Address_Resolution_Protocol
14 | '''
15 |
16 | class Arp(PacketHeaderBase):
17 | __slots__ = ['_hwtype','_prototype','_hwaddrlen','_protoaddrlen',
18 | '_operation','_senderhwaddr','_senderprotoaddr',
19 | '_targethwaddr','_targetprotoaddr']
20 | _PACKFMT = '!HHBBH6s4s6s4s'
21 | _MINLEN = struct.calcsize(_PACKFMT)
22 |
23 | def __init__(self, **kwargs):
24 | self._hwtype = ArpHwType.Ethernet
25 | self._prototype = EtherType.IP
26 | self._hwaddrlen = 6
27 | self._protoaddrlen = 4
28 | self.operation = ArpOperation.Request
29 | self.senderhwaddr = SpecialEthAddr.ETHER_ANY.value
30 | self.senderprotoaddr = SpecialIPv4Addr.IP_ANY.value
31 | self.targethwaddr = SpecialEthAddr.ETHER_BROADCAST.value
32 | self.targetprotoaddr = SpecialIPv4Addr.IP_ANY.value
33 | super().__init__(**kwargs)
34 |
35 | def size(self):
36 | return struct.calcsize(Arp._PACKFMT)
37 |
38 | def pre_serialize(self, raw, pkt, i):
39 | pass
40 |
41 | def to_bytes(self):
42 | '''
43 | Return packed byte representation of the ARP header.
44 | '''
45 | return struct.pack(Arp._PACKFMT, self._hwtype.value, self._prototype.value, self._hwaddrlen, self._protoaddrlen, self._operation.value, self._senderhwaddr.packed, self._senderprotoaddr.packed, self._targethwaddr.packed, self._targetprotoaddr.packed)
46 |
47 | def from_bytes(self, raw):
48 | '''Return an Ethernet object reconstructed from raw bytes, or an
49 | Exception if we can't resurrect the packet.'''
50 | if len(raw) < Arp._MINLEN:
51 | raise NotEnoughDataError("Not enough bytes ({}) to reconstruct an Arp object".format(len(raw)))
52 | fields = struct.unpack(Arp._PACKFMT, raw[:Arp._MINLEN])
53 | try:
54 | self._hwtype = ArpHwType(fields[0])
55 | self._prototype = EtherType(fields[1])
56 | self._hwaddrlen = fields[2]
57 | self._protoaddrlen = fields[3]
58 | self.operation = ArpOperation(fields[4])
59 | self.senderhwaddr = EthAddr(fields[5])
60 | self.senderprotoaddr = ip_address(fields[6])
61 | self.targethwaddr = EthAddr(fields[7])
62 | self.targetprotoaddr = ip_address(fields[8])
63 | except Exception as e:
64 | raise Exception("Error constructing Arp packet object from raw bytes: {}".format(str(e)))
65 | return raw[Arp._MINLEN:]
66 |
67 | def __eq__(self, other):
68 | return self.hardwaretype == other.hardwaretype and \
69 | self.protocoltype == other.protocoltype and \
70 | self.operation == other.operation and \
71 | self.senderhwaddr == other.senderhwaddr and \
72 | self.senderprotoaddr == other.senderprotoaddr and \
73 | self.targethwaddr == other.targethwaddr and \
74 | self.targetprotoaddr == other.targetprotoaddr
75 |
76 | @property
77 | def hardwaretype(self):
78 | return self._hwtype
79 |
80 | @property
81 | def protocoltype(self):
82 | return self._prototype
83 |
84 | @property
85 | def operation(self):
86 | return self._operation
87 |
88 | @operation.setter
89 | def operation(self, value):
90 | self._operation = ArpOperation(value)
91 |
92 | @property
93 | def senderhwaddr(self):
94 | return self._senderhwaddr
95 |
96 | @senderhwaddr.setter
97 | def senderhwaddr(self, value):
98 | self._senderhwaddr = EthAddr(value)
99 |
100 | @property
101 | def senderprotoaddr(self):
102 | return self._senderprotoaddr
103 |
104 | @senderprotoaddr.setter
105 | def senderprotoaddr(self, value):
106 | self._senderprotoaddr = ip_address(value)
107 |
108 | @property
109 | def targethwaddr(self):
110 | return self._targethwaddr
111 |
112 | @targethwaddr.setter
113 | def targethwaddr(self, value):
114 | self._targethwaddr = EthAddr(value)
115 |
116 | @property
117 | def targetprotoaddr(self):
118 | return self._targetprotoaddr
119 |
120 | @targetprotoaddr.setter
121 | def targetprotoaddr(self, value):
122 | self._targetprotoaddr = ip_address(value)
123 |
124 | def next_header_class(self):
125 | '''
126 | No other headers should follow ARP.
127 | '''
128 | return None
129 |
130 | def __str__(self):
131 | return '{} {}:{} {}:{}'.format(self.__class__.__name__,
132 | self.senderhwaddr, self.senderprotoaddr,
133 | self.targethwaddr, self.targetprotoaddr)
134 |
--------------------------------------------------------------------------------
/docs/_static/doctools.js:
--------------------------------------------------------------------------------
1 | /*
2 | * doctools.js
3 | * ~~~~~~~~~~~
4 | *
5 | * Base JavaScript utilities for all Sphinx HTML documentation.
6 | *
7 | * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS.
8 | * :license: BSD, see LICENSE for details.
9 | *
10 | */
11 | "use strict";
12 |
13 | const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([
14 | "TEXTAREA",
15 | "INPUT",
16 | "SELECT",
17 | "BUTTON",
18 | ]);
19 |
20 | const _ready = (callback) => {
21 | if (document.readyState !== "loading") {
22 | callback();
23 | } else {
24 | document.addEventListener("DOMContentLoaded", callback);
25 | }
26 | };
27 |
28 | /**
29 | * Small JavaScript module for the documentation.
30 | */
31 | const Documentation = {
32 | init: () => {
33 | Documentation.initDomainIndexTable();
34 | Documentation.initOnKeyListeners();
35 | },
36 |
37 | /**
38 | * i18n support
39 | */
40 | TRANSLATIONS: {},
41 | PLURAL_EXPR: (n) => (n === 1 ? 0 : 1),
42 | LOCALE: "unknown",
43 |
44 | // gettext and ngettext don't access this so that the functions
45 | // can safely bound to a different name (_ = Documentation.gettext)
46 | gettext: (string) => {
47 | const translated = Documentation.TRANSLATIONS[string];
48 | switch (typeof translated) {
49 | case "undefined":
50 | return string; // no translation
51 | case "string":
52 | return translated; // translation exists
53 | default:
54 | return translated[0]; // (singular, plural) translation tuple exists
55 | }
56 | },
57 |
58 | ngettext: (singular, plural, n) => {
59 | const translated = Documentation.TRANSLATIONS[singular];
60 | if (typeof translated !== "undefined")
61 | return translated[Documentation.PLURAL_EXPR(n)];
62 | return n === 1 ? singular : plural;
63 | },
64 |
65 | addTranslations: (catalog) => {
66 | Object.assign(Documentation.TRANSLATIONS, catalog.messages);
67 | Documentation.PLURAL_EXPR = new Function(
68 | "n",
69 | `return (${catalog.plural_expr})`
70 | );
71 | Documentation.LOCALE = catalog.locale;
72 | },
73 |
74 | /**
75 | * helper function to focus on search bar
76 | */
77 | focusSearchBar: () => {
78 | document.querySelectorAll("input[name=q]")[0]?.focus();
79 | },
80 |
81 | /**
82 | * Initialise the domain index toggle buttons
83 | */
84 | initDomainIndexTable: () => {
85 | const toggler = (el) => {
86 | const idNumber = el.id.substr(7);
87 | const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`);
88 | if (el.src.substr(-9) === "minus.png") {
89 | el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`;
90 | toggledRows.forEach((el) => (el.style.display = "none"));
91 | } else {
92 | el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`;
93 | toggledRows.forEach((el) => (el.style.display = ""));
94 | }
95 | };
96 |
97 | const togglerElements = document.querySelectorAll("img.toggler");
98 | togglerElements.forEach((el) =>
99 | el.addEventListener("click", (event) => toggler(event.currentTarget))
100 | );
101 | togglerElements.forEach((el) => (el.style.display = ""));
102 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler);
103 | },
104 |
105 | initOnKeyListeners: () => {
106 | // only install a listener if it is really needed
107 | if (
108 | !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS &&
109 | !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS
110 | )
111 | return;
112 |
113 | document.addEventListener("keydown", (event) => {
114 | // bail for input elements
115 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
116 | // bail with special keys
117 | if (event.altKey || event.ctrlKey || event.metaKey) return;
118 |
119 | if (!event.shiftKey) {
120 | switch (event.key) {
121 | case "ArrowLeft":
122 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
123 |
124 | const prevLink = document.querySelector('link[rel="prev"]');
125 | if (prevLink && prevLink.href) {
126 | window.location.href = prevLink.href;
127 | event.preventDefault();
128 | }
129 | break;
130 | case "ArrowRight":
131 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
132 |
133 | const nextLink = document.querySelector('link[rel="next"]');
134 | if (nextLink && nextLink.href) {
135 | window.location.href = nextLink.href;
136 | event.preventDefault();
137 | }
138 | break;
139 | }
140 | }
141 |
142 | // some keyboard layouts may need Shift to get /
143 | switch (event.key) {
144 | case "/":
145 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break;
146 | Documentation.focusSearchBar();
147 | event.preventDefault();
148 | }
149 | });
150 | },
151 | };
152 |
153 | // quick alias for translations
154 | const _ = Documentation.gettext;
155 |
156 | _ready(Documentation.init);
157 |
--------------------------------------------------------------------------------
/switchyard/lib/interface.py:
--------------------------------------------------------------------------------
1 | from ipaddress import ip_interface, ip_address, IPv6Interface, IPv4Interface, IPv6Address, IPv4Address
2 | from enum import Enum
3 | from socket import if_nametoindex
4 |
5 | from .address import EthAddr
6 | from .logging import log_debug
7 | from ..pcapffi import pcap_devices
8 |
9 | class InterfaceType(Enum):
10 | Unknown=1
11 | Loopback=2
12 | Wired=3
13 | Wireless=4
14 |
15 | class Interface(object):
16 | __slots__ = ['__name','__ethaddr','__ipaddrset','__ifnum','__iftype']
17 | __nextnum = 1
18 |
19 | '''
20 | Class that models a single logical interface on a network
21 | device. An interface has a name, 48-bit Ethernet MAC address,
22 | and (optionally) set of IP addresses with network masks. An interface
23 | also has a number associated with it and a type, which is one
24 | of the values of the enumerated type ``InterfaceType``.
25 | '''
26 | def __init__(self, name, ethaddr, ifnum=None, iftype=InterfaceType.Unknown):
27 | self.__name = name
28 | self.ethaddr = ethaddr
29 | self.__ipaddrset = set()
30 | self.ifnum = ifnum
31 | self.__iftype = iftype
32 |
33 | @property
34 | def name(self):
35 | '''Get the name of the interface'''
36 | return self.__name
37 |
38 | @property
39 | def ethaddr(self):
40 | '''Get the Ethernet address associated with the interface'''
41 | return self.__ethaddr
42 |
43 | @ethaddr.setter
44 | def ethaddr(self, value):
45 | if isinstance(value, EthAddr):
46 | self.__ethaddr = value
47 | elif isinstance(value, (str,bytes)):
48 | self.__ethaddr = EthAddr(value)
49 | elif value is None:
50 | self.__ethaddr = EthAddr('00:00:00:00:00:00')
51 | else:
52 | raise ValueError("Can't initialize ethaddr with {}".format(value))
53 |
54 | @property
55 | def ipaddrs(self):
56 | '''Get the IP addresses associated with the interface, as a (frozen) set
57 | of ipaddress.IPv4Interface or ipaddress.IPv6Interface objects'''
58 | return frozenset(self.__ipaddrset)
59 |
60 | def assign_ipaddr(self, value):
61 | '''
62 | Assign a new IP address (v4 or v6) to this interface.
63 | Address can either be a IPv4Interface object, IPv6Interface object,
64 | or string in the form 'addr/prefix' (i.e., something that
65 | ipaddress.ip_interface will parse).
66 | '''
67 | if isinstance(value, (IPv4Interface, IPv6Interface)):
68 | self.__ipaddrset.add(value)
69 | elif isinstance(value, (str,IPv4Address,IPv6Address)):
70 | self.__ipaddrset.add(ip_interface(value))
71 | elif value is None:
72 | self.__ipaddrset.add(ip_interface('0.0.0.0'))
73 | else:
74 | raise Exception("Invalid type assignment to IP address (must be string or existing IP address) ({})".format(value))
75 |
76 | def remove_ipaddr(self, value):
77 | '''
78 | Remove an IP address from this interface. Value to remove
79 | can be as a string or IPAddress object.
80 | '''
81 | ipa = ip_interface(value)
82 | for addr in self.__ipaddrset:
83 | if addr.ip == ipa.ip:
84 | self.__ipaddrset.remove(addr)
85 | return
86 | raise Exception("No such address {} exists to remove from interface".format(ipa))
87 |
88 | @property
89 | def ifnum(self):
90 | '''Get the interface number (integer) associated with the interface'''
91 | return self.__ifnum
92 |
93 | @ifnum.setter
94 | def ifnum(self, value):
95 | if not isinstance(value, int):
96 | value = Interface.__nextnum
97 | Interface.__nextnum += 1
98 | self.__ifnum = int(value)
99 |
100 | @property
101 | def iftype(self):
102 | '''Get the type of the interface as a value from the InterfaceType enumeration.'''
103 | return self.__iftype
104 |
105 | def __str__(self):
106 | s = "{} mac:{}".format(str(self.name), str(self.ethaddr))
107 | for ipa in self.ipaddrs:
108 | if int(ipa) != 0:
109 | s += " ip:{}".format(ipa)
110 | return s
111 |
112 | def make_device_list(includes=set(), excludes=set()):
113 | log_debug("Making device list. Includes: {}, Excludes: {}".format(includes, excludes))
114 | non_interfaces = set()
115 | devs = set([ dev.name for dev in pcap_devices() if not dev.isloop or dev.name in includes])
116 | includes = set(includes) # may have been given as a list
117 | includes.intersection_update(devs) # only include devs that actually exist
118 |
119 | for d in devs:
120 | try:
121 | ifnum = if_nametoindex(d)
122 | except:
123 | non_interfaces.add(d)
124 | devs.difference_update(non_interfaces)
125 | log_debug("Devices found: {}".format(devs))
126 |
127 | # remove devs from excludelist
128 | devs.difference_update(set(excludes))
129 |
130 | # if includelist is non-empty, perform
131 | # intersection with devs found and includelist
132 | if includes:
133 | devs.intersection_update(includes)
134 |
135 | log_debug("Using these devices: {}".format(devs))
136 | return devs
137 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Switchyard
2 | ==========
3 |
4 | Switchyard is a library and framework for implementing software-based networked systems in Python such as Ethernet switches, IP routers, middleboxes, and end-host protocol stacks. It is primarily intended for educational use and supports creating devices from layer 2 (link layer) all the way through the application layer.
5 |
6 | Documentation is available at https://jsommers.github.io/switchyard. Documentation is written using the Python Sphinx package; doc sources are available in the documentation directory.
7 |
8 | Switchyard can run in a standalone test harness mode or can also use "live" network interfaces on a Linux or macOS host. It works nicely within Mininet and other virtual host environments, too.
9 |
10 | Some history
11 | ^^^^^^^^^^^^
12 |
13 | The version of Switchyard on the master branch is a major revision of the code as of July 2020. There are non-backward compatible API changes (particularly with the ``Interface`` class). Please see the release notes for more info. If you need an earlier version, use ``pip3 install switchyard==2019.1.1``.
14 |
15 |
16 | Installation
17 | ------------
18 |
19 | Prequisites: you'll also likely need to install additional packages (do this before using pip to install the Python libraries) on different operating systems:
20 |
21 | * Fedora (20 and later): ``sudo yum install libffi-devel libpcap-devel python3-devel python3-pip python3-virtualenv``
22 | * FreeBSD 11 and later: ``pkg install libffi libpcap``
23 | * macOS: install libffi and libpcap through mac Homebrew (https://brew.sh)
24 | * Ubuntu (14.04 and later): ``sudo apt-get install libffi-dev libpcap-dev python3-dev python3-pip python3-venv``
25 |
26 | You can install Switchyard and the necessary related packages in an isolated Python virtual environment ("venv"), which is the recommended path, or in the system directories, which is often less desirable. The venv route is highly suggested, since it makes all installation "local" and can easily destroyed, cleaned up, and recreated.
27 |
28 | To create a new virtual environment, you could do something like the following::
29 |
30 | $ python3 -m venv syenv
31 |
32 | You can change the name *syenv* to whatever you'd like to name your virtual environment. Next, you need to activate the environment. The instructions vary depending on the shell you're using. On ``bash``, the command is::
33 |
34 | $ source ./syenv/bin/activate
35 |
36 | You'll need to replace *syenv* with whatever you named the virtual environment. If you're using a different shell than bash, refer to Python documentation on the venv module.
37 |
38 | Finally, install Switchyard. All the required additional libraries should be automatically installed, too.
39 |
40 | ::
41 |
42 | $ python3 -m pip install switchyard
43 |
44 |
45 | There is also a docker image, if you prefer: https://hub.docker.com/repository/docker/jsommers/switchyard. Just do::
46 |
47 | $ docker run -v `pwd`:/swyard -it jsommers/switchyard
48 |
49 | ---
50 |
51 | Results of tests run in Travis (on Ubuntu):
52 |
53 | .. image:: https://travis-ci.org/jsommers/switchyard.svg?branch=master
54 | :target: https://travis-ci.org/jsommers/switchyard
55 |
56 |
57 | Documentation and Exercises
58 | ---------------------------
59 |
60 | * Documentation sources can be found in the documentation directory. See http://jsommers.github.io/switchyard for compiled/built docs in HTML.
61 |
62 | * Sample exercises (in ReStructuredText format) can be found in the examples/exercises directory. The examples are *not* included with the Switchyard package that is installed through
63 |
64 | * Instructor-only materials such as test scenarios and other scripts available on request (jsommers@colgate.edu); they're in a separate private repository.
65 |
66 | Credits
67 | -------
68 |
69 | I gratefully acknowledge support from the NSF. The materials here are based upon work supported by the National Science Foundation under grants CNS-1814537 ("NeTS: Small: RUI: Automating Active Measurement Metadata Collection and Analysis"; 2018-2021) and CNS-1054985 ("CAREER: Expanding the functionality of Internet routers"; 2011-2017).
70 |
71 | Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author and do not necessarily reflect the views of the National Science Foundation.
72 |
73 | License
74 | -------
75 |
76 | Copyright 2015-2020 Joel Sommers. All rights reserved.
77 |
78 | The Switchyard software is distributed under terms of the GNU General Public License, version 3. See below for the standard GNU GPL v3 copying text.
79 |
80 | Switchyard's documentation is distributed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License: http://creativecommons.org/licenses/by-nc-sa/4.0/.
81 |
82 | ::
83 |
84 | This program is free software: you can redistribute it and/or modify
85 | it under the terms of the GNU General Public License as published by
86 | the Free Software Foundation, either version 3 of the License, or
87 | (at your option) any later version.
88 |
89 | This program is distributed in the hope that it will be useful,
90 | but WITHOUT ANY WARRANTY; without even the implied warranty of
91 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
92 | GNU General Public License for more details.
93 |
94 | You should have received a copy of the GNU General Public License
95 | along with this program. If not, see .
96 |
--------------------------------------------------------------------------------
/docs/thanks.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Acknowledgments and thanks — Switchyard 1.0.1 documentation
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Acknowledgments and thanks
42 | Thanks to Colgate COSC465 students from Spring 2014 and Spring 2015 for being guinea pigs and giving feedback for the very first versions of Switchyard. Thanks also to Prof. Paul Barford and CS640 students at the University of Wisconsin for using and providing feedback on Switchyard.
43 | Thanks to those students who have contributed fixes and made suggestions for improvements. In particular:
44 |
45 |
46 | Thanks to Saul Shanabrook for several specific suggestions and bug reports that have led to improvements in Switchyard.
47 | Thanks to Xuyi Ruan for identifying and suggesting a fix to bugs on one of the documentation diagrams.
48 | Thanks to Sean Wilson for a bug fix on an infinitely recursive property setter. Oops, but this dumb bug motivated me to significantly improve test coverage, so there’s that.
49 | Thanks to Leon Yang for identifying a problem with kwarg processing for ICMP.
50 | Thanks to Jordan Ansell at Victoria University-Wellington NZ for initial code for ICMPv6 neighbor discovery
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
86 |
87 |
88 |
104 |
108 |
109 |
--------------------------------------------------------------------------------