[CORD-1431] Support DHCPv6 by HostLocationProvider
Change-Id: I46d268ae4f61949e2cf48f58afa6bb72e8bf4c22
diff --git a/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java b/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
index 1ead471..445afad 100644
--- a/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
+++ b/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
@@ -23,17 +23,25 @@
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.packet.ARP;
+import org.onlab.packet.BasePacket;
import org.onlab.packet.DHCP;
+import org.onlab.packet.DHCP6;
import org.onlab.packet.Ethernet;
import org.onlab.packet.ICMP6;
import org.onlab.packet.IPacket;
import org.onlab.packet.IPv4;
import org.onlab.packet.IPv6;
+import org.onlab.packet.Ip6Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.TpPort;
import org.onlab.packet.UDP;
import org.onlab.packet.VlanId;
+import org.onlab.packet.dhcp.Dhcp6ClientIdOption;
+import org.onlab.packet.dhcp.Dhcp6IaAddressOption;
+import org.onlab.packet.dhcp.Dhcp6IaNaOption;
+import org.onlab.packet.dhcp.Dhcp6IaTaOption;
+import org.onlab.packet.dhcp.Dhcp6RelayOption;
import org.onlab.packet.ipv6.IExtensionHeader;
import org.onlab.packet.ndp.NeighborAdvertisement;
import org.onlab.packet.ndp.NeighborSolicitation;
@@ -145,6 +153,11 @@
"Host Location Provider; default is false")
private boolean useDhcp = false;
+ @Property(name = "useDhcp6", boolValue = false,
+ label = "Use DHCPv6 for neighbor discovery by the " +
+ "Host Location Provider; default is false")
+ private boolean useDhcp6 = false;
+
@Property(name = "requestInterceptsEnabled", boolValue = true,
label = "Enable requesting packet intercepts")
private boolean requestInterceptsEnabled = true;
@@ -509,10 +522,18 @@
pkt.getPayload() instanceof IExtensionHeader) {
pkt = pkt.getPayload();
}
-
- // Neighbor Discovery Protocol
pkt = pkt.getPayload();
+
+ // DHCPv6 protocol
+ DHCP6 dhcp6 = findDhcp6(pkt).orElse(null);
+ if (dhcp6 != null && useDhcp6) {
+ createOrUpdateHost(hid, srcMac, vlan, hloc, null);
+ handleDhcp6(dhcp6, vlan);
+ return;
+ }
+
if (pkt != null && pkt instanceof ICMP6) {
+ // Neighbor Discovery Protocol
pkt = pkt.getPayload();
// RouterSolicitation, RouterAdvertisement
if (pkt != null && (pkt instanceof RouterAdvertisement ||
@@ -541,6 +562,88 @@
}
}
+ /**
+ * Handles DHCPv6 packet, if message type is ACK, update IP address
+ * according to DHCPv6 payload (IA Address option).
+ *
+ * @param dhcp6 the DHCPv6 payload
+ * @param vlanId the vlan of this packet
+ */
+ private void handleDhcp6(DHCP6 dhcp6, VlanId vlanId) {
+ // extract the relay message if exist
+ while (dhcp6 != null && DHCP6.RELAY_MSG_TYPES.contains(dhcp6.getMsgType())) {
+ dhcp6 = dhcp6.getOptions().stream()
+ .filter(opt -> opt instanceof Dhcp6RelayOption)
+ .map(BasePacket::getPayload)
+ .map(pld -> (DHCP6) pld)
+ .findFirst()
+ .orElse(null);
+ }
+
+ if (dhcp6 == null) {
+ // Can't find dhcp payload
+ log.warn("Can't find dhcp payload from relay message");
+ return;
+ }
+
+ if (dhcp6.getMsgType() != DHCP6.MsgType.REPLY.value()) {
+ // Update IP address only when we received REPLY message
+ return;
+ }
+ Optional<Dhcp6ClientIdOption> clientIdOption = dhcp6.getOptions()
+ .stream()
+ .filter(opt -> opt instanceof Dhcp6ClientIdOption)
+ .map(opt -> (Dhcp6ClientIdOption) opt)
+ .findFirst();
+
+ if (!clientIdOption.isPresent()) {
+ // invalid DHCPv6 option
+ log.warn("Can't find client ID from DHCPv6 {}", dhcp6);
+ return;
+ }
+
+ byte[] linkLayerAddr = clientIdOption.get().getDuid().getLinkLayerAddress();
+ if (linkLayerAddr == null || linkLayerAddr.length != 6) {
+ // No any mac address found
+ log.warn("Can't find client mac from option {}", clientIdOption);
+ return;
+ }
+ MacAddress clientMac = MacAddress.valueOf(linkLayerAddr);
+
+ // Extract IPv6 address from IA NA ot IA TA option
+ Optional<Dhcp6IaNaOption> iaNaOption = dhcp6.getOptions()
+ .stream()
+ .filter(opt -> opt instanceof Dhcp6IaNaOption)
+ .map(opt -> (Dhcp6IaNaOption) opt)
+ .findFirst();
+ Optional<Dhcp6IaTaOption> iaTaOption = dhcp6.getOptions()
+ .stream()
+ .filter(opt -> opt instanceof Dhcp6IaTaOption)
+ .map(opt -> (Dhcp6IaTaOption) opt)
+ .findFirst();
+ Optional<Dhcp6IaAddressOption> iaAddressOption;
+ if (iaNaOption.isPresent()) {
+ iaAddressOption = iaNaOption.get().getOptions().stream()
+ .filter(opt -> opt instanceof Dhcp6IaAddressOption)
+ .map(opt -> (Dhcp6IaAddressOption) opt)
+ .findFirst();
+ } else if (iaTaOption.isPresent()) {
+ iaAddressOption = iaTaOption.get().getOptions().stream()
+ .filter(opt -> opt instanceof Dhcp6IaAddressOption)
+ .map(opt -> (Dhcp6IaAddressOption) opt)
+ .findFirst();
+ } else {
+ iaAddressOption = Optional.empty();
+ }
+ if (iaAddressOption.isPresent()) {
+ Ip6Address ip = iaAddressOption.get().getIp6Address();
+ HostId hostId = HostId.hostId(clientMac, vlanId);
+ updateHostIp(hostId, ip);
+ } else {
+ log.warn("Can't find IPv6 address from DHCPv6 {}", dhcp6);
+ }
+ }
+
private Optional<DHCP> findDhcp(Ethernet eth) {
IPacket pkt = eth.getPayload();
return Stream.of(pkt)
@@ -555,6 +658,17 @@
.map(p -> (DHCP) p)
.findFirst();
}
+
+ private Optional<DHCP6> findDhcp6(IPacket pkt) {
+ return Stream.of(pkt)
+ .filter(Objects::nonNull)
+ .filter(p -> p instanceof UDP)
+ .map(IPacket::getPayload)
+ .filter(Objects::nonNull)
+ .filter(p -> p instanceof DHCP6)
+ .map(p -> (DHCP6) p)
+ .findFirst();
+ }
}
// Auxiliary listener to device events.
diff --git a/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java b/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
index 72cb6d8..7cd566f 100644
--- a/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
+++ b/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
@@ -27,7 +27,12 @@
import org.onlab.packet.ARP;
import org.onlab.packet.ChassisId;
import org.onlab.packet.DHCP;
-import org.onlab.packet.dhcp.CircuitId;
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.dhcp.Dhcp6ClientIdOption;
+import org.onlab.packet.dhcp.Dhcp6Duid;
+import org.onlab.packet.dhcp.Dhcp6IaAddressOption;
+import org.onlab.packet.dhcp.Dhcp6IaNaOption;
+import org.onlab.packet.dhcp.Dhcp6Option;
import org.onlab.packet.dhcp.DhcpOption;
import org.onlab.packet.Ethernet;
import org.onlab.packet.ICMP6;
@@ -38,7 +43,6 @@
import org.onlab.packet.MacAddress;
import org.onlab.packet.UDP;
import org.onlab.packet.VlanId;
-import org.onlab.packet.dhcp.DhcpRelayAgentOption;
import org.onlab.packet.ndp.NeighborAdvertisement;
import org.onlab.packet.ndp.NeighborSolicitation;
import org.onlab.packet.ndp.RouterAdvertisement;
@@ -153,7 +157,6 @@
private static final byte[] IP3 = new byte[]{10, 0, 0, 2};
private static final IpAddress IP_ADDRESS3 =
IpAddress.valueOf(IpAddress.Version.INET, IP3);
-
private static final HostLocation LOCATION3 =
new HostLocation(deviceId(DEV1), portNumber(INPORT2), 0L);
private static final DefaultHost HOST3 =
@@ -161,6 +164,11 @@
VLAN, LOCATION3,
ImmutableSet.of(IP_ADDRESS3));
+ // DHCP6 Server
+ private static final MacAddress DHCP6_SERVER_MAC = MacAddress.valueOf("00:00:44:00:00:04");
+ private static final IpAddress DHCP6_SERVER_IP =
+ IpAddress.valueOf("2000::1:1000");
+
// Gateway information for relay agent
private static final InterfaceIpAddress GW_IFACE_ADDR = InterfaceIpAddress.valueOf("10.0.1.1/32");
private static final Interface GW_IFACE = new Interface("gateway",
@@ -369,7 +377,7 @@
assertThat(descr.vlan(), is(VLAN));
// DHCP Ack
- testProcessor.process(new TestDhcpAckPacketContext(DEV1, false));
+ testProcessor.process(new TestDhcpAckPacketContext(DEV1));
assertThat("receiveDhcpAck. Two additional host descriptions expected",
providerService.descriptions.size(), is(3));
@@ -391,6 +399,46 @@
}
/**
+ * When receiving DHCPv6 REQUEST, update MAC, location of client.
+ * When receiving DHCPv6 ACK, update MAC, location of server and IP of client.
+ */
+ @Test
+ public void receiveDhcp6() {
+ TestUtils.setField(provider, "useDhcp6", true);
+ // DHCP Request
+ testProcessor.process(new TestDhcp6RequestPacketContext(DEV4, VLAN));
+ assertThat("receiveDhcpRequest. One host description expected",
+ providerService.descriptions.size(), is(1));
+ // Should learn the MAC and location of DHCP client
+ HostDescription descr = providerService.descriptions.get(0);
+ assertThat(descr.location(), is(LOCATION2));
+ assertThat(descr.hwAddress(), is(MAC2));
+ assertThat(descr.ipAddress().size(), is(0));
+ assertThat(descr.vlan(), is(VLAN));
+
+ // DHCP Ack
+ testProcessor.process(new TestDhcp6AckPacketContext(DEV1));
+ assertThat("receiveDhcpAck. Two additional host descriptions expected",
+ providerService.descriptions.size(), is(3));
+
+ // Should also learn the MAC, location of DHCP server
+ HostDescription descr2 = providerService.descriptions.get(1);
+ assertThat(descr2.location(), is(LOCATION3));
+ assertThat(descr2.hwAddress(), is(DHCP6_SERVER_MAC));
+ assertThat(descr2.ipAddress().size(), is(0));
+ assertThat(descr2.vlan(), is(VLAN));
+
+ // Should update the IP address of the DHCP client.
+ HostDescription descr3 = providerService.descriptions.get(2);
+ assertThat(descr3.location(), is(LOCATION2));
+ assertThat(descr3.hwAddress(), is(MAC2));
+ assertThat(descr3.ipAddress().size(), is(1));
+ IpAddress ip = descr3.ipAddress().iterator().next();
+ assertThat(ip, is(IP_ADDRESS2.getIp6Address()));
+ assertThat(descr3.vlan(), is(VLAN));
+ }
+
+ /**
* The host store should not updated when we disabled "useDhcp".
*/
@Test
@@ -635,6 +683,7 @@
ByteBuffer.wrap(eth.serialize()));
}
}
+
/**
* Generates DHCP REQUEST packet.
*/
@@ -686,12 +735,10 @@
*/
private class TestDhcpAckPacketContext extends PacketContextAdapter {
private final String deviceId;
- private final boolean withRelayinfo;
- public TestDhcpAckPacketContext(String deviceId, boolean withRelayInfo) {
+ public TestDhcpAckPacketContext(String deviceId) {
super(0, null, null, false);
this.deviceId = deviceId;
- this.withRelayinfo = withRelayInfo;
}
@Override
@@ -705,23 +752,7 @@
dhcpOption.setLength((byte) 1);
DHCP dhcp = new DHCP();
-
- if (withRelayinfo) {
- CircuitId cid = new CircuitId(LOCATION.toString(), VLAN_100);
- byte[] circuitId = cid.serialize();
- DhcpOption circuitIdSubOption = new DhcpOption();
- circuitIdSubOption.setCode(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
- circuitIdSubOption.setData(circuitId);
- circuitIdSubOption.setLength((byte) circuitId.length);
-
- DhcpRelayAgentOption relayInfoOption = new DhcpRelayAgentOption();
- relayInfoOption.setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue());
- relayInfoOption.addSubOption(circuitIdSubOption);
- dhcp.setOptions(ImmutableList.of(dhcpOption, relayInfoOption));
- dhcp.setGatewayIPAddress(GW_IFACE_ADDR.ipAddress().getIp4Address().toInt());
- } else {
- dhcp.setOptions(ImmutableList.of(dhcpOption));
- }
+ dhcp.setOptions(ImmutableList.of(dhcpOption));
dhcp.setClientHardwareAddress(MAC.toBytes());
dhcp.setYourIPAddress(IP_ADDRESS.getIp4Address().toInt());
@@ -748,6 +779,118 @@
}
/**
+ * Generates DHCPv6 REQUEST packet.
+ */
+ private class TestDhcp6RequestPacketContext extends PacketContextAdapter {
+ private final String deviceId;
+ private final VlanId vlanId;
+
+ public TestDhcp6RequestPacketContext(String deviceId, VlanId vlanId) {
+ super(0, null, null, false);
+ this.deviceId = deviceId;
+ this.vlanId = vlanId;
+ }
+
+ @Override
+ public InboundPacket inPacket() {
+
+ DHCP6 dhcp6 = new DHCP6();
+ dhcp6.setMsgType(DHCP6.MsgType.REQUEST.value());
+ List<Dhcp6Option> options = Lists.newArrayList();
+
+ // IA address
+ Dhcp6IaAddressOption iaAddressOption = new Dhcp6IaAddressOption();
+ iaAddressOption.setIp6Address(IP_ADDRESS2.getIp6Address());
+
+ // IA NA
+ Dhcp6IaNaOption iaNaOption = new Dhcp6IaNaOption();
+ iaNaOption.setOptions(ImmutableList.of(iaAddressOption));
+ options.add(iaNaOption);
+
+ dhcp6.setOptions(options);
+
+ UDP udp = new UDP();
+ udp.setPayload(dhcp6);
+ udp.setSourcePort(UDP.DHCP_V6_CLIENT_PORT);
+ udp.setDestinationPort(UDP.DHCP_V6_SERVER_PORT);
+ IPv6 ipv6 = new IPv6();
+ ipv6.setPayload(udp);
+ ipv6.setDestinationAddress(Ip6Address.ZERO.toOctets());
+ ipv6.setSourceAddress(Ip6Address.ZERO.toOctets());
+ ipv6.setNextHeader(IPv6.PROTOCOL_UDP);
+ Ethernet eth = new Ethernet();
+ eth.setEtherType(Ethernet.TYPE_IPV6)
+ .setVlanID(this.vlanId.toShort())
+ .setSourceMACAddress(MAC2)
+ .setDestinationMACAddress(DHCP6_SERVER_MAC)
+ .setPayload(ipv6);
+ ConnectPoint receivedFrom = new ConnectPoint(deviceId(deviceId),
+ portNumber(INPORT));
+ return new DefaultInboundPacket(receivedFrom, eth,
+ ByteBuffer.wrap(eth.serialize()));
+ }
+ }
+
+ /**
+ * Generates DHCPv6 ACK packet.
+ */
+ private class TestDhcp6AckPacketContext extends PacketContextAdapter {
+ private final String deviceId;
+
+ public TestDhcp6AckPacketContext(String deviceId) {
+ super(0, null, null, false);
+ this.deviceId = deviceId;
+ }
+
+ @Override
+ public InboundPacket inPacket() {
+ DHCP6 dhcp6 = new DHCP6();
+ dhcp6.setMsgType(DHCP6.MsgType.REPLY.value());
+ List<Dhcp6Option> options = Lists.newArrayList();
+
+ // IA address
+ Dhcp6IaAddressOption iaAddressOption = new Dhcp6IaAddressOption();
+ iaAddressOption.setIp6Address(IP_ADDRESS2.getIp6Address());
+
+ // IA NA
+ Dhcp6IaNaOption iaNaOption = new Dhcp6IaNaOption();
+ iaNaOption.setOptions(ImmutableList.of(iaAddressOption));
+ options.add(iaNaOption);
+
+ // Client ID
+ Dhcp6Duid duid = new Dhcp6Duid();
+ duid.setDuidType(Dhcp6Duid.DuidType.DUID_LLT);
+ duid.setHardwareType((short) 1);
+ duid.setDuidTime(0);
+ duid.setLinkLayerAddress(MAC2.toBytes());
+ Dhcp6ClientIdOption clientIdOption = new Dhcp6ClientIdOption();
+ clientIdOption.setDuid(duid);
+ options.add(clientIdOption);
+ dhcp6.setOptions(options);
+
+ UDP udp = new UDP();
+ udp.setPayload(dhcp6);
+ udp.setSourcePort(UDP.DHCP_V6_CLIENT_PORT);
+ udp.setDestinationPort(UDP.DHCP_V6_SERVER_PORT);
+ IPv6 ipv6 = new IPv6();
+ ipv6.setPayload(udp);
+ ipv6.setDestinationAddress(Ip6Address.ZERO.toOctets());
+ ipv6.setSourceAddress(Ip6Address.ZERO.toOctets());
+ ipv6.setNextHeader(IPv6.PROTOCOL_UDP);
+ Ethernet eth = new Ethernet();
+ eth.setEtherType(Ethernet.TYPE_IPV6)
+ .setVlanID(VLAN.toShort())
+ .setSourceMACAddress(DHCP6_SERVER_MAC)
+ .setDestinationMACAddress(MAC2)
+ .setPayload(ipv6);
+ ConnectPoint receivedFrom = new ConnectPoint(deviceId(deviceId),
+ portNumber(INPORT2));
+ return new DefaultInboundPacket(receivedFrom, eth,
+ ByteBuffer.wrap(eth.serialize()));
+ }
+ }
+
+ /**
* Generates NeighborAdvertisement packet.
*/
private class TestNaPacketContext extends PacketContextAdapter {