Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor arp_responder to use arp_helper #119

Open
wants to merge 5 commits into
base: dart
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions pox/proto/arp_helper.py
Original file line number Diff line number Diff line change
@@ -19,8 +19,8 @@
"""
TODO
----
arp_responder should be refactored to use this. Also, it should be possible
to have a simple ARP learner which keeps an ARP table without responding...
It should be possible to have a simple ARP learner which keeps an ARP
table without responding...
"""

from pox.core import core
@@ -29,6 +29,7 @@

from pox.lib.packet.ethernet import ethernet, ETHER_BROADCAST
from pox.lib.packet.arp import arp
from pox.lib.packet.vlan import vlan
from pox.lib.addresses import EthAddr, IPAddr
from pox.lib.util import dpid_to_str, str_to_bool
from pox.lib.revent import EventHalt, Event, EventMixin
@@ -107,6 +108,15 @@ def send_arp_request (connection, ip, port = of.OFPP_FLOOD,
msg.in_port = of.OFPP_NONE
connection.send(msg)

def flood_packet (packet_in):
"""
Send a packet_out that floods the packet received in the packet_in event.
"""
msg = of.ofp_packet_out()
msg.actions.append(of.ofp_action_output(port = of.OFPP_FLOOD))
msg.data = packet_in.ofp
packet_in.connection.send(msg.pack())


class ARPRequest (Event):
@property
@@ -126,6 +136,7 @@ def __init__ (self, con, arpp, reply_from, eat_packet, port):

self.ip = arpp.protosrc
self.reply = None # Set to desired EthAddr
self.flood = False # Flood the original packet in case of no reply?


class ARPReply (Event):
@@ -143,7 +154,7 @@ def __init__ (self, con, arpp, eat_packet, port):
self.reply = arpp
self.eat_packet = eat_packet
self.port = port

self.flood = False # Should ARPHelper flood the packet?

_default_src_mac = object()

@@ -219,6 +230,13 @@ def _handle_PacketIn (self, event):
r.hwsrc = EthAddr(ev.reply)
e = ethernet(type=packet.type, src=ev.reply_from, dst=a.hwsrc)
e.payload = r
if packet.type == ethernet.VLAN_TYPE:
v_rcv = packet.find('vlan')
e.payload = vlan(eth_type = e.type,
payload = e.payload,
id = v_rcv.id,
pcp = v_rcv.pcp)
e.type = ethernet.VLAN_TYPE
log.debug("%s answering ARP for %s" % (dpid_to_str(dpid),
str(r.protosrc)))
msg = of.ofp_packet_out()
@@ -228,13 +246,18 @@ def _handle_PacketIn (self, event):
msg.in_port = inport
event.connection.send(msg)
return EventHalt if ev.eat_packet else None
elif ev.flood:
flood_packet(event)
return EventHalt if ev.eat_packet else None

elif a.opcode == arp.REPLY:
log.debug("%s ARP reply %s => %s", dpid_to_str(dpid),
a.protosrc, a.hwsrc)

ev = ARPReply(event.connection,a,self.eat_packets,inport)
self.raiseEvent(ev)
if ev.flood:
flood_packet(event)
return EventHalt if ev.eat_packet else None

return EventHalt if self.eat_packets else None
182 changes: 70 additions & 112 deletions pox/proto/arp_responder.py
Original file line number Diff line number Diff line change
@@ -25,13 +25,12 @@
Leave MAC unspecified if you want to use the switch MAC.
"""

from logging import INFO, DEBUG
from pox.core import core
import pox
log = core.getLogger()

from pox.lib.packet.ethernet import ethernet, ETHER_BROADCAST
from pox.lib.packet.arp import arp
from pox.lib.packet.vlan import vlan
from pox.lib.addresses import IPAddr, EthAddr
from pox.lib.util import dpid_to_str, str_to_bool
from pox.lib.recoco import Timer
@@ -140,127 +139,83 @@ def __init__ (self):
# This timer handles expiring stuff
self._expire_timer = Timer(5, _handle_expiration, recurring=True)

core.addListeners(self)
core.listen_to_dependencies(self, ['ARPHelper'])

def _handle_GoingUpEvent (self, event):
core.openflow.addListeners(self)
log.debug("Up...")

def _handle_ConnectionUp (self, event):
if _install_flow:
fm = of.ofp_flow_mod()
fm.priority -= 0x1000 # lower than the default
fm.match.dl_type = ethernet.ARP_TYPE
fm.actions.append(of.ofp_action_output(port=of.OFPP_CONTROLLER))
event.connection.send(fm)
def _learn (self, dpid, a):
"""
Learn or update port/MAC info.
a is the arp part of a packet, i.e., a = packet.find('arp').
"""
if not _learn:
return

def _handle_PacketIn (self, event):
# Note: arp.hwsrc is not necessarily equal to ethernet.src
# (one such example are arp replies generated by this module itself
# as ethernet mac is set to switch dpid) so we should be careful
# to use only arp addresses in the learning code!
squelch = False
old_entry = _arp_table.get(a.protosrc)
if old_entry is None:
log.info("%s learned %s", dpid_to_str(dpid), a.protosrc)
_arp_table[a.protosrc] = Entry(a.hwsrc)
else:
if old_entry.mac is True:
# We never replace these special cases.
# Might want to warn on conflict?
pass
elif old_entry.mac != a.hwsrc:
if old_entry.static:
log.warn("%s static entry conflict %s: %s->%s",
dpid_to_str(dpid), a.protosrc, old_entry.mac, a.hwsrc)
else:
log.warn("%s RE-learned %s: %s->%s", dpid_to_str(dpid),
a.protosrc, old_entry.mac, a.hwsrc)
_arp_table[a.protosrc] = Entry(a.hwsrc)
else:
# Update timestamp
_arp_table[a.protosrc] = Entry(a.hwsrc)

def _handle_ARPHelper_ARPRequest (self, event):
a = event.request
dpid = event.connection.dpid
inport = event.port
packet = event.parsed
if not packet.parsed:
log.warning("%s: ignoring unparsed packet", dpid_to_str(dpid))
return
squelch = False

a = packet.find('arp')
if not a: return

log.debug("%s ARP %s %s => %s", dpid_to_str(dpid),
{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:

if _learn:
# Learn or update port/MAC info
old_entry = _arp_table.get(a.protosrc)
if old_entry is None:
log.info("%s learned %s", dpid_to_str(dpid), a.protosrc)
_arp_table[a.protosrc] = Entry(a.hwsrc)
else:
if old_entry.mac is True:
# We never replace these special cases.
# Might want to warn on conflict?
pass
elif old_entry.mac != a.hwsrc:
if old_entry.static:
log.warn("%s static entry conflict %s: %s->%s",
dpid_to_str(dpid), a.protosrc, old_entry.mac, a.hwsrc)
else:
log.warn("%s RE-learned %s: %s->%s", dpid_to_str(dpid),
a.protosrc, old_entry.mac, a.hwsrc)
_arp_table[a.protosrc] = Entry(a.hwsrc)
else:
# Update timestamp
_arp_table[a.protosrc] = Entry(a.hwsrc)

if a.opcode == arp.REQUEST:
# Maybe we can answer

if a.protodst in _arp_table:
# We have an answer...

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
mac = _arp_table[a.protodst].mac
if mac is True:
# Special case -- use ourself
mac = event.connection.eth_addr
r.hwsrc = mac
e = ethernet(type=packet.type, src=event.connection.eth_addr,
dst=a.hwsrc)
e.payload = r
if packet.type == ethernet.VLAN_TYPE:
v_rcv = packet.find('vlan')
e.payload = vlan(eth_type = e.type,
payload = e.payload,
id = v_rcv.id,
pcp = v_rcv.pcp)
e.type = ethernet.VLAN_TYPE
log.info("%s answering ARP for %s" % (dpid_to_str(dpid),
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 EventHalt if _eat_packets else None
else:
# Keep track of failed queries
squelch = a.protodst in _failed_queries
_failed_queries[a.protodst] = time.time()
if a.protosrc != 0:
self._learn(dpid, a)

if a.protodst in _arp_table:
# We have an answer...
mac = _arp_table[a.protodst].mac
if mac is True:
# Special case -- use ourself
mac = event.connection.eth_addr
event.reply = mac
return EventHalt if _eat_packets else None
else:
# Keep track of failed queries
squelch = a.protodst in _failed_queries
_failed_queries[a.protodst] = time.time()

if self._check_for_flood(dpid, a):
# Didn't know how to handle this ARP, so just flood it
msg = "%s flooding ARP %s %s => %s" % (dpid_to_str(dpid),
{arp.REQUEST:"request",arp.REPLY:"reply"}.get(a.opcode,
'op:%i' % (a.opcode,)), a.protosrc, a.protodst)

event.flood = True
if squelch:
log.debug(msg)
self._log_flood(DEBUG, dpid, a)
else:
log.info(msg)
self._log_flood(INFO, dpid, a)

return EventHalt if _eat_packets else None

msg = of.ofp_packet_out()
msg.actions.append(of.ofp_action_output(port = of.OFPP_FLOOD))
msg.data = event.ofp
event.connection.send(msg.pack())
def _handle_ARPHelper_ARPReply (self, event):
a = event.reply
dpid = event.connection.dpid

if a.protosrc != 0:
self._learn(dpid, a)

if self._check_for_flood(dpid, a):
# Didn't know how to handle this ARP, so just flood it
event.flood = True
self._log_flood(INFO, dpid, a)

return EventHalt if _eat_packets else None

@@ -272,18 +227,21 @@ def _check_for_flood (self, dpid, a):
return _arp_table[a.protodst].flood
return True

def _log_flood (self, log_level, dpid, a):
msg = "%s flooding ARP %s %s => %s" % (dpid_to_str(dpid),
{arp.REQUEST:"request",arp.REPLY:"reply"}.get(a.opcode,
'op:%i' % (a.opcode,)), a.protosrc, a.protodst)
log.log(log_level, msg)


_arp_table = ARPTable() # IPAddr -> Entry
_install_flow = None
_eat_packets = None
_failed_queries = {} # IP -> time : queries we couldn't answer
_learn = None

def launch (timeout=ARP_TIMEOUT, no_flow=False, eat_packets=True,
no_learn=False, **kw):
def launch (timeout=ARP_TIMEOUT, eat_packets=True, no_learn=False, **kw):
global ARP_TIMEOUT, _install_flow, _eat_packets, _learn
ARP_TIMEOUT = timeout
_install_flow = not no_flow
_eat_packets = str_to_bool(eat_packets)
_learn = not no_learn