Skip to content

Instantly share code, notes, and snippets.

@smihir
Last active April 16, 2016 23:40
Show Gist options
  • Select an option

  • Save smihir/2d5446314a431b953760 to your computer and use it in GitHub Desktop.

Select an option

Save smihir/2d5446314a431b953760 to your computer and use it in GitHub Desktop.

Revisions

  1. smihir revised this gist Oct 20, 2015. 2 changed files with 0 additions and 0 deletions.
    File renamed without changes.
    File renamed without changes.
  2. smihir revised this gist Oct 20, 2015. 2 changed files with 17 additions and 2 deletions.
    8 changes: 6 additions & 2 deletions learning_switch.py
    Original file line number Diff line number Diff line change
    @@ -64,7 +64,11 @@ def run(self):
    self.net.send_packet(intf.name, packet)

    def learn(self, mac_addr, interface, time):
    self.lookup_table[mac_addr] = (interface, time)
    if mac_addr == "ff:ff:ff:ff:ff:ff":
    log_debug("Do not learn BCAST mac")
    else:
    self.lookup_table[mac_addr] = (interface, time)


    def lookup(self, mac_addr):
    if mac_addr in self.lookup_table:
    @@ -84,4 +88,4 @@ def start(self):

    def switchy_main(net):
    ls = LearningSwitch(net)
    ls.start()
    ls.start()
    11 changes: 11 additions & 0 deletions learningswitchtests.py
    Original file line number Diff line number Diff line change
    @@ -150,6 +150,17 @@ def switch_tests():
    testpkt = mk_pkt("30:00:00:00:00:02", "20:00:00:00:00:03", "172.16.42.2", "192.168.1.101")
    s.expect(PacketInputEvent("eth1", testpkt, display=Ethernet), "An Ethernet frame with 20:00:00:00:00:03 destination address should arrive on eth1")
    s.expect(PacketOutputEvent("eth0", testpkt, display=Ethernet), "An Ethernet frame should arrive on eth0")

    # test case 3: a frame with dest address of one of the interfaces should
    # result in nothing happening
    reqpkt = mk_pkt("ff:ff:ff:ff:ff:ff", "10:00:00:00:00:03", '255.255.255.255','172.16.42.2')
    s.expect(PacketInputEvent("eth0", reqpkt, display=Ethernet), "Negative test case: packet with bcast destination on eth0")

    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.")

    testpkt = mk_pkt("20:00:00:00:00:03", "ff:ff:ff:ff:ff:ff", '192.168.1.101', '255.255.255.255')
    s.expect(PacketInputEvent("eth0", testpkt, display=Ethernet), "An Ethernet frame with a broadcast destination address should arrive on eth2")
    s.expect(PacketOutputEvent("eth1", testpkt, "eth2", testpkt, display=Ethernet), "An Ethernet frame should be flooded on eth1 and eth2")
    return s

    scenario = switch_tests()
  3. smihir revised this gist Oct 19, 2015. 1 changed file with 155 additions and 0 deletions.
    155 changes: 155 additions & 0 deletions learningswitchtests.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,155 @@
    #!/usr/bin/env python

    import sys
    from switchyard.lib.address import *
    from switchyard.lib.packet import *
    from switchyard.lib.common import *
    from switchyard.lib.testing import *

    def mk_pkt(hwsrc, hwdst, ipsrc, ipdst, reply=False):
    ether = Ethernet()
    ether.src = EthAddr(hwsrc)
    ether.dst = EthAddr(hwdst)
    ether.ethertype = EtherType.IP

    ippkt = IPv4()
    ippkt.srcip = IPAddr(ipsrc)
    ippkt.dstip = IPAddr(ipdst)
    ippkt.protocol = IPProtocol.ICMP
    ippkt.ttl = 32

    icmppkt = ICMP()
    if reply:
    icmppkt.icmptype = ICMPType.EchoReply
    else:
    icmppkt.icmptype = ICMPType.EchoRequest

    return ether + ippkt + icmppkt

    def switch_tests():
    s = Scenario("switch tests")
    s.add_interface('eth0', '10:00:00:00:00:01')
    s.add_interface('eth1', '10:00:00:00:00:02')
    s.add_interface('eth2', '10:00:00:00:00:03')

    # test case 1: a frame with broadcast destination should get sent out
    # all ports except ingress
    testpkt = mk_pkt("30:00:00:00:00:02", "ff:ff:ff:ff:ff:ff", "172.16.42.2", "255.255.255.255")
    s.expect(PacketInputEvent("eth1", testpkt, display=Ethernet), "An Ethernet frame with a broadcast destination address should arrive on eth1")
    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")

    # test case 2: a frame with any unicast address except one assigned to learning switch
    # interface should be sent out all ports except ingress
    reqpkt = mk_pkt("20:00:00:00:00:01", "30:00:00:00:00:02", '192.168.1.100','172.16.42.2')
    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")
    s.expect(PacketOutputEvent("eth1", reqpkt, display=Ethernet), "Ethernet frame destined for 30:00:00:00:00:02 should be sent out on eth1")

    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)
    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")
    s.expect(PacketOutputEvent("eth0", resppkt, display=Ethernet), "Ethernet frame destined to 20:00:00:00:00:01 should be sent out on eth0")

    # test case 3: a frame with dest address of one of the interfaces should
    # result in nothing happening
    reqpkt = mk_pkt("20:00:00:00:00:02", "10:00:00:00:00:03", '192.168.1.100','172.16.42.2')
    s.expect(PacketInputEvent("eth2", reqpkt, display=Ethernet), "An Ethernet frame should arrive on eth2 with destination address the same as eth2's MAC address")

    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.")


    s.expect(PacketInputTimeoutEvent(10), "Silence")

    testpkt = mk_pkt("30:00:00:00:00:02", "ff:ff:ff:ff:ff:ff", "172.16.42.2", "255.255.255.255")
    s.expect(PacketInputEvent("eth1", testpkt, display=Ethernet), "An Ethernet frame with a broadcast destination address should arrive on eth1")
    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")


    resppkt = mk_pkt("20:00:00:00:00:01", "ff:ff:ff:ff:ff:ff", '172.16.42.2', '255.255.255.255')
    s.expect(PacketInputEvent("eth0", testpkt, display=Ethernet), "An Ethernet frame with a broadcast destination address should arrive on eth0")
    s.expect(PacketOutputEvent("eth1", testpkt, "eth2", testpkt, display=Ethernet), "The Ethernet frame with a broadcast destination address should be forwarded out ports eth1 and eth2")

    s.expect(PacketInputTimeoutEvent(10), "Silence")

    testpkt = mk_pkt("30:00:00:00:00:02", "ff:ff:ff:ff:ff:ff", "172.16.42.2", "255.255.255.255")
    s.expect(PacketInputEvent("eth1", testpkt, display=Ethernet), "An Ethernet frame with a broadcast destination address should arrive on eth1")
    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")


    resppkt = mk_pkt("20:00:00:00:00:01", "ff:ff:ff:ff:ff:ff", '172.16.42.2', '255.255.255.255')
    s.expect(PacketInputEvent("eth0", testpkt, display=Ethernet), "An Ethernet frame with a broadcast destination address should arrive on eth0")
    s.expect(PacketOutputEvent("eth1", testpkt, "eth2", testpkt, display=Ethernet), "The Ethernet frame with a broadcast destination address should be forwarded out ports eth1 and eth2")

    s.expect(PacketInputTimeoutEvent(10), "Silence")

    testpkt = mk_pkt("30:00:00:00:00:02", "ff:ff:ff:ff:ff:ff", "172.16.42.2", "255.255.255.255")
    s.expect(PacketInputEvent("eth1", testpkt, display=Ethernet), "An Ethernet frame with a broadcast destination address should arrive on eth1")
    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")


    resppkt = mk_pkt("20:00:00:00:00:01", "ff:ff:ff:ff:ff:ff", '172.16.42.2', '255.255.255.255')
    s.expect(PacketInputEvent("eth0", testpkt, display=Ethernet), "An Ethernet frame with a broadcast destination address should arrive on eth0")
    s.expect(PacketOutputEvent("eth1", testpkt, "eth2", testpkt, display=Ethernet), "The Ethernet frame with a broadcast destination address should be forwarded out ports eth1 and eth2")

    s.expect(PacketInputTimeoutEvent(10), "Silence")

    testpkt = mk_pkt("30:00:00:00:00:02", "ff:ff:ff:ff:ff:ff", "172.16.42.2", "255.255.255.255")
    s.expect(PacketInputEvent("eth1", testpkt, display=Ethernet), "An Ethernet frame with a broadcast destination address should arrive on eth1")
    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")


    resppkt = mk_pkt("20:00:00:00:00:01", "ff:ff:ff:ff:ff:ff", '172.16.42.2', '255.255.255.255')
    s.expect(PacketInputEvent("eth0", testpkt, display=Ethernet), "An Ethernet frame with a broadcast destination address should arrive on eth0")
    s.expect(PacketOutputEvent("eth1", testpkt, "eth2", testpkt, display=Ethernet), "The Ethernet frame with a broadcast destination address should be forwarded out ports eth1 and eth2")

    s.expect(PacketInputTimeoutEvent(10), "Silence")

    testpkt = mk_pkt("30:00:00:00:00:02", "ff:ff:ff:ff:ff:ff", "172.16.42.2", "255.255.255.255")
    s.expect(PacketInputEvent("eth1", testpkt, display=Ethernet), "An Ethernet frame with a broadcast destination address should arrive on eth1")
    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")


    testpkt = mk_pkt("20:00:00:00:00:01", "ff:ff:ff:ff:ff:ff", '172.16.42.2', '255.255.255.255')
    s.expect(PacketInputEvent("eth0", testpkt, display=Ethernet), "An Ethernet frame with a broadcast destination address should arrive on eth0")
    s.expect(PacketOutputEvent("eth1", testpkt, "eth2", testpkt, display=Ethernet), "The Ethernet frame with a broadcast destination address should be forwarded out ports eth1 and eth2")

    s.expect(PacketInputTimeoutEvent(10), "Silence")

    testpkt = mk_pkt("30:00:00:00:00:02", "ff:ff:ff:ff:ff:ff", "172.16.42.2", "255.255.255.255")
    s.expect(PacketInputEvent("eth1", testpkt, display=Ethernet), "An Ethernet frame with a broadcast destination address should arrive on eth1")
    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")


    testpkt = mk_pkt("20:00:00:00:00:01", "ff:ff:ff:ff:ff:ff", '172.16.42.2', '255.255.255.255')
    s.expect(PacketInputEvent("eth0", testpkt, display=Ethernet), "An Ethernet frame with a broadcast destination address should arrive on eth0")
    s.expect(PacketOutputEvent("eth1", testpkt, "eth2", testpkt, display=Ethernet), "The Ethernet frame with a broadcast destination address should be forwarded out ports eth1 and eth2")

    s.expect(PacketInputTimeoutEvent(10), "Silence")


    reqpkt = mk_pkt("20:00:00:00:00:01", "20:00:00:00:00:02", '192.168.100.1','192.168.1.100')
    s.expect(PacketInputEvent("eth0", reqpkt, display=Ethernet), "An Ethernet frame should arrive on eth0 with destination address for eth2")
    s.expect(PacketOutputEvent("eth1", reqpkt, "eth2", reqpkt, display=Ethernet), "The ethernet frame should be flooded in eth1 and eth2")

    reqpkt = mk_pkt("20:00:00:00:00:02", "20:00:00:00:00:01", '192.168.1.100','192.168.100.1', reply=True)
    s.expect(PacketInputEvent("eth2", reqpkt, display=Ethernet), "An Ethernet frame should arrive on eth2 destined for eth0")
    s.expect(PacketOutputEvent("eth0", reqpkt, display=Ethernet), "The reply ethernet frame should be sent on eth0")


    testpkt = mk_pkt("20:00:00:00:00:03", "ff:ff:ff:ff:ff:ff", '192.168.1.101', '255.255.255.255')
    s.expect(PacketInputEvent("eth2", testpkt, display=Ethernet), "An Ethernet frame with a broadcast destination address should arrive on eth2")
    s.expect(PacketOutputEvent("eth0", testpkt, "eth1", testpkt, display=Ethernet), "The reply ethernet frame should be flooded on eth0 and eth1")

    reqpkt = mk_pkt("20:00:00:00:00:01", "20:00:00:00:00:03", '192.168.100.1','192.168.1.101')
    s.expect(PacketInputEvent("eth0", reqpkt, display=Ethernet), "An Ethernet frame should arrive on eth0 with destination address for eth2")
    s.expect(PacketOutputEvent("eth2", reqpkt, display=Ethernet), "The ethernet frame should be sent on eth2")


    testpkt = mk_pkt("20:00:00:00:00:03", "ff:ff:ff:ff:ff:ff", '192.168.1.101', '255.255.255.255')
    s.expect(PacketInputEvent("eth0", testpkt, display=Ethernet), "An Ethernet frame with a broadcast destination address should arrive on eth2")
    s.expect(PacketOutputEvent("eth1", testpkt, "eth2", testpkt, display=Ethernet), "An Ethernet frame should be flooded on eth1 and eth2")

    testpkt = mk_pkt("30:00:00:00:00:02", "20:00:00:00:00:03", "172.16.42.2", "192.168.1.101")
    s.expect(PacketInputEvent("eth1", testpkt, display=Ethernet), "An Ethernet frame with 20:00:00:00:00:03 destination address should arrive on eth1")
    s.expect(PacketOutputEvent("eth0", testpkt, display=Ethernet), "An Ethernet frame should arrive on eth0")
    return s

    scenario = switch_tests()
  4. smihir revised this gist Oct 16, 2015. 1 changed file with 71 additions and 38 deletions.
    109 changes: 71 additions & 38 deletions learning_switch.py
    Original file line number Diff line number Diff line change
    @@ -10,45 +10,78 @@
    from switchyard.lib.address import *
    from switchyard.lib.packet import *
    from switchyard.lib.common import *
    import time

    def learn(dev, packet, lookuptable):
    lookuptable[packet[0].src] = dev
    class LearningSwitch:
    def __init__(self, net):
    self.lookup_table = {}
    self.texpire = 24

    def switchy_main(net):
    my_interfaces = net.interfaces()
    mymacs = [intf.ethaddr for intf in my_interfaces]
    lookuptable = dict()
    quite_time = 0
    tm = 9

    while True:
    try:
    dev,packet = net.recv_packet(timeout=7)
    except NoPackets:
    quite_time += tm
    if quite_time >= 3*tm:
    lookuptable.clear()
    continue
    except Shutdown:
    return

    quite_time = 0
    learn(dev, packet, lookuptable)

    log_failure ("In {} received packet {} on {}".format(net.name, packet, dev))
    if packet[0].dst in mymacs:
    log_failure ("Packet intended for me")
    else:
    if packet[0].dst in lookuptable:
    dst_dev = lookuptable[packet[0].dst]
    for intf in my_interfaces:
    if dst_dev == intf.name:
    log_failure ("Sending packet {} to {}".format(packet, intf.name))
    net.send_packet(intf.name, packet)
    self.net = net
    self.interfaces = net.interfaces()
    self.macs = [intf.ethaddr for intf in self.interfaces]

    def run(self):
    while True:
    try:
    # Don't use the timestamp in recv_packet
    # it looks like the timestamp is just representing
    # the packet number received while running in test mode
    dev,packet = self.net.recv_packet(timeout=self.texpire/3)
    except NoPackets:
    self.timeout += 1
    if self.timeout == 3:
    self.purge_lookup()
    continue
    except Shutdown:
    return

    ts = time.time()
    self.timeout = 0
    self.learn(packet[0].src, dev, ts)

    log_debug ("In {} received packet {} on {}".format(self.net.name, packet, dev))
    if packet[0].dst in self.macs:
    log_debug ("Packet intended for me")
    else:
    entry = self.lookup(packet[0].dst)

    log_debug ("lookup on {}: {}".format(packet[0].dst, entry))

    if entry:
    if ts < entry[1] + self.texpire:
    dst_dev = entry[0]
    log_debug ("Sending packet {} to {}".format(packet, dst_dev))
    self.net.send_packet(dst_dev, packet)
    continue
    else:
    # Clear entry from lookup and Flood!!!
    self.del_lookup_entry(packet[0].dst)

    for intf in self.interfaces:
    if dev != intf.name:
    log_debug ("Flooding packet {} to {}".format(packet, intf.name))
    self.net.send_packet(intf.name, packet)

    for intf in my_interfaces:
    if dev != intf.name:
    log_failure ("Flooding packet {} to {}".format(packet, intf.name))
    net.send_packet(intf.name, packet)
    net.shutdown()
    def learn(self, mac_addr, interface, time):
    self.lookup_table[mac_addr] = (interface, time)

    def lookup(self, mac_addr):
    if mac_addr in self.lookup_table:
    return self.lookup_table[mac_addr]
    else:
    return None

    def del_lookup_entry(self, mac_addr):
    del self.lookup_table[mac_addr]

    def purge_lookup(self):
    self.lookup_table.clear()

    def start(self):
    self.run()
    self.net.shutdown()

    def switchy_main(net):
    ls = LearningSwitch(net)
    ls.start()
  5. smihir created this gist Oct 11, 2015.
    54 changes: 54 additions & 0 deletions learning_switch.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,54 @@
    #!/usr/bin/env python3

    '''
    Ethernet learning switch in Python: HW3.
    Note that this file currently has the code to implement a "hub"
    in it, not a learning switch. (I.e., it's currently a switch
    that doesn't learn.)
    '''
    from switchyard.lib.address import *
    from switchyard.lib.packet import *
    from switchyard.lib.common import *

    def learn(dev, packet, lookuptable):
    lookuptable[packet[0].src] = dev

    def switchy_main(net):
    my_interfaces = net.interfaces()
    mymacs = [intf.ethaddr for intf in my_interfaces]
    lookuptable = dict()
    quite_time = 0
    tm = 9

    while True:
    try:
    dev,packet = net.recv_packet(timeout=7)
    except NoPackets:
    quite_time += tm
    if quite_time >= 3*tm:
    lookuptable.clear()
    continue
    except Shutdown:
    return

    quite_time = 0
    learn(dev, packet, lookuptable)

    log_failure ("In {} received packet {} on {}".format(net.name, packet, dev))
    if packet[0].dst in mymacs:
    log_failure ("Packet intended for me")
    else:
    if packet[0].dst in lookuptable:
    dst_dev = lookuptable[packet[0].dst]
    for intf in my_interfaces:
    if dst_dev == intf.name:
    log_failure ("Sending packet {} to {}".format(packet, intf.name))
    net.send_packet(intf.name, packet)
    continue

    for intf in my_interfaces:
    if dev != intf.name:
    log_failure ("Flooding packet {} to {}".format(packet, intf.name))
    net.send_packet(intf.name, packet)
    net.shutdown()