ProxyArpManager: Factor out common logic for handling ARP and NDP packets.

Change-Id: I0709f9cda2506f01dbc912b8c3e12443ec217fed
diff --git a/apps/proxyarp/src/main/java/org/onosproject/proxyarp/ProxyArp.java b/apps/proxyarp/src/main/java/org/onosproject/proxyarp/ProxyArp.java
index 742d7af..2eb96df 100644
--- a/apps/proxyarp/src/main/java/org/onosproject/proxyarp/ProxyArp.java
+++ b/apps/proxyarp/src/main/java/org/onosproject/proxyarp/ProxyArp.java
@@ -82,7 +82,7 @@
 
         packetService.addProcessor(processor, PacketProcessor.director(1));
         readComponentConfiguration(context);
-        requestPackests();
+        requestPackets();
 
         log.info("Started with Application ID {}", appId.id());
     }
@@ -99,13 +99,13 @@
     @Modified
     public void modified(ComponentContext context) {
         readComponentConfiguration(context);
-        requestPackests();
+        requestPackets();
     }
 
     /**
      * Request packet in via PacketService.
      */
-    private void requestPackests() {
+    private void requestPackets() {
         TrafficSelector.Builder selectorBuilder =
                 DefaultTrafficSelector.builder();
         selectorBuilder.matchEthType(TYPE_ARP);
diff --git a/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpService.java b/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpService.java
index 8ffe17a..9b27f4f 100644
--- a/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpService.java
+++ b/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpService.java
@@ -21,7 +21,7 @@
 import org.onosproject.net.packet.PacketContext;
 
 /**
- * Service for processing arp requests on behalf of applications.
+ * Service for processing ARP or NDP requests on behalf of applications.
  */
 // TODO: move to the peer host package
 public interface ProxyArpService {
diff --git a/core/net/src/main/java/org/onosproject/net/host/impl/HostMonitor.java b/core/net/src/main/java/org/onosproject/net/host/impl/HostMonitor.java
index fe25236..d437bf3 100644
--- a/core/net/src/main/java/org/onosproject/net/host/impl/HostMonitor.java
+++ b/core/net/src/main/java/org/onosproject/net/host/impl/HostMonitor.java
@@ -33,8 +33,6 @@
 import org.onosproject.net.Host;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficTreatment;
-import org.onosproject.net.flow.instructions.Instruction;
-import org.onosproject.net.flow.instructions.Instructions;
 import org.onosproject.net.host.HostProvider;
 import org.onosproject.net.host.InterfaceIpAddress;
 import org.onosproject.net.packet.DefaultOutboundPacket;
@@ -43,9 +41,7 @@
 import org.onosproject.net.provider.ProviderId;
 
 import java.nio.ByteBuffer;
-import java.util.ArrayList;
 import java.util.Collections;
-import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -149,7 +145,7 @@
             Set<Host> hosts = hostManager.getHostsByIp(ip);
 
             if (hosts.isEmpty()) {
-                sendArpNdpRequest(ip);
+                sendRequest(ip);
             } else {
                 for (Host host : hosts) {
                     HostProvider provider = hostProviders.get(host.providerId());
@@ -166,12 +162,11 @@
     }
 
     /**
-     * Sends an ARP or Neighbor Discovery Protocol request for the given IP
-     * address.
+     * Sends an ARP or NDP request for the given IP address.
      *
      * @param targetIp IP address to send the request for
      */
-    private void sendArpNdpRequest(IpAddress targetIp) {
+    private void sendRequest(IpAddress targetIp) {
         Interface intf = interfaceService.getMatchingInterface(targetIp);
 
         if (intf == null) {
@@ -180,31 +175,26 @@
 
         for (InterfaceIpAddress ia : intf.ipAddresses()) {
             if (ia.subnetAddress().contains(targetIp)) {
-                sendArpNdpProbe(intf.connectPoint(), targetIp, ia.ipAddress(),
+                sendProbe(intf.connectPoint(), targetIp, ia.ipAddress(),
                         intf.mac(), intf.vlan());
             }
         }
     }
 
-    private void sendArpNdpProbe(ConnectPoint connectPoint,
-                                 IpAddress targetIp,
-                                 IpAddress sourceIp, MacAddress sourceMac,
-                                 VlanId vlan) {
+    private void sendProbe(ConnectPoint connectPoint,
+                           IpAddress targetIp,
+                           IpAddress sourceIp, MacAddress sourceMac,
+                           VlanId vlan) {
         Ethernet probePacket = null;
 
         if (targetIp.isIp4()) {
             // IPv4: Use ARP
-            probePacket = buildArpRequest(targetIp, sourceIp, sourceMac,
-                                          vlan);
+            probePacket = buildArpRequest(targetIp, sourceIp, sourceMac, vlan);
         } else {
             // IPv6: Use Neighbor Discovery
-            probePacket = buildNdpRequest(targetIp, sourceIp, sourceMac,
-                                          vlan);
+            probePacket = buildNdpRequest(targetIp, sourceIp, sourceMac, vlan);
         }
 
-        List<Instruction> instructions = new ArrayList<>();
-        instructions.add(Instructions.createOutput(connectPoint.port()));
-
         TrafficTreatment treatment = DefaultTrafficTreatment.builder()
             .setOutput(connectPoint.port())
             .build();
@@ -273,7 +263,7 @@
         icmp6.setIcmpType(ICMP6.NEIGHBOR_SOLICITATION);
         icmp6.setIcmpCode((byte) 0);
 
-        // Create the Neighbor Solication packet
+        // Create the Neighbor Solicitation packet
         NeighborSolicitation ns = new NeighborSolicitation();
         ns.setTargetAddress(targetIp.toOctets());
         ns.addOption(NeighborDiscoveryOptions.TYPE_SOURCE_LL_ADDRESS,
diff --git a/core/net/src/main/java/org/onosproject/net/proxyarp/impl/ProxyArpManager.java b/core/net/src/main/java/org/onosproject/net/proxyarp/impl/ProxyArpManager.java
index 398260f..25a2640 100644
--- a/core/net/src/main/java/org/onosproject/net/proxyarp/impl/ProxyArpManager.java
+++ b/core/net/src/main/java/org/onosproject/net/proxyarp/impl/ProxyArpManager.java
@@ -69,11 +69,9 @@
 
     private final Logger log = getLogger(getClass());
 
-    private static final String MAC_ADDR_NULL = "Mac address cannot be null.";
+    private static final String MAC_ADDR_NULL = "MAC address cannot be null.";
     private static final String REQUEST_NULL = "ARP or NDP request cannot be null.";
-    private static final String REQUEST_NOT_ARP = "Ethernet frame does not contain ARP request.";
-    private static final String NOT_ARP_REQUEST = "ARP is not a request.";
-    private static final String NOT_ARP_REPLY = "ARP is not a reply.";
+    private static final String MSG_NOT_REQUEST = "Message is not an ARP or NDP request";
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected EdgePortService edgeService;
@@ -96,6 +94,14 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected InterfaceService interfaceService;
 
+    private enum Protocol {
+        ARP, NDP
+    }
+
+    private enum MessageType {
+        REQUEST, REPLY
+    }
+
     @Activate
     public void activate() {
         store.setDelegate(this::sendTo);
@@ -123,46 +129,48 @@
 
         checkNotNull(eth, REQUEST_NULL);
 
-        if (eth.getEtherType() == Ethernet.TYPE_ARP) {
-            replyArp(eth, inPort);
-        } else if (eth.getEtherType() == Ethernet.TYPE_IPV6) {
-            replyNdp(eth, inPort);
+        MessageContext context = createContext(eth, inPort);
+        if (context != null) {
+            replyInternal(context);
         }
     }
 
-    private void replyArp(Ethernet eth, ConnectPoint inPort) {
-        ARP arp = (ARP) eth.getPayload();
-        checkArgument(arp.getOpCode() == ARP.OP_REQUEST, NOT_ARP_REQUEST);
-        checkNotNull(inPort);
-        Ip4Address targetAddress = Ip4Address.valueOf(arp.getTargetProtocolAddress());
+    /**
+     * Handles a request message.
+     *
+     * If the MAC address of the target is known, we can reply directly to the
+     * requestor. Otherwise, we forward the request out other ports in an
+     * attempt to find the correct host.
+     *
+     * @param context request message context to process
+     */
+    private void replyInternal(MessageContext context) {
+        checkNotNull(context);
+        checkArgument(context.type() == MessageType.REQUEST, MSG_NOT_REQUEST);
 
-        VlanId vlan = vlanId(eth.getVlanID());
-
-        if (hasIpAddress(inPort)) {
+        if (hasIpAddress(context.inPort())) {
             // If the request came from outside the network, only reply if it was
             // for one of our external addresses.
 
-            interfaceService.getInterfacesByPort(inPort)
+            interfaceService.getInterfacesByPort(context.inPort())
                     .stream()
                     .filter(intf -> intf.ipAddresses()
                             .stream()
-                            .anyMatch(ia -> ia.ipAddress().equals(targetAddress)))
-                    .forEach(intf -> buildAndSendArp(targetAddress, intf.mac(), eth, inPort));
+                            .anyMatch(ia -> ia.ipAddress().equals(context.target())))
+                    .forEach(intf -> buildAndSendReply(context, intf.mac()));
 
             // Stop here and don't proxy ARPs if the port has an IP address
             return;
         }
 
         // See if we have the target host in the host store
-
-        Set<Host> hosts = hostService.getHostsByIp(targetAddress);
+        Set<Host> hosts = hostService.getHostsByIp(context.target());
 
         Host dst = null;
-        Host src = hostService.getHost(hostId(eth.getSourceMAC(),
-                vlanId(eth.getVlanID())));
+        Host src = hostService.getHost(hostId(context.srcMac(), context.vlan()));
 
         for (Host host : hosts) {
-            if (host.vlan().equals(vlan)) {
+            if (host.vlan().equals(context.vlan())) {
                 dst = host;
                 break;
             }
@@ -170,22 +178,19 @@
 
         if (src != null && dst != null) {
             // We know the target host so we can respond
-            buildAndSendArp(targetAddress, dst.mac(), eth, inPort);
+            buildAndSendReply(context, dst.mac());
             return;
         }
 
         // If the source address matches one of our external addresses
         // it could be a request from an internal host to an external
         // address. Forward it over to the correct port.
-        Ip4Address source =
-                Ip4Address.valueOf(arp.getSenderProtocolAddress());
-
         boolean matched = false;
-        Set<Interface> interfaces = interfaceService.getInterfacesByIp(source);
+        Set<Interface> interfaces = interfaceService.getInterfacesByIp(context.sender());
         for (Interface intf : interfaces) {
-            if (intf.vlan().equals(vlan)) {
+            if (intf.vlan().equals(context.vlan())) {
                 matched = true;
-                sendTo(eth, intf.connectPoint());
+                sendTo(context.packet(), intf.connectPoint());
                 break;
             }
         }
@@ -196,89 +201,33 @@
 
         // The request couldn't be resolved.
         // Flood the request on all ports except the incoming port.
-        flood(eth, inPort);
-    }
-
-    private void replyNdp(Ethernet eth, ConnectPoint inPort) {
-        IPv6 ipv6 = (IPv6) eth.getPayload();
-        ICMP6 icmpv6 = (ICMP6) ipv6.getPayload();
-        NeighborSolicitation nsol = (NeighborSolicitation) icmpv6.getPayload();
-        Ip6Address targetAddress = Ip6Address.valueOf(nsol.getTargetAddress());
-
-        VlanId vlan = vlanId(eth.getVlanID());
-
-        // If the request came from outside the network, only reply if it was
-        // for one of our external addresses.
-        if (hasIpAddress(inPort)) {
-            interfaceService.getInterfacesByPort(inPort)
-                    .stream()
-                    .filter(intf -> intf.ipAddresses()
-                            .stream()
-                            .anyMatch(ia -> ia.ipAddress().equals(targetAddress)))
-                    .forEach(intf -> buildAndSendNdp(targetAddress, intf.mac(), eth, inPort));
-            return;
-        }
-
-        // Continue with normal proxy ARP case
-
-        Set<Host> hosts = hostService.getHostsByIp(targetAddress);
-
-        Host dst = null;
-        Host src = hostService.getHost(hostId(eth.getSourceMAC(),
-                                              vlanId(eth.getVlanID())));
-
-        for (Host host : hosts) {
-            if (host.vlan().equals(vlan)) {
-                dst = host;
-                break;
-            }
-        }
-
-        if (src != null && dst != null) {
-            // We know the target host so we can respond
-            buildAndSendNdp(targetAddress, dst.mac(), eth, inPort);
-            return;
-        }
-
-        // If the source address matches one of our external addresses
-        // it could be a request from an internal host to an external
-        // address. Forward it over to the correct port.
-        Ip6Address source =
-                Ip6Address.valueOf(ipv6.getSourceAddress());
-
-        boolean matched = false;
-
-        Set<Interface> interfaces = interfaceService.getInterfacesByIp(source);
-        for (Interface intf : interfaces) {
-            if (intf.vlan().equals(vlan)) {
-                matched = true;
-                sendTo(eth, intf.connectPoint());
-                break;
-            }
-        }
-
-        if (matched) {
-            return;
-        }
-
-        // The request couldn't be resolved.
-        // Flood the request on all ports except the incoming ports.
-        flood(eth, inPort);
-    }
-    //TODO checkpoint
-
-    private void buildAndSendArp(Ip4Address srcIp, MacAddress srcMac,
-                                 Ethernet request, ConnectPoint port) {
-        sendTo(ARP.buildArpReply(srcIp, srcMac, request), port);
-    }
-
-    private void buildAndSendNdp(Ip6Address srcIp, MacAddress srcMac,
-                                 Ethernet request, ConnectPoint port) {
-        sendTo(buildNdpReply(srcIp, srcMac, request), port);
+        flood(context.packet(), context.inPort());
     }
 
     /**
-     * Outputs the given packet out the given port.
+     * Builds and sends a reply message given a request context and the resolved
+     * MAC address to answer with.
+     *
+     * @param context message context of request
+     * @param targetMac MAC address to be given in the response
+     */
+    private void buildAndSendReply(MessageContext context, MacAddress targetMac) {
+        switch (context.protocol()) {
+        case ARP:
+            sendTo(ARP.buildArpReply((Ip4Address) context.target(),
+                    targetMac, context.packet()), context.inPort());
+            break;
+        case NDP:
+            sendTo(buildNdpReply((Ip6Address) context.target(), targetMac,
+                    context.packet()), context.inPort());
+            break;
+        default:
+            break;
+        }
+    }
+
+    /**
+     * Outputs a packet out a specific port.
      *
      * @param packet  the packet to send
      * @param outPort the port to send it out
@@ -287,6 +236,12 @@
         sendTo(outPort, ByteBuffer.wrap(packet.serialize()));
     }
 
+    /**
+     * Outputs a packet out a specific port.
+     *
+     * @param outPort port to send it out
+     * @param packet packet to send
+     */
     private void sendTo(ConnectPoint outPort, ByteBuffer packet) {
         if (!edgeService.isEdgePoint(outPort)) {
             // Sanity check to make sure we don't send the packet out an
@@ -298,7 +253,7 @@
         TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
         builder.setOutput(outPort.port());
         packetService.emit(new DefaultOutboundPacket(outPort.deviceId(),
-                                                     builder.build(), packet));
+                builder.build(), packet));
     }
 
     /**
@@ -323,7 +278,7 @@
         checkNotNull(eth, REQUEST_NULL);
 
         Host h = hostService.getHost(hostId(eth.getDestinationMAC(),
-                                            vlanId(eth.getVlanID())));
+                vlanId(eth.getVlanID())));
 
         if (h == null) {
             flood(eth, inPort);
@@ -344,42 +299,24 @@
         if (ethPkt == null) {
             return false;
         }
-        if (ethPkt.getEtherType() == Ethernet.TYPE_ARP) {
-            return handleArp(context, ethPkt);
-        } else if (ethPkt.getEtherType() == Ethernet.TYPE_IPV6) {
-            return handleNdp(context, ethPkt);
-        }
-        return false;
-    }
 
-    private boolean handleArp(PacketContext context, Ethernet ethPkt) {
-        ARP arp = (ARP) ethPkt.getPayload();
+        MessageContext msgContext = createContext(ethPkt, pkt.receivedFrom());
 
-        if (arp.getOpCode() == ARP.OP_REPLY) {
-            forward(ethPkt, context.inPacket().receivedFrom());
-        } else if (arp.getOpCode() == ARP.OP_REQUEST) {
-            reply(ethPkt, context.inPacket().receivedFrom());
-        } else {
+        if (msgContext == null) {
             return false;
         }
-        context.block();
-        return true;
-    }
 
-    private boolean handleNdp(PacketContext context, Ethernet ethPkt) {
-        IPv6 ipv6 = (IPv6) ethPkt.getPayload();
-
-        if (ipv6.getNextHeader() != IPv6.PROTOCOL_ICMP6) {
+        switch (msgContext.type()) {
+        case REPLY:
+            forward(msgContext.packet(), msgContext.inPort());
+            break;
+        case REQUEST:
+            replyInternal(msgContext);
+            break;
+        default:
             return false;
         }
-        ICMP6 icmpv6 = (ICMP6) ipv6.getPayload();
-        if (icmpv6.getIcmpType() == ICMP6.NEIGHBOR_ADVERTISEMENT) {
-            forward(ethPkt, context.inPacket().receivedFrom());
-        } else if (icmpv6.getIcmpType() == ICMP6.NEIGHBOR_SOLICITATION) {
-            reply(ethPkt, context.inPacket().receivedFrom());
-        } else {
-            return false;
-        }
+
         context.block();
         return true;
     }
@@ -444,4 +381,148 @@
         eth.setPayload(ipv6);
         return eth;
     }
+
+    /**
+     * Attempts to create a MessageContext for the given Ethernet frame. If the
+     * frame is a valid ARP or NDP request or response, a context will be
+     * created.
+     *
+     * @param eth input Ethernet frame
+     * @param inPort in port
+     * @return MessageContext if the packet was ARP or NDP, otherwise null
+     */
+    private MessageContext createContext(Ethernet eth, ConnectPoint inPort) {
+        if (eth.getEtherType() == Ethernet.TYPE_ARP) {
+            return createArpContext(eth, inPort);
+        } else if (eth.getEtherType() == Ethernet.TYPE_IPV6) {
+            return createNdpContext(eth, inPort);
+        }
+
+        return null;
+    }
+
+    /**
+     * Extracts context information from ARP packets.
+     *
+     * @param eth input Ethernet frame that is thought to be ARP
+     * @param inPort in port
+     * @return MessageContext object if the packet was a valid ARP packet,
+     * otherwise null
+     */
+    private MessageContext createArpContext(Ethernet eth, ConnectPoint inPort) {
+        if (eth.getEtherType() != Ethernet.TYPE_ARP) {
+            return null;
+        }
+
+        ARP arp = (ARP) eth.getPayload();
+
+        IpAddress target = Ip4Address.valueOf(arp.getTargetProtocolAddress());
+        IpAddress sender = Ip4Address.valueOf(arp.getSenderProtocolAddress());
+
+        MessageType type;
+        if (arp.getOpCode() == ARP.OP_REQUEST) {
+            type = MessageType.REQUEST;
+        } else if (arp.getOpCode() == ARP.OP_REPLY) {
+            type = MessageType.REPLY;
+        } else {
+            return null;
+        }
+
+        return new MessageContext(eth, inPort, Protocol.ARP, type, target, sender);
+    }
+
+    /**
+     * Extracts context information from NDP packets.
+     *
+     * @param eth input Ethernet frame that is thought to be NDP
+     * @param inPort in port
+     * @return MessageContext object if the packet was a valid NDP packet,
+     * otherwise null
+     */
+    private MessageContext createNdpContext(Ethernet eth, ConnectPoint inPort) {
+        if (eth.getEtherType() != Ethernet.TYPE_IPV6) {
+            return null;
+        }
+        IPv6 ipv6 = (IPv6) eth.getPayload();
+
+        if (ipv6.getNextHeader() != IPv6.PROTOCOL_ICMP6) {
+            return null;
+        }
+        ICMP6 icmpv6 = (ICMP6) ipv6.getPayload();
+
+        IpAddress sender = Ip6Address.valueOf(ipv6.getSourceAddress());
+        IpAddress target = null;
+
+        MessageType type;
+        if (icmpv6.getIcmpType() == ICMP6.NEIGHBOR_SOLICITATION) {
+            type = MessageType.REQUEST;
+            NeighborSolicitation nsol = (NeighborSolicitation) icmpv6.getPayload();
+            target = Ip6Address.valueOf(nsol.getTargetAddress());
+        } else if (icmpv6.getIcmpType() == ICMP6.NEIGHBOR_ADVERTISEMENT) {
+            type = MessageType.REPLY;
+        } else {
+            return null;
+        }
+
+        return new MessageContext(eth, inPort, Protocol.NDP, type, target, sender);
+    }
+
+    /**
+     * Provides context information for a particular ARP or NDP message, with
+     * a unified interface to access data regardless of protocol.
+     */
+    private class MessageContext {
+        private Protocol protocol;
+        private MessageType type;
+
+        private IpAddress target;
+        private IpAddress sender;
+
+        private Ethernet eth;
+        private ConnectPoint inPort;
+
+
+        public MessageContext(Ethernet eth, ConnectPoint inPort,
+                              Protocol protocol, MessageType type,
+                              IpAddress target, IpAddress sender) {
+            this.eth = eth;
+            this.inPort = inPort;
+            this.protocol = protocol;
+            this.type = type;
+            this.target = target;
+            this.sender = sender;
+        }
+
+        public ConnectPoint inPort() {
+            return inPort;
+        }
+
+        public Ethernet packet() {
+            return eth;
+        }
+
+        public Protocol protocol() {
+            return protocol;
+        }
+
+        public MessageType type() {
+            return type;
+        }
+
+        public VlanId vlan() {
+            return VlanId.vlanId(eth.getVlanID());
+        }
+
+        public MacAddress srcMac() {
+            return MacAddress.valueOf(eth.getSourceMACAddress());
+        }
+
+        public IpAddress target() {
+            return target;
+        }
+
+        public IpAddress sender() {
+            return sender;
+        }
+    }
 }