Punt IPv6 NA packets to controller 67/71567/19
authorSomashekar Byrappa <somashekar.b@altencalsoftlabs.com>
Mon, 30 Apr 2018 09:23:11 +0000 (14:53 +0530)
committerSomashekar Byrappa <somashekar.b@altencalsoftlabs.com>
Sun, 1 Jul 2018 16:17:38 +0000 (21:47 +0530)
As part of implementation of the spec, this patch handles:

+ Punting IPv6 NA packets from hidden IPs to the controller. NA packets
  from Fixed IPs will be resubmitted to dispatcher table.
+ Provides RPC to send NS packet to an OF group. For this feature,
  NS packets will be sent to ELAN remote broadcast group.
+ Receives NA response, deserializes, publishes NA notifications to the
  registered users.

JIRA: NETVIRT-1213

Change-Id: Ic049b8868915f48e787b6e42cf813129e55f0682
Signed-off-by: Somashekar Byrappa <somashekar.b@altencalsoftlabs.com>
12 files changed:
ipv6service/api/src/main/yang/ipv6-ndutil.yang
ipv6service/impl/src/main/java/org/opendaylight/netvirt/ipv6service/IfMgr.java
ipv6service/impl/src/main/java/org/opendaylight/netvirt/ipv6service/Ipv6NdUtilServiceImpl.java
ipv6service/impl/src/main/java/org/opendaylight/netvirt/ipv6service/Ipv6NeighborSolicitation.java
ipv6service/impl/src/main/java/org/opendaylight/netvirt/ipv6service/Ipv6PktHandler.java
ipv6service/impl/src/main/java/org/opendaylight/netvirt/ipv6service/Ipv6ServiceInterfaceEventListener.java
ipv6service/impl/src/main/java/org/opendaylight/netvirt/ipv6service/NeutronPortChangeListener.java
ipv6service/impl/src/main/java/org/opendaylight/netvirt/ipv6service/VirtualNetwork.java
ipv6service/impl/src/main/java/org/opendaylight/netvirt/ipv6service/utils/Ipv6Constants.java
ipv6service/impl/src/main/java/org/opendaylight/netvirt/ipv6service/utils/Ipv6ServiceUtils.java
ipv6service/impl/src/main/resources/org/opendaylight/blueprint/ipv6service.xml
ipv6service/impl/src/test/java/org/opendaylight/netvirt/ipv6service/Ipv6PktHandlerTest.java

index 5c91f264bdf0078e87a465041c8c69d1285a9e4a..be4616c31d81f800c828bf620cdf317c9adccb75 100644 (file)
@@ -39,11 +39,69 @@ module ipv6-ndutil {
 
 
     rpc send-neighbor-solicitation {
-          input {
-              leaf target-ip-address {
-                  type inet:ipv6-address;
-              }
-              uses interfaces;
-          }
+        input {
+            leaf target-ip-address {
+                type inet:ipv6-address;
+            }
+            uses interfaces;
+        }
+    }
+
+    rpc send-neighbor-solicitation-to-of-group {
+        input {
+            leaf source-ipv6 {
+                type inet:ipv6-address;
+                mandatory "true";
+            }
+            leaf target-ip-address {
+                type inet:ipv6-address;
+                mandatory "true";
+            }
+            leaf source-ll-address {
+               type yang:mac-address;
+               mandatory "true";
+            }
+            leaf dp-id {
+               type uint64;
+               mandatory "true";
+            }
+            leaf of-group-id {
+               type uint32;
+               mandatory "true";
+               description "NS will be sent to the specified OpenFlow group ID.";
+            }
+        }
+    }
+
+    notification na-received {
+        leaf source-mac {
+            type yang:mac-address;
+        }
+        leaf destination-mac {
+            type yang:mac-address;
+        }
+        leaf source-ipv6 {
+            type inet:ipv6-address;
+        }
+        leaf destination-ipv6 {
+            type inet:ipv6-address;
+        }
+        leaf target-address {
+            type inet:ipv6-address;
+        }
+        leaf target-ll-address {
+            type yang:mac-address;
+        }
+        leaf of-table-id {
+            type uint32;
+        }
+        leaf metadata {
+            type uint64;
+        }
+        leaf interface {
+            type leafref {
+                path "/if:interfaces/if:interface/if:name";
+            }
+        }
     }
 }
index d9487196944762a3dd0f665294b111ad14ad6b86..8bb25d13b6fc304012642afe3c30596545b85067 100644 (file)
@@ -8,6 +8,7 @@
 
 package org.opendaylight.netvirt.ipv6service;
 
+import com.google.common.collect.Lists;
 import com.google.common.net.InetAddresses;
 import io.netty.util.Timeout;
 import java.math.BigInteger;
@@ -42,6 +43,7 @@ import org.opendaylight.netvirt.ipv6service.utils.Ipv6TimerWheel;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpPrefix;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv6Address;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv6Prefix;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.state.Interface;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.MacAddress;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
@@ -249,6 +251,7 @@ public class IfMgr implements ElementCache, AutoCloseable {
              */
             programIcmpv6RSPuntFlows(intf.getNetworkID(), Ipv6Constants.ADD_FLOW);
             programIcmpv6NSPuntFlowForAddress(intf.getNetworkID(), llAddr, Ipv6Constants.ADD_FLOW);
+            programIcmpv6NaForwardFlows(intf, snetId, Ipv6Constants.ADD_FLOW);
         } else {
             intf = prevIntf;
             intf.setSubnetInfo(snetId, fixedIp);
@@ -274,6 +277,7 @@ public class IfMgr implements ElementCache, AutoCloseable {
         }
 
         programIcmpv6NSPuntFlowForAddress(intf.getNetworkID(), fixedIp.getIpv6Address(), Ipv6Constants.ADD_FLOW);
+        programIcmpv6NaPuntFlow(networkId, intf.getSubnets(), Ipv6Constants.ADD_FLOW);
 
         if (newIntf) {
             LOG.debug("start the periodic RA Timer for routerIntf {}", portId);
@@ -281,16 +285,14 @@ public class IfMgr implements ElementCache, AutoCloseable {
         }
     }
 
-    public void updateRouterIntf(Uuid portId, Uuid rtrId, List<FixedIps> fixedIpsList) {
+    public void updateRouterIntf(Uuid portId, Uuid rtrId, List<FixedIps> fixedIpsList, Set<FixedIps> deletedIps) {
         LOG.info("updateRouterIntf portId {}, fixedIpsList {} ", portId, fixedIpsList);
         VirtualPort intf = getPort(portId);
         if (intf == null) {
             LOG.info("Skip Router interface update for non-ipv6 port {}", portId);
             return;
         }
-
-        List<Ipv6Address> existingIPv6AddressList = intf.getIpv6AddressesWithoutLLA();
-        List<Ipv6Address> newlyAddedIpv6AddressList = new ArrayList<>();
+        Uuid networkID = intf.getNetworkID();
         intf.clearSubnetInfo();
         for (FixedIps fip : fixedIpsList) {
             IpAddress fixedIp = fip.getIpAddress();
@@ -321,31 +323,20 @@ public class IfMgr implements ElementCache, AutoCloseable {
                 addUnprocessed(unprocessedSubnetIntfs, subnetId, intf);
             }
 
-
-            Uuid networkID = intf.getNetworkID();
             if (networkID != null) {
                 vrouterv6IntfMap.put(networkID, intf);
             }
-
-            if (existingIPv6AddressList.contains(fixedIp.getIpv6Address())) {
-                existingIPv6AddressList.remove(fixedIp.getIpv6Address());
-            } else {
-                newlyAddedIpv6AddressList.add(fixedIp.getIpv6Address());
-            }
         }
 
-        /* This is a port update event for routerPort. Check if any IPv6 subnet is added
-         or removed from the router port. Depending on subnet added/removed, we add/remove
-         the corresponding flows from IPV6_TABLE(45).
+        /*
+         * This is a port update event for routerPort. Check if any IPv6 subnet is added or removed from the
+         * router port. Depending on subnet added/removed, we add/remove the corresponding flows from
+         * IPV6_TABLE(45). Add is handled in addInterfaceInfo(), delete case is handled here.
          */
-        for (Ipv6Address ipv6Address: newlyAddedIpv6AddressList) {
-            // Some v6 subnets are associated to the routerPort add the corresponding NS Flows.
-            programIcmpv6NSPuntFlowForAddress(intf.getNetworkID(), ipv6Address, Ipv6Constants.ADD_FLOW);
-        }
-
-        for (Ipv6Address ipv6Address: existingIPv6AddressList) {
-            // Some v6 subnets are disassociated from the routerPort, remove the corresponding NS Flows.
-            programIcmpv6NSPuntFlowForAddress(intf.getNetworkID(), ipv6Address, Ipv6Constants.DEL_FLOW);
+        for (FixedIps ips : deletedIps) {
+            VirtualSubnet snet = getSubnet(ips.getSubnetId());
+            programIcmpv6NaPuntFlow(networkID, Lists.newArrayList(snet), Ipv6Constants.DEL_FLOW);
+            programIcmpv6NSPuntFlowForAddress(networkID, ips.getIpAddress().getIpv6Address(), Ipv6Constants.DEL_FLOW);
         }
     }
 
@@ -461,7 +452,7 @@ public class IfMgr implements ElementCache, AutoCloseable {
 
         List<String> ofportIds = interfaceState.getLowerLayerIf();
         NodeConnectorId nodeConnectorId = new NodeConnectorId(ofportIds.get(0));
-        BigInteger dpId = BigInteger.valueOf(MDSALUtil.getDpnIdFromPortName(nodeConnectorId));
+        BigInteger dpId = ipv6ServiceUtils.getDpIdFromInterfaceState(interfaceState);
         if (!dpId.equals(Ipv6Constants.INVALID_DPID)) {
             Long ofPort = MDSALUtil.getOfPortNumberFromPortName(nodeConnectorId);
             updateDpnInfo(portId, dpId, ofPort);
@@ -485,9 +476,13 @@ public class IfMgr implements ElementCache, AutoCloseable {
                 the dpnIds which were hosting the VMs on the network.
                  */
                 programIcmpv6RSPuntFlows(intf.getNetworkID(), Ipv6Constants.DEL_FLOW);
+                programIcmpv6NaPuntFlow(networkID, intf.getSubnets(), Ipv6Constants.DEL_FLOW);
                 for (Ipv6Address ipv6Address: intf.getIpv6Addresses()) {
                     programIcmpv6NSPuntFlowForAddress(intf.getNetworkID(), ipv6Address, Ipv6Constants.DEL_FLOW);
                 }
+                for (VirtualSubnet subnet : intf.getSubnets()) {
+                    programIcmpv6NaForwardFlows(intf, subnet.getSubnetUUID(), Ipv6Constants.DEL_FLOW);
+                }
                 transmitRouterAdvertisement(intf, Ipv6RtrAdvertType.CEASE_ADVERTISEMENT);
                 timer.cancelPeriodicTransmissionTimeout(intf.getPeriodicTimeout());
                 intf.resetPeriodicTimeout();
@@ -612,14 +607,15 @@ public class IfMgr implements ElementCache, AutoCloseable {
         if (vnet != null) {
             Collection<VirtualNetwork.DpnInterfaceInfo> dpnIfaceList = vnet.getDpnIfaceList();
             for (VirtualNetwork.DpnInterfaceInfo dpnIfaceInfo : dpnIfaceList) {
-                if (action == Ipv6Constants.ADD_FLOW && !dpnIfaceInfo.ndTargetFlowsPunted.contains(ipv6Address)
+                if (action == Ipv6Constants.ADD_FLOW && !dpnIfaceInfo.isNdTargetFlowAlreadyConfigured(ipv6Address)
                         && dpnIfaceInfo.getDpId() != null) {
-                    ipv6ServiceUtils.installIcmpv6NsPuntFlow(NwConstants.IPV6_TABLE, dpnIfaceInfo.getDpId(),
-                            elanTag, ipv6Address.getValue(), Ipv6Constants.ADD_FLOW);
+                    ipv6ServiceUtils.installIcmpv6NsPuntFlow(NwConstants.IPV6_TABLE, dpnIfaceInfo.getDpId(), elanTag,
+                            ipv6Address.getValue(), Ipv6Constants.ADD_FLOW);
                     dpnIfaceInfo.updateNDTargetAddress(ipv6Address, action);
-                } else if (action == Ipv6Constants.DEL_FLOW && dpnIfaceInfo.ndTargetFlowsPunted.contains(ipv6Address)) {
-                    ipv6ServiceUtils.installIcmpv6NsPuntFlow(NwConstants.IPV6_TABLE, dpnIfaceInfo.getDpId(),
-                            elanTag, ipv6Address.getValue(), Ipv6Constants.DEL_FLOW);
+                } else if (action == Ipv6Constants.DEL_FLOW
+                        && dpnIfaceInfo.isNdTargetFlowAlreadyConfigured(ipv6Address)) {
+                    ipv6ServiceUtils.installIcmpv6NsPuntFlow(NwConstants.IPV6_TABLE, dpnIfaceInfo.getDpId(), elanTag,
+                            ipv6Address.getValue(), Ipv6Constants.DEL_FLOW);
                     dpnIfaceInfo.updateNDTargetAddress(ipv6Address, action);
                 }
             }
@@ -628,36 +624,127 @@ public class IfMgr implements ElementCache, AutoCloseable {
         }
     }
 
-
-    public void programIcmpv6PuntFlowsIfNecessary(Uuid vmPortId, BigInteger dpId, VirtualPort routerPort) {
+    private void programIcmpv6NaPuntFlow(Uuid networkID, List<VirtualSubnet> subnets, int action) {
         if (!ipv6ServiceEosHandler.isClusterOwner()) {
             LOG.trace("Not a cluster Owner, skip flow programming.");
             return;
         }
+        Long elanTag = getNetworkElanTag(networkID);
+        VirtualNetwork vnet = getNetwork(networkID);
+        if (vnet != null) {
+            Collection<VirtualNetwork.DpnInterfaceInfo> dpnIfaceList = vnet.getDpnIfaceList();
+            for (VirtualNetwork.DpnInterfaceInfo dpnIfaceInfo : dpnIfaceList) {
+                if (dpnIfaceInfo.getDpId() == null) {
+                    continue;
+                }
+                for (VirtualSubnet subnet : subnets) {
+                    Ipv6Prefix ipv6SubnetPrefix = subnet.getSubnetCidr().getIpv6Prefix();
+                    if (ipv6SubnetPrefix != null) {
+                        if (action == Ipv6Constants.ADD_FLOW
+                                && !dpnIfaceInfo.isSubnetCidrFlowAlreadyConfigured(subnet.getSubnetUUID())) {
+                            ipv6ServiceUtils.installIcmpv6NaPuntFlow(NwConstants.IPV6_TABLE, ipv6SubnetPrefix,
+                                    dpnIfaceInfo.getDpId(), elanTag, action);
+                            dpnIfaceInfo.updateSubnetCidrFlowStatus(subnet.getSubnetUUID(), action);
+                        } else if (action == Ipv6Constants.DEL_FLOW
+                                && dpnIfaceInfo.isSubnetCidrFlowAlreadyConfigured(subnet.getSubnetUUID())) {
+                            ipv6ServiceUtils.installIcmpv6NaPuntFlow(NwConstants.IPV6_TABLE, ipv6SubnetPrefix,
+                                    dpnIfaceInfo.getDpId(), elanTag, action);
+                            dpnIfaceInfo.updateSubnetCidrFlowStatus(subnet.getSubnetUUID(), action);
+                        }
+                    }
+                }
+            }
+        }
+    }
 
-        IVirtualPort vmPort = getPort(vmPortId);
-        if (null != vmPort) {
-            VirtualNetwork vnet = getNetwork(vmPort.getNetworkID());
-            if (null != vnet) {
-                VirtualNetwork.DpnInterfaceInfo dpnInfo = vnet.getDpnIfaceInfo(dpId);
-                if (null != dpnInfo) {
-                    Long elanTag = getNetworkElanTag(routerPort.getNetworkID());
-                    if (vnet.getRSPuntFlowStatusOnDpnId(dpId) == Ipv6Constants.FLOWS_NOT_CONFIGURED) {
-                        ipv6ServiceUtils.installIcmpv6RsPuntFlow(NwConstants.IPV6_TABLE, dpId, elanTag,
-                                Ipv6Constants.ADD_FLOW);
-                        vnet.setRSPuntFlowStatusOnDpnId(dpId, Ipv6Constants.FLOWS_CONFIGURED);
+    public void handleInterfaceStateEvent(IVirtualPort port, BigInteger dpId, VirtualPort routerPort, int addOrRemove) {
+        Long elanTag = getNetworkElanTag(port.getNetworkID());
+        if (addOrRemove == Ipv6Constants.ADD_FLOW && routerPort != null) {
+            // Check and program icmpv6 punt flows on the dpnID if its the first VM on the host.
+            programIcmpv6PuntFlows(port, dpId, elanTag, routerPort);
+        }
+        programIcmpv6NaForwardFlow(port, dpId, elanTag, addOrRemove);
+    }
+
+    private void programIcmpv6PuntFlows(IVirtualPort vmPort, BigInteger dpId, Long elanTag, VirtualPort routerPort) {
+        VirtualNetwork vnet = getNetwork(vmPort.getNetworkID());
+        if (null != vnet) {
+            VirtualNetwork.DpnInterfaceInfo dpnInfo = vnet.getDpnIfaceInfo(dpId);
+            if (null != dpnInfo) {
+                if (vnet.getRSPuntFlowStatusOnDpnId(dpId) == Ipv6Constants.FLOWS_NOT_CONFIGURED) {
+                    ipv6ServiceUtils.installIcmpv6RsPuntFlow(NwConstants.IPV6_TABLE, dpId, elanTag,
+                            Ipv6Constants.ADD_FLOW);
+                    vnet.setRSPuntFlowStatusOnDpnId(dpId, Ipv6Constants.FLOWS_CONFIGURED);
+                }
+
+                for (Ipv6Address ipv6Address : routerPort.getIpv6Addresses()) {
+                    if (!dpnInfo.isNdTargetFlowAlreadyConfigured(ipv6Address)) {
+                        ipv6ServiceUtils.installIcmpv6NsPuntFlow(NwConstants.IPV6_TABLE, dpId,
+                                elanTag, ipv6Address.getValue(), Ipv6Constants.ADD_FLOW);
+                        dpnInfo.updateNDTargetAddress(ipv6Address, Ipv6Constants.ADD_FLOW);
                     }
+                }
 
-                    for (Ipv6Address ipv6Address: routerPort.getIpv6Addresses()) {
-                        if (!dpnInfo.ndTargetFlowsPunted.contains(ipv6Address)) {
-                            ipv6ServiceUtils.installIcmpv6NsPuntFlow(NwConstants.IPV6_TABLE, dpId,
-                                    elanTag, ipv6Address.getValue(), Ipv6Constants.ADD_FLOW);
-                            dpnInfo.updateNDTargetAddress(ipv6Address, Ipv6Constants.ADD_FLOW);
-                        }
+                for (VirtualSubnet subnet : routerPort.getSubnets()) {
+                    Ipv6Prefix ipv6SubnetPrefix = subnet.getSubnetCidr().getIpv6Prefix();
+                    if (ipv6SubnetPrefix != null
+                            && !dpnInfo.isSubnetCidrFlowAlreadyConfigured(subnet.getSubnetUUID())) {
+                        ipv6ServiceUtils.installIcmpv6NaPuntFlow(NwConstants.IPV6_TABLE, ipv6SubnetPrefix, dpId,
+                                elanTag, Ipv6Constants.ADD_FLOW);
+                        dpnInfo.updateSubnetCidrFlowStatus(subnet.getSubnetUUID(), Ipv6Constants.ADD_FLOW);
+                    }
+                }
+            }
+        }
+    }
+
+    private void programIcmpv6NaForwardFlows(IVirtualPort routerPort, Uuid snetId, int action) {
+        if (!ipv6ServiceEosHandler.isClusterOwner()) {
+            LOG.trace("Not a cluster Owner, skip flow programming.");
+            return;
+        }
+
+        VirtualNetwork vnet = getNetwork(routerPort.getNetworkID());
+        if (vnet != null) {
+            Long elanTag = getNetworkElanTag(routerPort.getNetworkID());
+            Collection<VirtualNetwork.DpnInterfaceInfo> dpnIfaceList = vnet.getDpnIfaceList();
+            for (VirtualNetwork.DpnInterfaceInfo dpnIfaceInfo : dpnIfaceList) {
+                if (dpnIfaceInfo.getDpId() == null) {
+                    continue;
+                }
+                List<VirtualPort> vmPorts = getVmPortsInSubnetByDpId(snetId, dpnIfaceInfo.getDpId());
+                for (VirtualPort vmPort : vmPorts) {
+                    programIcmpv6NaForwardFlow(vmPort, dpnIfaceInfo.getDpId(), elanTag, action);
+                }
+            }
+        }
+    }
+
+    /**
+     * Programs ICMPv6 NA forwarding flow for fixed IPs of neutron port. NA's from non-fixed IPs are
+     * punted to controller for learning.
+     *
+     * @param vmPort the VM port
+     * @param dpId the DP ID
+     * @param elanTag the ELAN tag
+     * @param addOrRemove the add or remove flag
+     */
+    private void programIcmpv6NaForwardFlow(IVirtualPort vmPort, BigInteger dpId, Long elanTag, int addOrRemove) {
+        ipv6ServiceUtils.installIcmpv6NaForwardFlow(NwConstants.IPV6_TABLE, vmPort, dpId, elanTag, addOrRemove);
+    }
+
+    public List<VirtualPort> getVmPortsInSubnetByDpId(Uuid snetId, BigInteger dpId) {
+        List<VirtualPort> vmPorts = new ArrayList<>();
+        for (VirtualPort port : vintfs.values()) {
+            if (dpId.equals(port.getDpId()) && Ipv6ServiceUtils.isVmPort(port.getDeviceOwner())) {
+                for (VirtualSubnet subnet : port.getSubnets()) {
+                    if (subnet.getSubnetUUID().equals(snetId)) {
+                        vmPorts.add(port);
                     }
                 }
             }
         }
+        return vmPorts;
     }
 
     public String getInterfaceNameFromTag(long portTag) {
index 3686fb38368f30195d68a1a1a5d3d6aa1499f02f..c05803f2da0215f8db5467a9e1a9418e205b1703 100644 (file)
@@ -26,6 +26,8 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeCon
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.ipv6service.ipv6util.rev170210.Ipv6NdutilService;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.ipv6service.ipv6util.rev170210.SendNeighborSolicitationInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.ipv6service.ipv6util.rev170210.SendNeighborSolicitationOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.ipv6service.ipv6util.rev170210.SendNeighborSolicitationToOfGroupInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.ipv6service.ipv6util.rev170210.SendNeighborSolicitationToOfGroupOutput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.ipv6service.ipv6util.rev170210.interfaces.InterfaceAddress;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingService;
 import org.opendaylight.yangtools.yang.common.RpcError;
@@ -120,4 +122,14 @@ public class Ipv6NdUtilServiceImpl implements Ipv6NdutilService {
         return result;
     }
 
+    @Override
+    public ListenableFuture<RpcResult<SendNeighborSolicitationToOfGroupOutput>> sendNeighborSolicitationToOfGroup(
+            SendNeighborSolicitationToOfGroupInput ndInput) {
+        RpcResultBuilder<SendNeighborSolicitationToOfGroupOutput> successBuilder = RpcResultBuilder.success();
+        ipv6NeighborSolicitation.transmitNeighborSolicitationToOfGroup(ndInput.getDpId(), ndInput.getSourceLlAddress(),
+                ndInput.getSourceIpv6(), ndInput.getTargetIpAddress(), ndInput.getOfGroupId());
+
+        return  successBuilder.buildFuture();
+    }
+
 }
index 72389425db9dc6e351b0874d584f6bf181a7457f..408a537dde59d3d7e38fee8ff89c1cf902e815fa 100644 (file)
@@ -12,10 +12,14 @@ import java.math.BigInteger;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
+import org.opendaylight.genius.mdsalutil.ActionInfo;
 import org.opendaylight.genius.mdsalutil.MDSALUtil;
 import org.opendaylight.genius.mdsalutil.NwConstants;
-import org.opendaylight.infrautils.utils.concurrent.JdkFutures;
+import org.opendaylight.genius.mdsalutil.actions.ActionGroup;
+import org.opendaylight.infrautils.utils.concurrent.ListenableFutures;
 import org.opendaylight.netvirt.ipv6service.utils.Ipv6Constants;
 import org.opendaylight.netvirt.ipv6service.utils.Ipv6ServiceUtils;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv6Address;
@@ -122,8 +126,22 @@ public class Ipv6NeighborSolicitation {
 
         // Tx the packet out of the controller.
         LOG.debug("Transmitting the Neighbor Solicitation packet out on {}", dpnId);
-        JdkFutures.addErrorLogging(packetService.transmitPacket(input), LOG, "transmitPacket");
+        ListenableFutures.addErrorLogging(packetService.transmitPacket(input), LOG, "transmitPacket");
         return true;
     }
 
+    public void transmitNeighborSolicitationToOfGroup(BigInteger dpId, MacAddress srcMacAddress,
+            Ipv6Address srcIpv6Address, Ipv6Address targetIpv6Address, long ofGroupId) {
+        byte[] txPayload = frameNeighborSolicitationRequest(srcMacAddress, srcIpv6Address, targetIpv6Address);
+        List<ActionInfo> lstActionInfo = new ArrayList<>();
+        lstActionInfo.add(new ActionGroup(ofGroupId));
+
+        TransmitPacketInput input = MDSALUtil.getPacketOutDefault(lstActionInfo, txPayload, dpId);
+        // Tx the packet out of the controller.
+        LOG.debug(
+                "Transmitting Neighbor Solicitation packet out. srcMacAddress={}, srcIpv6Address={}, "
+                        + "targetIpv6Address={}, dpId={}, ofGroupId={}",
+                srcMacAddress.getValue(), srcIpv6Address.getValue(), targetIpv6Address.getValue(), dpId, ofGroupId);
+        ListenableFutures.addErrorLogging(packetService.transmitPacket(input), LOG, "transmitPacket");
+    }
 }
index 58e2e77d90b2b8d9131905df29fbfc1c95d1837d..0e6eaebad4398afc490bc42ae2f6bedd284a1a96 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.netvirt.ipv6service;
 
+import com.google.common.util.concurrent.ListenableFuture;
 import java.math.BigInteger;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
@@ -18,6 +19,7 @@ import java.util.concurrent.atomic.AtomicLong;
 import javax.annotation.PreDestroy;
 import javax.inject.Inject;
 import javax.inject.Singleton;
+import org.opendaylight.controller.md.sal.binding.api.NotificationPublishService;
 import org.opendaylight.genius.mdsalutil.MetaDataUtil;
 import org.opendaylight.infrautils.utils.concurrent.JdkFutures;
 import org.opendaylight.netvirt.ipv6service.utils.Ipv6Constants;
@@ -30,6 +32,7 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.ipv6service.ipv6util.rev170210.NaReceivedBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.ipv6service.nd.packet.rev160620.Ipv6Header;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.ipv6service.nd.packet.rev160620.NeighborAdvertisePacket;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.ipv6service.nd.packet.rev160620.NeighborAdvertisePacketBuilder;
@@ -43,6 +46,7 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.Pa
 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInputBuilder;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.Notification;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -52,12 +56,16 @@ public class Ipv6PktHandler implements AutoCloseable, PacketProcessingListener {
     private final AtomicLong pktProccessedCounter = new AtomicLong(0);
     private final PacketProcessingService pktService;
     private final IfMgr ifMgr;
+    private final NotificationPublishService notificationPublishService;
+
     private final ExecutorService packetProcessor = Executors.newCachedThreadPool();
 
     @Inject
-    public Ipv6PktHandler(PacketProcessingService pktService, IfMgr ifMgr) {
+    public Ipv6PktHandler(PacketProcessingService pktService, IfMgr ifMgr,
+            NotificationPublishService notificationPublishService) {
         this.pktService = pktService;
         this.ifMgr = ifMgr;
+        this.notificationPublishService = notificationPublishService;
     }
 
     @Override
@@ -84,8 +92,7 @@ public class Ipv6PktHandler implements AutoCloseable, PacketProcessingListener {
                 if (v6NxtHdr == Ipv6Constants.ICMP_V6_TYPE) {
                     int icmpv6Type = BitBufferHelper.getInt(BitBufferHelper.getBits(data,
                             Ipv6Constants.ICMPV6_HDR_START, Ipv6Constants.ONE_BYTE));
-                    if (icmpv6Type == Ipv6Constants.ICMP_V6_RS_CODE
-                            || icmpv6Type == Ipv6Constants.ICMP_V6_NS_CODE) {
+                    if (isOfInterest(icmpv6Type)) {
                         packetProcessor.execute(new PacketHandler(icmpv6Type, packetReceived));
                     }
                 } else {
@@ -101,6 +108,11 @@ public class Ipv6PktHandler implements AutoCloseable, PacketProcessingListener {
         }
     }
 
+    private boolean isOfInterest(int icmpv6Type) {
+        return icmpv6Type == Ipv6Constants.ICMP_V6_RS_CODE || icmpv6Type == Ipv6Constants.ICMP_V6_NS_CODE
+                || icmpv6Type == Ipv6Constants.ICMP_V6_NA_CODE;
+    }
+
     public long getPacketProcessedCounter() {
         return pktProccessedCounter.get();
     }
@@ -122,6 +134,9 @@ public class Ipv6PktHandler implements AutoCloseable, PacketProcessingListener {
             } else if (type == Ipv6Constants.ICMP_V6_RS_CODE) {
                 LOG.info("Received Router Solicitation request");
                 processRouterSolicitationRequest();
+            } else if (type == Ipv6Constants.ICMP_V6_NA_CODE) {
+                LOG.trace("Received Neighbor Advertisement packet");
+                processNeighborAdvertisementPacket();
             }
         }
 
@@ -381,6 +396,98 @@ public class Ipv6PktHandler implements AutoCloseable, PacketProcessingListener {
             }
             return rsPdu.build();
         }
+
+        private void processNeighborAdvertisementPacket() {
+            byte[] data = packet.getPayload();
+            NeighborAdvertisePacket naPdu;
+            try {
+                naPdu = deserializeNAPacket(data);
+            } catch (UnknownHostException | BufferException e) {
+                LOG.warn("Exception occured during deserializing NA packet", e);
+                return;
+            }
+            Ipv6Header ipv6Header = naPdu;
+            if (Ipv6ServiceUtils.validateChecksum(data, ipv6Header, naPdu.getIcmp6Chksum()) == false) {
+                pktProccessedCounter.incrementAndGet();
+                LOG.warn("Received Neighbor Advertisement with invalid checksum on {}. Ignoring the packet.",
+                        packet.getIngress());
+                return;
+            }
+
+            short tableId = packet.getTableId().getValue();
+            BigInteger metadata = packet.getMatch().getMetadata().getMetadata();
+            long portTag = MetaDataUtil.getLportFromMetadata(metadata).intValue();
+            String interfaceName = ifMgr.getInterfaceNameFromTag(portTag);
+
+            NaReceivedBuilder naReceivedBuilder = new NaReceivedBuilder().setSourceMac(naPdu.getSourceMac())
+                    .setDestinationMac(naPdu.getDestinationMac()).setSourceIpv6(naPdu.getSourceIpv6())
+                    .setDestinationIpv6(naPdu.getDestinationIpv6()).setTargetAddress(naPdu.getTargetAddress())
+                    .setTargetLlAddress(naPdu.getTargetLlAddress()).setOfTableId((long) tableId).setMetadata(metadata)
+                    .setInterface(interfaceName);
+            fireNotification(naReceivedBuilder.build());
+        }
+
+        private NeighborAdvertisePacket deserializeNAPacket(byte[] data) throws BufferException, UnknownHostException {
+            NeighborAdvertisePacketBuilder naPdu = new NeighborAdvertisePacketBuilder();
+            int bitOffset = 0;
+
+            naPdu.setDestinationMac(
+                    new MacAddress(Ipv6ServiceUtils.bytesToHexString(BitBufferHelper.getBits(data, bitOffset, 48))));
+            bitOffset = bitOffset + 48;
+            naPdu.setSourceMac(
+                    new MacAddress(Ipv6ServiceUtils.bytesToHexString(BitBufferHelper.getBits(data, bitOffset, 48))));
+            bitOffset = bitOffset + 48;
+            naPdu.setEthertype(BitBufferHelper.getInt(BitBufferHelper.getBits(data, bitOffset, 16)));
+
+            bitOffset = Ipv6Constants.IP_V6_HDR_START;
+            naPdu.setVersion(BitBufferHelper.getShort(BitBufferHelper.getBits(data, bitOffset, 4)));
+            bitOffset = bitOffset + 4;
+            naPdu.setFlowLabel(BitBufferHelper.getLong(BitBufferHelper.getBits(data, bitOffset, 28)));
+            bitOffset = bitOffset + 28;
+            naPdu.setIpv6Length(BitBufferHelper.getInt(BitBufferHelper.getBits(data, bitOffset, 16)));
+            bitOffset = bitOffset + 16;
+            naPdu.setNextHeader(BitBufferHelper.getShort(BitBufferHelper.getBits(data, bitOffset, 8)));
+            bitOffset = bitOffset + 8;
+            naPdu.setHopLimit(BitBufferHelper.getShort(BitBufferHelper.getBits(data, bitOffset, 8)));
+            bitOffset = bitOffset + 8;
+            naPdu.setSourceIpv6(Ipv6Address.getDefaultInstance(
+                    InetAddress.getByAddress(BitBufferHelper.getBits(data, bitOffset, 128)).getHostAddress()));
+            bitOffset = bitOffset + 128;
+            naPdu.setDestinationIpv6(Ipv6Address.getDefaultInstance(
+                    InetAddress.getByAddress(BitBufferHelper.getBits(data, bitOffset, 128)).getHostAddress()));
+            bitOffset = bitOffset + 128;
+
+            naPdu.setIcmp6Type(BitBufferHelper.getShort(BitBufferHelper.getBits(data, bitOffset, 8)));
+            bitOffset = bitOffset + 8;
+            naPdu.setIcmp6Code(BitBufferHelper.getShort(BitBufferHelper.getBits(data, bitOffset, 8)));
+            bitOffset = bitOffset + 8;
+            naPdu.setIcmp6Chksum(BitBufferHelper.getInt(BitBufferHelper.getBits(data, bitOffset, 16)));
+            bitOffset = bitOffset + 16;
+            naPdu.setFlags(BitBufferHelper.getLong(BitBufferHelper.getBits(data, bitOffset, 32)));
+            bitOffset = bitOffset + 32;
+            naPdu.setTargetAddress(Ipv6Address.getDefaultInstance(
+                    InetAddress.getByAddress(BitBufferHelper.getBits(data, bitOffset, 128)).getHostAddress()));
+
+            if (naPdu.getIpv6Length() > Ipv6Constants.ICMPV6_NA_LENGTH_WO_OPTIONS) {
+                bitOffset = bitOffset + 128;
+                naPdu.setOptionType(BitBufferHelper.getShort(BitBufferHelper.getBits(data, bitOffset, 8)));
+                bitOffset = bitOffset + 8;
+                naPdu.setTargetAddrLength(BitBufferHelper.getShort(BitBufferHelper.getBits(data, bitOffset, 8)));
+                bitOffset = bitOffset + 8;
+                if (naPdu.getOptionType() == Ipv6Constants.ICMP_V6_OPTION_TARGET_LLA) {
+                    naPdu.setTargetLlAddress(new MacAddress(
+                            Ipv6ServiceUtils.bytesToHexString(BitBufferHelper.getBits(data, bitOffset, 48))));
+                }
+            }
+            return naPdu.build();
+        }
+    }
+
+    private void fireNotification(Notification notification) {
+        ListenableFuture<?> offerNotification = notificationPublishService.offerNotification(notification);
+        if (offerNotification != null && offerNotification.equals(NotificationPublishService.REJECTED)) {
+            LOG.warn("Offered Notification was rejected: {}", notification);
+        }
     }
 
     @Override
index 34630e7f25758acbad294e3856504da24e54810b..81b22cea9292096721275526b448e239bf962aff 100644 (file)
@@ -39,6 +39,7 @@ public class Ipv6ServiceInterfaceEventListener
     private final IfMgr ifMgr;
     private final Ipv6ServiceUtils ipv6ServiceUtils;
     private final JobCoordinator jobCoordinator;
+    private final Ipv6ServiceEosHandler ipv6ServiceEosHandler;
 
     /**
      * Intialize the member variables.
@@ -46,12 +47,13 @@ public class Ipv6ServiceInterfaceEventListener
      */
     @Inject
     public Ipv6ServiceInterfaceEventListener(DataBroker broker, IfMgr ifMgr, Ipv6ServiceUtils ipv6ServiceUtils,
-                                             final JobCoordinator jobCoordinator) {
+            final JobCoordinator jobCoordinator, Ipv6ServiceEosHandler ipv6ServiceEosHandler) {
         super(Interface.class, Ipv6ServiceInterfaceEventListener.class);
         this.dataBroker = broker;
         this.ifMgr = ifMgr;
         this.ipv6ServiceUtils = ipv6ServiceUtils;
         this.jobCoordinator = jobCoordinator;
+        this.ipv6ServiceEosHandler = ipv6ServiceEosHandler;
     }
 
     @PostConstruct
@@ -78,6 +80,10 @@ public class Ipv6ServiceInterfaceEventListener
             return;
         }
 
+        if (!ipv6ServiceEosHandler.isClusterOwner()) {
+            LOG.trace("Not a cluster Owner, skipping further IPv6 processing on this node.");
+            return;
+        }
         Uuid portId = new Uuid(del.getName());
         VirtualPort port = ifMgr.obtainV6Interface(portId);
         if (port == null) {
@@ -93,6 +99,10 @@ public class Ipv6ServiceInterfaceEventListener
                 return Collections.emptyList();
             }, SystemPropertyReader.getDataStoreJobCoordinatorMaxRetries());
         }
+
+        VirtualPort routerPort = ifMgr.getRouterV6InterfaceForNetwork(port.getNetworkID());
+        ifMgr.handleInterfaceStateEvent(port, ipv6ServiceUtils.getDpIdFromInterfaceState(del), routerPort,
+                Ipv6Constants.DEL_FLOW);
     }
 
     @Override
@@ -144,13 +154,17 @@ public class Ipv6ServiceInterfaceEventListener
                 Long ofPort = MDSALUtil.getOfPortNumberFromPortName(nodeConnectorId);
                 ifMgr.updateDpnInfo(portId, dpId, ofPort);
 
+                if (!ipv6ServiceEosHandler.isClusterOwner()) {
+                    LOG.trace("Not a cluster Owner, skipping further IPv6 processing on this node.");
+                    return;
+                }
+
                 VirtualPort routerPort = ifMgr.getRouterV6InterfaceForNetwork(port.getNetworkID());
                 if (routerPort == null) {
                     LOG.info("Port {} is not associated to a Router, skipping.", portId);
                     return;
                 }
-                // Check and program icmpv6 punt flows on the dpnID if its the first VM on the host.
-                ifMgr.programIcmpv6PuntFlowsIfNecessary(portId, dpId, routerPort);
+                ifMgr.handleInterfaceStateEvent(port, dpId, routerPort, Ipv6Constants.ADD_FLOW);
 
                 if (!port.getServiceBindingStatus()) {
                     jobCoordinator.enqueueJob("IPv6-" + String.valueOf(portId), () -> {
index fbde5388b0160834dbf463920f23eb55dc92a816..7c5b2df90d27dc5cc0b6351fb7adceb5735793b4 100644 (file)
@@ -103,13 +103,19 @@ public class NeutronPortChangeListener extends AsyncClusteredDataTreeChangeListe
 
         LOG.debug("update port notification handler is invoked for port {} ", update);
 
-        Set<FixedIps> oldIPs = getFixedIpSet(original.getFixedIps());
-        Set<FixedIps> newIPs = getFixedIpSet(update.getFixedIps());
-        if (!oldIPs.equals(newIPs)) {
+        Set<FixedIps> ipsBefore = getFixedIpSet(original.getFixedIps());
+        Set<FixedIps> ipsAfter = getFixedIpSet(update.getFixedIps());
+
+        Set<FixedIps> deletedIps = new HashSet<>(ipsBefore);
+        deletedIps.removeAll(ipsAfter);
+
+        if (!ipsBefore.equals(ipsAfter)) {
             Boolean portIncludesV6Address = Boolean.FALSE;
             ifMgr.clearAnyExistingSubnetInfo(update.getUuid());
-            List<FixedIps> ipList = update.getFixedIps();
-            for (FixedIps fixedip : ipList) {
+
+            Set<FixedIps> remainingIps = new HashSet<>(ipsAfter);
+            remainingIps.removeAll(deletedIps);
+            for (FixedIps fixedip : remainingIps) {
                 if (fixedip.getIpAddress().getIpv4Address() != null) {
                     continue;
                 }
@@ -118,7 +124,8 @@ public class NeutronPortChangeListener extends AsyncClusteredDataTreeChangeListe
             }
 
             if (update.getDeviceOwner().equalsIgnoreCase(Ipv6Constants.NETWORK_ROUTER_INTERFACE)) {
-                ifMgr.updateRouterIntf(update.getUuid(), new Uuid(update.getDeviceId()), update.getFixedIps());
+                ifMgr.updateRouterIntf(update.getUuid(), new Uuid(update.getDeviceId()), update.getFixedIps(),
+                        deletedIps);
             } else {
                 ifMgr.updateHostIntf(update.getUuid(), portIncludesV6Address);
             }
index 837b84a22f17e07a7886460ac6d4a4e924ba8a9f..670239f8cd470323bd392c187a8a7a6d3a782fcb 100644 (file)
@@ -17,6 +17,7 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.opendaylight.netvirt.ipv6service.api.IVirtualNetwork;
 import org.opendaylight.netvirt.ipv6service.utils.Ipv6Constants;
+import org.opendaylight.netvirt.ipv6service.utils.Ipv6ServiceUtils;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv6Address;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
 
@@ -100,6 +101,7 @@ public class VirtualNetwork implements IVirtualNetwork {
             dpnIfaceList.values().forEach(dpnInterfaceInfo -> {
                 dpnInterfaceInfo.clearOfPortList();
                 dpnInterfaceInfo.clearNdTargetFlowInfo();
+                dpnInterfaceInfo.clearsubnetCidrPuntFlowInfo();
             });
 
             clearDpnInterfaceList();
@@ -109,13 +111,12 @@ public class VirtualNetwork implements IVirtualNetwork {
     public static class DpnInterfaceInfo {
         BigInteger dpId;
         int rsPuntFlowConfigured;
-        Set<Long> ofPortList;
-        Set<Ipv6Address> ndTargetFlowsPunted;
+        final Set<Uuid> subnetCidrPuntFlowList = ConcurrentHashMap.newKeySet();
+        final Set<Long> ofPortList = ConcurrentHashMap.newKeySet();
+        final Set<Ipv6Address> ndTargetFlowsPunted = ConcurrentHashMap.newKeySet();
 
         DpnInterfaceInfo(BigInteger dpnId) {
             dpId = dpnId;
-            ofPortList = ConcurrentHashMap.newKeySet();
-            ndTargetFlowsPunted =  ConcurrentHashMap.newKeySet();
             rsPuntFlowConfigured = Ipv6Constants.FLOWS_NOT_CONFIGURED;
         }
 
@@ -135,18 +136,38 @@ public class VirtualNetwork implements IVirtualNetwork {
             return rsPuntFlowConfigured;
         }
 
+        public void updateSubnetCidrFlowStatus(Uuid subnetUUID, int addOrRemove) {
+            if (addOrRemove == Ipv6Constants.ADD_FLOW) {
+                this.subnetCidrPuntFlowList.add(subnetUUID);
+            } else {
+                this.subnetCidrPuntFlowList.remove(subnetUUID);
+            }
+        }
+
+        public boolean isSubnetCidrFlowAlreadyConfigured(Uuid subnetUUID) {
+            return subnetCidrPuntFlowList.contains(subnetUUID);
+        }
+
         public Set<Ipv6Address> getNDTargetFlows() {
             return ndTargetFlowsPunted;
         }
 
         public void updateNDTargetAddress(Ipv6Address ipv6Address, int addOrRemove) {
+            Ipv6Address ipv6 =
+                    Ipv6Address.getDefaultInstance(Ipv6ServiceUtils.getFormattedIpv6Address(ipv6Address.getValue()));
             if (addOrRemove == Ipv6Constants.ADD_ENTRY) {
-                this.ndTargetFlowsPunted.add(ipv6Address);
+                this.ndTargetFlowsPunted.add(ipv6);
             } else {
-                this.ndTargetFlowsPunted.remove(ipv6Address);
+                this.ndTargetFlowsPunted.remove(ipv6);
             }
         }
 
+        public boolean isNdTargetFlowAlreadyConfigured(Ipv6Address ipv6Address) {
+            Ipv6Address ipv6 =
+                    Ipv6Address.getDefaultInstance(Ipv6ServiceUtils.getFormattedIpv6Address(ipv6Address.getValue()));
+            return this.ndTargetFlowsPunted.contains(ipv6);
+        }
+
         public void clearNdTargetFlowInfo() {
             this.ndTargetFlowsPunted.clear();
         }
@@ -163,9 +184,14 @@ public class VirtualNetwork implements IVirtualNetwork {
             this.ofPortList.clear();
         }
 
+        public void clearsubnetCidrPuntFlowInfo() {
+            this.subnetCidrPuntFlowList.clear();
+        }
+
         @Override
         public String toString() {
-            return "DpnInterfaceInfo [dpId=" + dpId + " rsPuntFlowConfigured=" + rsPuntFlowConfigured + " ofPortList="
+            return "DpnInterfaceInfo [dpId=" + dpId + " rsPuntFlowConfigured=" + rsPuntFlowConfigured
+                    + "subnetCidrPuntFlowList=" + subnetCidrPuntFlowList + " ofPortList="
                     + ofPortList + "]";
         }
     }
index d64fe6110dfa8683002f693f2cf8d16035d7f3dc..6569fdc830051a7636906c336bd6a5338a552cba 100644 (file)
@@ -27,6 +27,8 @@ public interface Ipv6Constants {
     int ICMPV6_OPTION_SOURCE_LLA_LENGTH = 8;
     int ICMPV6_OPTION_PREFIX_LENGTH = 32;
 
+    int ICMPV6_NA_LENGTH_WO_OPTIONS = 24;
+
     int IPV6_DEFAULT_HOP_LIMIT = 64;
     int IPV6_ROUTER_LIFETIME = 4500;
     int IPV6_RA_VALID_LIFETIME = 2592000;
@@ -55,9 +57,11 @@ public interface Ipv6Constants {
     String NETWORK_ROUTER_INTERFACE = "network:router_interface";
     String NETWORK_ROUTER_GATEWAY = "network:router_gateway";
     String DEVICE_OWNER_DHCP = "network:dhcp";
+    String DEVICE_OWNER_COMPUTE_NOVA = "compute:nova";
 
     BigInteger INVALID_DPID = new BigInteger("-1");
     short DEFAULT_FLOW_PRIORITY = 50;
+    short PUNT_NA_FLOW_PRIORITY = 40;
     String FLOWID_PREFIX = "IPv6.";
     String FLOWID_SEPARATOR = ".";
 
index 8dee6b13b76cb19c313131d499a345f04c2a824d..370400440d2b3af3f98cf264e53c1759ff0f13a0 100644 (file)
@@ -31,6 +31,7 @@ import org.opendaylight.genius.mdsalutil.MDSALUtil;
 import org.opendaylight.genius.mdsalutil.MatchInfo;
 import org.opendaylight.genius.mdsalutil.MetaDataUtil;
 import org.opendaylight.genius.mdsalutil.NwConstants;
+import org.opendaylight.genius.mdsalutil.actions.ActionNxResubmit;
 import org.opendaylight.genius.mdsalutil.actions.ActionPuntToController;
 import org.opendaylight.genius.mdsalutil.instructions.InstructionApplyActions;
 import org.opendaylight.genius.mdsalutil.interfaces.IMdsalApiManager;
@@ -38,9 +39,12 @@ import org.opendaylight.genius.mdsalutil.matches.MatchEthernetType;
 import org.opendaylight.genius.mdsalutil.matches.MatchIcmpv6;
 import org.opendaylight.genius.mdsalutil.matches.MatchIpProtocol;
 import org.opendaylight.genius.mdsalutil.matches.MatchIpv6NdTarget;
+import org.opendaylight.genius.mdsalutil.matches.MatchIpv6Source;
 import org.opendaylight.genius.mdsalutil.matches.MatchMetadata;
 import org.opendaylight.genius.utils.ServiceIndex;
+import org.opendaylight.netvirt.ipv6service.api.IVirtualPort;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv6Address;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv6Prefix;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.Interfaces;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.InterfacesState;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.InterfaceKey;
@@ -57,6 +61,7 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.ser
 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.servicebinding.rev160406.service.bindings.services.info.BoundServices;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.servicebinding.rev160406.service.bindings.services.info.BoundServicesBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.servicebinding.rev160406.service.bindings.services.info.BoundServicesKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.ipv6service.nd.packet.rev160620.EthernetHeader;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.ipv6service.nd.packet.rev160620.Ipv6Header;
 import org.opendaylight.yangtools.yang.binding.DataObject;
@@ -389,6 +394,15 @@ public class Ipv6ServiceUtils {
         return matches;
     }
 
+    private List<MatchInfo> getIcmpv6NAMatch(Long elanTag) {
+        List<MatchInfo> matches = new ArrayList<>();
+        matches.add(MatchEthernetType.IPV6);
+        matches.add(MatchIpProtocol.ICMPV6);
+        matches.add(new MatchIcmpv6(Ipv6Constants.ICMP_V6_NA_CODE, (short) 0));
+        matches.add(new MatchMetadata(MetaDataUtil.getElanTagMetadata(elanTag), MetaDataUtil.METADATA_MASK_SERVICE));
+        return matches;
+    }
+
     private static String getIPv6FlowRef(BigInteger dpId, Long elanTag, String flowType) {
         return new StringBuffer().append(Ipv6Constants.FLOWID_PREFIX)
                 .append(dpId).append(Ipv6Constants.FLOWID_SEPARATOR)
@@ -396,6 +410,25 @@ public class Ipv6ServiceUtils {
                 .append(flowType).toString();
     }
 
+    /**
+     * Gets the formatted IPv6 address. <br>
+     * e.g., <br>
+     * 1. input = "1001:db8:0:2::1", return = "1001:db8:0:2:0:0:0:1" <br>
+     * 2. input = "2607:f0d0:1002:51::4", return = "2607:f0d0:1002:51:0:0:0:4" <br>
+     * 3. input = "1001:db8:0:2:0:0:0:1", return = "1001:db8:0:2:0:0:0:1"
+     *
+     * @param ipv6Address the ipv6 address
+     * @return the formatted ipv6 address
+     */
+    public static String getFormattedIpv6Address(String ipv6Address) {
+        try {
+            return InetAddress.getByName(ipv6Address).getHostAddress();
+        } catch (UnknownHostException e) {
+            LOG.warn("Unknown host {}", ipv6Address, e);
+            return null;
+        }
+    }
+
     public void installIcmpv6NsPuntFlow(short tableId, BigInteger dpId,  Long elanTag, String ipv6Address,
             int addOrRemove) {
         List<MatchInfo> neighborSolicitationMatch = getIcmpv6NSMatch(elanTag, ipv6Address);
@@ -403,8 +436,10 @@ public class Ipv6ServiceUtils {
         List<ActionInfo> actionsInfos = new ArrayList<>();
         actionsInfos.add(new ActionPuntToController());
         instructions.add(new InstructionApplyActions(actionsInfos));
+
+        String formattedIp = getFormattedIpv6Address(ipv6Address);
         FlowEntity rsFlowEntity = MDSALUtil.buildFlowEntity(dpId, tableId,
-                getIPv6FlowRef(dpId, elanTag, ipv6Address),Ipv6Constants.DEFAULT_FLOW_PRIORITY, "IPv6NS",
+                getIPv6FlowRef(dpId, elanTag, formattedIp),Ipv6Constants.DEFAULT_FLOW_PRIORITY, "IPv6NS",
                 0, 0, NwConstants.COOKIE_IPV6_TABLE, neighborSolicitationMatch, instructions);
         if (addOrRemove == Ipv6Constants.DEL_FLOW) {
             LOG.trace("Removing IPv6 Neighbor Solicitation Flow DpId {}, elanTag {}", dpId, elanTag);
@@ -437,6 +472,57 @@ public class Ipv6ServiceUtils {
         }
     }
 
+    public void installIcmpv6NaForwardFlow(short tableId, IVirtualPort vmPort, BigInteger dpId, Long elanTag,
+            int addOrRemove) {
+        List<MatchInfo> matches = getIcmpv6NAMatch(elanTag);
+        List<InstructionInfo> instructions = new ArrayList<>();
+        List<ActionInfo> actionsInfos = new ArrayList<>();
+        actionsInfos.add(new ActionNxResubmit(NwConstants.LPORT_DISPATCHER_TABLE));
+        instructions.add(new InstructionApplyActions(actionsInfos));
+
+        for (Ipv6Address ipv6Address : vmPort.getIpv6Addresses()) {
+            matches.add(new MatchIpv6Source(ipv6Address.getValue() + NwConstants.IPV6PREFIX));
+            String flowId = getIPv6FlowRef(dpId, elanTag,
+                    vmPort.getIntfUUID().getValue() + Ipv6Constants.FLOWID_SEPARATOR + ipv6Address.getValue());
+            FlowEntity rsFlowEntity =
+                    MDSALUtil.buildFlowEntity(dpId, tableId, flowId, Ipv6Constants.DEFAULT_FLOW_PRIORITY, "IPv6NA", 0,
+                            0, NwConstants.COOKIE_IPV6_TABLE, matches, instructions);
+            if (addOrRemove == Ipv6Constants.DEL_FLOW) {
+                LOG.trace("Removing IPv6 Neighbor Advertisement Flow DpId {}, elanTag {}, ipv6Address {}", dpId,
+                        elanTag, ipv6Address.getValue());
+                mdsalUtil.removeFlow(rsFlowEntity);
+            } else {
+                LOG.trace("Installing IPv6 Neighbor Advertisement Flow DpId {}, elanTag {}, ipv6Address {}", dpId,
+                        elanTag, ipv6Address.getValue());
+                mdsalUtil.installFlow(rsFlowEntity);
+            }
+        }
+    }
+
+    public void installIcmpv6NaPuntFlow(short tableId, Ipv6Prefix ipv6Prefix, BigInteger dpId, Long elanTag,
+            int addOrRemove) {
+        List<MatchInfo> naMatch = getIcmpv6NAMatch(elanTag);
+        naMatch.add(new MatchIpv6Source(ipv6Prefix));
+
+        List<InstructionInfo> instructions = new ArrayList<>();
+        List<ActionInfo> actionsInfos = new ArrayList<>();
+        actionsInfos.add(new ActionPuntToController());
+        actionsInfos.add(new ActionNxResubmit(NwConstants.LPORT_DISPATCHER_TABLE));
+        instructions.add(new InstructionApplyActions(actionsInfos));
+
+        String flowId = getIPv6FlowRef(dpId, elanTag, "IPv6NA." + ipv6Prefix.getValue());
+        FlowEntity rsFlowEntity = MDSALUtil.buildFlowEntity(dpId, tableId,
+                flowId, Ipv6Constants.PUNT_NA_FLOW_PRIORITY,
+                "IPv6NA", 0, 0, NwConstants.COOKIE_IPV6_TABLE, naMatch, instructions);
+        if (addOrRemove == Ipv6Constants.DEL_FLOW) {
+            LOG.trace("Removing IPv6 Neighbor Advertisement Flow DpId {}, elanTag {}", dpId, elanTag);
+            mdsalUtil.removeFlow(rsFlowEntity);
+        } else {
+            LOG.trace("Installing IPv6 Neighbor Advertisement Flow DpId {}, elanTag {}", dpId, elanTag);
+            mdsalUtil.installFlow(rsFlowEntity);
+        }
+    }
+
     public BoundServices getBoundServices(String serviceName, short servicePriority, int flowPriority,
                                           BigInteger cookie, List<Instruction> instructions) {
         StypeOpenflowBuilder augBuilder = new StypeOpenflowBuilder().setFlowCookie(cookie)
@@ -476,7 +562,25 @@ public class Ipv6ServiceUtils {
                         NwConstants.IPV6_SERVICE_INDEX)));
     }
 
+    public BigInteger getDpIdFromInterfaceState(org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf
+            .interfaces.rev140508.interfaces.state.Interface interfaceState) {
+        BigInteger dpId = null;
+        List<String> ofportIds = interfaceState.getLowerLayerIf();
+        if (ofportIds != null && !ofportIds.isEmpty()) {
+            NodeConnectorId nodeConnectorId = new NodeConnectorId(ofportIds.get(0));
+            dpId = BigInteger.valueOf(MDSALUtil.getDpnIdFromPortName(nodeConnectorId));
+        }
+        return dpId;
+    }
+
     public static long getRemoteBCGroup(long elanTag) {
         return Ipv6Constants.ELAN_GID_MIN + elanTag % Ipv6Constants.ELAN_GID_MIN * 2;
     }
+
+    public static boolean isVmPort(String deviceOwner) {
+        // FIXME: Currently for VM ports, Neutron is sending deviceOwner as empty instead of "compute:nova".
+        // return Ipv6Constants.DEVICE_OWNER_COMPUTE_NOVA.equalsIgnoreCase(deviceOwner);
+        return Ipv6Constants.DEVICE_OWNER_COMPUTE_NOVA.equalsIgnoreCase(deviceOwner)
+                || StringUtils.isEmpty(deviceOwner);
+    }
 }
index cf1a89bbb8a381e7f23e274ac2f7cf2ae73cf1c4..13f9fd1e6c4d12b9c72704583aeb8b0183fe35e1 100644 (file)
              interface="org.opendaylight.mdsal.eos.binding.api.EntityOwnershipService" />
   <reference id="jobCoordinator"
              interface="org.opendaylight.infrautils.jobcoordinator.JobCoordinator" />
+  <reference id="notificationPublishService"
+             interface="org.opendaylight.controller.md.sal.binding.api.NotificationPublishService" />
 
   <odl:rpc-service id="odlInterfaceRpcService"
                    interface="org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.OdlInterfaceRpcService" />
   <odl:rpc-service id="packetProcessingService"
                    interface="org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingService" />
 
+  <odl:rpc-implementation ref="ipv6NdUtilServiceImpl" />
+
   <odl:notification-listener ref="ipv6PktHandler" />
 
   <service ref="ifMgr" interface="org.opendaylight.netvirt.ipv6service.api.ElementCache" />
index 942bd2513ec4c4269a15d79cb93e9fbb2ebdfa08..7678223a85d954e2eccf898dd058b09d386fdceb 100644 (file)
@@ -20,6 +20,7 @@ import java.util.List;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mockito;
+import org.opendaylight.controller.md.sal.binding.api.NotificationPublishService;
 import org.opendaylight.genius.mdsalutil.NwConstants;
 import org.opendaylight.genius.mdsalutil.actions.ActionNxResubmit;
 import org.opendaylight.netvirt.ipv6service.utils.Ipv6Constants;
@@ -38,18 +39,22 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.N
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.Metadata;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.MetadataBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.ipv6service.ipv6util.rev170210.NaReceived;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.ipv6service.ipv6util.rev170210.NaReceivedBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingService;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketReceived;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketReceivedBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInputBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.packet.received.MatchBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.TableId;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 
 public class Ipv6PktHandlerTest {
     private PacketProcessingService pktProcessService;
     private Ipv6PktHandler pktHandler;
     private IfMgr ifMgrInstance;
+    private NotificationPublishService notificationPublishService;
     private long counter;
     private static final int THREAD_WAIT_TIME = 100;
     private Ipv6TestUtils ipv6TestUtils;
@@ -58,7 +63,8 @@ public class Ipv6PktHandlerTest {
     public void initTest() {
         pktProcessService = Mockito.mock(PacketProcessingService.class);
         ifMgrInstance = Mockito.mock(IfMgr.class);
-        pktHandler = new Ipv6PktHandler(pktProcessService, ifMgrInstance);
+        notificationPublishService = Mockito.mock(NotificationPublishService.class);
+        pktHandler = new Ipv6PktHandler(pktProcessService, ifMgrInstance, notificationPublishService);
         counter = pktHandler.getPacketProcessedCounter();
         ipv6TestUtils = new Ipv6TestUtils();
     }
@@ -497,6 +503,117 @@ public class Ipv6PktHandlerTest {
                 .setEgress(ncRef).setIngress(ncRef).setAction(any(List.class)).build());
     }
 
+    @Test
+    public void testonPacketReceivedNeighborAdvertisementWithValidPayload() throws Exception {
+        VirtualPort intf = Mockito.mock(VirtualPort.class);
+        when(intf.getNetworkID()).thenReturn(new Uuid("eeec9dba-d831-4ad7-84b9-00d7f65f0555"));
+        when(ifMgrInstance.getInterfaceNameFromTag(anyLong())).thenReturn("ddec9dba-d831-4ad7-84b9-00d7f65f052f");
+        when(ifMgrInstance.obtainV6Interface(any())).thenReturn(intf);
+
+        InstanceIdentifier<Node> ncId = InstanceIdentifier.builder(Nodes.class)
+                .child(Node.class, new NodeKey(new NodeId("openflow:1"))).build();
+        NodeConnectorRef ncRef = new NodeConnectorRef(ncId);
+
+        BigInteger mdata = new BigInteger(String.valueOf(0x1000000));
+        Metadata metadata = new MetadataBuilder().setMetadata(mdata).build();
+        MatchBuilder matchbuilder = new MatchBuilder().setMetadata(metadata);
+        pktHandler.onPacketReceived(new PacketReceivedBuilder().setPayload(ipv6TestUtils.buildPacket(
+                "FA 16 3E F7 69 4E",                               // Destination MAC
+                "FA 16 3E A9 38 94",                               // Source MAC
+                "86 DD",                                           // IPv6
+                "60 00 00 00",                                     // Version 6, traffic class 0, no flowlabel
+                "00 20",                                           // Payload length
+                "3A",                                              // Next header is ICMPv6
+                "FF",                                              // Hop limit
+                "20 01 0D B8 00 00 00 02 00 00 00 00 00 00 11 11", // Source IP
+                "10 01 0D B8 00 00 00 02 F8 16 3E FF FE F7 69 4E", // Destination IP
+                "88",                                              // ICMPv6 neighbor advertisement
+                "00",                                              // Code
+                "C9 9F",                                           // Checksum (valid)
+                "00 00 00 00",                                     // ICMPv6 message body
+                "20 01 0D B8 00 00 00 02 00 00 00 00 00 00 11 11", // Target
+                "02",                                              // ICMPv6 Option: Target Link Layer Address
+                "01",                                              // Length
+                "FA 16 3E A9 38 94"                                // Link Layer Address
+        )).setIngress(ncRef).setMatch(matchbuilder.build()).setTableId(new TableId((short) 45)).build());
+        //wait on this thread until the async job is completed in the packet handler.
+        waitForPacketProcessing();
+
+        NaReceivedBuilder naReceivedBuilder = new NaReceivedBuilder().setSourceMac(new MacAddress("fa:16:3e:a9:38:94"))
+                .setDestinationMac(new MacAddress("fa:16:3e:f7:69:4e"))
+                .setSourceIpv6(Ipv6Address.getDefaultInstance("2001:db8:0:2:0:0:0:1111"))
+                .setDestinationIpv6(Ipv6Address.getDefaultInstance("1001:db8:0:2:f816:3eff:fef7:694e"))
+                .setTargetAddress(Ipv6Address.getDefaultInstance("2001:db8:0:2:0:0:0:1111"))
+                .setTargetLlAddress(new MacAddress("fa:16:3e:a9:38:94")).setOfTableId(45L).setMetadata(mdata)
+                .setInterface("ddec9dba-d831-4ad7-84b9-00d7f65f052f");
+        verify(notificationPublishService).offerNotification(naReceivedBuilder.build());
+    }
+
+    @Test
+    public void testonPacketReceivedNeighborAdvertisementWithInvalidPayload() throws Exception {
+        //incorrect checksum
+        VirtualPort intf = Mockito.mock(VirtualPort.class);
+        when(intf.getNetworkID()).thenReturn(new Uuid("eeec9dba-d831-4ad7-84b9-00d7f65f0555"));
+        when(ifMgrInstance.getInterfaceNameFromTag(anyLong())).thenReturn("ddec9dba-d831-4ad7-84b9-00d7f65f052f");
+        when(ifMgrInstance.obtainV6Interface(any())).thenReturn(intf);
+
+        InstanceIdentifier<Node> ncId = InstanceIdentifier.builder(Nodes.class)
+                .child(Node.class, new NodeKey(new NodeId("openflow:1"))).build();
+        NodeConnectorRef ncRef = new NodeConnectorRef(ncId);
+
+        BigInteger mdata = new BigInteger(String.valueOf(0x1000000));
+        Metadata metadata = new MetadataBuilder().setMetadata(mdata).build();
+        MatchBuilder matchbuilder = new MatchBuilder().setMetadata(metadata);
+
+        // incorrect checksum
+        pktHandler.onPacketReceived(new PacketReceivedBuilder().setPayload(ipv6TestUtils.buildPacket(
+                "FA 16 3E F7 69 4E",                               // Destination MAC
+                "FA 16 3E A9 38 94",                               // Source MAC
+                "86 DD",                                           // IPv6
+                "60 00 00 00",                                     // Version 6, traffic class 0, no flowlabel
+                "00 20",                                           // Payload length
+                "3A",                                              // Next header is ICMPv6
+                "FF",                                              // Hop limit
+                "20 01 0D B8 00 00 00 02 00 00 00 00 00 00 11 11", // Source IP
+                "10 01 0D B8 00 00 00 02 F8 16 3E FF FE F7 69 4E", // Destination IP
+                "88",                                              // ICMPv6 neighbor advertisement
+                "00",                                              // Code
+                "C9 9A",                                           // Checksum (valid)
+                "00 00 00 00",                                     // ICMPv6 message body
+                "20 01 0D B8 00 00 00 02 00 00 00 00 00 00 11 11", // Target
+                "02",                                              // ICMPv6 Option: Target Link Layer Address
+                "01",                                              // Length
+                "FA 16 3E A9 38 94"                                // Link Layer Address
+        )).setIngress(ncRef).setMatch(matchbuilder.build()).setTableId(new TableId((short) 45)).build());
+        //wait on this thread until the async job is completed in the packet handler.
+        waitForPacketProcessing();
+        verify(notificationPublishService, times(0)).offerNotification(any(NaReceived.class));
+
+        // incorrect ICMPv6 type
+        pktHandler.onPacketReceived(new PacketReceivedBuilder().setPayload(ipv6TestUtils.buildPacket(
+                "FA 16 3E F7 69 4E",                               // Destination MAC
+                "FA 16 3E A9 38 94",                               // Source MAC
+                "86 DD",                                           // IPv6
+                "60 00 00 00",                                     // Version 6, traffic class 0, no flowlabel
+                "00 20",                                           // Payload length
+                "3A",                                              // Next header is ICMPv6
+                "FF",                                              // Hop limit
+                "20 01 0D B8 00 00 00 02 00 00 00 00 00 00 11 11", // Source IP
+                "10 01 0D B8 00 00 00 02 F8 16 3E FF FE F7 69 4E", // Destination IP
+                "87",                                              // ICMPv6 neighbor solicitation (invalid)
+                "00",                                              // Code
+                "C9 9F",                                           // Checksum (valid)
+                "00 00 00 00",                                     // ICMPv6 message body
+                "20 01 0D B8 00 00 00 02 00 00 00 00 00 00 11 11", // Target
+                "02",                                              // ICMPv6 Option: Target Link Layer Address
+                "01",                                              // Length
+                "FA 16 3E A9 38 94"                                // Link Layer Address
+        )).setIngress(ncRef).setMatch(matchbuilder.build()).setTableId(new TableId((short) 45)).build());
+        //wait on this thread until the async job is completed in the packet handler.
+        waitForPacketProcessing();
+        verify(notificationPublishService, times(0)).offerNotification(any(NaReceived.class));
+    }
+
     private void waitForPacketProcessing() throws InterruptedException {
         int timeOut = 1;
         while (timeOut < 20) {