On Jul 24, 2012, at 5:54 PM, Rashmi Pujar wrote:
I think we're talking about POX's l3_learning now, right? First, let me suggest that you might take a look at the one from my fork. It's going upstream eventually, and l3_learning has been/is being improved (e.g., you can make it handle gateways). I've attached it. (And If we're just talking about POX, let's drop NOX from the CC.) |
# Copyright 2011 James McCauley # # This file is part of POX. # # POX is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # POX is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with POX. If not, see <http://www.gnu.org/licenses/>.
"""
A stupid L3 switch
For each switch:
1) Keep a table that maps IP addresses to MAC addresses and switch ports.
Stock this table using information from ARP and IP packets.
2) When you see an ARP query, try to answer it using information in the table
from step 1. If the info in the table is old, just flood the query.
3) Flood all other ARPs.
4) When you see an IP packet, if you know the destination port (because it's
in the table from step 1), install a flow for it.
"""
from pox.core import core
import pox
log = core.getLogger()
from pox.lib.packet.ethernet import ethernet, ETHER_BROADCAST
from pox.lib.packet.ipv4 import ipv4
from pox.lib.packet.arp import arp
from pox.lib.addresses import IPAddr, EthAddr
import pox.openflow.libopenflow_01 as of
from pox.lib.revent import *
import time
# Timeout for flows
FLOW_IDLE_TIMEOUT = 10
# Timeout for ARP entries
ARP_TIMEOUT = 60 * 2
class Entry (object):
"""
Not strictly an ARP entry.
We use the port to determine which port to forward traffic out of.
We use the MAC to answer ARP replies.
We use the timeout so that if an entry is older than ARP_TIMEOUT, we
flood the ARP request rather than try to answer it ourselves.
"""
def __init__ (self, port, mac):
self.timeout = time.time() + ARP_TIMEOUT
self.port = port
self.mac = mac
def __eq__ (self, other):
if type(other) == tuple:
return (self.port,self.mac)==other
else:
return (self.port,self.mac)==(other.port,other.mac)
def __ne__ (self, other):
return not self.__eq__(other)
def isExpired (self):
if self.port == of.OFPP_NONE: return False
return time.time() > self.timeout
def dpid_to_mac (dpid):
return EthAddr("%012x" % (dpid & 0xffFFffFFffFF,))
class l3_switch (EventMixin):
def __init__ (self, fakeways = []):
# These are "fake gateways" -- we'll answer ARPs for them with MAC
# of the switch they're connected to.
self.fakeways = set(fakeways)
# For each switch, we map IP addresses to Entries
self.arpTable = {}
self.listenTo(core)
def _handle_GoingUpEvent (self, event):
self.listenTo(core.openflow)
log.debug("Up...")
def _handle_PacketIn (self, event):
dpid = event.connection.dpid
inport = event.port
packet = event.parsed
if not packet.parsed:
log.warning("%i %i ignoring unparsed packet", dpid, inport)
return
if dpid not in self.arpTable:
# New switch -- create an empty table
self.arpTable[dpid] = {}
for fake in self.fakeways:
self.arpTable[dpid][IPAddr(fake)] = Entry(of.OFPP_NONE,
dpid_to_mac(dpid))
if packet.type == ethernet.LLDP_TYPE:
# Ignore LLDP packets
return
if isinstance(packet.next, ipv4):
log.debug("%i %i IP %s => %s", dpid,inport,str(packet.next.srcip),str(packet.next.dstip))
# Learn or update port/MAC info
if packet.next.srcip in self.arpTable[dpid]:
if self.arpTable[dpid][packet.next.srcip] != (inport, packet.src):
log.info("%i %i RE-learned %s", dpid,inport,str(packet.next.srcip))
else:
log.debug("%i %i learned %s", dpid,inport,str(packet.next.srcip))
self.arpTable[dpid][packet.next.srcip] = Entry(inport, packet.src)
# Try to forward
dstaddr = packet.next.dstip
if dstaddr in self.arpTable[dpid]:
# We have info about what port to send it out on...
prt = self.arpTable[dpid][dstaddr].port
mac = self.arpTable[dpid][dstaddr].mac
if prt == inport:
log.warning("%i %i not sending packet for %s back out of the input port" % (
dpid, inport, str(dstaddr)))
else:
log.debug("%i %i installing flow for %s => %s out port %i" % (dpid,
inport, str(packet.next.srcip), str(dstaddr), prt))
actions = []
actions.append(of.ofp_action_dl_addr(type=of.OFPAT_SET_DL_DST,dl_addr=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())
else:
# We don't know this destination. So... let's ARP for it!
# Ultimately, this should result in it responding and us learning
# where it is.
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)
return
elif isinstance(packet.next, arp):
a = packet.next
log.debug("%i %i ARP %s %s => %s", dpid, inport,
{arp.REQUEST:"request",arp.REPLY:"reply"}.get(a.opcode,
'op:%i' % (a.opcode,)), str(a.protosrc), str(a.protodst))
if a.prototype == arp.PROTO_TYPE_IP:
if a.hwtype == arp.HW_TYPE_ETHERNET:
if a.protosrc != 0:
# Learn or update port/MAC info
if a.protosrc in self.arpTable[dpid]:
if self.arpTable[dpid][a.protosrc] != (inport, packet.src):
log.info("%i %i RE-learned %s", dpid,inport,str(a.protosrc))
else:
log.debug("%i %i learned %s", dpid,inport,str(a.protosrc))
self.arpTable[dpid][a.protosrc] = Entry(inport, packet.src)
if a.opcode == arp.REQUEST:
# Maybe we can answer
if a.protodst in self.arpTable[dpid]:
# We have an answer...
if not self.arpTable[dpid][a.protodst].isExpired():
# .. and it's relatively current, so we'll reply ourselves
r = arp()
r.hwtype = a.hwtype
r.prototype = a.prototype
r.hwlen = a.hwlen
r.protolen = a.protolen
r.opcode = arp.REPLY
r.hwdst = a.hwsrc
r.protodst = a.protosrc
r.protosrc = a.protodst
r.hwsrc = self.arpTable[dpid][a.protodst].mac
e = ethernet(type=packet.type, src=dpid_to_mac(dpid), dst=a.hwsrc)
e.set_payload(r)
log.debug("%i %i answering ARP for %s" % (dpid, inport,
str(r.protosrc)))
msg = of.ofp_packet_out()
msg.data = e.pack()
msg.actions.append(of.ofp_action_output(port =
of.OFPP_IN_PORT))
msg.in_port = inport
event.connection.send(msg)
return
# Didn't know how to answer or otherwise handle this ARP, so just flood it
log.debug("%i %i flooding ARP %s %s => %s" % (dpid, inport,
{arp.REQUEST:"request",arp.REPLY:"reply"}.get(a.opcode,
'op:%i' % (a.opcode,)), str(a.protosrc), str(a.protodst)))
msg = of.ofp_packet_out(in_port = inport, action = of.ofp_action_output(port = of.OFPP_FLOOD))
if event.ofp.buffer_id is of.NO_BUFFER:
# Try sending the (probably incomplete) raw data
msg.data = event.data
else:
msg.buffer_id = event.ofp.buffer_id
event.connection.send(msg.pack())
return
def launch (fakeways=""):
fakeways = fakeways.replace(","," ").split()
fakeways = [IPAddr(x) for x in fakeways]
core.registerNew(l3_switch, fakeways)
