Created
November 22, 2017 20:34
-
-
Save MurphyMc/602e6da045bbc72e3104c404cdadd833 to your computer and use it in GitHub Desktop.
Proposed new arp_helper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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