Learning POX OpenFlow controller : Imitating L3

The application name is l3_learning.py.
This module works in a reactive mode.

The module does several things
1. Learns the correspondence between IP and MAC addresses.
2. Uses this information to install the rule that replaces a destination MAC while forwarding a packet to the correct port.
3. Generates ARP requests in case if a destination IP is unknown.
4. Replies to ARP requests.

1. The PacketIn handler updates ARP table of a switch with particular DPID.

self.arpTable[dpid][packet.next.srcip] = Entry(inport, packet.src)

The field packet.next.srcip is a source IP address while packet.src is its source MAC address.

2. In case if there is match between destination IP address and pair of port and MAC address the packet is sent out, plus routing rule is installed to the switch. The essence of routing rule installed in the same handler is presented below.

if dstaddr in self.arpTable[dpid]:
  prt = self.arpTable[dpid][dstaddr].port
  mac = self.arpTable[dpid][dstaddr].mac

  actions = []
  actions.append(of.ofp_action_dl_addr.set_dst(mac))
  actions.append(of.ofp_action_output(port = prt))
  match = of.ofp_match.from_packet(packet, inport)
  match.dl_src = None # Wildcard source MAC

  msg = of.ofp_flow_mod(command=of.OFPFC_ADD,
                        idle_timeout=FLOW_IDLE_TIMEOUT,
                        hard_timeout=of.OFP_FLOW_PERMANENT,
                        buffer_id=event.ofp.buffer_id,
                        actions=actions,
                        match=of.ofp_match.from_packet(packet, inport))

  event.connection.send(msg.pack())

The interesting part of the above flow modification request is ofp_action_dl_addr action that is
responsible to replace a destination MAC.

We can check the rule in OVS using “sudo ovs-ofctl dump-flows s1” command.

cookie=0x0, duration=4.122s, table=0, n_packets=1, n_bytes=98,
idle_timeout=10, idle_age=4, priority=65535,icmp,in_port=1,vlan_tci=0x0000,
dl_src=56:08:eb:75:57:d7,dl_dst=5e:37:62:da:fb:24,
nw_src=10.0.0.1,nw_dst=10.0.0.2,nw_tos=0,icmp_type=8,
icmp_code=0 actions=mod_dl_dst:5e:37:62:da:fb:24,output:2

3. In case if a destination port and MAC are unknown yet, the packet buffer id is stored in the waiting list.

entry = (time.time() + MAX_BUFFER_TIME,event.ofp.buffer_id,inport)
bucket.append(entry)

These packets will be sent out later on, once the controller learns yet unknown IPs.

self._send_lost_buffers(dpid, packet.next.srcip, packet.src, inport)

And ARP request is flooded from all ports asking about the unknown IP address.

r = arp()
r.hwtype = r.HW_TYPE_ETHERNET
r.prototype = r.PROTO_TYPE_IP
r.hwlen = 6
r.protolen = r.protolen
r.opcode = r.REQUEST
r.hwdst = ETHER_BROADCAST
r.protodst = dstaddr
r.hwsrc = packet.src
r.protosrc = packet.next.srcip
e = ethernet(type=ethernet.ARP_TYPE, src=packet.src, dst=ETHER_BROADCAST)
e.set_payload(r)
log.debug("%i %i ARPing for %s on behalf of %s" % (dpid, inport,
str(r.protodst), str(r.protosrc)))
msg = of.ofp_packet_out()
msg.data = e.pack()
msg.actions.append(of.ofp_action_output(port = of.OFPP_FLOOD))
msg.in_port = inport
event.connection.send(msg)

4. In case if ARP request is received and the requested IP address is already known, the ARP reply is constructed and sent to the curious host. Otherwise the ARP request is simply flooded from all the ports of the particular switch.

5. One more detail regarding this module is an option to specify default gateways IP addresses in command line. That will result in populating ARP tables of every switch with the following entry for each gateway address.

self.arpTable[dpid][IPAddr(fake)] = Entry(of.OFPP_NONE, dpid_to_mac(dpid))

References

Leave a comment