[ONOS-1264] Add IPv6 unit test for ProxyArpManager

Change-Id: I1d4ec1f06508bd1bf1d54b299b0ae4ac1d5482d9
diff --git a/core/net/src/test/java/org/onosproject/net/proxyarp/impl/ProxyArpManagerTest.java b/core/net/src/test/java/org/onosproject/net/proxyarp/impl/ProxyArpManagerTest.java
index 1a160d9..3e806a7 100644
--- a/core/net/src/test/java/org/onosproject/net/proxyarp/impl/ProxyArpManagerTest.java
+++ b/core/net/src/test/java/org/onosproject/net/proxyarp/impl/ProxyArpManagerTest.java
@@ -21,11 +21,19 @@
 import org.junit.Test;
 import org.onlab.packet.ARP;
 import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP6;
+import org.onlab.packet.IPacket;
+import org.onlab.packet.IPv6;
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.Ip6Prefix;
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
+import org.onlab.packet.ndp.NeighborAdvertisement;
+import org.onlab.packet.ndp.NeighborDiscoveryOptions;
+import org.onlab.packet.ndp.NeighborSolicitation;
 import org.onosproject.incubator.net.intf.Interface;
 import org.onosproject.incubator.net.intf.InterfaceService;
 import org.onosproject.net.ConnectPoint;
@@ -66,9 +74,13 @@
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.replay;
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -83,6 +95,8 @@
 
     private static final Ip4Address IP1 = Ip4Address.valueOf("192.168.1.1");
     private static final Ip4Address IP2 = Ip4Address.valueOf("192.168.1.2");
+    private static final Ip6Address IP3 = Ip6Address.valueOf("1000::1");
+    private static final Ip6Address IP4 = Ip6Address.valueOf("1000::2");
 
     private static final ProviderId PID = new ProviderId("of", "foo");
 
@@ -90,8 +104,14 @@
     private static final VlanId VLAN2 = VlanId.vlanId((short) 2);
     private static final MacAddress MAC1 = MacAddress.valueOf("00:00:11:00:00:01");
     private static final MacAddress MAC2 = MacAddress.valueOf("00:00:22:00:00:02");
+    private static final MacAddress MAC3 = MacAddress.valueOf("00:00:33:00:00:03");
+    private static final MacAddress MAC4 = MacAddress.valueOf("00:00:44:00:00:04");
+    private static final MacAddress SOLICITED_MAC3 = MacAddress.valueOf("33:33:FF:00:00:01");
     private static final HostId HID1 = HostId.hostId(MAC1, VLAN1);
     private static final HostId HID2 = HostId.hostId(MAC2, VLAN1);
+    private static final HostId HID3 = HostId.hostId(MAC3, VLAN1);
+    private static final HostId HID4 = HostId.hostId(MAC4, VLAN1);
+    private static final HostId SOLICITED_HID3 = HostId.hostId(SOLICITED_MAC3, VLAN1);
 
     private static final DeviceId DID1 = getDeviceId(1);
     private static final DeviceId DID2 = getDeviceId(2);
@@ -222,21 +242,29 @@
 
         for (int i = 1; i <= NUM_ADDRESS_PORTS; i++) {
             ConnectPoint cp = new ConnectPoint(getDeviceId(i), P1);
-            Ip4Prefix prefix1 =
-                    Ip4Prefix.valueOf("10.0." + (2 * i - 1) + ".0/24");
-            Ip4Address addr1 =
-                    Ip4Address.valueOf("10.0." + (2 * i - 1) + ".1");
+
+            // Interface address for IPv4
+            Ip4Prefix prefix1 = Ip4Prefix.valueOf("10.0." + (2 * i - 1) + ".0/24");
+            Ip4Address addr1 = Ip4Address.valueOf("10.0." + (2 * i - 1) + ".1");
             Ip4Prefix prefix2 = Ip4Prefix.valueOf("10.0." + (2 * i) + ".0/24");
             Ip4Address addr2 = Ip4Address.valueOf("10.0." + (2 * i) + ".1");
             InterfaceIpAddress ia1 = new InterfaceIpAddress(addr1, prefix1);
             InterfaceIpAddress ia2 = new InterfaceIpAddress(addr2, prefix2);
-            Interface intf1 = new Interface(cp, Sets.newHashSet(ia1),
+
+            // Interface address for IPv6
+            Ip6Prefix prefix3 = Ip6Prefix.valueOf((2 * i - 1) + "000::0/64");
+            Ip6Address addr3 = Ip6Address.valueOf((2 * i - 1) + "000::1");
+            Ip6Prefix prefix4 = Ip6Prefix.valueOf((2 * i) + "000::0/64");
+            Ip6Address addr4 = Ip6Address.valueOf((2 * i) + "000::1");
+            InterfaceIpAddress ia3 = new InterfaceIpAddress(addr3, prefix3);
+            InterfaceIpAddress ia4 = new InterfaceIpAddress(addr4, prefix4);
+
+            Interface intf1 = new Interface(cp, Sets.newHashSet(ia1, ia3),
                     MacAddress.valueOf(2 * i - 1),
                     VlanId.vlanId((short) 1));
-            Interface intf2 = new Interface(cp, Sets.newHashSet(ia2),
+            Interface intf2 = new Interface(cp, Sets.newHashSet(ia2, ia4),
                     MacAddress.valueOf(2 * i),
                     VlanId.NONE);
-
             interfaces.add(intf1);
             interfaces.add(intf2);
 
@@ -321,6 +349,41 @@
 
     /**
      * Tests {@link ProxyArpManager#reply(Ethernet, ConnectPoint)} in the case where the
+     * destination host is known.
+     * Verifies the correct NDP reply is sent out the correct port.
+     */
+    @Test
+    public void testReplyKnownIpv6() {
+        //Set the return value of isEdgePoint from the edgemanager.
+        isEdgePointReturn = true;
+
+        Host replyer = new DefaultHost(PID, HID3, MAC3, VLAN1, getLocation(4),
+                                       Collections.singleton(IP3));
+
+        Host requestor = new DefaultHost(PID, HID4, MAC4, VLAN1, getLocation(5),
+                                         Collections.singleton(IP4));
+
+        expect(hostService.getHostsByIp(IP3))
+                .andReturn(Collections.singleton(replyer));
+        expect(hostService.getHost(HID4)).andReturn(requestor);
+
+        replay(hostService);
+        replay(interfaceService);
+
+        Ethernet ndpRequest = buildNDP(ICMP6.NEIGHBOR_SOLICITATION,
+                                       MAC4, SOLICITED_MAC3,
+                                       IP4, IP3);
+
+        proxyArp.reply(ndpRequest, getLocation(5));
+
+        assertEquals(1, packetService.packets.size());
+        Ethernet ndpReply = buildNDP(ICMP6.NEIGHBOR_ADVERTISEMENT,
+                                     MAC3, MAC4, IP3, IP4);
+        verifyPacketOut(ndpReply, getLocation(5), packetService.packets.get(0));
+    }
+
+    /**
+     * Tests {@link ProxyArpManager#reply(Ethernet, ConnectPoint)} in the case where the
      * destination host is not known.
      * Verifies the ARP request is flooded out the correct edge ports.
      */
@@ -337,7 +400,6 @@
                 .andReturn(Collections.emptySet());
         expect(hostService.getHost(HID2)).andReturn(requestor);
 
-
         replay(hostService);
         replay(interfaceService);
 
@@ -355,6 +417,41 @@
 
     /**
      * Tests {@link ProxyArpManager#reply(Ethernet, ConnectPoint)} in the case where the
+     * destination host is not known.
+     * Verifies the NDP request is flooded out the correct edge ports.
+     */
+    @Test
+    public void testReplyUnknownIpv6() {
+        isEdgePointReturn = true;
+
+        Host requestor = new DefaultHost(PID, HID4, MAC4, VLAN1, getLocation(5),
+                                         Collections.singleton(IP4));
+
+        expect(hostService.getHostsByIp(IP3))
+                .andReturn(Collections.emptySet());
+        expect(interfaceService.getInterfacesByIp(IP4))
+                .andReturn(Collections.emptySet());
+        expect(hostService.getHost(HID4)).andReturn(requestor);
+
+        replay(hostService);
+        replay(interfaceService);
+
+        Ethernet ndpRequest = buildNDP(ICMP6.NEIGHBOR_SOLICITATION,
+                                       MAC4, SOLICITED_MAC3,
+                                       IP4, IP3);
+
+        //Setup the set of edge ports to be used in the reply method
+        getEdgePointsNoArg = Lists.newLinkedList();
+        getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("5"), PortNumber.portNumber(1)));
+        getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("4"), PortNumber.portNumber(1)));
+
+        proxyArp.reply(ndpRequest, getLocation(6));
+
+        verifyFlood(ndpRequest);
+    }
+
+    /**
+     * Tests {@link ProxyArpManager#reply(Ethernet, ConnectPoint)} in the case where the
      * destination host is known for that IP address, but is not on the same
      * VLAN as the source host.
      * Verifies the ARP request is flooded out the correct edge ports.
@@ -388,6 +485,46 @@
         verifyFlood(arpRequest);
     }
 
+    /**
+     * Tests {@link ProxyArpManager#reply(Ethernet, ConnectPoint)} in the case where the
+     * destination host is known for that IP address, but is not on the same
+     * VLAN as the source host.
+     * Verifies the NDP request is flooded out the correct edge ports.
+     */
+    @Test
+    public void testReplyDifferentVlanIpv6() {
+
+        Host replyer = new DefaultHost(PID, HID3, MAC3, VLAN2, getLocation(4),
+                                       Collections.singleton(IP3));
+
+        Host requestor = new DefaultHost(PID, HID4, MAC4, VLAN1, getLocation(5),
+                                         Collections.singleton(IP4));
+
+        expect(hostService.getHostsByIp(IP3))
+                .andReturn(Collections.singleton(replyer));
+        expect(interfaceService.getInterfacesByIp(IP4))
+                .andReturn(Collections.emptySet());
+        expect(hostService.getHost(HID4)).andReturn(requestor);
+
+        replay(hostService);
+        replay(interfaceService);
+
+        Ethernet ndpRequest = buildNDP(ICMP6.NEIGHBOR_SOLICITATION,
+                                       MAC4, SOLICITED_MAC3,
+                                       IP4, IP3);
+
+        //Setup for flood test
+        getEdgePointsNoArg = Lists.newLinkedList();
+        getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("5"), PortNumber.portNumber(1)));
+        getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("4"), PortNumber.portNumber(1)));
+        proxyArp.reply(ndpRequest, getLocation(6));
+
+        verifyFlood(ndpRequest);
+    }
+
+    /**
+     * Test ARP request from external network to an internal host.
+     */
     @Test
     public void testReplyToRequestForUs() {
         Ip4Address theirIp = Ip4Address.valueOf("10.0.1.254");
@@ -422,6 +559,63 @@
         verifyPacketOut(arpReply, LOC1, packetService.packets.get(0));
     }
 
+    /**
+     * Test NDP request from external network to an internal host.
+     */
+    @Test
+    public void testReplyToRequestForUsIpv6() {
+        Ip6Address theirIp = Ip6Address.valueOf("1000::ffff");
+        Ip6Address ourFirstIp = Ip6Address.valueOf("1000::1");
+        Ip6Address ourSecondIp = Ip6Address.valueOf("2000::1");
+        MacAddress firstMac = MacAddress.valueOf(1L);
+        MacAddress secondMac = MacAddress.valueOf(2L);
+
+        Host requestor = new DefaultHost(PID, HID2, MAC2, VLAN1, LOC1,
+                                         Collections.singleton(theirIp));
+
+        expect(hostService.getHost(HID2)).andReturn(requestor);
+        expect(hostService.getHostsByIp(ourFirstIp))
+                .andReturn(Collections.singleton(requestor));
+        replay(hostService);
+        replay(interfaceService);
+
+        Ethernet ndpRequest = buildNDP(ICMP6.NEIGHBOR_SOLICITATION,
+                                       MAC2,
+                                       MacAddress.valueOf("33:33:ff:00:00:01"),
+                                       theirIp,
+                                       ourFirstIp);
+        isEdgePointReturn = true;
+        proxyArp.reply(ndpRequest, LOC1);
+        assertEquals(1, packetService.packets.size());
+
+        Ethernet ndpReply = buildNDP(ICMP6.NEIGHBOR_ADVERTISEMENT,
+                                     firstMac,
+                                     MAC2,
+                                     ourFirstIp,
+                                     theirIp);
+        verifyPacketOut(ndpReply, LOC1, packetService.packets.get(0));
+
+        // Test a request for the second address on that port
+        packetService.packets.clear();
+        ndpRequest = buildNDP(ICMP6.NEIGHBOR_SOLICITATION,
+                              MAC2,
+                                       MacAddress.valueOf("33:33:ff:00:00:01"),
+                                       theirIp,
+                                       ourSecondIp);
+        proxyArp.reply(ndpRequest, LOC1);
+        assertEquals(1, packetService.packets.size());
+
+        ndpReply = buildNDP(ICMP6.NEIGHBOR_ADVERTISEMENT,
+                                     secondMac,
+                                     MAC2,
+                                     ourSecondIp,
+                                     theirIp);
+        verifyPacketOut(ndpReply, LOC1, packetService.packets.get(0));
+    }
+
+    /**
+     * Request for a valid external IPv4 address but coming in the wrong port.
+     */
     @Test
     public void testReplyExternalPortBadRequest() {
         replay(hostService); // no further host service expectations
@@ -442,6 +636,38 @@
         assertEquals(0, packetService.packets.size());
     }
 
+    /**
+     * Request for a valid external IPv6 address but coming in the wrong port.
+     */
+    @Test
+    public void testReplyExternalPortBadRequestIpv6() {
+        replay(hostService); // no further host service expectations
+        replay(interfaceService);
+
+        Ip6Address theirIp = Ip6Address.valueOf("1000::ffff");
+
+        Ethernet ndpRequest = buildNDP(ICMP6.NEIGHBOR_SOLICITATION,
+                                       MAC1,
+                                       MacAddress.valueOf("33:33:ff:00:00:01"),
+                                       theirIp,
+                                       Ip6Address.valueOf("3000::1"));
+        proxyArp.reply(ndpRequest, LOC1);
+        assertEquals(0, packetService.packets.size());
+
+        // Request for a valid internal IP address but coming in an external port
+        packetService.packets.clear();
+        ndpRequest = buildNDP(ICMP6.NEIGHBOR_SOLICITATION,
+                                       MAC1,
+                                       MacAddress.valueOf("33:33:ff:00:00:01"),
+                                       theirIp,
+                                       IP3);
+        proxyArp.reply(ndpRequest, LOC1);
+        assertEquals(0, packetService.packets.size());
+    }
+
+    /**
+     * Test ARP request from internal network to an external host.
+     */
     @Test
     public void testReplyToRequestFromUs() {
         Ip4Address ourIp = Ip4Address.valueOf("10.0.1.1");
@@ -474,6 +700,48 @@
     }
 
     /**
+     * Test NDP request from internal network to an external host.
+     */
+    @Test
+    public void testReplyToRequestFromUsIpv6() {
+        Ip6Address ourIp = Ip6Address.valueOf("1000::1");
+        MacAddress ourMac = MacAddress.valueOf(1L);
+        Ip6Address theirIp = Ip6Address.valueOf("1000::100");
+
+        expect(hostService.getHostsByIp(theirIp)).andReturn(Collections.emptySet());
+        expect(interfaceService.getInterfacesByIp(ourIp))
+                .andReturn(Collections.singleton(new Interface(getLocation(1),
+                        Collections.singleton(new InterfaceIpAddress(
+                                ourIp,
+                                IpPrefix.valueOf("1000::1/64"))),
+                                ourMac,
+                                VLAN1)));
+        expect(hostService.getHost(HostId.hostId(ourMac, VLAN1))).andReturn(null);
+        replay(hostService);
+        replay(interfaceService);
+
+        // This is a request from something inside our network (like a BGP
+        // daemon) to an external host.
+        Ethernet ndpRequest = buildNDP(ICMP6.NEIGHBOR_SOLICITATION,
+                                       ourMac,
+                                       MacAddress.valueOf("33:33:ff:00:00:01"),
+                                       ourIp,
+                                       theirIp);
+
+        //Ensure the packet is allowed through (it is not to an internal port)
+        isEdgePointReturn = true;
+
+        proxyArp.reply(ndpRequest, getLocation(5));
+        assertEquals(1, packetService.packets.size());
+        verifyPacketOut(ndpRequest, getLocation(1), packetService.packets.get(0));
+
+        // The same request from a random external port should fail
+        packetService.packets.clear();
+        proxyArp.reply(ndpRequest, getLocation(2));
+        assertEquals(0, packetService.packets.size());
+    }
+
+    /**
      * Tests {@link ProxyArpManager#forward(Ethernet, ConnectPoint)} in the case where the
      * destination host is known.
      * Verifies the correct ARP request is sent out the correct port.
@@ -502,6 +770,35 @@
 
     /**
      * Tests {@link ProxyArpManager#forward(Ethernet, ConnectPoint)} in the case where the
+     * destination host is known.
+     * Verifies the correct ARP request is sent out the correct port.
+     */
+    @Test
+    public void testForwardToHostIpv6() {
+        Host host1 = new DefaultHost(PID, HID3, MAC3, VLAN1, LOC1,
+                                     Collections.singleton(IP3));
+        Host host2 = new DefaultHost(PID, HID4, MAC4, VLAN1, LOC2,
+                                     Collections.singleton(IP4));
+
+        expect(hostService.getHost(SOLICITED_HID3)).andReturn(host1);
+        expect(hostService.getHost(HID4)).andReturn(host2);
+        replay(hostService);
+        replay(interfaceService);
+
+        Ethernet ndpRequest = buildNDP(ICMP6.NEIGHBOR_SOLICITATION,
+                                       MAC4, SOLICITED_MAC3,
+                                       IP4, IP3);
+
+        proxyArp.forward(ndpRequest, LOC2);
+
+        assertEquals(1, packetService.packets.size());
+        OutboundPacket packet = packetService.packets.get(0);
+
+        verifyPacketOut(ndpRequest, LOC1, packet);
+    }
+
+    /**
+     * Tests {@link ProxyArpManager#forward(Ethernet, ConnectPoint)} in the case where the
      * destination host is not known.
      * Verifies the correct ARP request is flooded out the correct edge ports.
      */
@@ -526,6 +823,33 @@
     }
 
     /**
+     * Tests {@link ProxyArpManager#forward(Ethernet, ConnectPoint)} in the case where the
+     * destination host is not known.
+     * Verifies the correct NDP request is flooded out the correct edge ports.
+     */
+    @Test
+    public void testForwardFloodIpv6() {
+        expect(hostService.getHost(SOLICITED_HID3)).andReturn(null);
+        replay(hostService);
+        replay(interfaceService);
+
+        Ethernet ndpRequest = buildNDP(ICMP6.NEIGHBOR_SOLICITATION,
+                                       MAC4, SOLICITED_MAC3,
+                                       IP4, IP3);
+
+        //populate the list of edges when so that when forward hits flood in the manager it contains the values
+        //that should continue on
+        getEdgePointsNoArg = Lists.newLinkedList();
+        getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("3"), PortNumber.portNumber(1)));
+        getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("5"), PortNumber.portNumber(1)));
+        getEdgePointsNoArg.add(new ConnectPoint(DeviceId.deviceId("4"), PortNumber.portNumber(1)));
+
+        proxyArp.forward(ndpRequest, getLocation(6));
+
+        verifyFlood(ndpRequest);
+    }
+
+    /**
      * Verifies that the given packet was flooded out all available edge ports,
      * except for the input port.
      *
@@ -626,6 +950,61 @@
     }
 
     /**
+     * Builds an NDP packet with the given parameters.
+     *
+     * @param type NeighborSolicitation or NeighborAdvertisement
+     * @param srcMac source MAC address
+     * @param dstMac destination MAC address, or null if this is a request
+     * @param srcIp  source IP address
+     * @param dstIp  destination IP address
+     * @return the NDP packet
+     */
+    private Ethernet buildNDP(byte type, MacAddress srcMac, MacAddress dstMac,
+                              Ip6Address srcIp, Ip6Address dstIp) {
+        assertThat(type, anyOf(
+                is(ICMP6.NEIGHBOR_SOLICITATION),
+                is(ICMP6.NEIGHBOR_ADVERTISEMENT)
+        ));
+        assertNotNull(srcMac);
+        assertNotNull(dstMac);
+        assertNotNull(srcIp);
+        assertNotNull(dstIp);
+
+        IPacket ndp;
+        if (type == ICMP6.NEIGHBOR_SOLICITATION) {
+            ndp = new NeighborSolicitation().setTargetAddress(dstIp.toOctets());
+        } else {
+            ndp = new NeighborAdvertisement()
+                    .setSolicitedFlag((byte) 1)
+                    .setOverrideFlag((byte) 1)
+                    .setTargetAddress(srcIp.toOctets())
+                    .addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS,
+                       srcMac.toBytes());
+        }
+
+        ICMP6 icmp6 = new ICMP6();
+        icmp6.setIcmpType(type);
+        icmp6.setIcmpCode((byte) 0);
+        icmp6.setPayload(ndp);
+
+        IPv6 ipv6 = new IPv6();
+        ipv6.setDestinationAddress(dstIp.toOctets());
+        ipv6.setSourceAddress(srcIp.toOctets());
+        ipv6.setNextHeader(IPv6.PROTOCOL_ICMP6);
+        ipv6.setHopLimit((byte) 255);
+        ipv6.setPayload(icmp6);
+
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(dstMac);
+        eth.setSourceMACAddress(srcMac);
+        eth.setEtherType(Ethernet.TYPE_IPV6);
+        eth.setVlanID(VLAN1.toShort());
+        eth.setPayload(ipv6);
+
+        return eth;
+    }
+
+    /**
      * Test PacketService implementation that simply stores OutboundPackets
      * passed to {@link #emit(OutboundPacket)} for later verification.
      */