WWW.DUMAIS.IO
ARTICLES
OVERLAY NETWORKS WITH MY SDN CONTROLLERSIMPLE LEARNING SWITCH WITH OPENFLOWINSTALLING KUBERNETES MANUALLYWRITING A HYPERVISOR WITH INTEL VT-X CREATING YOUR OWN LINUX CONTAINERSVIRTIO DRIVER IMPLEMENTATIONNETWORKING IN MY OSESP8266 BASED IRRIGATION CONTROLLERLED STRIP CONTROLLER USING ESP8266.OPENVSWITCH ON SLACKWARESHA256 ASSEMBLY IMPLEMENTATIONPROCESS CONTEXT ID AND THE TLBTHREAD MANAGEMENT IN MY HOBBY OSENABLING MULTI-PROCESSORS IN MY HOBBY OSNEW HOME AUTOMATION SYSTEMINSTALLING AND USING DOCKER ON SLACKWARESYSTEM ON A CHIP EMULATORUSING JSSIP AND ASTERISK TO MAKE A WEBPHONEC++ WEBSOCKET SERVERSIP ATTACK BANNINGBLOCK CACHING AND WRITEBACKBEAGLEBONE BLACK BARE METAL DEVELOPEMENTARM BARE METAL DEVELOPMENTUSING EPOLLMEMORY PAGINGIMPLEMENTING HTTP DIGEST AUTHENTICATIONSTACK FRAME AND THE RED ZONE (X86_64)AVX/SSE AND CONTEXT SWITCHINGHOW TO ANSWER A QUESTION THE SMART WAY.REALTEK 8139 NETWORK CARD DRIVERREST INTERFACE ENGINECISCO 1760 AS AN FXS GATEWAYHOME AUTOMATION SYSTEMEZFLORA IRRIGATION SYSTEMSUMP PUMP MONITORINGBUILDING A HOSTED MAILSERVER SERVICEI AM NOW HOSTING MY OWN DNS AND MAIL SERVERS ON AMAZON EC2DEPLOYING A LAYER3 SWITCH ON MY NETWORKACD SERVER WITH RESIPROCATEC++ JSON LIBRARYIMPLEMENTING YOUR OWN MUTEX WITH CMPXCHGWAKEUPCALL SERVER USING RESIPROCATEFFT ON AMD64CLONING A HARD DRIVECONFIGURING AND USING KVM-QEMUUSING COUCHDBINSTALLING COUCHDB ON SLACKWARENGW100 MY OS AND EDXS/LSENGW100 - MY OSASTERISK FILTER APPLICATIONCISCO ROUTER CONFIGURATIONAASTRA 411 XML APPLICATIONSPA941 PHONEBOOKSPEEDTOUCH 780 DOCUMENTATIONAASTRA CONTACT LIST XML APPLICATIONAVR32 OS FOR NGW100ASTERISK SOUND INJECTION APPLICATIONNGW100 - DIFFERENT PROBLEMS AND SOLUTIONSAASTRA PRIME RATE XML APPLICATIONSPEEDTOUCH 780 CONFIGURATIONUSING COUCHDB WITH PHPAVR32 ASSEMBLY TIPAP7000 AND NGW100 ARCHITECTUREAASTRA WEATHER XML APPLICATIONNGW100 - GETTING STARTEDAASTRA ALI XML APPLICATION

SIMPLE LEARNING SWITCH WITH OPENFLOW

2017-12-16

Introduction

I recently wanted to learn more about Openflow so I decided to write a controller that acts as a simple L2 learning switch. The controller is written in c++. I couldn't find a good c++ library for openflow so I ended up writing my own from scratch. The openflow code is far from being spec1.3-compliant because it is missing a lot of things, but it does enough to accomplish my goals. The code for the controller, including the Openflow code, is available on github. https://github.com/pdumais/OpenflowController

Openflow Controller

The general idea behind my implementation of the openflow controller, is to program the switch as the controller discovers the network. The controller is made aware of the vlans on which ports should belong to through a configuration file.

When the switch connects to the controller, the controller will delete all flows in tables 0,1,2. It will then install a table-miss rule (match all, priority 0) in table 0 and 1 to send packets the controller. table 2 will stay empty; the default behaviour of this will be to drop packets. So at this point, any packets comming in on the switch will trigger a table-miss and be sent to the controller. The controller will then make decisions on how the packet should be forwarded based on the port type (access or trunk) and vlan. Whenever a packet gets sent to the controller, this is done in a PacketIn message. The controller then send a PacketOut message to tell the switch on which port(s) to forward that packet and to maybe add/delete a vlan tag. In order to avoid having to do this processing everytime, the controller also adds a flow on the switch with a specific match criteria and a set of action so that the logic of the packetOut can be applied by the switch automatically the next time instead of sending the packet to the controller.

Learning Switch

Many examples of a learning switch I saw were a bit naive. For example, those implementation would fail if more than one host would be connected behind a port (for example, another switch could be connected on that port). So my implementation is an attempt to be a bit more robust.

My learning switch uses two tables because there are two decisions that need to be taken eveytime a packet comes in: 1) Should the controller learn about that mac/in_port? 2) What port needs to be used to forward the packet?

When the first packet will be sent to the switch, it will be sent to the controller, because there are only table-miss rules defined. The controller will learn the src_mac/in_port of that packet and install a rule in table 0 to match that src_mac/in_port and forward to table 1. The next time the same host sends a packet, it will not hit the table-miss rule in table 0 and will be forwarded to table 1. After adding that rule, the contoller will look at the dst_mac of the packet and will not have an entry in its FIB so it will send a PacketOut to the switch with a flood action (if vlans are used, this is a bit different).

As packets come in, table0 will get populated with entries to avoid sending already learned sources to the controller. But when hitting table 1, packets will hit the table-miss rule. So the packets will be sent to the controller. The controller will look at the dst_mac and decide to flood (as described above) or not. If the dst_mac was found in the FIB (because it was previously learned through another packet's table-miss on table 0), the a packetOut is sent with only the port found in the FIB. Also, a rule will be installed in table 1 to match the dst_mac.

Example: At time 0, the bridge only has table-miss flows installed, so anything going in the switch will hit that and be sent to the controller.

  • first packet sent from A on port 1, destined to B, on port 2
    • table0-miss
    • learn(A is on port 1)
    • installRule(in t0, if from A and on port 1, goto t1)
    • check destination
      • not found!
      • PacketOut(flood)
  • Then B responds on port 2
    • table-0-miss
    • learn(B is on port 2)
    • installRule(in t0, if from B and on port 2, goto t1)
    • check destination
      • Found! A is on port 1.
      • PacketOut(1)
      • installRule(in t1, if for A, forward to 1)
  • Then A sends something else to B
    • table-0 Pass, jump to t1
    • table1-miss (no rule for destination B)
    • check destination
      • Found! B is on port 2.
      • PacketOut(2)
      • installRule(in t1, if for B, forward to 2)
  • And B replies to A again
    • table-0 Pass, jump to t1
    • table-1 Pass, forward according to rule.

Vlans

When vlans are involved, things changes a bit. The switch is configured with a set of port in different vlans. The switch determines the vlan that the incomming packet belongs to by looking at the vlanID in the packet if it came in through a port that is configured with a "trunk" personality. If the traffic is untagged and came in on a trunk, then a "default vlan" config is also available. Note that these configs are set on my controller when I define the virtual topology of the switch, and not on openvswitch itself. If the port is an access port, then traffic is assumed to be for the vlan configured on the port. The controller maintains a forwarding table on a per-vlan basis. So it would be technically possible to have the same MAC twice but on different vlans. Flooding then happen only on ports part of the same vlan. Also, when flooding out of trunk ports, a vlan tag must be added. This is all doable with flows.

Test network

This is the test network I am using:

I am using veth pairs with 1 end in the bridge and the other end in its own network namespace. This is easier than to create 6 VMs. The controller is configured to recognize interfaces 1,2 as access ports for vlan 100. Interfaces 3,4 as access ports for vlan 200 and interfaces 5,6 as trunk ports for both vlans.

To better understand what the controller does, here is a series of openflow rules installed after the controller has seen some packets go through

After a packet (ARP query) was sent from port1, we learned the mac, so we can skip table 0

table=0, n_packets=6, n_bytes=532, priority=100,in_port=1,dl_src=da:1d:64:e8:e6:86 actions=goto_table:1

After getting the arp response, we learned the address so we can skip table 0 next time

table=0, n_packets=5, n_bytes=434, priority=100,in_port=4,dl_src=b2:5b:4e:49:85:59 actions=goto_table:1

After a packet came out of trunk port5 with vlan tag 100, we can skip table0 because we learned it. Notice how we match against the vlan tag here.

table=0, n_packets=6, n_bytes=556, priority=100,in_port=5,dl_vlan=100,dl_src=5a:54:c5:e5:6b:27 actions=goto_table:1

Same as above but for vlan 200

table=0, n_packets=5, n_bytes=454, priority=100,in_port=5,dl_vlan=200,dl_src=5a:54:c5:e5:6b:27 actions=goto_table:1

This is the table-miss flow. If none of the above rules match a packet in table0, we send it to the controller. This would happen because we need to learn the source MAC

table=0, n_packets=4, n_bytes=288, priority=0 actions=CONTROLLER:65535

At this point, these are the rules for table1. If a packet makes it to here, it means that the source_port/mac was already learned before and we need to check for a destination mac flow to forward it to the correct port. When trying to send a packet, it triggered a table miss but the controller found that the destination mac was in it's FIB. So it installed a flow matching the source port,vlanID and destination. The actions here are to pop the vlan tag because in_port 5 is a trunk and we need to send the packet to port 1, which is an access port.

table=1, n_packets=6, n_bytes=556, priority=100,in_port=5,dl_vlan=100,dl_dst=da:1d:64:e8:e6:86 actions=pop_vlan,output:1

Same as above, different values

table=1, n_packets=4, n_bytes=352, priority=100,in_port=5,dl_vlan=200,dl_dst=b2:5b:4e:49:85:59 actions=pop_vlan,output:4

That flow pushes a vlan tag because the destination mac was previously found behind a trunk port

table=1, n_packets=6, n_bytes=532, priority=100,in_port=1,dl_dst=5a:54:c5:e5:6b:27 actions=push_vlan:0x8100,set_field:4196->vlan_vid,output:5

Same as above, different values

table=1, n_packets=5, n_bytes=434, priority=100,in_port=4,dl_dst=5a:54:c5:e5:6b:27 actions=push_vlan:0x8100,set_field:4296->vlan_vid,output:5

This is the table-miss flow. If none of the above rules match a packet in table1, we send it to the controller. This would happen because there are no flows installed to forward the destination MAC to the correct port. The controller will either have that destination in its FIB and will install a flow in table 1 for next time Or it will flood on all ports of the same VLAN if the destination is not in the FIB.

table=1, n_packets=1, n_bytes=102, priority=0 actions=CONTROLLER:65535

Final thoughts

There are many other things that should be considered here by that I haven't implemented. For example, if port is removed from the switch or if the port goes down, flows that forwards to that port should be removed, and flows that match that incoming port should also be removed. Broadcast flows should be modified. And if changing the vlan config on a port, within the controller configuration, flows also need to be removed/modified.