Mininet Assignment 2: myswitch.py

Due: Friday, July 21

Edit the file myswitch.py (similar to pox/misc/of_tutorial.py) so that the controller makes the switches behave like learning switches instead of hubs.

The code has been structured to make this change relatively straightforward; you will have to implement act_like_switch() and then change _handle_PacketIn so that it calls act_like_switch() instead of act_like_hub().

Copy myswitch.py to the directory pox/pox/misc, before you start, and edit only the latter.

To run this, you will need two root-login windows. In the first window, invoke mininet as follows:

python lineNstandalone.py

Then, start up pox in the second window as follows, from /root/pox; this will run myswitch.py (note the .py extension is not entered here). Using log.level --WARNING gets rid of a lot of annoying messages; the default log.level is INFO.

./pox.py log.level --WARNING misc.myswitch

At any time you can halt Pox (with CNTL-C) and restart it, without restarting mininet. Stopping and restarting Pox will leave the openflow tables in the switches alone, also, at least for a while.

Since you're running pox in its own window, print statements will appear right there, and you won't need the logging facility.

Handy Mininet commands are:

h1 ping h4
h1 ping -c 1 h2    # single ping
pingall            # should install all hosts into all switch forwarding tables

Information about Pox objects can be found, along with examples, in openflow.stanford.edu/display/ONL/POX+Wiki. Find misc.of_tutorial in the index and follow the link; that leads to a very detailed page on the exercise.

The program, as it stands now, keeps for each switch a Python dictionary mac_to_port that maps an Ethernet address to the switch port by which it is reached. The line in act_like_switch() implements the source learning:

self.mac_to_port[psrc] = inport

There are really two approaches here. Approach one should be sufficient, but once you get that working, approach two is cool:

Approach one: use self.resend_packet(packet_in, port) to have the controller tell the switch to resend the packet out the given port. This means that the controller will look in the switch's mac_to_port dictionary to see if the appropriate port (next_hop) is known. If it is, use self.resend_packet() to send packet_in to that port. If It is not, then send packet_in to of.OFPP_FLOOD (or ALL).

Approach two: use msg = of.ofp_flow_mod() / msg.match.dl_dst = ... / msg.actions.append(...) to install a new forwarding rule in the switch (so packets with the matching destination can be handled by the switch with no further interaction with the controller).

If working on approach two, remember that you can use the command ovs-ofctl dump-tables s1 to examine the forwarding table of a switch (here s1). Approach two will require matching on both dst and src, much like in the example l2_pairs.py.

For either approach, you need to complete the code in act_like_switch(). Once it's reasonable (that is, once you think it might compile), change _handle_PacketIn() according to this:

# Comment out the following line and uncomment the one after
# when starting the exercise.
self.act_like_hub(packet, packet_in)
#self.act_like_switch(packet, packet_in)

For act_like_switch() itself, the Pox website has more on what's in the parameters packet and packet_in. However, I've set the important values:

psrc = packet.src
pdst = packet.dst
inport = packet_in.in_port
# outport defined below
# switch is self.switchnum

The basic idea is that you will replace act_like_hub()'s

    self.resend_packet(packet_in, of.OFPP_ALL)

by changing of.OFPP_ALL to the specific next_hop port number.


Two Failed Designs

1. What if the controller keeps a mac_to_port table for each switch, and, once it knows how to reach both ha and hb, installs destination-based forwarding routes for ha and hb:

msg = of.ofp_flow_mod()     # pld: always start here
msg.match.dl_dst = ha
msg.actions.append(of.ofp_action_output(port = self.mac_to_port[ha]))
self.connection.send(msg)   # send flow message to switch via connection

And the same for hb. (This was the approach outlined in the July 27 class.) What happens? This works for 1 switch. This works for two switches.

But for three or more switches in the switchline.py configuration, after a single pingall the last switch is unknown to the earlier switches. Packets from the earlier switches to the last one have to be flooded; this is not correct. Suppose N=3. Then after h1 pings h2, s1 and s2 both have forwarding entries for h1 and h2. When h1 pings h3, s1 and s2 flood the packet. When h3 replies, s1 and s2 simply forward the packet, as they have destination entries for h1, and never report the packet to the controller. So the controller never installs a forwarding entry for h3, even though the controller knows where h3 is.

Hint: wait until the controller knows about both entries, and then add two pair-style forwarding entries, where the match is on both the destination address and the source address.


2. What if the controller uses this (not working) strategy:
    if (srcaddr is NOT in the forwarding table)
        add flow rule: reach srcaddr via srcport

This is what happens with 5 linear switches and a pingall:

h1 sends ping: s1-s5 learn where h1 is
hi sends response: s1-s5 KNOW where h1 is, so the controller is NOT contacted, so the controller does not learn where h2-h5 are
h2 sends ping to h1: s2 knows where h1 is; no controller involvement
h1 responds: packet gets flooded but there's no new source for controller to learn, so no new flows
h2 sends ping to h3/h4/h5: packet gets flooded, s1 (and s2-s5) learns where h2 is
...
h3 sends ping to h1 or h2: s1 and s2 know where h1,h2 are: no flooding, no new flows
h1/h2 respond to h3: packet is flooded; no new source so no new flows
h3 sends ping to h4/h5: packet gets flooded and all switches learn where h3 is
...

In the end, all switches know where h1-h4 are, but not h5:

The core problem is that it's new source addresses that switches must inform the controller about, but the flows above match only on the destination address. So, if a packet arrives at a switch S with a destination known to S, then S will not contact the controller even if the source is new.

The fix, as with the first failed example, is to identify flows by a pair (dstaddr,srcaddr). Doing this means that a new srcaddr cannot -- because it is new -- be part of an existing flow, and so will always be reported to the controller. The controller would typically install a bidirectional flow for that (dstaddr,srcaddr) pair if it knows the port it should use to reach dstaddr. If it does not, it will record the port used to reach srcaddr (that is, the port by which this packet arrived), flood the packet, and wait for the destaddr node to reply.

This is done in the pox example forwarding/l2_pairs.py.