Reimplemented the ProxyArp app on top of the NeighbourResolutionService.

Also some small API changes to track app ID of handler registrations, and
improved interface matching in NeighbourPacketManager.

Added CLI to view handler registrations.

Change-Id: I8cd0b91a16d9ec60287b65f9d8fc5e3cd87560e8
diff --git a/apps/proxyarp/BUCK b/apps/proxyarp/BUCK
index 5406f4e..70e3c9d 100644
--- a/apps/proxyarp/BUCK
+++ b/apps/proxyarp/BUCK
@@ -1,5 +1,6 @@
 COMPILE_DEPS = [
     '//lib:CORE_DEPS',
+    '//incubator/api:onos-incubator-api',
 ]
 
 osgi_jar (
diff --git a/apps/proxyarp/pom.xml b/apps/proxyarp/pom.xml
index 86664f4..0d2b3a9 100644
--- a/apps/proxyarp/pom.xml
+++ b/apps/proxyarp/pom.xml
@@ -43,6 +43,10 @@
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.compendium</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-incubator-api</artifactId>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/apps/proxyarp/src/main/java/org/onosproject/proxyarp/DefaultProxyArp.java b/apps/proxyarp/src/main/java/org/onosproject/proxyarp/DefaultProxyArp.java
new file mode 100644
index 0000000..175989f
--- /dev/null
+++ b/apps/proxyarp/src/main/java/org/onosproject/proxyarp/DefaultProxyArp.java
@@ -0,0 +1,97 @@
+/*
+ * 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.proxyarp;
+
+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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.neighbour.DefaultNeighbourMessageHandler;
+import org.onosproject.incubator.net.neighbour.NeighbourResolutionService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.edge.EdgePortEvent;
+import org.onosproject.net.edge.EdgePortListener;
+import org.onosproject.net.edge.EdgePortService;
+
+/**
+ * Implements proxy ARP and NDP functionality by considering the entire network
+ * as a single L2 broadcast domain.
+ * <p>
+ * This application maintains a DefaultNeighbourMessageHandler on all edge ports
+ * in the network, and the handler implements the desired proxying functionality.
+ * </p>
+ */
+@Component(immediate = true)
+public class DefaultProxyArp {
+
+    private static final String APP_NAME = "org.onosproject.proxyarp";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected EdgePortService edgeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NeighbourResolutionService neighbourResolutionService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    private ApplicationId appId;
+
+    private InternalEdgeListener edgeListener = new InternalEdgeListener();
+    private DefaultNeighbourMessageHandler defaultHandler = new DefaultNeighbourMessageHandler();
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(APP_NAME);
+
+        edgeService.addListener(edgeListener);
+        edgeService.getEdgePoints().forEach(this::addDefault);
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        edgeService.removeListener(edgeListener);
+        neighbourResolutionService.unregisterNeighbourHandlers(appId);
+    }
+
+    private void addDefault(ConnectPoint port) {
+        neighbourResolutionService.registerNeighbourHandler(port, defaultHandler, appId);
+    }
+
+    private void removeDefault(ConnectPoint port) {
+        neighbourResolutionService.unregisterNeighbourHandler(port, defaultHandler, appId);
+    }
+
+    private class InternalEdgeListener implements EdgePortListener {
+        @Override
+        public void event(EdgePortEvent event) {
+            switch (event.type()) {
+            case EDGE_PORT_ADDED:
+                addDefault(event.subject());
+                break;
+            case EDGE_PORT_REMOVED:
+                removeDefault(event.subject());
+                break;
+            default:
+                break;
+            }
+        }
+    }
+}
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 612301e..d441aee 100644
--- a/apps/proxyarp/src/main/java/org/onosproject/proxyarp/ProxyArp.java
+++ b/apps/proxyarp/src/main/java/org/onosproject/proxyarp/ProxyArp.java
@@ -51,8 +51,11 @@
 
 /**
  * Sample reactive proxy arp application.
+ *
+ * @deprecated in Hummingbird release
  */
-@Component(immediate = true)
+@Deprecated
+@Component(enabled = false)
 public class ProxyArp {
 
     private final Logger log = getLogger(getClass());
diff --git a/apps/proxyarp/src/main/java/org/onosproject/proxyarp/package-info.java b/apps/proxyarp/src/main/java/org/onosproject/proxyarp/package-info.java
index d8ed846..960382f 100644
--- a/apps/proxyarp/src/main/java/org/onosproject/proxyarp/package-info.java
+++ b/apps/proxyarp/src/main/java/org/onosproject/proxyarp/package-info.java
@@ -15,6 +15,6 @@
  */
 
 /**
- * Proxy Arp  application that handles arp resolution for you.
+ * Application that handles ARP and NDP resolution on a single L2 broadcast domain.
  */
 package org.onosproject.proxyarp;
diff --git a/cli/src/main/java/org/onosproject/cli/net/NeighbourHandlersListCommand.java b/cli/src/main/java/org/onosproject/cli/net/NeighbourHandlersListCommand.java
new file mode 100644
index 0000000..1fd618e
--- /dev/null
+++ b/cli/src/main/java/org/onosproject/cli/net/NeighbourHandlersListCommand.java
@@ -0,0 +1,46 @@
+/*
+ * 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.cli.net;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.neighbour.NeighbourResolutionService;
+
+/**
+ * Lists neighbour message handlers.
+ */
+@Command(scope = "onos", name = "neighbour-handlers",
+        description = "Lists neighbour message handlers")
+public class NeighbourHandlersListCommand extends AbstractShellCommand {
+
+    private static final String FORMAT = "%20s: interface=%s, class=%s";
+
+    @Override
+    protected void execute() {
+        NeighbourResolutionService service = get(NeighbourResolutionService.class);
+
+        service.getHandlerRegistrations().forEach((cp, list) -> {
+            list.forEach(hr -> print(FORMAT, cp, intfToName(hr.intf()),
+                    hr.handler().getClass().getCanonicalName()));
+        });
+    }
+
+    private String intfToName(Interface intf) {
+        return (intf == null) ?  "(None)" : intf.name();
+    }
+}
diff --git a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 5664cc3..4da1d8b 100644
--- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -485,6 +485,9 @@
         <command>
             <action class="org.onosproject.cli.net.PacketProcessorsListCommand"/>
         </command>
+        <command>
+            <action class="org.onosproject.cli.net.NeighbourHandlersListCommand"/>
+        </command>
 
         <command>
             <action class="org.onosproject.cli.net.AddTestFlowsCommand"/>
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 37d91a8..015a263 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
@@ -22,8 +22,11 @@
 
 /**
  * Service for processing ARP or NDP requests on behalf of applications.
+ *
+ * @deprecated in Hummingbird release. Use NeighbourResolutionService instead.
  */
 // TODO: move to the peer host package
+@Deprecated
 public interface ProxyArpService {
 
     /**
diff --git a/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpStore.java b/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpStore.java
index e386fb1..708f92f 100644
--- a/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpStore.java
+++ b/core/api/src/main/java/org/onosproject/net/proxyarp/ProxyArpStore.java
@@ -22,7 +22,11 @@
 
 /**
  * State distribution mechanism for the proxy ARP service.
+ *
+ * @deprecated in Hummingbird release. This is no longer necessary as there are
+ * other solutions for the problem this was solving.
  */
+@Deprecated
 public interface ProxyArpStore {
 
     /**
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 0c84651..3937e9c 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
@@ -63,7 +63,12 @@
 import static org.slf4j.LoggerFactory.getLogger;
 import static org.onosproject.security.AppPermission.Type.*;
 
-
+/**
+ * Implementation of the proxy ARP service.
+ *
+ * @deprecated in Hummingbird release
+ */
+@Deprecated
 @Component(immediate = true)
 @Service
 public class ProxyArpManager implements ProxyArpService {
diff --git a/core/store/dist/src/main/java/org/onosproject/store/proxyarp/impl/DistributedProxyArpStore.java b/core/store/dist/src/main/java/org/onosproject/store/proxyarp/impl/DistributedProxyArpStore.java
index f388aa9..00768dc 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/proxyarp/impl/DistributedProxyArpStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/proxyarp/impl/DistributedProxyArpStore.java
@@ -50,7 +50,11 @@
 
 /**
  * Implementation of proxy ARP distribution mechanism.
+ *
+ * @deprecated in Hummingbird release. This is no longer necessary as there are
+ * other solutions for the problem this was solving.
  */
+@Deprecated
 @Component(immediate = true)
 @Service
 public class DistributedProxyArpStore implements ProxyArpStore {
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/neighbour/NeighbourHandlerRegistration.java b/incubator/api/src/main/java/org/onosproject/incubator/net/neighbour/NeighbourHandlerRegistration.java
new file mode 100644
index 0000000..cc800a3
--- /dev/null
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/neighbour/NeighbourHandlerRegistration.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.incubator.net.intf.Interface;
+
+/**
+ * Information about the registration of a neighbour message handler.
+ */
+public interface NeighbourHandlerRegistration {
+
+    /**
+     * Gets the interface of the registration.
+     *
+     * @return interface
+     */
+    Interface intf();
+
+    /**
+     * Gets the neighbour message handler.
+     *
+     * @return message handler
+     */
+    NeighbourMessageHandler handler();
+
+    /**
+     * Gets the ID of the application that registered the handler.
+     *
+     * @return application ID
+     */
+    ApplicationId appId();
+}
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 f498f17..0c609ae 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
@@ -76,6 +76,16 @@
     MacAddress srcMac();
 
     /**
+     * Gets the destination MAC address of the message.
+     * <p>
+     * Only valid for reply packets, will be null for request packets.
+     * </p>
+     *
+     * @return target MAC address
+     */
+    MacAddress dstMac();
+
+    /**
      * Gets the target IP address of the message.
      *
      * @return target IP address
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 94401cc..4e4adb9 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
@@ -17,9 +17,13 @@
 package org.onosproject.incubator.net.neighbour;
 
 import com.google.common.annotations.Beta;
+import org.onosproject.core.ApplicationId;
 import org.onosproject.incubator.net.intf.Interface;
 import org.onosproject.net.ConnectPoint;
 
+import java.util.Collection;
+import java.util.Map;
+
 /**
  * Provides a means of registering logic for handling neighbour messages.
  */
@@ -32,8 +36,10 @@
      *
      * @param connectPoint connect point
      * @param handler neighbour message handler
+     * @param appId application ID
      */
-    void registerNeighbourHandler(ConnectPoint connectPoint, NeighbourMessageHandler handler);
+    void registerNeighbourHandler(ConnectPoint connectPoint, NeighbourMessageHandler handler,
+                                  ApplicationId appId);
 
     /**
      * Registers a neighbour message handler for all neighbour messages incoming
@@ -42,8 +48,10 @@
      *
      * @param intf interface
      * @param handler neighbour message handler
+     * @param appId application ID
      */
-    void registerNeighbourHandler(Interface intf, NeighbourMessageHandler handler);
+    void registerNeighbourHandler(Interface intf, NeighbourMessageHandler handler,
+                                  ApplicationId appId);
 
     /**
      * Unregisters a neighbour message handler that was assigned to a connect
@@ -51,14 +59,34 @@
      *
      * @param connectPoint connect point
      * @param handler neighbour message handler
+     * @param appId application ID
      */
-    void unregisterNeighbourHandler(ConnectPoint connectPoint, NeighbourMessageHandler handler);
+    void unregisterNeighbourHandler(ConnectPoint connectPoint, NeighbourMessageHandler handler,
+                                    ApplicationId appId);
 
     /**
      * Unregisters a neighbour message handler that was assigned to an interface.
      *
      * @param intf interface
      * @param handler neighbour message handler
+     * @param appId application ID
      */
-    void unregisterNeighbourHandler(Interface intf, NeighbourMessageHandler handler);
+    void unregisterNeighbourHandler(Interface intf, NeighbourMessageHandler handler,
+                                    ApplicationId appId);
+
+    /**
+     * Unregisters all neighbour handlers that were registered by the given
+     * application.
+     *
+     * @param appId application ID
+     */
+    void unregisterNeighbourHandlers(ApplicationId appId);
+
+    /**
+     * Gets the neighbour message handlers that have been registered with the
+     * service.
+     *
+     * @return neighbour message handlers indexed by connect point
+     */
+    Map<ConnectPoint, Collection<NeighbourHandlerRegistration>> getHandlerRegistrations();
 }
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
index e056f79..44e97f9 100644
--- 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
@@ -105,7 +105,12 @@
 
     @Override
     public MacAddress srcMac() {
-        return MacAddress.valueOf(eth.getSourceMACAddress());
+        return eth.getSourceMAC();
+    }
+
+    @Override
+    public MacAddress dstMac() {
+        return eth.getDestinationMAC();
     }
 
     @Override
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
index 2292ecb..e0e9c98 100644
--- 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
@@ -16,9 +16,10 @@
 
 package org.onosproject.incubator.net.neighbour.impl;
 
-import com.google.common.collect.LinkedListMultimap;
-import com.google.common.collect.ListMultimap;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -33,6 +34,7 @@
 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.NeighborAdvertisement;
@@ -42,6 +44,7 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.neighbour.NeighbourHandlerRegistration;
 import org.onosproject.incubator.net.neighbour.NeighbourMessageActions;
 import org.onosproject.incubator.net.neighbour.NeighbourMessageContext;
 import org.onosproject.incubator.net.neighbour.NeighbourMessageHandler;
@@ -63,8 +66,10 @@
 import org.slf4j.LoggerFactory;
 
 import java.nio.ByteBuffer;
+import java.util.Collection;
 import java.util.Dictionary;
-import java.util.List;
+import java.util.Iterator;
+import java.util.Map;
 import java.util.Objects;
 
 import static com.google.common.base.Preconditions.checkNotNull;
@@ -79,7 +84,7 @@
  * Manages handlers for neighbour messages.
  */
 @Service
-@Component(immediate = true, enabled = false)
+@Component(immediate = true)
 public class NeighbourPacketManager implements NeighbourResolutionService {
 
     private final Logger log = LoggerFactory.getLogger(getClass());
@@ -106,8 +111,8 @@
     private static final String APP_NAME = "org.onosproject.neighbour";
     private ApplicationId appId;
 
-    private ListMultimap<ConnectPoint, HandlerRegistration> packetHandlers =
-            Multimaps.synchronizedListMultimap(LinkedListMultimap.create());
+    private SetMultimap<ConnectPoint, NeighbourHandlerRegistration> packetHandlers =
+            Multimaps.synchronizedSetMultimap(HashMultimap.create());
 
     private final InternalPacketProcessor processor = new InternalPacketProcessor();
     private final InternalNeighbourMessageActions actions = new InternalNeighbourMessageActions();
@@ -191,23 +196,52 @@
     }
 
     @Override
-    public void registerNeighbourHandler(ConnectPoint connectPoint, NeighbourMessageHandler handler) {
-        packetHandlers.put(connectPoint, new HandlerRegistration(handler));
+    public void registerNeighbourHandler(ConnectPoint connectPoint,
+                                         NeighbourMessageHandler handler,
+                                         ApplicationId appId) {
+        packetHandlers.put(connectPoint, new HandlerRegistration(handler, appId));
     }
 
     @Override
-    public void registerNeighbourHandler(Interface intf, NeighbourMessageHandler handler) {
-        packetHandlers.put(intf.connectPoint(), new HandlerRegistration(handler, intf));
+    public void registerNeighbourHandler(Interface intf,
+                                         NeighbourMessageHandler handler,
+                                         ApplicationId appId) {
+        packetHandlers.put(intf.connectPoint(),
+                new HandlerRegistration(handler, intf, appId));
     }
 
     @Override
-    public void unregisterNeighbourHandler(ConnectPoint connectPoint, NeighbourMessageHandler handler) {
-        packetHandlers.remove(connectPoint, handler);
+    public void unregisterNeighbourHandler(ConnectPoint connectPoint,
+                                           NeighbourMessageHandler handler,
+                                           ApplicationId appId) {
+        packetHandlers.remove(connectPoint, new HandlerRegistration(handler, appId));
     }
 
     @Override
-    public void unregisterNeighbourHandler(Interface intf, NeighbourMessageHandler handler) {
-        packetHandlers.remove(intf.connectPoint(), handler);
+    public void unregisterNeighbourHandler(Interface intf,
+                                           NeighbourMessageHandler handler,
+                                           ApplicationId appId) {
+        packetHandlers.remove(intf.connectPoint(),
+                new HandlerRegistration(handler, intf, appId));
+    }
+
+    @Override
+    public void unregisterNeighbourHandlers(ApplicationId appId) {
+        synchronized (packetHandlers) {
+            Iterator<NeighbourHandlerRegistration> it = packetHandlers.values().iterator();
+
+            while (it.hasNext()) {
+                NeighbourHandlerRegistration registration = it.next();
+                if (registration.appId().equals(appId)) {
+                    it.remove();
+                }
+            }
+        }
+    }
+
+    @Override
+    public Map<ConnectPoint, Collection<NeighbourHandlerRegistration>> getHandlerRegistrations() {
+        return ImmutableMap.copyOf(Multimaps.asMap(packetHandlers));
     }
 
     public void handlePacket(PacketContext context) {
@@ -227,7 +261,7 @@
     }
 
     private void handleMessage(NeighbourMessageContext context) {
-        List<HandlerRegistration> handlers = packetHandlers.get(context.inPort());
+        Collection<NeighbourHandlerRegistration> handlers = packetHandlers.get(context.inPort());
 
         handlers.forEach(registration -> {
             if (registration.intf() == null || matches(context, registration.intf())) {
@@ -236,18 +270,53 @@
         });
     }
 
+    /**
+     * Checks that incoming packet matches the parameters of the interface.
+     * This means that if the interface specifies a particular parameter
+     * (VLAN, IP address, etc.) then the incoming packet should match those
+     * parameters.
+     *
+     * @param context incoming message context
+     * @param intf interface to check
+     * @return true if the incoming message matches the interface, otherwise false
+     */
     private boolean matches(NeighbourMessageContext context, Interface intf) {
         checkNotNull(context);
         checkNotNull(intf);
 
         boolean matches = true;
+        // For non-broadcast packets, if the interface has a MAC address check that
+        // the destination MAC address of the packet matches the interface MAC
+        if (!context.dstMac().isBroadcast() &&
+                !intf.mac().equals(MacAddress.NONE) &&
+                !intf.mac().equals(context.dstMac())) {
+            matches = false;
+        }
+        // If the interface has a VLAN, check that the packet's VLAN matches
         if (!intf.vlan().equals(VlanId.NONE) && !intf.vlan().equals(context.vlan())) {
             matches = false;
         }
+        // If the interface has IP addresses, check that the packet's target IP
+        // address matches one of the interface IP addresses
+        if (!intf.ipAddressesList().isEmpty() && !hasIp(intf, context.target())) {
+            matches = false;
+        }
 
         return matches;
     }
 
+    /**
+     * Returns true if the interface has the given IP address.
+     *
+     * @param intf interface to check
+     * @param ip IP address
+     * @return true if the IP is configured on the interface, otherwise false
+     */
+    private boolean hasIp(Interface intf, IpAddress ip) {
+        return intf.ipAddressesList().stream()
+                .anyMatch(intfAddress -> intfAddress.ipAddress().equals(ip));
+    }
+
 
     private void reply(NeighbourMessageContext context, MacAddress targetMac) {
         switch (context.protocol()) {
@@ -295,7 +364,7 @@
     }
 
     /**
-     * Builds an Neighbor Discovery reply based on a request.
+     * Builds an NDP 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
@@ -336,17 +405,18 @@
     /**
      * Stores a neighbour message handler registration.
      */
-    private class HandlerRegistration {
+    private class HandlerRegistration implements NeighbourHandlerRegistration {
         private final Interface intf;
         private final NeighbourMessageHandler handler;
+        private final ApplicationId appId;
 
         /**
          * Creates a new handler registration.
          *
          * @param handler neighbour message handler
          */
-        public HandlerRegistration(NeighbourMessageHandler handler) {
-            this(handler, null);
+        public HandlerRegistration(NeighbourMessageHandler handler, ApplicationId appId) {
+            this(handler, null, appId);
         }
 
         /**
@@ -355,30 +425,28 @@
          * @param handler neighbour message handler
          * @param intf interface
          */
-        public HandlerRegistration(NeighbourMessageHandler handler, Interface intf) {
+        public HandlerRegistration(NeighbourMessageHandler handler, Interface intf, ApplicationId appId) {
             this.intf = intf;
             this.handler = handler;
+            this.appId = appId;
         }
 
-        /**
-         * Gets the interface of the registration.
-         *
-         * @return interface
-         */
+        @Override
         public Interface intf() {
             return intf;
         }
 
-        /**
-         * Gets the neighbour message handler.
-         *
-         * @return message handler
-         */
+        @Override
         public NeighbourMessageHandler handler() {
             return handler;
         }
 
         @Override
+        public ApplicationId appId() {
+            return appId;
+        }
+
+        @Override
         public boolean equals(Object other) {
             if (this == other) {
                 return true;
@@ -391,12 +459,13 @@
             HandlerRegistration that = (HandlerRegistration) other;
 
             return Objects.equals(intf, that.intf) &&
-                    Objects.equals(handler, that.handler);
+                    Objects.equals(handler, that.handler) &&
+                    Objects.equals(appId, that.appId);
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(intf, handler);
+            return Objects.hash(intf, handler, appId);
         }
     }
 
@@ -450,7 +519,7 @@
 
         @Override
         public void proxy(NeighbourMessageContext context, Interface outIntf) {
-
+            // TODO implement
         }
 
         @Override