ArpHandler to send ARP reply on proper vlan
[controller.git] / opendaylight / arphandler / src / main / java / org / opendaylight / controller / arphandler / internal / ArpHandler.java
index 8c91c71533a19282c9f1c48e8697dd3f81d181ee..fe456f3f8ebe6f6237352cb09f5b6a7dc8056398 100644 (file)
@@ -49,12 +49,12 @@ import org.opendaylight.controller.sal.core.NodeConnector;
 import org.opendaylight.controller.sal.packet.ARP;
 import org.opendaylight.controller.sal.packet.Ethernet;
 import org.opendaylight.controller.sal.packet.IDataPacketService;
+import org.opendaylight.controller.sal.packet.IEEE8021Q;
 import org.opendaylight.controller.sal.packet.IListenDataPacket;
 import org.opendaylight.controller.sal.packet.IPv4;
 import org.opendaylight.controller.sal.packet.Packet;
 import org.opendaylight.controller.sal.packet.PacketResult;
 import org.opendaylight.controller.sal.packet.RawPacket;
-import org.opendaylight.controller.sal.routing.IRouting;
 import org.opendaylight.controller.sal.utils.EtherTypes;
 import org.opendaylight.controller.sal.utils.HexEncode;
 import org.opendaylight.controller.sal.utils.NetUtils;
@@ -64,6 +64,31 @@ import org.opendaylight.controller.topologymanager.ITopologyManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+/**
+ * The ArpHandler offers services to react on ARP requests and replies
+ * sent by network hosts. Moreover it allows for creating ARP messages
+ * by the controller itself.
+ *
+ * The ARP Handler on ODL doesn't use the requester MAC address in
+ * order to avoid to have to build a spanning tree where to forward
+ * ARP Requests. The ARP requests are broadcast packets so in order to
+ * reach everywhere need to be flooded, when you flood in a network
+ * that is not a tree (all the networks has some level of redundancy)
+ * that would create forwarding loops without a spanning tree. Given
+ * the need is only to send out the ARP requests toward all the hosts
+ * we actually don't need to implement a flooding mechanism in software
+ * (which would be expensive) we just send out the ARP request toward
+ * all the ports that are suspected to be host ports on all the
+ * switches (from the controller). Now the condition for which a port
+ * is marked as host port could potentially be incorrect so when the
+ * controller sends out the ARP Request that could come back to the
+ * controller and could cause another request not needed. So changing
+ * the source MAC address of the request to be the one of the controller,
+ * controller can protect itself from honoring twice the same request.
+ * This enables an ARP handler resolution, without the need of spanning
+ * tree and limiting software flooding to the minimum required.
+ */
+
 public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateAware<ARPEvent, Boolean> {
     private static final Logger log = LoggerFactory.getLogger(ArpHandler.class);
     static final String ARP_EVENT_CACHE_NAME = "arphandler.arpRequestReplyEvent";
@@ -71,7 +96,6 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
     private ISwitchManager switchManager;
     private ITopologyManager topologyManager;
     private IDataPacketService dataPacketService;
-    private IRouting routing;
     private IClusterContainerServices clusterContainerService;
     private IConnectionManager connectionManager;
     private Set<IfHostListener> hostListeners = new CopyOnWriteArraySet<IfHostListener>();
@@ -113,16 +137,6 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
         }
     }
 
-    void setRouting(IRouting r) {
-        this.routing = r;
-    }
-
-    void unsetRouting(IRouting r) {
-        if (this.routing == r) {
-            this.routing = null;
-        }
-    }
-
     void setHostListener(IfHostListener s) {
         if (this.hostListeners != null) {
             this.hostListeners.add(s);
@@ -167,12 +181,18 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
         }
     }
 
-    protected void sendARPReply(NodeConnector p, byte[] sMAC, InetAddress sIP, byte[] tMAC, InetAddress tIP) {
+    protected void sendARPReply(NodeConnector p, byte[] sMAC, InetAddress sIP, byte[] tMAC, InetAddress tIP, short vlan) {
         byte[] senderIP = sIP.getAddress();
         byte[] targetIP = tIP.getAddress();
         ARP arp = createARP(ARP.REPLY, sMAC, senderIP, tMAC, targetIP);
 
-        Ethernet ethernet = createEthernet(sMAC, tMAC, arp);
+        if(log.isTraceEnabled()) {
+            log.trace("Sending Arp Reply with srcMac {} - srcIp {} - dstMac {} - dstIp {} - outport {}",
+                    HexEncode.bytesToHexString(sMAC),
+                    sIP, HexEncode.bytesToHexString(tMAC), tIP, p);
+        }
+
+        Ethernet ethernet = createEthernet(sMAC, tMAC, arp, vlan);
 
         RawPacket destPkt = this.dataPacketService.encodeDataPacket(ethernet);
         destPkt.setOutgoingNodeConnector(p);
@@ -180,7 +200,25 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
         this.dataPacketService.transmitDataPacket(destPkt);
     }
 
-    protected void handleARPPacket(Ethernet eHeader, ARP pkt, NodeConnector p) {
+    private void logArpPacket(ARP pkt, NodeConnector p, short vlan) {
+        try {
+            log.trace("Received Arp {} with srcMac {} - srcIp {} - dstMac {} - dstIp {} - inport {} {}",
+                    ((pkt.getOpCode() == ARP.REQUEST) ? "Request" : "Reply"),
+                    HexEncode.bytesToHexString(pkt.getSenderHardwareAddress()),
+                    InetAddress.getByAddress(pkt.getSenderProtocolAddress()),
+                    HexEncode.bytesToHexString(pkt.getTargetHardwareAddress()),
+                    InetAddress.getByAddress(pkt.getTargetProtocolAddress()), p, (vlan != 0 ? "on vlan " + vlan : ""));
+
+        } catch (UnknownHostException e) {
+            log.warn("Illegal Ip Address in the ARP packet", e);
+        }
+    }
+
+    protected void handleARPPacket(Ethernet eHeader, ARP pkt, NodeConnector p, short vlan) {
+
+        if(log.isTraceEnabled()) {
+            logArpPacket(pkt, p, vlan);
+        }
 
         byte[] sourceMAC = eHeader.getSourceMACAddress();
         byte[] targetMAC = eHeader.getDestinationMACAddress();
@@ -223,7 +261,7 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
         HostNodeConnector requestor = null;
         if (NetUtils.isUnicastMACAddr(sourceMAC) && p.getNode() != null) {
             try {
-                requestor = new HostNodeConnector(sourceMAC, sourceIP, p, subnet.getVlan());
+                requestor = new HostNodeConnector(sourceMAC, sourceIP, p, vlan);
             } catch (ConstructionException e) {
                 log.debug("Received ARP packet with invalid MAC: {}", HexEncode.bytesToHexString(sourceMAC));
                 return;
@@ -253,7 +291,7 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
             // the true value indicates we should generate replies to requestors
             // across the cluster
             log.trace("Received ARP reply packet from {}, reply to all requestors.", sourceIP);
-            arpRequestReplyEvent.put(new ARPReply(sourceIP, sourceMAC), true);
+            arpRequestReplyEvent.put(new ARPReply(sourceIP, sourceMAC, vlan), true);
             return;
         }
 
@@ -274,11 +312,11 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
                     log.trace("Received local ARP req. for default gateway. Replying with controller MAC: {}",
                             HexEncode.bytesToHexString(getControllerMAC()));
                 }
-                sendARPReply(p, getControllerMAC(), targetIP, pkt.getSenderHardwareAddress(), sourceIP);
+                sendARPReply(p, getControllerMAC(), targetIP, pkt.getSenderHardwareAddress(), sourceIP, vlan);
             } else {
                 log.trace("Received non-local ARP req. for default gateway. Raising reply event");
                 arpRequestReplyEvent.put(
-                        new ARPReply(p, targetIP, getControllerMAC(), sourceIP, pkt.getSenderHardwareAddress()), false);
+                        new ARPReply(p, targetIP, getControllerMAC(), sourceIP, pkt.getSenderHardwareAddress(), vlan), false);
             }
             return;
         }
@@ -312,10 +350,10 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
                 log.trace("Received ARP req. for known host {}, sending reply...", targetIP);
                 if (connectionManager.getLocalityStatus(p.getNode()) == ConnectionLocality.LOCAL) {
                     sendARPReply(p, host.getDataLayerAddressBytes(), host.getNetworkAddress(),
-                            pkt.getSenderHardwareAddress(), sourceIP);
+                            pkt.getSenderHardwareAddress(), sourceIP, vlan);
                 } else {
                     arpRequestReplyEvent.put(new ARPReply(p, host.getNetworkAddress(), host.getDataLayerAddressBytes(),
-                            sourceIP, pkt.getSenderHardwareAddress()), false);
+                            sourceIP, pkt.getSenderHardwareAddress(), vlan), false);
                 }
             } else {
                 /*
@@ -357,8 +395,13 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
             byte[] targetIPByte = targetIP.getAddress();
             ARP arp = createARP(ARP.REQUEST, getControllerMAC(), senderIP, targetHardwareAddress, targetIPByte);
 
+            if(log.isTraceEnabled()) {
+                log.trace("Sending Broadcast Arp Request with srcMac {} - srcIp {} - dstMac {} - dstIp {} - outport {}", HexEncode.bytesToHexString(getControllerMAC()),
+                        subnet.getNetworkAddress(), HexEncode.bytesToHexString(targetHardwareAddress), targetIP, p);
+            }
+
             byte[] destMACAddress = NetUtils.getBroadcastMACAddr();
-            Ethernet ethernet = createEthernet(getControllerMAC(), destMACAddress, arp);
+            Ethernet ethernet = createEthernet(getControllerMAC(), destMACAddress, arp, (short)0);
 
             // TODO For now send port-by-port, see how to optimize to
             // send to multiple ports at once
@@ -370,9 +413,9 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
     }
 
     /**
-     * Send a unicast ARP Request to the known host on a specific switch/port as
-     * defined in the host. The sender IP is the networkAddress of the subnet
-     * The sender MAC is the controller's MAC
+     * Send a unicast ARP Request to the known host on specific (switch/port,
+     * vlan) as defined in the host. The sender IP is the networkAddress of the
+     * subnet The sender MAC is the controller's MAC
      */
     protected void sendUcastARPRequest(HostNodeConnector host, Subnet subnet) {
         log.trace("sendUcastARPRequest host:{} subnet:{}", host, subnet);
@@ -387,7 +430,14 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
         byte[] targetMAC = host.getDataLayerAddressBytes();
         ARP arp = createARP(ARP.REQUEST, getControllerMAC(), senderIP, targetMAC, targetIP);
 
-        Ethernet ethernet = createEthernet(getControllerMAC(), targetMAC, arp);
+        if(log.isTraceEnabled()) {
+            log.trace("Sending Unicast Arp Request with srcMac {} - srcIp {} - dstMac {} - dstIp {} - outport {}",
+                    HexEncode.bytesToHexString(getControllerMAC()),
+                    subnet.getNetworkAddress(), HexEncode.bytesToHexString(targetMAC), host.getNetworkAddress(),
+                    outPort);
+        }
+
+        Ethernet ethernet = createEthernet(getControllerMAC(), targetMAC, arp, host.getVlan());
 
         RawPacket destPkt = this.dataPacketService.encodeDataPacket(ethernet);
         destPkt.setOutgoingNodeConnector(outPort);
@@ -445,7 +495,7 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
      * @param pkt
      * @param p
      */
-    protected void handlePuntedIPPacket(IPv4 pkt, NodeConnector p) {
+    protected void handlePuntedIPPacket(IPv4 pkt, NodeConnector p, short vlan) {
 
         InetAddress dIP = NetUtils.getInetAddress(pkt.getDestinationAddress());
         if (dIP == null) {
@@ -461,44 +511,26 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
             log.debug("Can't find subnet matching {}, drop packet", dIP);
             return;
         }
+        // If packet is sent to the default gw (us), ignore it for now
+        if (subnet.getNetworkAddress().equals(dIP)) {
+            log.trace("Ignore IP packet destined to default gw");
+            return;
+        }
 
         // see if we know about the host
         // Hosttracker hosts db key implementation
-        IHostId id = HostIdFactory.create(dIP, null);
-        HostNodeConnector host = hostTracker.hostFind(id);
+        HostNodeConnector host = hostTracker.hostFind(dIP);
 
         if (host == null) {
-            // if we don't, know about the host, try to find it
+            // if we don't know about the host, try to find it
             log.trace("Punted IP pkt to {}, sending bcast ARP event...", dIP);
             /*
              * unknown destination host, initiate bcast ARP request
              */
             arpRequestReplyEvent.put(new ARPRequest(dIP, subnet), false);
 
-        } else if (routing == null || routing.getRoute(p.getNode(), host.getnodeconnectorNode()) != null) {
-            /*
-             * if IRouting is available, make sure that this packet can get it's
-             * destination normally before teleporting it there. If it's not
-             * available, then assume it's reachable.
-             *
-             * TODO: come up with a way to do this in the absence of IRouting
-             */
-
-            log.trace("forwarding punted IP pkt to {} received at {}", dIP, p);
-
-            /*
-             * if we know where the host is and there's a path from where this
-             * packet was punted to where the host is, then deliver it to the
-             * host for now
-             */
-            NodeConnector nc = host.getnodeConnector();
-
-            // re-encode the Ethernet packet (the parent of the IPv4 packet)
-            RawPacket rp = this.dataPacketService.encodeDataPacket(pkt.getParent());
-            rp.setOutgoingNodeConnector(nc);
-            this.dataPacketService.transmitDataPacket(rp);
         } else {
-            log.trace("ignoring punted IP pkt to {} because there is no route from {}", dIP, p);
+            log.trace("Ignoring punted IP pkt to known host: {} (received on: {})", dIP, p);
         }
     }
 
@@ -618,13 +650,19 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
         log.trace("Received a frame of size: {}", inPkt.getPacketData().length);
         Packet formattedPak = this.dataPacketService.decodeDataPacket(inPkt);
         if (formattedPak instanceof Ethernet) {
-            Object nextPak = formattedPak.getPayload();
+            Packet nextPak = formattedPak.getPayload();
+            short vlan = 0;
+            if (nextPak instanceof IEEE8021Q) {
+                vlan = ((IEEE8021Q) nextPak).getVid();
+                log.trace("Moved after the dot1Q header");
+                nextPak = ((IEEE8021Q) nextPak).getPayload();
+            }
             if (nextPak instanceof IPv4) {
                 log.trace("Handle IP packet: {}", formattedPak);
-                handlePuntedIPPacket((IPv4) nextPak, inPkt.getIncomingNodeConnector());
+                handlePuntedIPPacket((IPv4) nextPak, inPkt.getIncomingNodeConnector(), vlan);
             } else if (nextPak instanceof ARP) {
                 log.trace("Handle ARP packet: {}", formattedPak);
-                handleARPPacket((Ethernet) formattedPak, (ARP) nextPak, inPkt.getIncomingNodeConnector());
+                handleARPPacket((Ethernet) formattedPak, (ARP) nextPak, inPkt.getIncomingNodeConnector(), vlan);
             }
         }
         return PacketResult.IGNORED;
@@ -645,12 +683,21 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
         return arp;
     }
 
-    private Ethernet createEthernet(byte[] sourceMAC, byte[] targetMAC, ARP arp) {
+    private Ethernet createEthernet(byte[] sourceMAC, byte[] targetMAC, ARP arp, short vlan) {
         Ethernet ethernet = new Ethernet();
         ethernet.setSourceMACAddress(sourceMAC);
         ethernet.setDestinationMACAddress(targetMAC);
-        ethernet.setEtherType(EtherTypes.ARP.shortValue());
-        ethernet.setPayload(arp);
+        if (vlan == 0) {
+            ethernet.setEtherType(EtherTypes.ARP.shortValue());
+            ethernet.setPayload(arp);
+        } else {
+            IEEE8021Q dot1q = new IEEE8021Q();
+            dot1q.setVid(vlan);
+            dot1q.setEtherType(EtherTypes.ARP.shortValue());
+            dot1q.setPayload(arp);
+            ethernet.setEtherType(EtherTypes.VLANTAGGED.shortValue());
+            ethernet.setPayload(dot1q);
+        }
         return ethernet;
     }
 
@@ -696,7 +743,7 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
         }
     }
 
-    private void generateAndSendReply(InetAddress sourceIP, byte[] sourceMAC) {
+    private void generateAndSendReply(InetAddress sourceIP, byte[] sourceMAC, short vlan) {
         if (log.isTraceEnabled()) {
             log.trace("generateAndSendReply called with params sourceIP:{} sourceMAC:{}", sourceIP,
                     HexEncode.bytesToHexString(sourceMAC));
@@ -710,13 +757,14 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
         for (HostNodeConnector host : hosts) {
             if (log.isTraceEnabled()) {
                 log.trace(
-                        "Sending ARP Reply with src {}/{}, target {}/{}",
+                        "Sending ARP Reply with src {}/{}, target {}/{} {}",
                         new Object[] { HexEncode.bytesToHexString(sourceMAC), sourceIP,
-                                HexEncode.bytesToHexString(host.getDataLayerAddressBytes()), host.getNetworkAddress() });
+                                HexEncode.bytesToHexString(host.getDataLayerAddressBytes()), host.getNetworkAddress(),
+                                (vlan != 0 ? "on vlan " + vlan : "") });
             }
             if (connectionManager.getLocalityStatus(host.getnodeconnectorNode()) == ConnectionLocality.LOCAL) {
                 sendARPReply(host.getnodeConnector(), sourceMAC, sourceIP, host.getDataLayerAddressBytes(),
-                        host.getNetworkAddress());
+                        host.getNetworkAddress(), vlan);
             } else {
                 /*
                  * In the remote event a requestor moved to another controller
@@ -725,7 +773,7 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
                  */
                 arpRequestReplyEvent.put(
                         new ARPReply(host.getnodeConnector(), sourceIP, sourceMAC, host.getNetworkAddress(), host
-                                .getDataLayerAddressBytes()), false);
+                                .getDataLayerAddressBytes(), vlan), false);
             }
         }
     }
@@ -787,12 +835,12 @@ public class ArpHandler implements IHostFinder, IListenDataPacket, ICacheUpdateA
                         // requestors across the cluster
                         if (ev.isNewReply()) {
                             log.trace("Trigger a generateAndSendReply in response to {}", rep);
-                            generateAndSendReply(rep.getTargetIP(), rep.getTargetMac());
+                            generateAndSendReply(rep.getTargetIP(), rep.getTargetMac(), rep.getVlan());
                             // Otherwise, a specific reply. If local, send out.
                         } else if (connectionManager.getLocalityStatus(rep.getPort().getNode()) == ConnectionLocality.LOCAL) {
                             log.trace("ARPCacheEventHandler - sendUcatARPReply locally in response to {}", rep);
                             sendARPReply(rep.getPort(), rep.getSourceMac(), rep.getSourceIP(), rep.getTargetMac(),
-                                    rep.getTargetIP());
+                                    rep.getTargetIP(), rep.getVlan());
                         }
                     }
                 } catch (InterruptedException e) {