In this exercise, you'll progressively improve the performance and capabilities of an OpenFlow controller built on the Beacon controller platform. The same concepts learned here apply to other OpenFlow controller platforms, and your Mininet-based VM provides a nice environment to continue with after this tutorial.
Your Tutorial VM includes starter code for a hub controller, which inefficiently copies packets to every port.
First, you'll verify that this controller works and test its performance.
In Phase 1, you'll modify this provided hub to act as an L2 learning switch. An L2 learning switch learns the mapping between MAC addresses and ports by watching packets. If the switch has already seen a particular destination, it can send to exactly one port; otherwise it must flood the packet out all ports, like a hub.
In Phase 2, you'll turn the learning switch into a flow-based switch, where seeing a packet with a known source and destination causes a flow entry to get pushed down. With a flow-based switch, further packets in the flow are handled in the dataplane, and they avoid the expensive hop to the controller and back.
If you have time in this session, you can continue to the Extra Credit sections.
In Extra Credit 1, you can extend your controller to support multiple switches.
In Extra Credit 2, you can extend your controller to handle switches that do not buffer packets, or have run out of buffers.
Beacon is a Java-based OpenFlow controller platform. For more details and tutorials see the http://www.beaconcontroller.net.
Start Eclipse by double clicking the Eclipse shortcut on the desktop.
Make sure any other controllers have shut down before running Beacon. Run the following in a terminal:
$ sudo killall controller ovs-controller
You should also restart Mininet to make sure that everything is "clean". From your Mininet console:
mininet> exit $ sudo mn --topo single,3 --mac --controller remote
Make sure you do not add extra spaces around the comma.
Now, start Beacon in Debug mode. Within Eclipse do the following:
- In the menu, find Run -> Debug Configurations
- Look for the OSGi Framework on the left, expand its children and select 'beacon Tutorial LearningSwitch', then click Debug.
Wait until the console log indicates that the OpenFlow switch has connected. When the switch connects, your Eclipse console should print something like this:
01:07:31.095 [pool-2-thread-2] INFO n.b.core.internal.Controller - Switch connected from java.nio.channels.SocketChannel[connected]
Now we verify that hosts can ping each other, and that all hosts see the exact same traffic - the behavior of a hub. To do this, we'll create xterms for each host, and view the traffic in each. In the Mininet console, start up three xterms:
mininet> xterm h1 h2 h3
Arrange each xterm so that they're all on the screen at once. This may require reducing the height of to fit a cramped laptop screen.
In the xterms for h2 and h3, run tcpdump, a utility to print the packets seen by a host, and ignore IPv6 packets:
# tcpdump -XX -n not ip6
In the xterm for h1, send a ping:
# ping -c1 10.0.0.2
The ping packets are now going up to the controller, which then floods them out all interfaces except the sending one. You should see identical ARP and ICMP packets corresponding to the ping in both xterms running tcpdump.
Now, see what happens when a non-existent host doesn't reply. From the h1 xterm:
# ping -c1 10.0.0.5
You should see three unanswered ARP requests in the tcpdump xterms. If your code is off later, three unanswered ARP requests is a signal that you might be accidentally dropping packets.
You can close the xterms now.
Here, you'll benchmark the provided hub code, part of the Tutorial bundle.
First, verify reachability. Mininet should be running, along with your Beacon tutorial controller. In the Mininet console, run:
mininet> pingall
This is just a sanity check for connectivity. Now, in the Mininet console, run:
mininet> iperf
Now, compare your number with the reference controller you saw before. How does that compare?
Go to Eclipse and stop Beacon (you should see a square, red button in the console window, or the top left section of Eclipse if you are in the Debug perspective).
NOTE : You can run only one controller at the same time (otherwise you'll get a port conflict). Make sure that you stop any running instance of Beacon before you start another one. With Eclipse this might be tricky. To check the running programs open the Debug perspective (Window->Open Perspective->Debug). On the top-left corner you can see the running programs, click on it then hit the red square to terminate the redundant ones...
images/Eclipse_console_stop.png images/Eclipse_debug_stop.png
The file you'll modify is called LearningSwitchTutorial.java. It is located at: net.beaconcontroller.tutorial/src/main/java/net/beaconcontroller/tutorial/LearningSwitchTutorial.java.
If this file is not already open in Eclipse, find it in the left pane of Eclipse and double-click it to open it in the editor window.
After opening the file, you may want to compress the left pane of Eclipse to free up some screen space, as you won't need to open any additional files in Eclipse. Click the Minimize button at the top right of the left pane, which looks like a dash.
To jump between functions more quickly, you may want to expand the right pane to get the Outline view. Click the Restore button at the top right of the right pane, which looks like two stacked windows.
Take a quick look through the file. The key things to notice:
- The receive method is where packets initially arrive from switches, and by default this method calls forwardAsHub. Once you have implemented the code in forwardAsLearningSwitch you should change receive by commenting out the call to forwardAsHub, and uncommenting the call to forwardAsLearningSwitch.
- The forwardAsHub method behaves as a broadcast hub, and has code that gives you an example of how to send an OFPacketOut message.
- The forwardAsLearningSwitch method is where you will be writing all of your code (with the exception of the receive method mentioned earlier). It should take you around 30 lines of code in this method to implement a learning switch.
Each time you change and save the LearningSwitchTutorial file, make sure to stop and start your running instance, then use pings to verify hub or Ethernet learning switch behavior. Hosts that are not the destination should display no tcpdump traffic after the initial broadcast ARP request.
There is a significant number of comments in the LearningSwitchTutorial file to help you create the proper functionality, and tips provided in the subsequent sections of this tutorial (read these!).
Continue on: the next sections will guide you through the exercise.
- Find the receive method at the top of the LearningSwitchTutorial class
- Comment out the call to forwardAsHub
- Uncomment the call to forwardAsLearningSwitch
- Scroll down and find the forwardAsLearningSwitch method. This is the method where you will be making the bulk of your edits.
- Tip: instantiate an OFMatch object:
OFMatch match = new OFMatch();
- The OFMatch class can be used to retrieve packet header data contained in the OFPacketIn class. The following command will build an OFMatch object based on data contained in the OFPacketIn pi object:
match.loadFromPacket(pi.getPacketData(), pi.getInPort());
- Tip: To log a message to the console
log.debug("Your message here")
Using the tips above, build an OFMatch object from the input packet.
- Tip: The following command shows how to add an element to a Map
macTable.put(mac_address_key, port)
- Tip: The macTable Map object is keyed by the Long data type, but MAC addresses in the OFMatch class are byte[]. The following command will take a byte array from the match object and return a Long:
Ethernet.toLong(match.getDataLayerSource())
- Tip: You can also convert a byte[] to a String for easy logging:
log.debug("MAC Address:" + HexString.toHexString(match.getDataLayerSource()))
Using the tips above, modify your code to learn the source MAC at the input port and store it in the Map.
- Tip: To check for an element in your HashMap:
if (macTable.containsKey(mac_address_key)) { do..something..here }
- Tip: To get an element from the HashMap:
learned_port = macTable.get(mac_address_key)
Using the tips above (in this step, not all are necessary), retrieve the port for the destination MAC address, if it is known.
- Check the forwardAsHub method for an example of how to send an OFPacketOut. This method shows how to instantiate and set members of the OFPacketOut object. It also shows how to send a packet-out object to a switch.
Also, remember to replace the FLOOD action with an actual port number if the port is known.
When your code has no compilation errors, move on to testing.
- Start Beacon
- Start Wireshark capturing on loopback, set to see OpenFlow packets as before
- Start Mininet (only if not already started):
$ sudo mn --topo single,3 --switch ovsk --controller remote
- Wait for Beacon's console to report the switch has connected
- Send a single ping from h1 to h2
mininet> h1 ping -c1 h2
- Check that the ping was successful
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data. 64 bytes from 10.0.0.2: icmp_req=1 ttl=64 time=1.68 ms
--- 10.0.0.2 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 1.683/1.683/1.683/0.000 ms
- Switch to your Wireshark view, find the first Packet Out in the top pane, select it, then expand the OpenFlow Protocol section at the bottom until you get to the Action's output port. The first Packet Out's output port should be Flood, as seen in this screenshot:
- Subsequent Packet Out actions should be directed to a single port, to indicate the switch is learning properly, as seen in this screenshot:
- To verify the behavior, of a flood followed by a direct forward, you may want to load three xterms, one per host, and run tcpdump on them.
- Once you're confident your switch works, cancel any tcpdumps on nodes, and test the speed using iperf:
mininet> iperf
This phase pushes down flow entries to speed up your switch, by avoiding the hop to the controller and back for each packet. If the destination port is known, instead of sending an OFPacketOut, your modified code will send an OFFlowMod so that subsequent pings are matched on the switch.
At the end of phase 2, when pinging and viewing the controller traffic in Wireshark, you should see an initial OFPacketOut that floods for the request/response, then an OFFlodMod being sent for both request/response, then no further traffic for the same ping.
- To install a flow in the network you will create an OFFlowMod object:
OFFlowMod fm = new OFFlowMod();
- Before you can send the OFFlowMod to the switch you will need to initialize the following fields of the object:
- buffer id - Retrieve this from the OFPacketIn
- match - You created this in Phase 1
- command - Add, OFFlowMod.OFPFC_ADD
- idle timeout - 5 seconds
- actions - See the next Tip
- Your OFFlowMod when matched should output to the port learned for this destination. Use the snippet below to create the action and set it on the OFFlowMod instance:
OFActionOutput action = new OFActionOutput(outPort); fm.setActions(Collections.singletonList((OFAction)action));
- Reminder: to send a message to an OpenFlow switch do the following:
sw.getOutputStream().write(message);
- Start Beacon
- Start Wireshark and turn on its capturing
- Start Mininet
$ sudo mn --topo single,3 --switch ovsk --controller remote
- Wait for Beacon's console to report the switch has connected
- Send a single ping from h1 to h2
mininet> h1 ping -c1 h2
- Check that the ping was successful
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data. 64 bytes from 10.0.0.2: icmp_req=1 ttl=64 time=1.68 ms --- 10.0.0.2 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 1.683/1.683/1.683/0.000 ms
- Switch to your Wireshark view. You should see an initial Packet In/Out for the first ARP request where the switch does not know where the destination. All subsequent Packet Ins should have a corresponding Flow Mod, but no Packet Out. An example can be seen in the following screenshot:
Your learning switch so far only supports a single switch, with a single MAC table. In this section, you'll extend it to support multiple switches.
Start mininet with a different topology. In the Mininet console:
mininet> exit $ sudo mn --topo linear --switch ovsk --controller remote
Your created topology looks like this:
This will create a 2-switch topology where each switch has a single connected host.
- Modify the application to have a unique MAC table for each connected switch.
- Change the existing single Map macTable to a Map of macTables, indexed by the switch.
protected Map<IOFSwitch, Map<Long,Short>> macTables = new HashMap<IOFSwitch, Map<Long,Short>>();
- You will need to create a macTable once for each switch, and store it into the macTables Map. One good place to do this is in the addedSwitch method, which is called once when a new switch connects.
- In the forwardAsLearningSwitch method you will need to retrieve the proper macTable to use for the current OFPacketIn, use the sw object as the key to retrieve it.
- Start Beacon
- Start Wireshark and turn on its capturing
- Start Mininet
$ sudo mn --topo linear --switch ovsk --controller remote
- Wait for Beacon's console to report the switches have connected
- Run the pingall test
mininet> pingall
- Check that the pings were successful
*** Ping: testing ping reachability h1 -> h2 h2 -> h1 *** Results: 0% dropped (0/2 lost)
When OpenFlow switches send a Packet In to a controller they also typically keep a copy of the packet locally, with an assigned id - the buffer id, which is also included in the Packet In message. Some switches may not support buffering packets locally, or could be out of space to buffer additional packets. In this case the 'buffer id' on a Packet In will be set to OFPacketOut.BUFFER_ID_NONE.
In phase 2 when you sent a Flow Mod to the switch, it included the buffer id of the packet, and once the Flow Mod was installed the switch sent the packet with the provided buffer id. However this does not work if the packet has not been buffered. The solution is to send a Flow Mod followed by a Packet Out that contains the actual packet data. The Flow Mod's buffer id should be set to OFPacketOut.BUFFER_ID_NONE.
- Modify the phase 2 code to send an OFPacketOut after the OFFlowMod when the OFPacketIn's buffer id is set to OFPacketOut.BUFFER_ID_NONE.
- After sending the Flow Mod from phase 2, test if the OFPacketIn's buffer id is none
if (pi.getBufferId() == OFPacketOut.BUFFER_ID_NONE) {
- You'll need to create an OFPacketOut object much like you did in phase 1.
- Make sure you copy the packet data from the OFPacketIn to the OFPacketOut
- Also ensure you set the OFActionOutputs port to the outgoing port if known.
- Because the switches included in Mininet buffer all packets by default, you'll need to simulate this behavior in Beacon by modifying the actual OFPacketIn object and overwriting its buffer id. Add the following line of code right after the forwardAsLearningSwitch method declaration:
pi.setBufferId(OFPacketOut.BUFFER_ID_NONE);
- Start Beacon
- Start Wireshark and turn on its capturing
- Start Mininet
$ sudo mn --topo single,2 --switch ovsk --controller remote
- Wait for Beacon's console to report the switch has connected
- Send a single ping from h1 to h2
mininet> h1 ping -c1 h2
- Check that the ping was successful
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data. 64 bytes from 10.0.0.2: icmp_req=1 ttl=64 time=1.68 ms
--- 10.0.0.2 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 1.683/1.683/1.683/0.000 ms
- Switch to your Wireshark view. After the initial Packet In/Packet Out you should see all subsequent Packet In's followed by a Flow Mod and a Packet Out.
- Note that when a single network packet contains both a Flow Mod and a Packet Out, it shows up in the Wireshark top view as only a Packet Out; if you select it then expand it in the bottom view you will see two OpenFlow packets, the Flow Mod followed by the Packet Out. Watch out for these!
- The Flow Mod and Packet Out's buffer id should both be set to None, as seen in the following screenshot:
Have you completed at least phase 1 and 2, and maybe some extra credit? Continue to the FlowVisor exercise.