[CORD-653] Fix host monitor and provide utility functions
Changes:
- fixes host monitor generating correct probe;
- provides and tests utility functions to calculate well known address;
- provides utility functions to craft ndp solicit and ndp adv;
- provides utility functions to craft arp request;
- provides utility functions to craft icmp and icmpv6 response;
Change-Id: I5a4fa89e549fd665a48e51ba3438932849f6627c
diff --git a/core/net/src/main/java/org/onosproject/net/host/impl/HostMonitor.java b/core/net/src/main/java/org/onosproject/net/host/impl/HostMonitor.java
index 9206176..66b5fa5 100644
--- a/core/net/src/main/java/org/onosproject/net/host/impl/HostMonitor.java
+++ b/core/net/src/main/java/org/onosproject/net/host/impl/HostMonitor.java
@@ -19,12 +19,10 @@
import org.jboss.netty.util.TimerTask;
import org.onlab.packet.ARP;
import org.onlab.packet.Ethernet;
-import org.onlab.packet.ICMP6;
import org.onlab.packet.IPv6;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
-import org.onlab.packet.ndp.NeighborDiscoveryOptions;
import org.onlab.packet.ndp.NeighborSolicitation;
import org.onlab.util.Timer;
import org.onosproject.incubator.net.intf.Interface;
@@ -212,14 +210,31 @@
IpAddress targetIp,
IpAddress sourceIp, MacAddress sourceMac,
VlanId vlan) {
- Ethernet probePacket = null;
+ Ethernet probePacket;
if (targetIp.isIp4()) {
// IPv4: Use ARP
probePacket = buildArpRequest(targetIp, sourceIp, sourceMac, vlan);
} else {
- // IPv6: Use Neighbor Discovery
- probePacket = buildNdpRequest(targetIp, sourceIp, sourceMac, vlan);
+ /*
+ * IPv6: Use Neighbor Discovery. According to the NDP protocol,
+ * we should use the solicitation node address as IPv6 destination
+ * and the multicast mac address as Ethernet destination.
+ */
+ byte[] destIp = IPv6.solicitationNodeAddress(targetIp.toOctets());
+ probePacket = NeighborSolicitation.buildNdpSolicit(
+ targetIp.toOctets(),
+ sourceIp.toOctets(),
+ destIp,
+ sourceMac.toBytes(),
+ IPv6.multicastMacAddress(destIp),
+ vlan
+ );
+ }
+
+ if (probePacket == null) {
+ log.warn("Not able to build the probe packet");
+ return;
}
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
@@ -263,43 +278,4 @@
return ethernet;
}
- private Ethernet buildNdpRequest(IpAddress targetIp, IpAddress sourceIp,
- MacAddress sourceMac, VlanId vlan) {
-
- // Create the Ethernet packet
- Ethernet ethernet = new Ethernet();
- ethernet.setEtherType(Ethernet.TYPE_IPV6)
- .setDestinationMACAddress(MacAddress.BROADCAST)
- .setSourceMACAddress(sourceMac);
- if (!vlan.equals(VlanId.NONE)) {
- ethernet.setVlanID(vlan.toShort());
- }
-
- //
- // Create the IPv6 packet
- //
- // TODO: The destination IP address should be the
- // solicited-node multicast address
- IPv6 ipv6 = new IPv6();
- ipv6.setSourceAddress(sourceIp.toOctets());
- ipv6.setDestinationAddress(targetIp.toOctets());
- ipv6.setHopLimit((byte) 255);
-
- // Create the ICMPv6 packet
- ICMP6 icmp6 = new ICMP6();
- icmp6.setIcmpType(ICMP6.NEIGHBOR_SOLICITATION);
- icmp6.setIcmpCode((byte) 0);
-
- // Create the Neighbor Solicitation packet
- NeighborSolicitation ns = new NeighborSolicitation();
- ns.setTargetAddress(targetIp.toOctets());
- ns.addOption(NeighborDiscoveryOptions.TYPE_SOURCE_LL_ADDRESS,
- sourceMac.toBytes());
-
- icmp6.setPayload(ns);
- ipv6.setPayload(icmp6);
- ethernet.setPayload(ipv6);
-
- return ethernet;
- }
}
diff --git a/utils/misc/src/main/java/org/onlab/packet/ARP.java b/utils/misc/src/main/java/org/onlab/packet/ARP.java
index 80584ba..798b88c 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ARP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ARP.java
@@ -341,6 +341,47 @@
}
/**
+ * Builds an ARP request using the supplied parameters.
+ *
+ * @param senderMacAddress the mac address of the sender
+ * @param senderIpAddress the ip address of the sender
+ * @param targetAddress the address to resolve
+ * @param vlanId the vlan id
+ * @return the Ethernet frame containing the ARP request
+ */
+ public static Ethernet buildArpRequest(byte[] senderMacAddress,
+ byte[] senderIpAddress,
+ byte[] targetAddress,
+ short vlanId) {
+
+ if (senderMacAddress.length != MacAddress.MAC_ADDRESS_LENGTH ||
+ senderIpAddress.length != Ip4Address.BYTE_LENGTH ||
+ targetAddress.length != Ip4Address.BYTE_LENGTH) {
+ return null;
+ }
+
+ ARP arpRequest = new ARP();
+ arpRequest.setHardwareType(ARP.HW_TYPE_ETHERNET)
+ .setProtocolType(ARP.PROTO_TYPE_IP)
+ .setHardwareAddressLength(
+ (byte) Ethernet.DATALAYER_ADDRESS_LENGTH)
+ .setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH)
+ .setOpCode(ARP.OP_REQUEST)
+ .setSenderHardwareAddress(senderMacAddress)
+ .setTargetHardwareAddress(MacAddress.ZERO.toBytes())
+ .setSenderProtocolAddress(senderIpAddress)
+ .setTargetProtocolAddress(targetAddress);
+
+ Ethernet eth = new Ethernet();
+ eth.setDestinationMACAddress(MacAddress.BROADCAST.toBytes())
+ .setSourceMACAddress(senderMacAddress)
+ .setEtherType(Ethernet.TYPE_ARP)
+ .setVlanID(vlanId)
+ .setPayload(arpRequest);
+ return eth;
+ }
+
+ /**
* Builds an ARP reply based on a request.
*
* @param srcIp the IP address to use as the reply source
diff --git a/utils/misc/src/main/java/org/onlab/packet/ICMP.java b/utils/misc/src/main/java/org/onlab/packet/ICMP.java
index 88a18ec..add1c0c 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ICMP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ICMP.java
@@ -225,4 +225,51 @@
.add("checksum", Short.toString(checksum))
.toString();
}
+
+ /**
+ * Builds an ICMP reply using the supplied ICMP request.
+ *
+ * @param ethRequest the Ethernet packet containing the ICMP ECHO request
+ * @return the Ethernet packet containing the ICMP ECHO reply
+ */
+ public static Ethernet buildIcmpReply(Ethernet ethRequest) {
+
+ if (ethRequest.getEtherType() != Ethernet.TYPE_IPV4) {
+ return null;
+ }
+
+ IPv4 ipRequest = (IPv4) ethRequest.getPayload();
+
+ if (ipRequest.getProtocol() != IPv4.PROTOCOL_ICMP) {
+ return null;
+ }
+
+ Ethernet ethReply = new Ethernet();
+ IPv4 ipReply = new IPv4();
+
+ int destAddress = ipRequest.getDestinationAddress();
+ ipReply.setDestinationAddress(ipRequest.getSourceAddress());
+ ipReply.setSourceAddress(destAddress);
+ ipReply.setTtl((byte) 64);
+ ipReply.setChecksum((short) 0);
+ ipReply.setProtocol(IPv4.PROTOCOL_ICMP);
+
+ ICMP icmpRequest = (ICMP) ipRequest.getPayload();
+ ICMP icmpReply = new ICMP();
+
+ icmpReply.setPayload(icmpRequest.getPayload());
+ icmpReply.setIcmpType(ICMP.TYPE_ECHO_REPLY);
+ icmpReply.setIcmpCode(ICMP.SUBTYPE_ECHO_REPLY);
+ icmpReply.setChecksum((short) 0);
+ ipReply.setPayload(icmpReply);
+
+ ethReply.setEtherType(Ethernet.TYPE_IPV4);
+ ethReply.setVlanID(ethRequest.getVlanID());
+ ethReply.setDestinationMACAddress(ethRequest.getSourceMACAddress());
+ ethReply.setSourceMACAddress(ethRequest.getDestinationMACAddress());
+ ethReply.setPayload(ipReply);
+
+
+ return ethReply;
+ }
}
diff --git a/utils/misc/src/main/java/org/onlab/packet/ICMP6.java b/utils/misc/src/main/java/org/onlab/packet/ICMP6.java
index 64552bd..4e6139b 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ICMP6.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ICMP6.java
@@ -373,4 +373,48 @@
.add("checksum", Short.toString(checksum))
.toString();
}
+
+ /**
+ * Builds an ICMPv6 reply using the supplied ICMPv6 request.
+ *
+ * @param ethRequest the Ethernet packet containing the ICMPv6 ECHO request
+ * @return the Ethernet packet containing the ICMPv6 ECHO reply
+ */
+ public static Ethernet buildIcmp6Reply(Ethernet ethRequest) {
+
+ if (ethRequest.getEtherType() != Ethernet.TYPE_IPV6) {
+ return null;
+ }
+
+ IPv6 ipv6Request = (IPv6) ethRequest.getPayload();
+
+ if (ipv6Request.getNextHeader() != IPv6.PROTOCOL_ICMP6) {
+ return null;
+ }
+
+ Ethernet ethReply = new Ethernet();
+
+
+ IPv6 ipv6Reply = new IPv6();
+
+ byte[] destAddress = ipv6Request.getDestinationAddress();
+ ipv6Reply.setDestinationAddress(ipv6Request.getSourceAddress());
+ ipv6Reply.setSourceAddress(destAddress);
+ ipv6Reply.setHopLimit((byte) 64);
+ ipv6Reply.setNextHeader(IPv6.PROTOCOL_ICMP6);
+
+ ICMP6 icmpv6Reply = new ICMP6();
+ icmpv6Reply.setPayload(ipv6Request.getPayload().getPayload());
+ icmpv6Reply.setIcmpType(ICMP6.ECHO_REPLY);
+ icmpv6Reply.setIcmpCode((byte) 0);
+ ipv6Reply.setPayload(icmpv6Reply);
+
+ ethReply.setEtherType(Ethernet.TYPE_IPV6);
+ ethReply.setVlanID(ethRequest.getVlanID());
+ ethReply.setDestinationMACAddress(ethRequest.getSourceMACAddress());
+ ethReply.setSourceMACAddress(ethRequest.getDestinationMACAddress());
+ ethReply.setPayload(ipv6Reply);
+
+ return ethReply;
+ }
}
diff --git a/utils/misc/src/main/java/org/onlab/packet/IPv6.java b/utils/misc/src/main/java/org/onlab/packet/IPv6.java
index ed26268..f466ca4 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IPv6.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IPv6.java
@@ -385,4 +385,52 @@
.add("destinationAddress", Arrays.toString(destinationAddress))
.toString();
}
+
+ /**
+ * According to the RFC 4291, the solicitation node addresses are
+ * formed by taking the low-order 24 bits of an address (unicast or anycast)
+ * and appending those bits to the prefix FF02:0:0:0:0:1:FF00::/104.
+ *
+ * Solicited-Node Address: FF02:0:0:0:0:1:FFXX:XXXX
+ *
+ * @param targetIp the unicast or anycast address
+ * @return the computed solicitation node address
+ */
+ public static byte[] solicitationNodeAddress(byte[] targetIp) {
+ return targetIp.length != Ip6Address.BYTE_LENGTH ? null : new byte[] {
+ (byte) 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, (byte) 0xff,
+ targetIp[targetIp.length - 3],
+ targetIp[targetIp.length - 2],
+ targetIp[targetIp.length - 1]
+ };
+ }
+
+ /**
+ * According to the RFC 2464, an IPv6 packet with a multicast
+ * destination address DST, consisting of the sixteen octets DST[1]
+ * through DST[16], is transmitted to the Ethernet multicast address
+ * whose first two octets are the value 3333 hexadecimal and whose last
+ * four octets are the last four octets of DST.
+ *
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |0 0 1 1 0 0 1 1|0 0 1 1 0 0 1 1|
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | DST[13] | DST[14] |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | DST[15] | DST[16] |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * @param targetIp the multicast address.
+ * @return the multicast mac address
+ */
+ public static byte[] multicastMacAddress(byte[] targetIp) {
+ return targetIp.length != Ip6Address.BYTE_LENGTH ? null : new byte[] {
+ 0x33, 0x33,
+ targetIp[targetIp.length - 4],
+ targetIp[targetIp.length - 3],
+ targetIp[targetIp.length - 2],
+ targetIp[targetIp.length - 1],
+ };
+ }
}
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 b218ab8..96e2fd7 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
@@ -17,8 +17,12 @@
import org.onlab.packet.BasePacket;
import org.onlab.packet.Deserializer;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP6;
import org.onlab.packet.IPacket;
+import org.onlab.packet.IPv6;
import org.onlab.packet.Ip6Address;
+import org.onlab.packet.MacAddress;
import java.nio.ByteBuffer;
import java.util.Arrays;
@@ -286,4 +290,66 @@
.add("targetAddress", Arrays.toString(targetAddress))
.toString();
}
+
+ /**
+ * 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(byte[] srcIp,
+ byte[] srcMac,
+ Ethernet request) {
+
+ if (srcIp.length != Ip6Address.BYTE_LENGTH ||
+ srcMac.length != MacAddress.MAC_ADDRESS_LENGTH) {
+ return null;
+ }
+
+ if (request.getEtherType() != Ethernet.TYPE_IPV6) {
+ return null;
+ }
+
+ IPv6 ipv6Request = (IPv6) request.getPayload();
+
+ if (ipv6Request.getNextHeader() != IPv6.PROTOCOL_ICMP6) {
+ return null;
+ }
+
+ ICMP6 icmpv6 = (ICMP6) ipv6Request.getPayload();
+
+ if (icmpv6.getIcmpType() != ICMP6.NEIGHBOR_SOLICITATION) {
+ return null;
+ }
+
+ 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);
+ ipv6.setDestinationAddress(ipv6Request.getSourceAddress());
+ ipv6.setHopLimit((byte) 255);
+
+ ICMP6 icmp6 = new ICMP6();
+ icmp6.setIcmpType(ICMP6.NEIGHBOR_ADVERTISEMENT);
+ icmp6.setIcmpCode((byte) 0);
+
+ NeighborAdvertisement nadv = new NeighborAdvertisement();
+ nadv.setTargetAddress(srcIp);
+ nadv.setSolicitedFlag((byte) 1);
+ nadv.setOverrideFlag((byte) 1);
+ nadv.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS,
+ srcMac);
+
+ 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 9eb2376..0df878e 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
@@ -17,8 +17,13 @@
import org.onlab.packet.BasePacket;
import org.onlab.packet.Deserializer;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP6;
import org.onlab.packet.IPacket;
+import org.onlab.packet.IPv6;
import org.onlab.packet.Ip6Address;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
import java.nio.ByteBuffer;
import java.util.Arrays;
@@ -198,4 +203,67 @@
.toString();
// TODO: need to handle options
}
+
+ /**
+ * 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(byte[] targetIp,
+ byte[] sourceIp,
+ byte[] destinationIp,
+ byte[] sourceMac,
+ byte[] destinationMac,
+ VlanId vlan) {
+
+ if (targetIp.length != Ip6Address.BYTE_LENGTH ||
+ sourceIp.length != Ip6Address.BYTE_LENGTH ||
+ destinationIp.length != Ip6Address.BYTE_LENGTH ||
+ sourceMac.length != MacAddress.MAC_ADDRESS_LENGTH ||
+ destinationMac.length != MacAddress.MAC_ADDRESS_LENGTH) {
+ return 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);
+ ipv6.setDestinationAddress(destinationIp);
+ ipv6.setHopLimit((byte) 255);
+ /*
+ * Create the ICMPv6 packet.
+ */
+ ICMP6 icmp6 = new ICMP6();
+ icmp6.setIcmpType(ICMP6.NEIGHBOR_SOLICITATION);
+ icmp6.setIcmpCode((byte) 0);
+ /*
+ * Create the Neighbor Solicitation packet.
+ */
+ NeighborSolicitation ns = new NeighborSolicitation();
+ ns.setTargetAddress(targetIp);
+ ns.addOption(NeighborDiscoveryOptions.TYPE_SOURCE_LL_ADDRESS, sourceMac);
+ /*
+ * 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/IPv6Test.java b/utils/misc/src/test/java/org/onlab/packet/IPv6Test.java
index f1d4d38..b1b555d 100644
--- a/utils/misc/src/test/java/org/onlab/packet/IPv6Test.java
+++ b/utils/misc/src/test/java/org/onlab/packet/IPv6Test.java
@@ -24,12 +24,10 @@
import org.junit.Test;
import java.nio.ByteBuffer;
+import java.util.Arrays;
import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
/**
* Tests for class {@link IPv6}.
@@ -43,6 +41,13 @@
(byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18, (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15,
(byte) 0xe6, (byte) 0xce, (byte) 0x8f, (byte) 0xff, (byte) 0xfe, (byte) 0x54, (byte) 0x37, (byte) 0xc8
};
+ private static final byte[] SOLICITATION_NODE_ADDRESS = {
+ (byte) 0xff, (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0xff, (byte) 0x54, (byte) 0x37, (byte) 0xc8
+ };
+ private static final byte[] MULTICAST_ADDRESS = {
+ (byte) 0x33, (byte) 0x33, (byte) 0xfe, (byte) 0x54, (byte) 0x37, (byte) 0xc8
+ };
private static Data data;
private static UDP udp;
private static byte[] bytePacket;
@@ -168,4 +173,20 @@
assertTrue(StringUtils.contains(str, "hopLimit=" + (byte) 32));
// TODO: test IPv6 source and destination address
}
+
+ /**
+ * Tests the proper operation of the solicitationNodeAddress function.
+ */
+ @Test
+ public void testSolicitationNodeAddress() {
+ assertTrue(Arrays.equals(SOLICITATION_NODE_ADDRESS, IPv6.solicitationNodeAddress(DESTINATION_ADDRESS)));
+ }
+
+ /**
+ * Tests the proper operation of the multicastAddress function.
+ */
+ @Test
+ public void testMulticastAddress() {
+ assertTrue(Arrays.equals(MULTICAST_ADDRESS, IPv6.multicastMacAddress(DESTINATION_ADDRESS)));
+ }
}