[ONOS-6634] Add method to create NDP replay for ICMP6
Change-Id: Ibc24470072cc6810a428ac9caf1d3343310df80c
diff --git a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java
index 96e2fd7..6ca76ed 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java
@@ -29,6 +29,8 @@
import java.util.List;
import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.packet.PacketUtils.checkInput;
/**
@@ -36,6 +38,11 @@
*/
public class NeighborAdvertisement extends BasePacket {
public static final byte HEADER_LENGTH = 20; // bytes
+ // Constants for NDP reply
+ protected static final byte NDP_HOP_LIMIT = (byte) 0x255;
+ protected static final byte NDP_SOLICITED_FLAG = (byte) 0x1;
+ protected static final byte NDP_OVERRIDE_FLAG = (byte) 0x1;
+ protected static final byte RESERVED_CODE = (byte) 0x0;
protected byte routerFlag;
protected byte solicitedFlag;
@@ -298,7 +305,9 @@
* @param srcMac the MAC address to use as the reply source
* @param request the Neighbor Solicitation request we got
* @return an Ethernet frame containing the Neighbor Advertisement reply
+ * @deprecated since 1.11.0
*/
+ @Deprecated
public static Ethernet buildNdpAdv(byte[] srcIp,
byte[] srcMac,
Ethernet request) {
@@ -352,4 +361,61 @@
return eth;
}
+ /**
+ * 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
+ * @param request the Neighbor Solicitation request we got
+ * @return an Ethernet frame containing the Neighbor Advertisement reply
+ */
+ public static Ethernet buildNdpAdv(Ip6Address srcIp,
+ MacAddress srcMac,
+ Ethernet request) {
+
+ checkNotNull(srcIp, "IP address cannot be null");
+ checkNotNull(srcMac, "MAC address cannot be null");
+ checkNotNull(request, "Request cannot be null");
+ checkArgument(request.getEtherType() == Ethernet.TYPE_IPV6,
+ "EtherType must be IPv6");
+
+ final IPv6 ipv6Request = (IPv6) request.getPayload();
+
+ checkArgument(ipv6Request.getNextHeader() == IPv6.PROTOCOL_ICMP6,
+ "Protocol must be ICMP6");
+
+ final ICMP6 icmpv6 = (ICMP6) ipv6Request.getPayload();
+
+ checkArgument(icmpv6.getIcmpType() == ICMP6.NEIGHBOR_SOLICITATION,
+ "ICMP6 type must be NEIGHBOR_SOLICITATION");
+
+ Ethernet eth = new Ethernet();
+ eth.setDestinationMACAddress(request.getSourceMAC());
+ eth.setSourceMACAddress(srcMac);
+ eth.setEtherType(Ethernet.TYPE_IPV6);
+ eth.setVlanID(request.getVlanID());
+
+ IPv6 ipv6 = new IPv6();
+ ipv6.setSourceAddress(srcIp.toOctets());
+ ipv6.setDestinationAddress(ipv6Request.getSourceAddress());
+ ipv6.setHopLimit(NDP_HOP_LIMIT);
+ ipv6.setNextHeader(IPv6.PROTOCOL_ICMP6);
+
+ ICMP6 icmp6 = new ICMP6();
+ icmp6.setIcmpType(ICMP6.NEIGHBOR_ADVERTISEMENT);
+ icmp6.setIcmpCode(RESERVED_CODE);
+
+ NeighborAdvertisement nadv = new NeighborAdvertisement();
+ nadv.setTargetAddress(srcIp.toOctets());
+ nadv.setSolicitedFlag(NDP_SOLICITED_FLAG);
+ nadv.setOverrideFlag(NDP_OVERRIDE_FLAG);
+ nadv.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS,
+ srcMac.toBytes());
+
+ icmp6.setPayload(nadv);
+ ipv6.setPayload(icmp6);
+ eth.setPayload(ipv6);
+ return eth;
+ }
+
}
diff --git a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java
index 82071da..05fd414 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java
@@ -30,6 +30,7 @@
import java.util.List;
import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.packet.PacketUtils.checkInput;
/**
@@ -37,6 +38,9 @@
*/
public class NeighborSolicitation extends BasePacket {
public static final byte HEADER_LENGTH = 20; // bytes
+ // Constants for NDP reply
+ protected static final byte NDP_HOP_LIMIT = (byte) 0x255;
+ protected static final byte RESERVED_CODE = (byte) 0x0;
protected byte[] targetAddress = new byte[Ip6Address.BYTE_LENGTH];
@@ -214,7 +218,9 @@
* @param destinationMac the destination mac address
* @param vlan the vlan id
* @return the ethernet packet containing the ndp solicitation
+ * @deprecated since 1.11.0
*/
+ @Deprecated
public static Ethernet buildNdpSolicit(byte[] targetIp,
byte[] sourceIp,
byte[] destinationIp,
@@ -259,4 +265,59 @@
return ethernet;
}
+
+ /**
+ * Builds a NDP solicitation using the supplied parameters.
+ *
+ * @param targetIp the target ip
+ * @param sourceIp the source ip
+ * @param destinationIp the destination ip
+ * @param sourceMac the source mac address
+ * @param destinationMac the destination mac address
+ * @param vlan the vlan id
+ * @return the ethernet packet containing the ndp solicitation
+ */
+ public static Ethernet buildNdpSolicit(Ip6Address targetIp,
+ Ip6Address sourceIp,
+ Ip6Address destinationIp,
+ MacAddress sourceMac,
+ MacAddress destinationMac,
+ VlanId vlan) {
+
+ checkNotNull(targetIp, "Target IP address cannot be null");
+ checkNotNull(sourceIp, "Source IP address cannot be null");
+ checkNotNull(destinationIp, "Destination IP address cannot be null");
+ checkNotNull(sourceMac, "Source MAC address cannot be null");
+ checkNotNull(destinationMac, "Destination MAC address cannot be null");
+ checkNotNull(vlan, "Vlan cannot be null");
+
+ // Here we craft the Ethernet packet.
+ Ethernet ethernet = new Ethernet();
+ ethernet.setEtherType(Ethernet.TYPE_IPV6)
+ .setDestinationMACAddress(destinationMac)
+ .setSourceMACAddress(sourceMac);
+ ethernet.setVlanID(vlan.id());
+ // IPv6 packet is created.
+ IPv6 ipv6 = new IPv6();
+ ipv6.setSourceAddress(sourceIp.toOctets());
+ ipv6.setDestinationAddress(destinationIp.toOctets());
+ ipv6.setHopLimit(NDP_HOP_LIMIT);
+ // Create the ICMPv6 packet.
+ ICMP6 icmp6 = new ICMP6();
+ icmp6.setIcmpType(ICMP6.NEIGHBOR_SOLICITATION);
+ icmp6.setIcmpCode(RESERVED_CODE);
+ // Create the Neighbor Solicitation packet.
+ NeighborSolicitation ns = new NeighborSolicitation();
+ ns.setTargetAddress(targetIp.toOctets());
+ // DAD packets should not contain SRC_LL_ADDR option
+ if (!Arrays.equals(sourceIp.toOctets(), Ip6Address.ZERO.toOctets())) {
+ ns.addOption(NeighborDiscoveryOptions.TYPE_SOURCE_LL_ADDRESS, sourceMac.toBytes());
+ }
+ // Set the payloads
+ icmp6.setPayload(ns);
+ ipv6.setPayload(icmp6);
+ ethernet.setPayload(ipv6);
+
+ return ethernet;
+ }
}
diff --git a/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborAdvertisementTest.java b/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborAdvertisementTest.java
index ac00096..db34514 100644
--- a/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborAdvertisementTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborAdvertisementTest.java
@@ -16,16 +16,24 @@
package org.onlab.packet.ndp;
import org.apache.commons.lang3.StringUtils;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
import org.junit.BeforeClass;
import org.junit.Test;
import org.onlab.packet.DeserializationException;
import org.onlab.packet.Deserializer;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP6;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.Ip6Address;
import org.onlab.packet.MacAddress;
import org.onlab.packet.PacketTestUtils;
import java.nio.ByteBuffer;
+import java.util.Arrays;
import static org.hamcrest.Matchers.is;
+import static org.hamcrest.core.IsCollectionContaining.hasItem;
import static org.junit.Assert.*;
/**
@@ -39,7 +47,18 @@
(byte) 0xfe, (byte) 0x35, (byte) 0x26, (byte) 0xce
};
private static final MacAddress MAC_ADDRESS =
- MacAddress.valueOf("11:22:33:44:55:66");
+ MacAddress.valueOf("11:22:33:44:55:66");
+ private static final MacAddress MAC_ADDRESS2 =
+ MacAddress.valueOf("10:20:30:40:50:60");
+ private static final byte[] IPV6_SOURCE_ADDRESS = {
+ (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01
+ };
+ private static final byte[] IPV6_DESTINATION_ADDRESS = {
+ (byte) 0xfe, (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02
+ };
+ private static final Ip6Address IP_6_ADDRESS = Ip6Address.valueOf(IPV6_DESTINATION_ADDRESS);
private static byte[] bytePacket;
@@ -149,4 +168,86 @@
assertTrue(StringUtils.contains(str, "overrideFlag=" + (byte) 1));
// TODO: need to handle TARGET_ADDRESS
}
+
+ /**
+ * Test Neighbor Advertisement reply build.
+ */
+ @Test
+ public void testBuildNdpAdv() {
+ Ethernet eth = new Ethernet();
+ eth.setSourceMACAddress(MAC_ADDRESS);
+ eth.setDestinationMACAddress(MAC_ADDRESS2);
+
+ IPv6 ipv6 = new IPv6();
+ ipv6.setSourceAddress(IPV6_SOURCE_ADDRESS);
+ ipv6.setDestinationAddress(IPV6_DESTINATION_ADDRESS);
+ ipv6.setNextHeader(IPv6.PROTOCOL_ICMP6);
+
+ eth.setEtherType(Ethernet.TYPE_IPV6);
+ eth.setPayload(ipv6);
+
+ ICMP6 icmp6 = new ICMP6();
+ icmp6.setIcmpType(ICMP6.NEIGHBOR_SOLICITATION);
+ icmp6.setIcmpCode(NeighborAdvertisement.RESERVED_CODE);
+ ipv6.setPayload(icmp6);
+
+ final Ethernet ethResponse = NeighborAdvertisement.buildNdpAdv(IP_6_ADDRESS, MAC_ADDRESS2, eth);
+
+ assertTrue(ethResponse.getDestinationMAC().equals(MAC_ADDRESS));
+ assertTrue(ethResponse.getSourceMAC().equals(MAC_ADDRESS2));
+ assertTrue(ethResponse.getEtherType() == Ethernet.TYPE_IPV6);
+
+ final IPv6 responseIpv6 = (IPv6) ethResponse.getPayload();
+
+ assertArrayEquals(responseIpv6.getSourceAddress(), ipv6.getDestinationAddress());
+ assertArrayEquals(responseIpv6.getDestinationAddress(), ipv6.getSourceAddress());
+ assertTrue(responseIpv6.getNextHeader() == IPv6.PROTOCOL_ICMP6);
+
+ final ICMP6 responseIcmp6 = (ICMP6) responseIpv6.getPayload();
+
+ assertTrue(responseIcmp6.getIcmpType() == ICMP6.NEIGHBOR_ADVERTISEMENT);
+ assertTrue(responseIcmp6.getIcmpCode() == NeighborAdvertisement.RESERVED_CODE);
+
+ final NeighborAdvertisement responseNadv = (NeighborAdvertisement) responseIcmp6.getPayload();
+
+ assertArrayEquals(responseNadv.getTargetAddress(), IPV6_DESTINATION_ADDRESS);
+ assertTrue(responseNadv.getSolicitedFlag() == NeighborAdvertisement.NDP_SOLICITED_FLAG);
+ assertTrue(responseNadv.getOverrideFlag() == NeighborAdvertisement.NDP_OVERRIDE_FLAG);
+ assertThat(responseNadv.getOptions(),
+ hasItem(hasOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS, MAC_ADDRESS2.toBytes())));
+ }
+
+ private NeighborDiscoveryOptionMatcher hasOption(byte type, byte[] data) {
+ return new NeighborDiscoveryOptionMatcher(type, data);
+ }
+
+ private static class NeighborDiscoveryOptionMatcher extends TypeSafeMatcher<NeighborDiscoveryOptions.Option> {
+
+ private final byte type;
+ private final byte[] data;
+ private String reason = "";
+
+ NeighborDiscoveryOptionMatcher(byte type, byte[] data) {
+ this.type = type;
+ this.data = data;
+ }
+
+ @Override
+ protected boolean matchesSafely(NeighborDiscoveryOptions.Option option) {
+ if (type != option.type()) {
+ reason = "Wrong Option type";
+ return false;
+ }
+ if (!Arrays.equals(data, option.data())) {
+ reason = "Wrong Option data";
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(reason);
+ }
+ }
}
diff --git a/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborSolicitationTest.java b/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborSolicitationTest.java
index e01db27..abcd20e 100644
--- a/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborSolicitationTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/ndp/NeighborSolicitationTest.java
@@ -158,12 +158,23 @@
*/
@Test
public void testBuildNdpSolicit() throws Exception {
- Ethernet ethPacket = NeighborSolicitation.buildNdpSolicit(TARGET_IP.toOctets(),
- SRC_IP.toOctets(), DST_IP.toOctets(),
- SRC_MAC.toBytes(), DST_MAC.toBytes(), VLAN_ID);
- IPv6 ipPacket = (IPv6) ethPacket.getPayload();
- ICMP6 icmp6Packet = (ICMP6) ipPacket.getPayload();
- NeighborSolicitation nsPacket = (NeighborSolicitation) icmp6Packet.getPayload();
+ final Ethernet ethPacket = NeighborSolicitation.buildNdpSolicit(TARGET_IP,
+ SRC_IP, DST_IP, SRC_MAC, DST_MAC, VLAN_ID);
+
+ assertTrue(ethPacket.getDestinationMAC().equals(DST_MAC));
+ assertTrue(ethPacket.getSourceMAC().equals(SRC_MAC));
+ assertTrue(ethPacket.getEtherType() == Ethernet.TYPE_IPV6);
+ assertTrue(ethPacket.getVlanID() == VLAN_ID.id());
+
+ final IPv6 ipPacket = (IPv6) ethPacket.getPayload();
+
+ assertArrayEquals(ipPacket.getSourceAddress(), SRC_IP.toOctets());
+ assertArrayEquals(ipPacket.getDestinationAddress(), DST_IP.toOctets());
+
+ final ICMP6 icmp6Packet = (ICMP6) ipPacket.getPayload();
+ final NeighborSolicitation nsPacket = (NeighborSolicitation) icmp6Packet.getPayload();
+
+ assertArrayEquals(nsPacket.getTargetAddress(), TARGET_IP.toOctets());
assertEquals("Non-DAD NS should have 1 option", 1, nsPacket.getOptions().size());
assertEquals("The option should be SRC_LL_ADDR type", TYPE_SOURCE_LL_ADDRESS,
@@ -176,9 +187,8 @@
*/
@Test
public void testBuildNdpSolicitDad() throws Exception {
- Ethernet ethPacket = NeighborSolicitation.buildNdpSolicit(TARGET_IP.toOctets(),
- Ip6Address.ZERO.toOctets(), DST_IP.toOctets(),
- SRC_MAC.toBytes(), DST_MAC.toBytes(), VLAN_ID);
+ Ethernet ethPacket = NeighborSolicitation.buildNdpSolicit(TARGET_IP,
+ Ip6Address.ZERO, DST_IP, SRC_MAC, DST_MAC, VLAN_ID);
IPv6 ipPacket = (IPv6) ethPacket.getPayload();
ICMP6 icmp6Packet = (ICMP6) ipPacket.getPayload();
NeighborSolicitation nsPacket = (NeighborSolicitation) icmp6Packet.getPayload();