Implementation of the NeighbourResolutionService.

Change-Id: I41fc48578df3027ec71ee7369171c8988ee7a85e
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/neighbour/DefaultNeighbourMessageHandler.java b/incubator/api/src/main/java/org/onosproject/incubator/net/neighbour/DefaultNeighbourMessageHandler.java
index 3686c47..86fb848 100644
--- a/incubator/api/src/main/java/org/onosproject/incubator/net/neighbour/DefaultNeighbourMessageHandler.java
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/neighbour/DefaultNeighbourMessageHandler.java
@@ -21,36 +21,54 @@
 
 import java.util.Set;
 
+import static org.onlab.packet.VlanId.vlanId;
 import static org.onosproject.net.HostId.hostId;
 
 /**
- * Default neighbour message handler which implements basic proxying on a flat
+ * Default neighbour message handler which implements basic proxying on an
  * L2 network (i.e. ProxyArp behaviour).
  */
 public class DefaultNeighbourMessageHandler implements NeighbourMessageHandler {
     @Override
     public void handleMessage(NeighbourMessageContext context, HostService hostService) {
-        // See if we have the target host in the host store
-        Set<Host> hosts = hostService.getHostsByIp(context.target());
+        switch (context.type()) {
+        case REPLY:
+            Host h = hostService.getHost(hostId(context.packet().getDestinationMAC(),
+                    vlanId(context.packet().getVlanID())));
 
-        Host dst = null;
-        Host src = hostService.getHost(hostId(context.srcMac(), context.vlan()));
-
-        for (Host host : hosts) {
-            if (host.vlan().equals(context.vlan())) {
-                dst = host;
-                break;
+            if (h == null) {
+                context.flood();
+            } else {
+                context.proxy(h.location());
             }
+            break;
+        case REQUEST:
+            // See if we have the target host in the host store
+            Set<Host> hosts = hostService.getHostsByIp(context.target());
+
+            Host dst = null;
+            Host src = hostService.getHost(hostId(context.srcMac(), context.vlan()));
+
+            for (Host host : hosts) {
+                if (host.vlan().equals(context.vlan())) {
+                    dst = host;
+                    break;
+                }
+            }
+
+            if (src != null && dst != null) {
+                // We know the target host so we can respond
+                context.reply(dst.mac());
+                return;
+            }
+
+            // The request couldn't be resolved.
+            // Flood the request on all ports except the incoming port.
+            context.flood();
+            break;
+        default:
+            break;
         }
 
-        if (src != null && dst != null) {
-            // We know the target host so we can respond
-            context.reply(dst.mac());
-            return;
-        }
-
-        // The request couldn't be resolved.
-        // Flood the request on all ports except the incoming port.
-        context.flood();
     }
 }
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/neighbour/NeighbourMessageContext.java b/incubator/api/src/main/java/org/onosproject/incubator/net/neighbour/NeighbourMessageContext.java
index 307a783..f498f17 100644
--- a/incubator/api/src/main/java/org/onosproject/incubator/net/neighbour/NeighbourMessageContext.java
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/neighbour/NeighbourMessageContext.java
@@ -24,8 +24,6 @@
 import org.onosproject.incubator.net.intf.Interface;
 import org.onosproject.net.ConnectPoint;
 
-import static com.google.common.base.Preconditions.checkState;
-
 /**
  * Context of an incoming neighbor message (e.g. ARP, NDP).
  *
@@ -34,156 +32,91 @@
  * response to the incoming message.</p>
  */
 @Beta
-public class NeighbourMessageContext {
-
-    private final NeighbourProtocol protocol;
-    private final NeighbourMessageType type;
-
-    private final IpAddress target;
-    private final IpAddress sender;
-
-    private final Ethernet eth;
-    private final ConnectPoint inPort;
-
-    private final NeighbourMessageActions actions;
-
-    /**
-     * Creates a new neighbour message context.
-     *
-     * @param actions actions
-     * @param eth ethernet frame
-     * @param inPort incoming port
-     * @param protocol message protocol
-     * @param type message type
-     * @param target target IP address
-     * @param sender sender IP address
-     */
-    public NeighbourMessageContext(NeighbourMessageActions actions,
-                                   Ethernet eth, ConnectPoint inPort,
-                                   NeighbourProtocol protocol, NeighbourMessageType type,
-                                   IpAddress target, IpAddress sender) {
-        this.actions = actions;
-        this.eth = eth;
-        this.inPort = inPort;
-        this.protocol = protocol;
-        this.type = type;
-        this.target = target;
-        this.sender = sender;
-    }
-
+public interface NeighbourMessageContext {
     /**
      * Gets the port where the packet came in to the network.
      *
      * @return connect point
      */
-    public ConnectPoint inPort() {
-        return inPort;
-    }
+    ConnectPoint inPort();
 
     /**
      * Gets the full parsed representation of the packet.
      *
      * @return ethernet header
      */
-    public Ethernet packet() {
-        return eth;
-    }
+    Ethernet packet();
 
     /**
      * Gets the protocol of the packet.
      *
      * @return protocol
      */
-    public NeighbourProtocol protocol() {
-        return protocol;
-    }
+    NeighbourProtocol protocol();
 
     /**
      * Gets the message type of the packet.
      *
      * @return message type
      */
-    public NeighbourMessageType type() {
-        return type;
-    }
+    NeighbourMessageType type();
 
     /**
      * Gets the vlan of the packet, if any.
      *
      * @return vlan
      */
-    public VlanId vlan() {
-        return VlanId.vlanId(eth.getVlanID());
-    }
+    VlanId vlan();
 
     /**
      * Gets the source MAC address of the message.
      *
      * @return source MAC address
      */
-    public MacAddress srcMac() {
-        return MacAddress.valueOf(eth.getSourceMACAddress());
-    }
+    MacAddress srcMac();
 
     /**
      * Gets the target IP address of the message.
      *
      * @return target IP address
      */
-    public IpAddress target() {
-        return target;
-    }
+    IpAddress target();
 
     /**
      * Gets the source IP address of the message.
      *
      * @return source IP address
      */
-    public IpAddress sender() {
-        return sender;
-    }
+    IpAddress sender();
 
     /**
      * Proxies the message to a given output port.
      *
      * @param outPort output port
      */
-    public void proxy(ConnectPoint outPort) {
-        actions.proxy(this, outPort);
-    }
+    void proxy(ConnectPoint outPort);
 
     /**
      * Proxies the message to a given interface.
      *
      * @param outIntf output interface
      */
-    public void proxy(Interface outIntf) {
-        actions.proxy(this, outIntf);
-    }
+    void proxy(Interface outIntf);
 
     /**
      * Replies to the request message with a given MAC address.
      *
      * @param targetMac target MAC address
      */
-    public void reply(MacAddress targetMac) {
-        checkState(type == NeighbourMessageType.REQUEST, "can only reply to requests");
-
-        actions.reply(this, targetMac);
-    }
+    void reply(MacAddress targetMac);
 
     /**
      * Floods the incoming message out all ports except the input port.
      */
-    public void flood() {
-        actions.flood(this);
-    }
+    void flood();
 
     /**
      * Drops the incoming message.
      */
-    public void drop() {
-        actions.drop(this);
-    }
-
+    void drop();
 }
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/neighbour/NeighbourResolutionService.java b/incubator/api/src/main/java/org/onosproject/incubator/net/neighbour/NeighbourResolutionService.java
index f7adb04..94401cc 100644
--- a/incubator/api/src/main/java/org/onosproject/incubator/net/neighbour/NeighbourResolutionService.java
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/neighbour/NeighbourResolutionService.java
@@ -45,4 +45,20 @@
      */
     void registerNeighbourHandler(Interface intf, NeighbourMessageHandler handler);
 
+    /**
+     * Unregisters a neighbour message handler that was assigned to a connect
+     * point.
+     *
+     * @param connectPoint connect point
+     * @param handler neighbour message handler
+     */
+    void unregisterNeighbourHandler(ConnectPoint connectPoint, NeighbourMessageHandler handler);
+
+    /**
+     * Unregisters a neighbour message handler that was assigned to an interface.
+     *
+     * @param intf interface
+     * @param handler neighbour message handler
+     */
+    void unregisterNeighbourHandler(Interface intf, NeighbourMessageHandler handler);
 }
diff --git a/incubator/net/src/main/java/org/onosproject/incubator/net/neighbour/impl/DefaultNeighbourMessageContext.java b/incubator/net/src/main/java/org/onosproject/incubator/net/neighbour/impl/DefaultNeighbourMessageContext.java
new file mode 100644
index 0000000..e056f79
--- /dev/null
+++ b/incubator/net/src/main/java/org/onosproject/incubator/net/neighbour/impl/DefaultNeighbourMessageContext.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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.
+ */
+
+package org.onosproject.incubator.net.neighbour.impl;
+
+import com.google.common.annotations.Beta;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP6;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.packet.ndp.NeighborSolicitation;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.neighbour.NeighbourMessageActions;
+import org.onosproject.incubator.net.neighbour.NeighbourMessageContext;
+import org.onosproject.incubator.net.neighbour.NeighbourMessageType;
+import org.onosproject.incubator.net.neighbour.NeighbourProtocol;
+import org.onosproject.net.ConnectPoint;
+
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Default implementation of a neighbour message context.
+ */
+@Beta
+public class DefaultNeighbourMessageContext implements NeighbourMessageContext {
+
+    private final NeighbourProtocol protocol;
+    private final NeighbourMessageType type;
+
+    private final IpAddress target;
+    private final IpAddress sender;
+
+    private final Ethernet eth;
+    private final ConnectPoint inPort;
+
+    private final NeighbourMessageActions actions;
+
+    /**
+     * Creates a new neighbour message context.
+     *
+     * @param actions actions
+     * @param eth ethernet frame
+     * @param inPort incoming port
+     * @param protocol message protocol
+     * @param type message type
+     * @param target target IP address
+     * @param sender sender IP address
+     */
+    DefaultNeighbourMessageContext(NeighbourMessageActions actions,
+                                   Ethernet eth, ConnectPoint inPort,
+                                   NeighbourProtocol protocol,
+                                   NeighbourMessageType type,
+                                   IpAddress target, IpAddress sender) {
+        this.actions = actions;
+        this.eth = eth;
+        this.inPort = inPort;
+        this.protocol = protocol;
+        this.type = type;
+        this.target = target;
+        this.sender = sender;
+    }
+
+    @Override
+    public ConnectPoint inPort() {
+        return inPort;
+    }
+
+    @Override
+    public Ethernet packet() {
+        return eth;
+    }
+
+    @Override
+    public NeighbourProtocol protocol() {
+        return protocol;
+    }
+
+    @Override
+    public NeighbourMessageType type() {
+        return type;
+    }
+
+    @Override
+    public VlanId vlan() {
+        return VlanId.vlanId(eth.getVlanID());
+    }
+
+    @Override
+    public MacAddress srcMac() {
+        return MacAddress.valueOf(eth.getSourceMACAddress());
+    }
+
+    @Override
+    public IpAddress target() {
+        return target;
+    }
+
+    @Override
+    public IpAddress sender() {
+        return sender;
+    }
+
+    @Override
+    public void proxy(ConnectPoint outPort) {
+        actions.proxy(this, outPort);
+    }
+
+    @Override
+    public void proxy(Interface outIntf) {
+        actions.proxy(this, outIntf);
+    }
+
+    @Override
+    public void reply(MacAddress targetMac) {
+        checkState(type == NeighbourMessageType.REQUEST, "can only reply to requests");
+
+        actions.reply(this, targetMac);
+    }
+
+    @Override
+    public void flood() {
+        actions.flood(this);
+    }
+
+    @Override
+    public void drop() {
+        actions.drop(this);
+    }
+
+    /**
+     * 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
+     */
+    public static NeighbourMessageContext createContext(Ethernet eth,
+                                                        ConnectPoint inPort,
+                                                        NeighbourMessageActions actions) {
+        if (eth.getEtherType() == Ethernet.TYPE_ARP) {
+            return createArpContext(eth, inPort, actions);
+        } else if (eth.getEtherType() == Ethernet.TYPE_IPV6) {
+            return createNdpContext(eth, inPort, actions);
+        }
+
+        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 static NeighbourMessageContext createArpContext(Ethernet eth,
+                                                            ConnectPoint inPort,
+                                                            NeighbourMessageActions actions) {
+        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());
+
+        NeighbourMessageType type;
+        if (arp.getOpCode() == ARP.OP_REQUEST) {
+            type = NeighbourMessageType.REQUEST;
+        } else if (arp.getOpCode() == ARP.OP_REPLY) {
+            type = NeighbourMessageType.REPLY;
+        } else {
+            return null;
+        }
+
+        return new DefaultNeighbourMessageContext(actions, eth, inPort,
+                NeighbourProtocol.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 static NeighbourMessageContext createNdpContext(Ethernet eth,
+                                                            ConnectPoint inPort,
+                                                            NeighbourMessageActions actions) {
+        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;
+
+        NeighbourMessageType type;
+        if (icmpv6.getIcmpType() == ICMP6.NEIGHBOR_SOLICITATION) {
+            type = NeighbourMessageType.REQUEST;
+            NeighborSolicitation nsol = (NeighborSolicitation) icmpv6.getPayload();
+            target = Ip6Address.valueOf(nsol.getTargetAddress());
+        } else if (icmpv6.getIcmpType() == ICMP6.NEIGHBOR_ADVERTISEMENT) {
+            type = NeighbourMessageType.REPLY;
+        } else {
+            return null;
+        }
+
+        return new DefaultNeighbourMessageContext(actions, eth, inPort,
+                NeighbourProtocol.NDP, type, target, sender);
+    }
+
+}
diff --git a/incubator/net/src/main/java/org/onosproject/incubator/net/neighbour/impl/NeighbourPacketManager.java b/incubator/net/src/main/java/org/onosproject/incubator/net/neighbour/impl/NeighbourPacketManager.java
new file mode 100644
index 0000000..0e665d3
--- /dev/null
+++ b/incubator/net/src/main/java/org/onosproject/incubator/net/neighbour/impl/NeighbourPacketManager.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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.
+ */
+
+package org.onosproject.incubator.net.neighbour.impl;
+
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Multimaps;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP6;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.packet.ndp.NeighborAdvertisement;
+import org.onlab.packet.ndp.NeighborDiscoveryOptions;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.neighbour.NeighbourMessageActions;
+import org.onosproject.incubator.net.neighbour.NeighbourMessageContext;
+import org.onosproject.incubator.net.neighbour.NeighbourMessageHandler;
+import org.onosproject.incubator.net.neighbour.NeighbourResolutionService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.edge.EdgePortService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.packet.Ethernet.TYPE_ARP;
+import static org.onlab.packet.Ethernet.TYPE_IPV6;
+import static org.onlab.packet.ICMP6.NEIGHBOR_ADVERTISEMENT;
+import static org.onlab.packet.ICMP6.NEIGHBOR_SOLICITATION;
+import static org.onlab.packet.IPv6.PROTOCOL_ICMP6;
+import static org.onosproject.net.packet.PacketPriority.CONTROL;
+
+/**
+ * Manages handlers for neighbour messages.
+ */
+@Service
+@Component(immediate = true)
+public class NeighbourPacketManager implements NeighbourResolutionService {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected EdgePortService edgeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ComponentConfigService componentConfigService;
+
+    @Property(name = "ndpEnabled", boolValue = false,
+            label = "Enable IPv6 neighbour discovery")
+    protected boolean ndpEnabled = false;
+
+    private static final String APP_NAME = "org.onosproject.neighbour";
+    private ApplicationId appId;
+
+    private ListMultimap<ConnectPoint, HandlerRegistration> packetHandlers =
+            Multimaps.synchronizedListMultimap(LinkedListMultimap.create());
+
+    private final InternalPacketProcessor processor = new InternalPacketProcessor();
+    private final InternalNeighbourMessageActions actions = new InternalNeighbourMessageActions();
+
+    @Activate
+    protected void activate(ComponentContext context) {
+        appId = coreService.registerApplication(APP_NAME);
+
+        componentConfigService.registerProperties(getClass());
+        modified(context);
+
+        packetService.addProcessor(processor, PacketProcessor.director(1));
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        cancelPackets();
+        packetService.removeProcessor(processor);
+        componentConfigService.unregisterProperties(getClass(), false);
+    }
+
+    @Modified
+    protected void modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+        Boolean flag;
+
+        flag = Tools.isPropertyEnabled(properties, "ndpEnabled");
+        if (flag != null) {
+            ndpEnabled = flag;
+            log.info("IPv6 neighbor discovery is {}",
+                    ndpEnabled ? "enabled" : "disabled");
+        }
+
+        requestPackets();
+    }
+
+    private void requestPackets() {
+        packetService.requestPackets(buildArpSelector(), CONTROL, appId);
+
+        if (ndpEnabled) {
+            packetService.requestPackets(buildNeighborSolicitationSelector(),
+                    CONTROL, appId);
+            packetService.requestPackets(buildNeighborAdvertisementSelector(),
+                    CONTROL, appId);
+        } else {
+            packetService.cancelPackets(buildNeighborSolicitationSelector(),
+                    CONTROL, appId);
+            packetService.cancelPackets(buildNeighborAdvertisementSelector(),
+                    CONTROL, appId);
+        }
+    }
+
+    private void cancelPackets() {
+        packetService.cancelPackets(buildArpSelector(), CONTROL, appId);
+        packetService.cancelPackets(buildNeighborSolicitationSelector(),
+                CONTROL, appId);
+        packetService.cancelPackets(buildNeighborAdvertisementSelector(),
+                CONTROL, appId);
+    }
+
+    private TrafficSelector buildArpSelector() {
+        return DefaultTrafficSelector.builder()
+                .matchEthType(TYPE_ARP)
+                .build();
+    }
+
+    private TrafficSelector buildNeighborSolicitationSelector() {
+        return DefaultTrafficSelector.builder()
+                .matchEthType(TYPE_IPV6)
+                .matchIPProtocol(PROTOCOL_ICMP6)
+                .matchIcmpv6Type(NEIGHBOR_SOLICITATION)
+                .build();
+    }
+
+    private TrafficSelector buildNeighborAdvertisementSelector() {
+        return DefaultTrafficSelector.builder()
+                .matchEthType(TYPE_IPV6)
+                .matchIPProtocol(PROTOCOL_ICMP6)
+                .matchIcmpv6Type(NEIGHBOR_ADVERTISEMENT)
+                .build();
+    }
+
+    @Override
+    public void registerNeighbourHandler(ConnectPoint connectPoint, NeighbourMessageHandler handler) {
+        packetHandlers.put(connectPoint, new HandlerRegistration(handler));
+    }
+
+    @Override
+    public void registerNeighbourHandler(Interface intf, NeighbourMessageHandler handler) {
+        packetHandlers.put(intf.connectPoint(), new HandlerRegistration(handler, intf));
+    }
+
+    @Override
+    public void unregisterNeighbourHandler(ConnectPoint connectPoint, NeighbourMessageHandler handler) {
+        packetHandlers.remove(connectPoint, handler);
+    }
+
+    @Override
+    public void unregisterNeighbourHandler(Interface intf, NeighbourMessageHandler handler) {
+        packetHandlers.remove(intf.connectPoint(), handler);
+    }
+
+    public void handlePacket(PacketContext context) {
+        InboundPacket pkt = context.inPacket();
+        Ethernet ethPkt = pkt.parsed();
+
+        NeighbourMessageContext msgContext =
+                DefaultNeighbourMessageContext.createContext(ethPkt, pkt.receivedFrom(), actions);
+
+        if (msgContext == null) {
+            return;
+        }
+
+        handleMessage(msgContext);
+
+        context.block();
+    }
+
+    private void handleMessage(NeighbourMessageContext context) {
+        List<HandlerRegistration> handlers = packetHandlers.get(context.inPort());
+
+        handlers.forEach(registration -> {
+            if (registration.intf() == null || matches(context, registration.intf())) {
+                registration.handler().handleMessage(context, hostService);
+            }
+        });
+    }
+
+    private boolean matches(NeighbourMessageContext context, Interface intf) {
+        checkNotNull(context);
+        checkNotNull(intf);
+
+        boolean matches = true;
+        if (!intf.vlan().equals(VlanId.NONE) && !intf.vlan().equals(context.vlan())) {
+            matches = false;
+        }
+
+        return matches;
+    }
+
+
+    private void reply(NeighbourMessageContext 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
+     */
+    private void sendTo(Ethernet packet, ConnectPoint outPort) {
+        sendTo(ByteBuffer.wrap(packet.serialize()), outPort);
+    }
+
+    /**
+     * Outputs a packet out a specific port.
+     *
+     * @param packet packet to send
+     * @param outPort port to send it out
+     */
+    private void sendTo(ByteBuffer packet, ConnectPoint outPort) {
+        if (!edgeService.isEdgePoint(outPort)) {
+            // Sanity check to make sure we don't send the packet out an
+            // internal port and create a loop (could happen due to
+            // misconfiguration).
+            return;
+        }
+
+        TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
+        builder.setOutput(outPort.port());
+        packetService.emit(new DefaultOutboundPacket(outPort.deviceId(),
+                builder.build(), packet));
+    }
+
+    /**
+     * Builds an Neighbor Discovery reply based on a request.
+     *
+     * @param srcIp   the IP address to use as the reply source
+     * @param srcMac  the MAC address to use as the reply source
+     * @param request the Neighbor Solicitation request we got
+     * @return an Ethernet frame containing the Neighbor Advertisement reply
+     */
+    private Ethernet buildNdpReply(Ip6Address srcIp, MacAddress srcMac,
+                                   Ethernet request) {
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(request.getSourceMAC());
+        eth.setSourceMACAddress(srcMac);
+        eth.setEtherType(Ethernet.TYPE_IPV6);
+        eth.setVlanID(request.getVlanID());
+
+        IPv6 requestIp = (IPv6) request.getPayload();
+        IPv6 ipv6 = new IPv6();
+        ipv6.setSourceAddress(srcIp.toOctets());
+        ipv6.setDestinationAddress(requestIp.getSourceAddress());
+        ipv6.setHopLimit((byte) 255);
+
+        ICMP6 icmp6 = new ICMP6();
+        icmp6.setIcmpType(ICMP6.NEIGHBOR_ADVERTISEMENT);
+        icmp6.setIcmpCode((byte) 0);
+
+        NeighborAdvertisement nadv = new NeighborAdvertisement();
+        nadv.setTargetAddress(srcIp.toOctets());
+        nadv.setSolicitedFlag((byte) 1);
+        nadv.setOverrideFlag((byte) 1);
+        nadv.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS,
+                srcMac.toBytes());
+
+        icmp6.setPayload(nadv);
+        ipv6.setPayload(icmp6);
+        eth.setPayload(ipv6);
+        return eth;
+    }
+
+    /**
+     * Stores a neighbour message handler registration.
+     */
+    private class HandlerRegistration {
+        private final Interface intf;
+        private final NeighbourMessageHandler handler;
+
+        /**
+         * Creates a new handler registration.
+         *
+         * @param handler neighbour message handler
+         */
+        public HandlerRegistration(NeighbourMessageHandler handler) {
+            this(handler, null);
+        }
+
+        /**
+         * Creates a new handler registration.
+         *
+         * @param handler neighbour message handler
+         * @param intf interface
+         */
+        public HandlerRegistration(NeighbourMessageHandler handler, Interface intf) {
+            this.intf = intf;
+            this.handler = handler;
+        }
+
+        /**
+         * Gets the interface of the registration.
+         *
+         * @return interface
+         */
+        public Interface intf() {
+            return intf;
+        }
+
+        /**
+         * Gets the neighbour message handler.
+         *
+         * @return message handler
+         */
+        public NeighbourMessageHandler handler() {
+            return handler;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+
+            if (!(other instanceof HandlerRegistration)) {
+                return false;
+            }
+
+            HandlerRegistration that = (HandlerRegistration) other;
+
+            return Objects.equals(intf, that.intf) &&
+                    Objects.equals(handler, that.handler);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(intf, handler);
+        }
+    }
+
+    /**
+     * Packet processor for incoming packets.
+     */
+    private class InternalPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            // Stop processing if the packet has been handled, since we
+            // can't do any more to it.
+            if (context.isHandled()) {
+                return;
+            }
+
+            InboundPacket pkt = context.inPacket();
+            Ethernet ethPkt = pkt.parsed();
+            if (ethPkt == null) {
+                return;
+            }
+
+            if (ethPkt.getEtherType() == TYPE_ARP) {
+                // handle ARP packets
+                handlePacket(context);
+            } else if (ethPkt.getEtherType() == TYPE_IPV6) {
+                IPv6 ipv6 = (IPv6) ethPkt.getPayload();
+                if (ipv6.getNextHeader() == IPv6.PROTOCOL_ICMP6) {
+                    ICMP6 icmp6 = (ICMP6) ipv6.getPayload();
+                    if (icmp6.getIcmpType() == NEIGHBOR_SOLICITATION ||
+                            icmp6.getIcmpType() == NEIGHBOR_ADVERTISEMENT) {
+                        // handle ICMPv6 solicitations and advertisements (NDP)
+                        handlePacket(context);
+                    }
+                }
+            }
+        }
+    }
+
+    private class InternalNeighbourMessageActions implements NeighbourMessageActions {
+
+        @Override
+        public void reply(NeighbourMessageContext context, MacAddress targetMac) {
+            NeighbourPacketManager.this.reply(context, targetMac);
+        }
+
+        @Override
+        public void proxy(NeighbourMessageContext context, ConnectPoint outPort) {
+            sendTo(context.packet(), outPort);
+        }
+
+        @Override
+        public void proxy(NeighbourMessageContext context, Interface outIntf) {
+
+        }
+
+        @Override
+        public void flood(NeighbourMessageContext context) {
+            edgeService.getEdgePoints().forEach(connectPoint -> {
+                if (!connectPoint.equals(context.inPort())) {
+                    sendTo(context.packet(), connectPoint);
+                }
+            });
+        }
+
+        @Override
+        public void drop(NeighbourMessageContext context) {
+
+        }
+    }
+
+}
diff --git a/incubator/net/src/main/java/org/onosproject/incubator/net/neighbour/impl/package-info.java b/incubator/net/src/main/java/org/onosproject/incubator/net/neighbour/impl/package-info.java
new file mode 100644
index 0000000..e4c43a1
--- /dev/null
+++ b/incubator/net/src/main/java/org/onosproject/incubator/net/neighbour/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/**
+ * Implementation of neighbour resolution service.
+ */
+package org.onosproject.incubator.net.neighbour.impl;