Skip to content

Instantly share code, notes, and snippets.

@MurphyMc
Created November 22, 2017 20:34
Show Gist options
  • Select an option

  • Save MurphyMc/602e6da045bbc72e3104c404cdadd833 to your computer and use it in GitHub Desktop.

Select an option

Save MurphyMc/602e6da045bbc72e3104c404cdadd833 to your computer and use it in GitHub Desktop.
Proposed new arp_helper
# Copyright 2011,2012,2013,2017 James McCauley
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
A utility module for handling some mundane parts of ARP
"""
"""
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...
"""
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.addresses import EthAddr, IPAddr
from pox.lib.util import dpid_to_str, str_to_bool
from pox.lib.revent import EventHalt, Event, EventMixin
import pox.openflow.libopenflow_01 as of
def send_arp_reply (reply_to, mac, src_mac = None):
"""
Send an ARP reply.
reply_to is a PacketIn event corresponding to an ARP request
mac is the MAC address to reply with
src_mac is the MAC address that the reply comes from (the L2 address)
mac and src_mac can be EthAddrs, or the following special values:
False - use the "DPID MAC" (MAC based on switch DPID)
True - use the MAC of the port the event was received by
Additionally, src_mac can be None (the default), which means to use
the same value as mac.
"""
if mac is False:
mac = reply_to.connection.eth_addr
elif mac is True:
mac = reply_to.connection.ports[reply_to.port].hw_addr
mac = EthAddr(mac)
if src_mac is None:
src_mac = mac
elif src_mac is False:
src_mac = reply_to.connection.eth_addr
elif src_mac is True:
src_mac = reply_to.connection.ports[reply_to.port].hw_addr
src_mac = EthAddr(src_mac)
arpp = reply_to.parsed.find('arp')
r = arp()
r.opcode = r.REPLY
r.hwdst = arpp.hwsrc
r.protodst = arpp.protosrc
r.hwsrc = mac
r.protosrc = IPAddr(arpp.protodst)
e = ethernet(type=ethernet.ARP_TYPE, src=src_mac, dst=r.hwdst)
e.payload = r
msg = of.ofp_packet_out()
msg.data = e.pack()
msg.actions.append(of.ofp_action_output(port = reply_to.port))
msg.in_port = of.OFPP_NONE
reply_to.connection.send(msg)
def send_arp_request (connection, ip, port = of.OFPP_FLOOD,
src_mac = False, src_ip = None):
"""
Send an ARP request
src_mac can be an EthAddr, or one of the following special values:
False - use the "DPID MAC" (MAC based on switch DPID) -- default
True - use the MAC of the port the event was received by
"""
if src_mac is False:
src_mac = connection.eth_addr
elif src_mac is True:
if port in (of.OFPP_FLOOD, of.OFPP_ALL):
for p in connection.ports.values():
if p.config & OFPPC_NO_FLOOD:
if port == of.ofPP_FLOOD:
continue
if p.port_no < 0: continue
if p.port_no > of.OFPP_MAX: continue # Off by one?
send_arp_request(connection, ip, p.port_no,
src_mac=p.hw_addr, src_ip=src_ip)
return
src_mac = connection.ports[port].hw_addr
else:
src_mac = EthAddr(src_mac)
r = arp()
r.opcode = r.REQUEST
r.hwdst = ETHER_BROADCAST
r.protodst = IPAddr(ip)
r.hwsrc = src_mac
r.protosrc = IPAddr("0.0.0.0") if src_ip is None else IPAddr(src_ip)
e = ethernet(type=ethernet.ARP_TYPE, src=src_mac, dst=r.hwdst)
e.payload = r
msg = of.ofp_packet_out()
msg.data = e.pack()
msg.actions.append(of.ofp_action_output(port = port))
msg.in_port = of.OFPP_NONE
connection.send(msg)
class ARPRequest (Event):
@property
def dpid (self):
return self.connection.dpid
def __str__ (self):
return "ARPRequest for %s on %s" % (self.ip, dpid_to_str(self.dpid))
def __init__ (self, con, arpp, reply_from, eat_packet, port):
super(ARPRequest,self).__init__()
self.connection = con
self.request = arpp # ARP packet
self.reply_from = reply_from # MAC or special value from send_arp_request.
# Don't modify to use ARPHelper default.
self.eat_packet = eat_packet
self.port = port
self.ip = arpp.protosrc
self.reply = None # Set to desired EthAddr
class ARPReply (Event):
@property
def dpid (self):
return self.connection.dpid
def __str__ (self):
return "ARPReply for %s on %s" % (self.reply.protodst,
dpid_to_str(self.dpid))
def __init__ (self, con, arpp, eat_packet, port):
super(ARPReply,self).__init__()
self.connection = con
self.reply = arpp
self.eat_packet = eat_packet
self.port = port
_default_mac = object()
class ARPHelper (EventMixin):
_eventMixin_events = set([ARPRequest,ARPReply])
_rule_priority_adjustment = -0x1000 # lower than the default
def __init__ (self, no_flow, eat_packets, default_request_src_mac = False,
default_reply_src_mac = None):
"""
Initialize
default_request_src_mac and default_reply_src_mac are the default source
MAC addresses for send_arp_request() and send_arp_reply().
"""
core.addListeners(self)
self._install_flow = not no_flow
self.eat_packets = eat_packets
self.default_request_src_mac = default_request_src_mac
self.default_reply_src_mac = default_reply_src_mac
def send_arp_request (self, connection, ip, port = of.OFPP_FLOOD,
src_mac = _default_mac, src_ip = None):
if src_mac is _default_mac:
src_mac = self.default_request_src_mac
return send_arp_request(connection, ip, port, src_mac, src_ip)
def send_arp_reply (self, reply_to, mac, src_mac = _default_mac):
"""
Send an ARP reply
reply_to is a an ARP request PacketIn event
mac is the MAC address to reply with, True for the port MAC or False
for the "DPID MAC".
src_mac can be a MAC, True/False as above, None to use "mac", or if
unspecified, defaults to self.default_src_mac.
"""
if src_mac is _default_mac:
src_mac = self.default_reply_src_mac
return send_arp_reply(reply_to, mac, src_mac)
def _handle_GoingUpEvent (self, event):
core.openflow.addListeners(self)
log.debug("Up...")
def _handle_ConnectionUp (self, event):
if self._install_flow:
fm = of.ofp_flow_mod()
fm.priority += self._rule_priority_adjustment
fm.match.dl_type = ethernet.ARP_TYPE
fm.actions.append(of.ofp_action_output(port=of.OFPP_CONTROLLER))
event.connection.send(fm)
def _handle_PacketIn (self, event):
dpid = event.connection.dpid
inport = event.port
packet = event.parsed
a = packet.find('arp')
if not a: return
if a.prototype != arp.PROTO_TYPE_IP:
return
if a.hwtype != arp.HW_TYPE_ETHERNET:
return
if a.opcode == arp.REQUEST:
log.debug("%s ARP request %s => %s", dpid_to_str(dpid),
a.protosrc, a.protodst)
src_mac = _default_mac
ev = ARPRequest(event.connection, a, src_mac,
self.eat_packets, inport)
self.raiseEvent(ev)
if ev.reply is not None:
log.debug("%s answering ARP for %s" % (dpid_to_str(dpid),
str(a.protodst)))
self.send_arp_reply(event, ev.reply, ev.reply_from)
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)
return EventHalt if ev.eat_packet else None
return EventHalt if self.eat_packets else None
def launch (no_flow=False, eat_packets=True, use_port_mac=False,
reply_from_dst=False):
"""
Start an ARP helper
If use_port_mac, use the specific port's MAC instead of the "DPID MAC".
If reply_from_dst, then replies will appear to come from the MAC address
that is used in the reply (otherwise, it comes from the same place as
requests).
"""
use_port_mac = str_to_bool(use_port_mac)
reply_from_dst = str_to_bool(reply_from_dst)
request_src = True if use_port_mac else False
reply_src = None if reply_from_dst else request_src
core.registerNew(ARPHelper, str_to_bool(no_flow), str_to_bool(eat_packets),
request_src, reply_src)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment