CORD-1430 Dhcpv6 Relay APP
Change-Id: Ib913b5d53305acfa47c13676c6d6bbd9fd0023f4
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerImpl.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerImpl.java
index 19123dc..27d59d9 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerImpl.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerImpl.java
@@ -17,78 +17,1228 @@
package org.onosproject.dhcprelay;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Deactivate;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.Sets;
+import com.google.common.collect.ImmutableSet;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.packet.BasePacket;
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip6Address;
import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
+import org.onlab.packet.UDP;
import org.onlab.packet.VlanId;
+import org.onlab.packet.dhcp.Dhcp6RelayOption;
+import org.onlab.packet.dhcp.Dhcp6InterfaceIdOption;
+import org.onlab.packet.dhcp.Dhcp6Option;
+import org.onlab.packet.dhcp.Dhcp6IaNaOption;
+import org.onlab.packet.dhcp.Dhcp6IaTaOption;
+import org.onlab.packet.dhcp.Dhcp6IaPdOption;
+import org.onlab.packet.dhcp.Dhcp6IaAddressOption;
+import org.onlab.packet.dhcp.Dhcp6IaPrefixOption;
+import org.onlab.util.HexString;
import org.onosproject.dhcprelay.api.DhcpHandler;
+import org.onosproject.dhcprelay.store.DhcpRelayStore;
+import org.onosproject.net.host.HostStore;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.intf.InterfaceService;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteStore;
import org.onosproject.dhcprelay.config.DhcpServerConfig;
import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import java.nio.ByteBuffer;
+import java.util.List;
import java.util.Collection;
import java.util.Optional;
+import java.util.Set;
+import java.util.ArrayList;
+
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
@Component
@Service
@Property(name = "version", value = "6")
public class Dhcp6HandlerImpl implements DhcpHandler {
+ private static Logger log = LoggerFactory.getLogger(Dhcp6HandlerImpl.class);
- @Override
- public void processDhcpPacket(PacketContext context, BasePacket dhcp6Payload) {
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DhcpRelayStore dhcpRelayStore;
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostStore hostStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected RouteStore routeStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InterfaceService interfaceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+ private InternalHostListener hostListener = new InternalHostListener();
+
+ private Ip6Address dhcpServerIp = null;
+ // dhcp server may be connected directly to the SDN network or
+ // via an external gateway. When connected directly, the dhcpConnectPoint, dhcpConnectMac,
+ // and dhcpConnectVlan refer to the server. When connected via the gateway, they refer
+ // to the gateway.
+ private ConnectPoint dhcpServerConnectPoint = null;
+ private MacAddress dhcpConnectMac = null;
+ private VlanId dhcpConnectVlan = null;
+ private Ip6Address dhcpGatewayIp = null;
+ private Ip6Address relayAgentIpFromCfg = null;
+
+ private Ip6Address indirectDhcpServerIp = null;
+ private ConnectPoint indirectDhcpServerConnectPoint = null;
+ private MacAddress indirectDhcpConnectMac = null;
+ private VlanId indirectDhcpConnectVlan = null;
+ private Ip6Address indirectDhcpGatewayIp = null;
+ private Ip6Address indirectRelayAgentIpFromCfg = null;
+
+
+ // CLIENT message types
+ public static final Set<Byte> MSG_TYPE_FROM_CLIENT =
+ ImmutableSet.of(DHCP6.MsgType.SOLICIT.value(),
+ DHCP6.MsgType.REQUEST.value(),
+ DHCP6.MsgType.REBIND.value(),
+ DHCP6.MsgType.RENEW.value(),
+ DHCP6.MsgType.RELEASE.value(),
+ DHCP6.MsgType.DECLINE.value(),
+ DHCP6.MsgType.CONFIRM.value(),
+ DHCP6.MsgType.RELAY_FORW.value());
+ // SERVER message types
+ public static final Set<Byte> MSG_TYPE_FROM_SERVER =
+ ImmutableSet.of(DHCP6.MsgType.RELAY_REPL.value());
+
+ @Activate
+ protected void activate() {
+ hostService.addListener(hostListener);
}
- @Override
- public Optional<IpAddress> getDhcpServerIp() {
- return null;
+ @Deactivate
+ protected void deactivate() {
+ hostService.removeListener(hostListener);
+ this.dhcpConnectMac = null;
+ this.dhcpConnectVlan = null;
+
+ if (dhcpGatewayIp != null) {
+ hostService.stopMonitoringIp(dhcpGatewayIp);
+ } else if (dhcpServerIp != null) {
+ hostService.stopMonitoringIp(dhcpServerIp);
+ }
}
- @Override
- public Optional<IpAddress> getDhcpGatewayIp() {
- return null;
- }
@Override
- public Optional<MacAddress> getDhcpConnectMac() {
- return null;
- }
-
- @Override
- public void setDhcpGatewayIp(IpAddress dhcpGatewayIp) {
-
- }
-
- @Override
- public void setDhcpConnectVlan(VlanId dhcpConnectVlan) {
-
- }
-
- @Override
- public void setDhcpConnectMac(MacAddress dhcpConnectMac) {
-
+ public void setDhcpServerIp(IpAddress dhcpServerIp) {
+ checkNotNull(dhcpServerIp, "DHCP server IP can't be null");
+ checkState(dhcpServerIp.isIp6(), "Invalid server IP for DHCPv6 relay handler");
+ this.dhcpServerIp = dhcpServerIp.getIp6Address();
}
@Override
public void setDhcpServerConnectPoint(ConnectPoint dhcpServerConnectPoint) {
-
+ checkNotNull(dhcpServerConnectPoint, "Server connect point can't null");
+ this.dhcpServerConnectPoint = dhcpServerConnectPoint;
}
@Override
- public void setDhcpServerIp(IpAddress dhcpServerIp) {
+ public void setDhcpConnectMac(MacAddress dhcpConnectMac) {
+ this.dhcpConnectMac = dhcpConnectMac;
+ }
+ @Override
+ public void setDhcpConnectVlan(VlanId dhcpConnectVlan) {
+ this.dhcpConnectVlan = dhcpConnectVlan;
+ }
+
+ @Override
+ public void setDhcpGatewayIp(IpAddress dhcpGatewayIp) {
+ if (dhcpGatewayIp != null) {
+ checkState(dhcpGatewayIp.isIp6(), "Invalid gateway IP for DHCPv6 relay handler");
+ this.dhcpGatewayIp = dhcpGatewayIp.getIp6Address();
+ } else {
+ // removes gateway config
+ this.dhcpGatewayIp = null;
+ }
+ }
+ @Override
+ public Optional<IpAddress> getDhcpServerIp() {
+ return Optional.ofNullable(dhcpServerIp);
+ }
+
+ @Override
+ public Optional<IpAddress> getDhcpGatewayIp() {
+ return Optional.ofNullable(dhcpGatewayIp);
+ }
+
+ @Override
+ public Optional<MacAddress> getDhcpConnectMac() {
+ return Optional.ofNullable(dhcpConnectMac);
+ }
+
+ // Indirect DHCP server
+
+ public void setIndirectDhcpServerIp(IpAddress dhcpServerIp) {
+ checkNotNull(dhcpServerIp, "DHCP indirect server IP can't be null");
+ checkState(dhcpServerIp.isIp6(), "Invalid indirect server IP for DHCPv6 relay handler");
+ this.indirectDhcpServerIp = dhcpServerIp.getIp6Address();
+ }
+
+
+ public void setIndirectDhcpServerConnectPoint(ConnectPoint dhcpServerConnectPoint) {
+ checkNotNull(dhcpServerConnectPoint, "Indirect Server connect point can't null");
+ this.indirectDhcpServerConnectPoint = dhcpServerConnectPoint;
+ }
+
+
+ public void setIndirectDhcpConnectMac(MacAddress dhcpConnectMac) {
+ this.indirectDhcpConnectMac = dhcpConnectMac;
+ }
+
+
+ public void setIndirectDhcpConnectVlan(VlanId dhcpConnectVlan) {
+ this.indirectDhcpConnectVlan = dhcpConnectVlan;
+ }
+
+
+ public void setIndirectDhcpGatewayIp(IpAddress dhcpGatewayIp) {
+ if (dhcpGatewayIp != null) {
+ checkState(dhcpGatewayIp.isIp6(), "Invalid indirect gateway IP for DHCPv6 relay handler");
+ this.indirectDhcpGatewayIp = dhcpGatewayIp.getIp6Address();
+ } else {
+ // removes gateway config
+ this.indirectDhcpGatewayIp = null;
+ }
+ }
+
+ public Optional<IpAddress> getIndirectDhcpServerIp() {
+ return Optional.ofNullable(indirectDhcpServerIp);
+ }
+
+
+ public Optional<IpAddress> getIndirectDhcpGatewayIp() {
+ return Optional.ofNullable(indirectDhcpGatewayIp);
+ }
+
+
+ public Optional<MacAddress> getIndirectDhcpConnectMac() {
+ return Optional.ofNullable(indirectDhcpConnectMac);
+ }
+
+
+ @Override
+ public void processDhcpPacket(PacketContext context, BasePacket payload) {
+ checkNotNull(payload, "DHCP6 payload can't be null");
+ checkState(payload instanceof DHCP6, "Payload is not a DHCP6");
+ DHCP6 dhcp6Payload = (DHCP6) payload;
+ Ethernet receivedPacket = context.inPacket().parsed();
+
+ if (!configured()) {
+ log.warn("Missing DHCP6 relay server config. Abort packet processing");
+ log.warn("dhcp6 payload {}", dhcp6Payload);
+
+ return;
+ }
+
+ byte msgType = dhcp6Payload.getMsgType();
+ log.warn("msgType is {}", msgType);
+
+ ConnectPoint inPort = context.inPacket().receivedFrom();
+ if (inPort == null) {
+ log.warn("incommin ConnectPoint is null");
+ }
+ Set<Interface> receivingInterfaces = interfaceService.getInterfacesByPort(inPort);
+ //ignore the packets if dhcp client interface is not configured on onos.
+ if (receivingInterfaces.isEmpty()) {
+ log.warn("Virtual interface is not configured on {}", inPort);
+ return;
+ }
+
+
+ if (MSG_TYPE_FROM_CLIENT.contains(msgType)) {
+
+ InternalPacket ethernetClientPacket =
+ processDhcp6PacketFromClient(context, receivedPacket, receivingInterfaces);
+ if (ethernetClientPacket != null) {
+ forwardPacket(ethernetClientPacket);
+ }
+
+ } else if (MSG_TYPE_FROM_SERVER.contains(msgType)) {
+ log.warn("calling processDhcp6PacketFromServer with RELAY_REPL", msgType);
+ InternalPacket ethernetPacketReply =
+ processDhcp6PacketFromServer(context, receivedPacket, receivingInterfaces);
+ if (ethernetPacketReply != null) {
+ forwardPacket(ethernetPacketReply);
+ }
+ } else {
+ log.warn("Not so fast, packet type {} not supported yet", msgType);
+ }
+ }
+
+
+ /**
+ * Checks if this app has been configured.
+ *
+ * @return true if all information we need have been initialized
+ */
+ public boolean configured() {
+ log.warn("dhcpServerConnectPoint {} dhcpServerIp {}",
+ this.dhcpServerConnectPoint, this.dhcpServerIp);
+ return this.dhcpServerConnectPoint != null && this.dhcpServerIp != null;
+ }
+
+ // the new class the contains Ethernet packet and destination port, kind of like adding
+ // internal header to the packet
+ private class InternalPacket {
+ Ethernet packet;
+ ConnectPoint destLocation;
+ public InternalPacket(Ethernet newPacket, ConnectPoint newLocation) {
+ packet = newPacket;
+ destLocation = newLocation;
+ }
+ void setLocation(ConnectPoint newLocation) {
+ destLocation = newLocation;
+ }
+ }
+
+ //forward the packet to ConnectPoint where the DHCP server is attached.
+ private void forwardPacket(InternalPacket packet) {
+ //send Packetout to dhcp server connectpoint.
+ if (packet.destLocation != null) {
+ TrafficTreatment t = DefaultTrafficTreatment.builder()
+ .setOutput(packet.destLocation.port()).build();
+ OutboundPacket o = new DefaultOutboundPacket(
+ packet.destLocation.deviceId(), t, ByteBuffer.wrap(packet.packet.serialize()));
+ if (log.isTraceEnabled()) {
+ log.trace("Relaying packet to destination {}", packet.destLocation);
+ }
+ packetService.emit(o);
+ } // if
+ }
+
+ /**
+ * Check if the host is directly connected to the network or not.
+ *
+ * @param dhcp6Payload the dhcp6 payload
+ * @return true if the host is directly connected to the network; false otherwise
+ */
+ private boolean directlyConnected(DHCP6 dhcp6Payload) {
+ log.debug("directlyConnected enters");
+
+ if (dhcp6Payload.getMsgType() != DHCP6.MsgType.RELAY_FORW.value() &&
+ dhcp6Payload.getMsgType() != DHCP6.MsgType.RELAY_REPL.value()) {
+ log.debug("directlyConnected true. MsgType {}", dhcp6Payload.getMsgType());
+
+ return true;
+ }
+
+ // Regardless of relay-forward or relay-replay, check if we see another relay message
+ DHCP6 dhcp6Payload2 = dhcp6PacketFromRelayPacket(dhcp6Payload);
+ if (dhcp6Payload2 != null) {
+ if (dhcp6Payload.getMsgType() == DHCP6.MsgType.RELAY_FORW.value()) {
+ log.debug("directlyConnected false. 1st realy-foward, 2nd MsgType {}", dhcp6Payload2.getMsgType());
+ return false;
+ } else {
+ // relay-reply
+ if (dhcp6Payload2.getMsgType() != DHCP6.MsgType.RELAY_REPL.value()) {
+ log.debug("directlyConnected true. 2nd MsgType {}", dhcp6Payload2.getMsgType());
+ return true; // must be directly connected
+ } else {
+ log.debug("directlyConnected false. 1st relay-reply, 2nd relay-reply MsgType {}",
+ dhcp6Payload2.getMsgType());
+ return false; // must be indirectly connected
+ }
+ }
+ } else {
+ log.warn("directlyConnected true.");
+ return true;
+ }
+ }
+
+ /**
+ * extract DHCP6 payload from dhcp6 relay message within relay-forwrd/reply.
+ *
+ * @param dhcp6 dhcp6 relay-reply or relay-foward
+ * @return dhcp6Packet dhcp6 packet extracted from relay-message
+ */
+ private DHCP6 dhcp6PacketFromRelayPacket(DHCP6 dhcp6) {
+ log.debug("dhcp6PacketFromRelayPacket enters. dhcp6 {}", dhcp6);
+
+ // extract the relay message if exist
+ DHCP6 dhcp6Payload = dhcp6.getOptions().stream()
+ .filter(opt -> opt instanceof Dhcp6RelayOption)
+ .map(BasePacket::getPayload)
+ .map(pld -> (DHCP6) pld)
+ .findFirst()
+ .orElse(null);
+
+
+ if (dhcp6Payload == null) {
+ // Can't find dhcp payload
+ log.debug("Can't find dhcp6 payload from relay message");
+ } else {
+ log.debug("dhcp6 payload found from relay message {}", dhcp6Payload);
+ }
+
+ return dhcp6Payload;
+ }
+
+ /**
+ * find the leaf DHCP6 packet from multi-level relay packet.
+ *
+ * @param relayPacket dhcp6 relay packet
+ * @return leafPacket non-relay dhcp6 packet
+ */
+ private DHCP6 getDhcp6Leaf(DHCP6 relayPacket) {
+ DHCP6 dhcp6Parent = relayPacket;
+ DHCP6 dhcp6Child = null;
+
+ log.debug("getDhcp6Leaf entered.");
+ while (dhcp6Parent != null) {
+ dhcp6Child = dhcp6PacketFromRelayPacket(dhcp6Parent);
+
+ if (dhcp6Child != null) {
+ if (dhcp6Child.getMsgType() != DHCP6.MsgType.RELAY_FORW.value() &&
+ dhcp6Child.getMsgType() != DHCP6.MsgType.RELAY_REPL.value()) {
+ log.debug("leaf dhcp6 packet found.");
+ break;
+ } else {
+ // found another relay
+ // go for another loop
+ dhcp6Parent = dhcp6Child;
+ }
+ } else {
+ log.warn("malformed pkt! Expected dhcp6 within relay pkt, but no dhcp6 leaf found.");
+ break;
+ }
+ }
+ return dhcp6Child;
+ }
+
+ /**
+ * check if DHCP6 relay-reply is reply.
+ *
+ * @param relayPacket dhcp6 relay-reply
+ * @return boolean relay-reply contains ack
+ */
+ private boolean isDhcp6Reply(DHCP6 relayPacket) {
+ log.debug("isDhcp6Reply entered.");
+
+ DHCP6 leafDhcp6 = getDhcp6Leaf(relayPacket);
+
+ if (leafDhcp6 != null) {
+ if (leafDhcp6.getMsgType() == DHCP6.MsgType.REPLY.value()) {
+ log.debug("isDhcp6Reply true.");
+ return true; // must be directly connected
+ } else {
+ log.debug("isDhcp6Reply false. leaf dhcp6 is not replay. MsgType {}", leafDhcp6.getMsgType());
+ }
+ } else {
+ log.debug("isDhcp6Reply false. Expected dhcp6 within relay pkt but not found.");
+ }
+ log.debug("isDhcp6Reply false.");
+ return false;
+ }
+
+ /**
+ * check if DHCP6 is release or relay-forward contains release.
+ *
+ * @param dhcp6Payload dhcp6 packet
+ * @return boolean dhcp6 contains release
+ */
+ private boolean isDhcp6Release(DHCP6 dhcp6Payload) {
+
+ log.debug("isDhcp6Release entered.");
+
+ if (dhcp6Payload.getMsgType() == DHCP6.MsgType.RELEASE.value()) {
+ log.debug("isDhcp6Release true.");
+ return true; // must be directly connected
+ } else {
+ DHCP6 dhcp6Leaf = getDhcp6Leaf(dhcp6Payload);
+ if (dhcp6Leaf != null) {
+ if (dhcp6Leaf.getMsgType() == DHCP6.MsgType.RELEASE.value()) {
+ log.debug("isDhcp6Release true. indirectlry connected");
+ return true;
+ } else {
+ log.debug("leaf dhcp6 is not release. MsgType {}", dhcp6Leaf.getMsgType());
+ return false;
+ }
+ } else {
+ log.debug("isDhcp6Release false. dhcp6 is niether relay nor release.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * extract from dhcp6 packet client ipv6 address of given by dhcp server.
+ *
+ * @param dhcp6 the dhcp6 packet
+ * @return Ip6Address Ip6Address given by dhcp server, or null if not exists
+ */
+ private Ip6Address extractIpAddress(DHCP6 dhcp6) {
+ Ip6Address ip = null;
+
+ log.debug("extractIpAddress enters dhcp6 {}.", dhcp6);
+ // 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()) {
+ log.debug("Found IPv6 address from iaNaOption {}", iaNaOption);
+
+ iaAddressOption = iaNaOption.get().getOptions().stream()
+ .filter(opt -> opt instanceof Dhcp6IaAddressOption)
+ .map(opt -> (Dhcp6IaAddressOption) opt)
+ .findFirst();
+ } else if (iaTaOption.isPresent()) {
+ log.debug("Found IPv6 address from iaTaOption {}", iaTaOption);
+
+ iaAddressOption = iaTaOption.get().getOptions().stream()
+ .filter(opt -> opt instanceof Dhcp6IaAddressOption)
+ .map(opt -> (Dhcp6IaAddressOption) opt)
+ .findFirst();
+ } else {
+ iaAddressOption = Optional.empty();
+ }
+ if (iaAddressOption.isPresent()) {
+ ip = iaAddressOption.get().getIp6Address();
+ log.debug("Found IPv6 address from iaAddressOption {}", iaAddressOption);
+
+
+ } else {
+ log.debug("Can't find IPv6 address from DHCPv6 {}", dhcp6);
+ }
+
+ return ip;
+ }
+ /**
+ * extract from dhcp6 packet Prefix prefix provided by dhcp server.
+ *
+ * @param dhcp6 the dhcp6 payload
+ * @return IpPrefix Prefix Delegation prefix, or null if not exists.
+ */
+ private IpPrefix extractPrefix(DHCP6 dhcp6) {
+ log.warn("extractPrefix enters {}", dhcp6);
+
+ // extract prefix
+ IpPrefix prefixPrefix = null;
+
+ Ip6Address prefixAddress = null;
+
+ // Extract IPv6 prefix from IA PD option
+ Optional<Dhcp6IaPdOption> iaPdOption = dhcp6.getOptions()
+ .stream()
+ .filter(opt -> opt instanceof Dhcp6IaPdOption)
+ .map(opt -> (Dhcp6IaPdOption) opt)
+ .findFirst();
+
+ Optional<Dhcp6IaPrefixOption> iaPrefixOption;
+ if (iaPdOption.isPresent()) {
+ log.warn("IA_PD option found {}", iaPdOption);
+
+ iaPrefixOption = iaPdOption.get().getOptions().stream()
+ .filter(opt -> opt instanceof Dhcp6IaPrefixOption)
+ .map(opt -> (Dhcp6IaPrefixOption) opt)
+ .findFirst();
+ } else {
+ log.warn("IA_PD option NOT found");
+
+ iaPrefixOption = Optional.empty();
+ }
+ if (iaPrefixOption.isPresent()) {
+ log.warn("IAPrefix Option within IA_PD option found {}", iaPrefixOption);
+
+ prefixAddress = iaPrefixOption.get().getIp6Prefix();
+ int prefixLen = (int) iaPrefixOption.get().getPrefixLength();
+ log.warn("Prefix length is {} bits", prefixLen);
+ prefixPrefix = IpPrefix.valueOf(prefixAddress, prefixLen);
+
+ } else {
+ log.warn("Can't find IPv6 prefix from DHCPv6 {}", dhcp6);
+ }
+
+ return prefixPrefix;
+ }
+
+ /**
+ * remove host or route.
+ *
+ * @param directConnFlag flag to show that packet is from directly connected client
+ * @param dhcp6Packet the dhcp6 payload
+ * @param clientPacket client's ethernet packet
+ * @param clientIpv6 client's Ipv6 packet
+ * @param clientInterfaces set of client interfaces
+ */
+ private void removeHostOrRoute(boolean directConnFlag, DHCP6 dhcp6Packet,
+ Ethernet clientPacket, IPv6 clientIpv6,
+ Set<Interface> clientInterfaces) {
+ log.debug("extractPrefix enters {}", dhcp6Packet);
+ // add host or route
+ if (isDhcp6Release(dhcp6Packet)) {
+ IpAddress ip = null;
+ if (directConnFlag) {
+ // Add to host store if it is connected to network directly
+ ip = extractIpAddress(dhcp6Packet);
+ if (ip != null) {
+ VlanId vlanId = clientInterfaces.iterator().next().vlan();
+ MacAddress clientMac = clientPacket.getSourceMAC();
+ HostId hostId = HostId.hostId(clientMac, vlanId);
+ log.debug("remove Host {} ip for directly connected.", hostId.toString());
+
+ log.debug("client mac {} client vlan {}", HexString.toHexString(clientMac.toBytes(), ":"), vlanId);
+
+
+ // Remove host's ip of when dhcp release msg is received
+ hostStore.removeIp(hostId, ip);
+ } else {
+ log.debug("ipAddress not found. Do not add Host for directly connected.");
+ }
+ } else {
+ // Remove from route store if it is not connected to network directly
+ IpAddress nextHopIp = IpAddress.valueOf(IpAddress.Version.INET6, clientIpv6.getSourceAddress());
+
+ DHCP6 leafDhcp = getDhcp6Leaf(dhcp6Packet);
+ ip = extractIpAddress(leafDhcp);
+ if (ip == null) {
+ log.debug("ip is null");
+ } else {
+ Route routeForIP = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
+ log.debug("removing route of 128 address for indirectly connected.");
+ log.debug("128 ip {}, nexthop {}", HexString.toHexString(ip.toOctets(), ":"),
+ HexString.toHexString(nextHopIp.toOctets(), ":"));
+ routeStore.removeRoute(routeForIP);
+ }
+
+ IpPrefix ipPrefix = extractPrefix(leafDhcp);
+ if (ipPrefix == null) {
+ log.debug("ipPrefix is null ");
+ } else {
+ Route routeForPrefix = new Route(Route.Source.STATIC, ipPrefix, nextHopIp);
+ log.debug("removing route of PD for indirectly connected.");
+ log.debug("pd ip {}, nexthop {}", HexString.toHexString(ipPrefix.address().toOctets(), ":"),
+ HexString.toHexString(nextHopIp.toOctets(), ":"));
+
+ routeStore.removeRoute(routeForPrefix);
+ }
+ }
+ }
+ }
+
+ /**
+ * add host or route.
+ *
+ * @param directConnFlag flag to show that packet is from directly connected client
+ * @param dhcp6Relay the dhcp6 payload
+ * @param embeddedDhcp6 client's ethernet packetthe dhcp6 payload within relay
+ * @param clientMac client macAddress
+ * @param clientInterfaces set of client interfaces
+ */
+ private void addHostOrRoute(boolean directConnFlag, DHCP6 dhcp6Relay,
+ DHCP6 embeddedDhcp6,
+ MacAddress clientMac,
+ Set<Interface> clientInterfaces) {
+ log.debug("addHostOrRoute entered.");
+ // add host or route
+ if (isDhcp6Reply(dhcp6Relay)) {
+ IpAddress ip = null;
+ if (directConnFlag) {
+ // Add to host store if it connect to network directly
+ ip = extractIpAddress(embeddedDhcp6);
+ if (ip != null) {
+ Set<IpAddress> ips = Sets.newHashSet(ip);
+ VlanId vlanId = clientInterfaces.iterator().next().vlan();
+ HostId hostId = HostId.hostId(clientMac, vlanId);
+ HostLocation hostLocation = new HostLocation(clientInterfaces.iterator().next().connectPoint(),
+ System.currentTimeMillis());
+ HostDescription desc = new DefaultHostDescription(clientMac, vlanId,
+ hostLocation, ips);
+ log.debug("adding Host for directly connected.");
+ log.debug("client mac {} client vlan {} hostlocation {}",
+ HexString.toHexString(clientMac.toBytes(), ":"),
+ vlanId, hostLocation.toString());
+
+ // Replace the ip when dhcp server give the host new ip address
+ hostStore.createOrUpdateHost(DhcpRelayManager.PROVIDER_ID, hostId, desc, true);
+ } else {
+ log.warn("ipAddress not found. Do not add Host for directly connected.");
+ }
+ } else {
+ // Add to route store if it does not connect to network directly
+ IpAddress nextHopIp = IpAddress.valueOf(IpAddress.Version.INET6, dhcp6Relay.getPeerAddress());
+
+ DHCP6 leafDhcp = getDhcp6Leaf(embeddedDhcp6);
+ ip = extractIpAddress(leafDhcp);
+ if (ip == null) {
+ log.warn("ip is null");
+ } else {
+ Route routeForIP = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
+ log.warn("adding Route of 128 address for indirectly connected.");
+ routeStore.updateRoute(routeForIP);
+ }
+
+ IpPrefix ipPrefix = extractPrefix(leafDhcp);
+ if (ipPrefix == null) {
+ log.warn("ipPrefix is null ");
+ } else {
+ Route routeForPrefix = new Route(Route.Source.STATIC, ipPrefix, nextHopIp);
+ log.warn("adding Route of PD for indirectly connected.");
+ routeStore.updateRoute(routeForPrefix);
+ }
+ }
+ }
+ }
+
+ /**
+ *
+ * build the DHCP6 solicit/request packet with gatewayip.
+ *
+ * @param context packet context
+ * @param clientPacket client ethernet packet
+ * @param clientInterfaces set of client side interfaces
+ */
+ private InternalPacket processDhcp6PacketFromClient(PacketContext context,
+ Ethernet clientPacket, Set<Interface> clientInterfaces) {
+ Ip6Address relayAgentIp = getRelayAgentIPv6Address(clientInterfaces);
+ MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
+ if (relayAgentIp == null || relayAgentMac == null) {
+ log.warn("Missing DHCP relay agent interface Ipv6 addr config for "
+ + "packet from client on port: {}. Aborting packet processing",
+ clientInterfaces.iterator().next().connectPoint());
+ return null;
+ }
+
+ // get dhcp6 header.
+
+ IPv6 clientIpv6 = (IPv6) clientPacket.getPayload();
+ UDP clientUdp = (UDP) clientIpv6.getPayload();
+ DHCP6 clientDhcp6 = (DHCP6) clientUdp.getPayload();
+
+ boolean directConnFlag = directlyConnected(clientDhcp6);
+
+ Ethernet etherReply = (Ethernet) clientPacket.clone();
+ etherReply.setSourceMACAddress(relayAgentMac);
+
+ if (directConnFlag && this.dhcpConnectMac == null) {
+ log.warn("DHCP6 {} not yet resolved .. Aborting DHCP "
+ + "packet processing from client on port: {}",
+ (this.dhcpGatewayIp == null) ? "server IP " + this.dhcpServerIp
+ : "gateway IP " + this.dhcpGatewayIp,
+ clientInterfaces.iterator().next().connectPoint());
+
+ return null;
+ }
+
+ if (!directConnFlag && this.indirectDhcpConnectMac == null) {
+ log.warn("DHCP6 {} not yet resolved .. Aborting DHCP "
+ + "packet processing from client on port: {}",
+ (this.indirectDhcpGatewayIp == null) ? "server IP " + this.indirectDhcpServerIp
+ : "gateway IP " + this.indirectDhcpGatewayIp,
+ clientInterfaces.iterator().next().connectPoint());
+
+ return null;
+
+ }
+
+ if (this.dhcpServerConnectPoint == null) {
+ log.warn("DHCP6 server connection point is not set up yet");
+ return null;
+ }
+
+ etherReply.setDestinationMACAddress(directConnFlag ? this.dhcpConnectMac : this.indirectDhcpConnectMac);
+ etherReply.setVlanID(directConnFlag ? this.dhcpConnectVlan.toShort() : this.indirectDhcpConnectVlan.toShort());
+
+ IPv6 ipv6Packet = (IPv6) etherReply.getPayload();
+ byte[] peerAddress = clientIpv6.getSourceAddress();
+ ipv6Packet.setSourceAddress(relayAgentIp.toOctets());
+ ipv6Packet.setDestinationAddress(directConnFlag ? this.dhcpServerIp.toOctets() :
+ this.indirectDhcpServerIp.toOctets());
+
+ UDP udpPacket = (UDP) ipv6Packet.getPayload();
+ udpPacket.setSourcePort(UDP.DHCP_V6_SERVER_PORT);
+ DHCP6 dhcp6Packet = (DHCP6) udpPacket.getPayload();
+ byte[] dhcp6PacketByte = dhcp6Packet.serialize();
+
+ // notify onos and quagga to release PD
+ //releasePD(dhcp6Packet);
+
+ removeHostOrRoute(directConnFlag, dhcp6Packet, clientPacket, clientIpv6, clientInterfaces);
+
+ DHCP6 dhcp6Relay = new DHCP6();
+ dhcp6Relay.setMsgType(DHCP6.MsgType.RELAY_FORW.value());
+ // link address: server uses the address to identify the link on which the client
+ // is located.
+ if (directConnFlag) {
+ dhcp6Relay.setLinkAddress(relayAgentIp.toOctets());
+ log.debug("direct connection: relayAgentIp obtained dynamically {}",
+ HexString.toHexString(relayAgentIp.toOctets(), ":"));
+
+ } else {
+ if (this.indirectRelayAgentIpFromCfg == null) {
+ dhcp6Relay.setLinkAddress(relayAgentIp.toOctets());
+ log.warn("indirect connection: relayAgentIp NOT availale from config file! {}",
+ HexString.toHexString(relayAgentIp.toOctets(), ":"));
+
+ } else {
+ dhcp6Relay.setLinkAddress(this.indirectRelayAgentIpFromCfg.toOctets());
+ log.debug("indirect connection: relayAgentIp from config file is available! {}",
+ HexString.toHexString(this.indirectRelayAgentIpFromCfg.toOctets(), ":"));
+ }
+ }
+
+ // peer address: address of the client or relay agent from which
+ // the message to be relayed was received.
+ dhcp6Relay.setPeerAddress(peerAddress);
+ List<Dhcp6Option> options = new ArrayList<Dhcp6Option>();
+
+ // directly connected case, hop count is zero
+ // otherwise, hop count + 1
+ if (directConnFlag) {
+ dhcp6Relay.setHopCount((byte) 0);
+ } else {
+ dhcp6Relay.setHopCount((byte) (dhcp6Packet.getHopCount() + 1));
+ }
+
+ // create relay message option
+ Dhcp6Option relayMessage = new Dhcp6Option();
+ relayMessage.setCode(DHCP6.OptionCode.RELAY_MSG.value());
+ relayMessage.setLength((short) dhcp6PacketByte.length);
+ relayMessage.setData(dhcp6PacketByte);
+ options.add(relayMessage);
+
+ // create interfaceId option
+ String inPortString = "-" + context.inPacket().receivedFrom().toString();
+ Dhcp6Option interfaceId = new Dhcp6Option();
+ interfaceId.setCode(DHCP6.OptionCode.INTERFACE_ID.value());
+ byte[] clientSoureMacBytes = clientPacket.getSourceMACAddress();
+ byte[] inPortStringBytes = inPortString.getBytes();
+ byte[] interfaceIdBytes = new byte[clientSoureMacBytes.length + inPortStringBytes.length];
+ log.debug("Length: interfaceIdBytes {} clientSoureMacBytes {} inPortStringBytes {} ",
+ interfaceIdBytes.length, clientSoureMacBytes.length, inPortStringBytes.length);
+
+ System.arraycopy(clientSoureMacBytes, 0, interfaceIdBytes, 0, clientSoureMacBytes.length);
+ System.arraycopy(inPortStringBytes, 0, interfaceIdBytes, clientSoureMacBytes.length, inPortStringBytes.length);
+
+ interfaceId.setData(interfaceIdBytes);
+ interfaceId.setLength((short) interfaceIdBytes.length);
+
+ options.add(interfaceId);
+
+ log.debug("interfaceId write srcMac {} portString {}",
+ HexString.toHexString(clientSoureMacBytes, ":"), inPortString);
+ dhcp6Relay.setOptions(options);
+ //dhcp6Packet.setPayload(dhcp6Relay);
+ //udpPacket.setPayload(dhcp6Packet);
+ udpPacket.setPayload(dhcp6Relay);
+ udpPacket.resetChecksum();
+ ipv6Packet.setPayload(udpPacket);
+ etherReply.setPayload(ipv6Packet);
+
+
+ return new InternalPacket(etherReply, this.dhcpServerConnectPoint);
+ }
+
+ /**
+ *
+ * process the DHCP6 relay-reply packet from dhcp server.
+ *
+ * @param context packet context
+ * @param receivedPacket server ethernet packet
+ * @param recevingInterfaces set of server side interfaces
+ */
+ private InternalPacket processDhcp6PacketFromServer(PacketContext context,
+ Ethernet receivedPacket, Set<Interface> recevingInterfaces) {
+ ConnectPoint inPort = context.inPacket().receivedFrom();
+ if (!inPort.equals(this.dhcpServerConnectPoint)) {
+ log.warn("Receiving port {} is not the same as server port {}",
+ inPort, this.dhcpServerConnectPoint);
+ return null;
+ }
+ // get dhcp6 header.
+ Ethernet etherReply = (Ethernet) receivedPacket.clone();
+ IPv6 ipv6Packet = (IPv6) etherReply.getPayload();
+ UDP udpPacket = (UDP) ipv6Packet.getPayload();
+ DHCP6 dhcp6Relay = (DHCP6) udpPacket.getPayload();
+
+ Boolean directConnFlag = directlyConnected(dhcp6Relay);
+
+ Dhcp6InterfaceIdOption interfaceIdOption = dhcp6Relay.getOptions().stream()
+ .filter(opt -> opt instanceof Dhcp6InterfaceIdOption)
+ .map(opt -> (Dhcp6InterfaceIdOption) opt)
+ .findFirst()
+ .orElse(null);
+
+ if (interfaceIdOption == null) {
+ log.warn("Interface Id option is not present, abort packet...");
+ return null;
+ }
+
+ MacAddress peerMac = interfaceIdOption.getMacAddress();
+ String clientConnectionPointStr = new String(interfaceIdOption.getInPort());
+
+ ConnectPoint clientConnectionPoint = ConnectPoint.deviceConnectPoint(clientConnectionPointStr);
+
+ Set<Interface> clientInterfaces = interfaceService.getInterfacesByPort(clientConnectionPoint);
+ if (clientInterfaces.isEmpty()) {
+ log.warn("Can not get client interface from packet, abort..");
+ return null;
+ }
+ MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
+ if (relayAgentMac == null) {
+ log.warn("Can not get interface mac, abort packet..");
+ return null;
+ }
+ etherReply.setSourceMACAddress(relayAgentMac);
+
+ // find destMac
+ MacAddress clientMac = null;
+ Set<Host> clients = hostService.getHostsByIp(Ip6Address.valueOf(dhcp6Relay.getPeerAddress()));
+ if (clients.isEmpty()) {
+ log.warn("There's no host found for this address {}",
+ HexString.toHexString(dhcp6Relay.getPeerAddress(), ":"));
+ log.warn("Let's look up interfaceId {}", HexString.toHexString(peerMac.toBytes(), ":"));
+ clientMac = peerMac;
+ } else {
+ clientMac = clients.iterator().next().mac();
+ if (clientMac == null) {
+ log.warn("No client mac address found, abort packet...");
+ return null;
+ }
+ log.warn("Client mac address found from getHostByIp");
+
+ }
+ etherReply.setDestinationMACAddress(clientMac);
+
+ // ip header
+ ipv6Packet.setSourceAddress(dhcp6Relay.getLinkAddress());
+ ipv6Packet.setDestinationAddress(dhcp6Relay.getPeerAddress());
+ // udp header
+ udpPacket.setSourcePort(UDP.DHCP_V6_SERVER_PORT);
+ if (directConnFlag) {
+ udpPacket.setDestinationPort(UDP.DHCP_V6_CLIENT_PORT);
+ } else {
+ udpPacket.setDestinationPort(UDP.DHCP_V6_SERVER_PORT);
+ }
+
+ DHCP6 embeddedDhcp6 = dhcp6Relay.getOptions().stream()
+ .filter(opt -> opt instanceof Dhcp6RelayOption)
+ .map(BasePacket::getPayload)
+ .map(pld -> (DHCP6) pld)
+ .findFirst()
+ .orElse(null);
+
+
+ // add host or route
+ addHostOrRoute(directConnFlag, dhcp6Relay, embeddedDhcp6, clientMac, clientInterfaces);
+
+ udpPacket.setPayload(embeddedDhcp6);
+ udpPacket.resetChecksum();
+ ipv6Packet.setPayload(udpPacket);
+ etherReply.setPayload(ipv6Packet);
+
+ return new InternalPacket(etherReply, clientConnectionPoint);
+ }
+
+ // Returns the first v4 interface ip out of a set of interfaces or null.
+ // Checks all interfaces, and ignores v6 interface ips
+ private Ip6Address getRelayAgentIPv6Address(Set<Interface> intfs) {
+ for (Interface intf : intfs) {
+ for (InterfaceIpAddress ip : intf.ipAddressesList()) {
+ Ip6Address relayAgentIp = ip.ipAddress().getIp6Address();
+ if (relayAgentIp != null) {
+ return relayAgentIp;
+ }
+ }
+ }
+ return null;
}
@Override
public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
+ if (configs.size() == 0) {
+ // no config to update
+ return;
+ }
+
+ // TODO: currently we pick up first DHCP server config.
+ // Will use other server configs in the future for HA.
+ DhcpServerConfig serverConfig = configs.iterator().next();
+ if (!serverConfig.getDhcpServerConnectPoint().isPresent()) {
+ log.warn("Connect point not exists");
+ return;
+ }
+ if (!serverConfig.getDhcpServerIp6().isPresent()) {
+ log.warn("IP of DHCP6 server not exists");
+ return;
+ }
+ Ip6Address oldServerIp = this.dhcpServerIp;
+ Ip6Address oldGatewayIp = this.dhcpGatewayIp;
+
+ // stop monitoring gateway or server
+ if (oldGatewayIp != null) {
+ hostService.stopMonitoringIp(oldGatewayIp);
+ } else if (oldServerIp != null) {
+ hostService.stopMonitoringIp(oldServerIp);
+ }
+
+ this.dhcpServerConnectPoint = serverConfig.getDhcpServerConnectPoint().get();
+ this.dhcpServerIp = serverConfig.getDhcpServerIp6().get();
+ this.dhcpGatewayIp = serverConfig.getDhcpGatewayIp6().orElse(null);
+ this.relayAgentIpFromCfg = serverConfig.getRelayAgentIp6().orElse(null);
+
+
+ // reset server mac and vlan
+ this.dhcpConnectMac = null;
+ this.dhcpConnectVlan = null;
+
+ log.info("DHCP6 server connect point: " + this.dhcpServerConnectPoint);
+ log.info("DHCP6 server IP: " + this.dhcpServerIp);
+
+ IpAddress ipToProbe = MoreObjects.firstNonNull(this.dhcpGatewayIp, this.dhcpServerIp);
+ String hostToProbe = this.dhcpGatewayIp != null ? "gateway" : "DHCP6 server";
+
+ if (ipToProbe == null) {
+ log.warn("Server IP6 not set, can't probe it");
+ return;
+ }
+
+ log.info("Probing to resolve {} IP6 {}", hostToProbe, ipToProbe);
+ hostService.startMonitoringIp(ipToProbe);
+
+ Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
+ if (!hosts.isEmpty()) {
+ Host host = hosts.iterator().next();
+ this.dhcpConnectVlan = host.vlan();
+ this.dhcpConnectMac = host.mac();
+ }
}
@Override
public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
+ if (configs.size() == 0) {
+ // no config to update
+ return;
+ }
+ // TODO: currently we pick up Second DHCP server config for indirect.
+ // Will use other server configs in the future for HA.
+ DhcpServerConfig serverConfig = configs.iterator().next();
+ checkState(serverConfig.getDhcpServerConnectPoint().isPresent(),
+ "Connect point not exists");
+ checkState(serverConfig.getDhcpServerIp6().isPresent(),
+ "IP of DHCP6 server not exists");
+ Ip6Address oldServerIp = this.indirectDhcpServerIp;
+ Ip6Address oldGatewayIp = this.indirectDhcpGatewayIp;
+
+ // stop monitoring gateway or server
+ if (oldGatewayIp != null) {
+ hostService.stopMonitoringIp(oldGatewayIp);
+ } else if (oldServerIp != null) {
+ hostService.stopMonitoringIp(oldServerIp);
+ }
+
+ this.indirectDhcpServerConnectPoint = serverConfig.getDhcpServerConnectPoint().get();
+ this.indirectDhcpServerIp = serverConfig.getDhcpServerIp6().get();
+ this.indirectDhcpGatewayIp = serverConfig.getDhcpGatewayIp6().orElse(null);
+ this.indirectRelayAgentIpFromCfg = serverConfig.getRelayAgentIp6().orElse(null);
+
+
+ // reset server mac and vlan
+ this.indirectDhcpConnectMac = null;
+ this.indirectDhcpConnectVlan = null;
+
+ log.info("DHCP6 server connect point: " + this.indirectDhcpServerConnectPoint);
+ log.info("DHCP6 server IP: " + this.indirectDhcpServerIp);
+
+ IpAddress ipToProbe = MoreObjects.firstNonNull(this.indirectDhcpGatewayIp, this.indirectDhcpServerIp);
+ String hostToProbe = this.indirectDhcpGatewayIp != null ? "gateway" : "DHCP6 server";
+
+ if (ipToProbe == null) {
+ log.warn("Server IP6 not set, can't probe it");
+ return;
+ }
+
+ log.info("Probing to resolve {} IP6 {}", hostToProbe, ipToProbe);
+ hostService.startMonitoringIp(ipToProbe);
+
+ Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
+ if (!hosts.isEmpty()) {
+ Host host = hosts.iterator().next();
+ this.indirectDhcpConnectVlan = host.vlan();
+ this.indirectDhcpConnectMac = host.mac();
+ }
+ }
+
+ class InternalHostListener implements HostListener {
+ @Override
+ public void event(HostEvent event) {
+ switch (event.type()) {
+ case HOST_ADDED:
+ case HOST_UPDATED:
+ hostUpdated(event.subject());
+ break;
+ case HOST_REMOVED:
+ hostRemoved(event.subject());
+ break;
+ case HOST_MOVED:
+ hostMoved(event.subject());
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Handle host move.
+ * If the host DHCP server or gateway and it moved to the location different
+ * to user configured, unsets the connect mac and vlan
+ *
+ * @param host the host
+ */
+ private void hostMoved(Host host) {
+ if (this.dhcpServerConnectPoint == null && this.indirectDhcpServerConnectPoint == null) {
+ return;
+ }
+ if (this.dhcpGatewayIp != null) {
+ if (host.ipAddresses().contains(this.dhcpGatewayIp) &&
+ !host.locations().contains(this.dhcpServerConnectPoint)) {
+ this.dhcpConnectMac = null;
+ this.dhcpConnectVlan = null;
+ }
+ }
+ if (this.dhcpServerIp != null) {
+ if (host.ipAddresses().contains(this.dhcpServerIp) &&
+ !host.locations().contains(this.dhcpServerConnectPoint)) {
+ this.dhcpConnectMac = null;
+ this.dhcpConnectVlan = null;
+ }
+ }
+ if (this.indirectDhcpGatewayIp != null) {
+ if (host.ipAddresses().contains(this.indirectDhcpGatewayIp) &&
+ !host.locations().contains(this.indirectDhcpServerConnectPoint)) {
+ this.indirectDhcpConnectMac = null;
+ this.indirectDhcpConnectVlan = null;
+ }
+ }
+ if (this.indirectDhcpServerIp != null) {
+ if (host.ipAddresses().contains(this.indirectDhcpServerIp) &&
+ !host.locations().contains(this.indirectDhcpServerConnectPoint)) {
+ this.indirectDhcpConnectMac = null;
+ this.indirectDhcpConnectVlan = null;
+ }
+ }
+ }
+
+ /**
+ * Handle host updated.
+ * If the host is DHCP server or gateway, update connect mac and vlan.
+ *
+ * @param host the host
+ */
+ private void hostUpdated(Host host) {
+ if (this.dhcpGatewayIp != null) {
+ if (host.ipAddresses().contains(this.dhcpGatewayIp)) {
+ this.dhcpConnectMac = host.mac();
+ this.dhcpConnectVlan = host.vlan();
+ }
+ }
+ if (this.dhcpServerIp != null) {
+ if (host.ipAddresses().contains(this.dhcpServerIp)) {
+ this.dhcpConnectMac = host.mac();
+ this.dhcpConnectVlan = host.vlan();
+ }
+ }
+ if (this.indirectDhcpGatewayIp != null) {
+ if (host.ipAddresses().contains(this.indirectDhcpGatewayIp)) {
+ this.indirectDhcpConnectMac = host.mac();
+ this.indirectDhcpConnectVlan = host.vlan();
+ }
+ }
+ if (this.indirectDhcpServerIp != null) {
+ if (host.ipAddresses().contains(this.indirectDhcpServerIp)) {
+ this.indirectDhcpConnectMac = host.mac();
+ this.indirectDhcpConnectVlan = host.vlan();
+ }
+ }
+ }
+
+ /**
+ * Handle host removed.
+ * If the host is DHCP server or gateway, unset connect mac and vlan.
+ *
+ * @param host the host
+ */
+ private void hostRemoved(Host host) {
+ if (this.dhcpGatewayIp != null) {
+ if (host.ipAddresses().contains(this.dhcpGatewayIp)) {
+ this.dhcpConnectMac = null;
+ this.dhcpConnectVlan = null;
+ }
+ //return;
+ }
+ if (this.dhcpServerIp != null) {
+ if (host.ipAddresses().contains(this.dhcpServerIp)) {
+ this.dhcpConnectMac = null;
+ this.dhcpConnectVlan = null;
+ }
+ }
+ if (this.indirectDhcpGatewayIp != null) {
+ if (host.ipAddresses().contains(this.indirectDhcpGatewayIp)) {
+ this.indirectDhcpConnectMac = null;
+ this.indirectDhcpConnectVlan = null;
+ }
+ //return;
+ }
+ if (this.indirectDhcpServerIp != null) {
+ if (host.ipAddresses().contains(this.indirectDhcpServerIp)) {
+ this.indirectDhcpConnectMac = null;
+ this.indirectDhcpConnectVlan = null;
+ }
+ }
}
}
diff --git a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
index 4494ddb..3d89d76 100644
--- a/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
+++ b/apps/dhcprelay/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
@@ -36,6 +36,8 @@
import org.onlab.packet.Ethernet;
import org.onlab.packet.IPv4;
import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpPrefix;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.UDP;
@@ -43,11 +45,18 @@
import org.onlab.packet.dhcp.CircuitId;
import org.onlab.packet.dhcp.DhcpOption;
import org.onlab.packet.dhcp.DhcpRelayAgentOption;
+import org.onlab.packet.dhcp.Dhcp6InterfaceIdOption;
+import org.onlab.packet.dhcp.Dhcp6RelayOption;
+import org.onlab.packet.dhcp.Dhcp6IaNaOption;
+import org.onlab.packet.dhcp.Dhcp6IaPdOption;
+import org.onlab.packet.dhcp.Dhcp6IaAddressOption;
+import org.onlab.packet.dhcp.Dhcp6IaPrefixOption;
+import org.onlab.packet.dhcp.Dhcp6Option;
+import org.onosproject.net.ConnectPoint;
import org.onosproject.TestApplicationId;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
-import org.onosproject.dhcprelay.api.DhcpHandler;
import org.onosproject.dhcprelay.config.DefaultDhcpRelayConfig;
import org.onosproject.dhcprelay.config.DhcpServerConfig;
import org.onosproject.dhcprelay.config.IgnoreDhcpConfig;
@@ -68,7 +77,6 @@
import org.onosproject.net.packet.PacketPriority;
import org.onosproject.routeservice.Route;
import org.onosproject.routeservice.RouteStoreAdapter;
-import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultHost;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
@@ -89,11 +97,14 @@
import org.onosproject.net.provider.ProviderId;
import org.onosproject.store.StoreDelegate;
import org.osgi.service.component.ComponentContext;
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.IPv6;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Dictionary;
import java.util.List;
+import java.util.ArrayList;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@@ -109,10 +120,14 @@
private static final DeviceId DEV_2_ID = DeviceId.deviceId("of:0000000000000002");
// Ip address for interfaces
private static final InterfaceIpAddress INTERFACE_IP = InterfaceIpAddress.valueOf("10.0.3.254/32");
- private static final List<InterfaceIpAddress> INTERFACE_IPS = ImmutableList.of(INTERFACE_IP);
+ private static final InterfaceIpAddress INTERFACE_IP_V6 = InterfaceIpAddress.valueOf("2001:db8:1::254/128");
+ private static final List<InterfaceIpAddress> INTERFACE_IPS = ImmutableList.of(INTERFACE_IP, INTERFACE_IP_V6);
// DHCP client (will send without option 82)
private static final Ip4Address IP_FOR_CLIENT = Ip4Address.valueOf("10.0.0.1");
+ private static final Ip6Address IP_FOR_CLIENT_V6 = Ip6Address.valueOf("2001:db8:1::110");
+ private static final IpPrefix PREFIX_FOR_CLIENT_V6 = IpPrefix.valueOf("2001:db8:10::/56");
+ private static final IpPrefix PREFIX_FOR_ZERO = IpPrefix.valueOf("::/0");
private static final MacAddress CLIENT_MAC = MacAddress.valueOf("00:00:00:00:00:01");
private static final VlanId CLIENT_VLAN = VlanId.vlanId("100");
private static final ConnectPoint CLIENT_CP = ConnectPoint.deviceConnectPoint("of:0000000000000001/1");
@@ -136,7 +151,8 @@
// Outer relay information
private static final Ip4Address OUTER_RELAY_IP = Ip4Address.valueOf("10.0.5.253");
- private static final Set<IpAddress> OUTER_RELAY_IPS = ImmutableSet.of(OUTER_RELAY_IP);
+ private static final Ip6Address OUTER_RELAY_IP_V6 = Ip6Address.valueOf("2001:db8:1::4");
+ private static final Set<IpAddress> OUTER_RELAY_IPS = ImmutableSet.of(OUTER_RELAY_IP, OUTER_RELAY_IP_V6);
private static final MacAddress OUTER_RELAY_MAC = MacAddress.valueOf("00:01:02:03:04:05");
private static final VlanId OUTER_RELAY_VLAN = VlanId.NONE;
private static final ConnectPoint OUTER_RELAY_CP = ConnectPoint.deviceConnectPoint("of:0000000000000001/2");
@@ -156,8 +172,12 @@
ConnectPoint.deviceConnectPoint("of:0000000000000001/5");
private static final HostLocation SERVER_LOCATION =
new HostLocation(SERVER_CONNECT_POINT, 0);
+ private static final Ip4Address GATEWAY_IP = Ip4Address.valueOf("10.0.5.253");
+ private static final Ip6Address GATEWAY_IP_V6 = Ip6Address.valueOf("2000::105:253");
private static final Ip4Address SERVER_IP = Ip4Address.valueOf("10.0.3.253");
- private static final Set<IpAddress> DHCP_SERVER_IPS = ImmutableSet.of(SERVER_IP);
+ private static final Ip6Address SERVER_IP_V6 = Ip6Address.valueOf("2000::103:253");
+ private static final Ip6Address SERVER_IP_V6_MCAST = Ip6Address.valueOf("ff02::1:2");
+ private static final Set<IpAddress> DHCP_SERVER_IPS = ImmutableSet.of(SERVER_IP, SERVER_IP_V6);
private static final HostId SERVER_HOST_ID = HostId.hostId(SERVER_MAC, SERVER_VLAN);
private static final Host SERVER_HOST = new DefaultHost(DhcpRelayManager.PROVIDER_ID,
SERVER_HOST_ID,
@@ -178,6 +198,7 @@
// Components
private static final ApplicationId APP_ID = TestApplicationId.create(DhcpRelayManager.DHCP_RELAY_APP);
private static final DefaultDhcpRelayConfig CONFIG = new MockDefaultDhcpRelayConfig();
+ private static final IndirectDhcpRelayConfig CONFIG_INDIRECT = new MockIndirectDhcpRelayConfig();
private static final Set<Interface> INTERFACES = ImmutableSet.of(
CLIENT_INTERFACE,
CLIENT2_INTERFACE,
@@ -204,7 +225,7 @@
// TODO: add indirect test
expect(manager.cfgService.getConfig(APP_ID, IndirectDhcpRelayConfig.class))
- .andReturn(null)
+ .andReturn(CONFIG_INDIRECT)
.anyTimes();
manager.coreService = createNiceMock(CoreService.class);
@@ -237,7 +258,14 @@
manager.v4Handler = v4Handler;
// TODO: initialize v6 handler.
- DhcpHandler v6Handler = createNiceMock(DhcpHandler.class);
+ //DhcpHandler v6Handler = createNiceMock(DhcpHandler.class);
+ Dhcp6HandlerImpl v6Handler = new Dhcp6HandlerImpl();
+ v6Handler.dhcpRelayStore = mockDhcpRelayStore;
+ v6Handler.hostService = manager.hostService;
+ v6Handler.hostStore = mockHostStore;
+ v6Handler.interfaceService = manager.interfaceService;
+ v6Handler.packetService = manager.packetService;
+ v6Handler.routeStore = mockRouteStore;
manager.v6Handler = v6Handler;
// properties
@@ -446,6 +474,75 @@
assertEquals(0, manager.ignoredVlans.size());
}
+ /**
+ * Relay a DHCP6 packet without relay option
+ * Note: Should add new host to host store after dhcp ack.
+ */
+ @Test
+ public void relayDhcp6WithoutAgentInfo() {
+ // send request
+ packetService.processPacket(new TestDhcp6RequestPacketContext(CLIENT_MAC,
+ VlanId.NONE,
+ CLIENT_CP,
+ INTERFACE_IP_V6.ipAddress().getIp6Address(),
+ 0));
+
+ Set<Host> hosts = ImmutableSet.copyOf(mockHostStore.getHosts());
+ assertEquals(0, hosts.size());
+ assertEquals(0, mockRouteStore.routes.size());
+
+ // send reply
+ packetService.processPacket(new TestDhcp6ReplyPacketContext(CLIENT_CP, CLIENT_MAC,
+ CLIENT_VLAN,
+ INTERFACE_IP_V6.ipAddress().getIp6Address(),
+ 0));
+ hosts = ImmutableSet.copyOf(mockHostStore.getHosts());
+ assertEquals(1, hosts.size());
+ assertEquals(0, mockRouteStore.routes.size());
+
+ Host host = hosts.iterator().next();
+ assertEquals(CLIENT_MAC, host.mac());
+ assertEquals(CLIENT_VLAN, host.vlan());
+ assertEquals(CLIENT_CP.deviceId(), host.location().elementId());
+ assertEquals(CLIENT_CP.port(), host.location().port());
+ assertEquals(1, host.ipAddresses().size());
+ assertEquals(IP_FOR_CLIENT_V6, host.ipAddresses().iterator().next());
+ assertEquals(HostId.hostId(CLIENT_MAC, CLIENT_VLAN), host.id());
+ }
+
+ /**
+ * Relay a DHCP6 packet with Relay Message opion (Indirectly connected host).
+ */
+ @Test
+ public void relayDhcp6WithAgentInfo() {
+ // Assume outer dhcp6 relay agent exists in store already
+ // send request
+ packetService.processPacket(new TestDhcp6RequestPacketContext(CLIENT2_MAC,
+ CLIENT2_VLAN,
+ CLIENT2_CP,
+ INTERFACE_IP_V6.ipAddress().getIp6Address(),
+ 1));
+
+ Set<Host> hosts = ImmutableSet.copyOf(mockHostStore.getHosts());
+ assertEquals(0, hosts.size());
+ assertEquals(0, mockRouteStore.routes.size());
+
+ // send reply
+ packetService.processPacket(new TestDhcp6ReplyPacketContext(CLIENT2_CP,
+ CLIENT2_MAC,
+ CLIENT2_VLAN,
+ INTERFACE_IP_V6.ipAddress().getIp6Address(),
+ 1));
+
+ hosts = ImmutableSet.copyOf(mockHostStore.getHosts());
+ assertEquals(0, hosts.size());
+ assertEquals(2, mockRouteStore.routes.size()); // ipAddress and prefix
+
+ Route route = mockRouteStore.routes.get(0);
+ assertEquals(OUTER_RELAY_IP_V6, route.nextHop());
+ assertEquals(IP_FOR_CLIENT_V6.toIpPrefix(), route.prefix());
+ assertEquals(Route.Source.STATIC, route.source());
+ }
private static class MockDefaultDhcpRelayConfig extends DefaultDhcpRelayConfig {
@Override
public boolean isValid() {
@@ -458,6 +555,18 @@
}
}
+ private static class MockIndirectDhcpRelayConfig extends IndirectDhcpRelayConfig {
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public List<DhcpServerConfig> dhcpServerConfigs() {
+ return ImmutableList.of(new MockDhcpServerConfig(null));
+ }
+ }
+
private static class MockDhcpServerConfig extends DhcpServerConfig {
Ip4Address relayAgentIp;
@@ -484,6 +593,21 @@
public Optional<Ip4Address> getDhcpServerIp4() {
return Optional.of(SERVER_IP);
}
+
+ @Override
+ public Optional<Ip4Address> getDhcpGatewayIp4() {
+ return Optional.of(GATEWAY_IP);
+ }
+
+ @Override
+ public Optional<Ip6Address> getDhcpServerIp6() {
+ return Optional.of(SERVER_IP_V6);
+ }
+
+ @Override
+ public Optional<Ip6Address> getDhcpGatewayIp6() {
+ return Optional.of(GATEWAY_IP_V6);
+ }
}
private class MockHostStore extends HostStoreAdapter {
@@ -821,4 +945,266 @@
return this.inPacket;
}
}
+
+ /**
+ * Generates DHCP6 REQUEST packet.
+ */
+ private void buildDhcp6Packet(DHCP6 dhcp6, byte msgType, Ip6Address ip6Addr, IpPrefix prefix) {
+
+ // build address option
+ Dhcp6IaAddressOption iaAddressOption = new Dhcp6IaAddressOption();
+ iaAddressOption.setCode(DHCP6.OptionCode.IAADDR.value());
+ iaAddressOption.setIp6Address(ip6Addr);
+ iaAddressOption.setPreferredLifetime(3600);
+ iaAddressOption.setValidLifetime(1200);
+ iaAddressOption.setLength((short) Dhcp6IaAddressOption.DEFAULT_LEN);
+
+ Dhcp6IaNaOption iaNaOption = new Dhcp6IaNaOption();
+ iaNaOption.setCode(DHCP6.OptionCode.IA_NA.value());
+ iaNaOption.setIaId(0);
+ iaNaOption.setT1(302400);
+ iaNaOption.setT2(483840);
+ List<Dhcp6Option> iaNaSubOptions = new ArrayList<Dhcp6Option>();
+ iaNaSubOptions.add(iaAddressOption);
+ iaNaOption.setOptions(iaNaSubOptions);
+ iaNaOption.setLength((short) (Dhcp6IaNaOption.DEFAULT_LEN + iaAddressOption.getLength()));
+
+ // build prefix option
+ Dhcp6IaPrefixOption iaPrefixOption = new Dhcp6IaPrefixOption();
+ iaPrefixOption.setCode(DHCP6.OptionCode.IAPREFIX.value());
+ iaPrefixOption.setIp6Prefix(prefix.address().getIp6Address());
+ iaPrefixOption.setPrefixLength((byte) prefix.prefixLength());
+ iaPrefixOption.setPreferredLifetime(3601);
+ iaPrefixOption.setValidLifetime(1201);
+ iaPrefixOption.setLength((short) Dhcp6IaPrefixOption.DEFAULT_LEN);
+
+ Dhcp6IaPdOption iaPdOption = new Dhcp6IaPdOption();
+ iaPdOption.setCode(DHCP6.OptionCode.IA_PD.value());
+ iaPdOption.setIaId(0);
+ iaPdOption.setT1(302401);
+ iaPdOption.setT2(483841);
+ List<Dhcp6Option> iaPdSubOptions = new ArrayList<Dhcp6Option>();
+ iaPdSubOptions.add(iaPrefixOption);
+ iaPdOption.setOptions(iaPdSubOptions);
+ iaPdOption.setLength((short) (Dhcp6IaPdOption.DEFAULT_LEN + iaPrefixOption.getLength()));
+
+ dhcp6.setMsgType(msgType);
+ List<Dhcp6Option> dhcp6Options = new ArrayList<Dhcp6Option>();
+ dhcp6Options.add(iaNaOption);
+ dhcp6Options.add(iaPdOption);
+ dhcp6.setOptions(dhcp6Options);
+
+ }
+
+ private void buildRelayMsg(DHCP6 dhcp6Relay, byte msgType, Ip6Address linkAddr,
+ Ip6Address peerAddr, byte hop, byte[] interfaceIdBytes,
+ DHCP6 dhcp6Payload) {
+
+ dhcp6Relay.setMsgType(msgType);
+
+ dhcp6Relay.setLinkAddress(linkAddr.toOctets());
+ dhcp6Relay.setPeerAddress(peerAddr.toOctets());
+ dhcp6Relay.setHopCount(hop);
+ List<Dhcp6Option> options = new ArrayList<Dhcp6Option>();
+
+ // interfaceId option
+ Dhcp6Option interfaceId = new Dhcp6Option();
+ interfaceId.setCode(DHCP6.OptionCode.INTERFACE_ID.value());
+
+
+ interfaceId.setData(interfaceIdBytes);
+ interfaceId.setLength((short) interfaceIdBytes.length);
+ Dhcp6InterfaceIdOption interfaceIdOption = new Dhcp6InterfaceIdOption(interfaceId);
+ ByteBuffer bb = ByteBuffer.wrap(interfaceIdBytes);
+
+ byte[] macAddr = new byte[MacAddress.MAC_ADDRESS_LENGTH];
+ byte[] port = new byte[21];
+ bb.get(macAddr);
+ bb.get(); // separator
+ bb.get(port);
+ interfaceIdOption.setMacAddress(MacAddress.valueOf(macAddr));
+ interfaceIdOption.setInPort(port);
+
+ options.add(interfaceIdOption);
+
+ // relay message option
+ Dhcp6Option relayMsgOption = new Dhcp6Option();
+ relayMsgOption.setCode(DHCP6.OptionCode.RELAY_MSG.value());
+ byte[] dhcp6PayloadByte = dhcp6Payload.serialize();
+ relayMsgOption.setLength((short) dhcp6PayloadByte.length);
+ relayMsgOption.setPayload(dhcp6Payload);
+ Dhcp6RelayOption relayOpt = new Dhcp6RelayOption(relayMsgOption);
+
+ options.add(relayOpt);
+
+ dhcp6Relay.setOptions(options);
+ }
+ private byte[] buildInterfaceId(MacAddress clientMac, ConnectPoint clientCp) {
+ String inPortString = "-" + clientCp.toString();
+ byte[] clientSoureMacBytes = clientMac.toBytes();
+ byte[] inPortStringBytes = inPortString.getBytes();
+ byte[] interfaceIdBytes = new byte[clientSoureMacBytes.length + inPortStringBytes.length];
+
+ System.arraycopy(clientSoureMacBytes, 0, interfaceIdBytes, 0, clientSoureMacBytes.length);
+ System.arraycopy(inPortStringBytes, 0, interfaceIdBytes, clientSoureMacBytes.length, inPortStringBytes.length);
+
+ return interfaceIdBytes;
+ }
+
+ private class TestDhcp6RequestPacketContext extends PacketContextAdapter {
+
+
+ private InboundPacket inPacket;
+
+
+ public TestDhcp6RequestPacketContext(MacAddress clientMac, VlanId vlanId,
+ ConnectPoint clientCp,
+ Ip6Address clientGwAddr,
+ int relayLevel) {
+ super(0, null, null, false);
+
+ DHCP6 dhcp6 = new DHCP6();
+ if (relayLevel > 0) {
+ DHCP6 dhcp6Payload = new DHCP6();
+ buildDhcp6Packet(dhcp6Payload, DHCP6.MsgType.REQUEST.value(),
+ IP_FOR_CLIENT_V6,
+ PREFIX_FOR_ZERO);
+ DHCP6 dhcp6Parent = null;
+ DHCP6 dhcp6Child = dhcp6Payload;
+ for (int i = 0; i < relayLevel; i++) {
+ dhcp6Parent = new DHCP6();
+ byte[] interfaceId = buildInterfaceId(clientMac, clientCp);
+ buildRelayMsg(dhcp6Parent, DHCP6.MsgType.RELAY_FORW.value(),
+ INTERFACE_IP_V6.ipAddress().getIp6Address(),
+ OUTER_RELAY_IP_V6,
+ (byte) (relayLevel - 1), interfaceId,
+ dhcp6Child);
+ dhcp6Child = dhcp6Parent;
+ }
+ if (dhcp6Parent != null) {
+ dhcp6 = dhcp6Parent;
+ }
+ } else {
+ buildDhcp6Packet(dhcp6, DHCP6.MsgType.REQUEST.value(),
+ IP_FOR_CLIENT_V6,
+ PREFIX_FOR_ZERO);
+ }
+
+ UDP udp = new UDP();
+ udp.setPayload(dhcp6);
+ if (relayLevel > 0) {
+ udp.setSourcePort(UDP.DHCP_V6_SERVER_PORT);
+ } else {
+ udp.setSourcePort(UDP.DHCP_V6_CLIENT_PORT);
+ }
+ udp.setDestinationPort(UDP.DHCP_V6_SERVER_PORT);
+
+ IPv6 ipv6 = new IPv6();
+ ipv6.setPayload(udp);
+ ipv6.setNextHeader(IPv6.PROTOCOL_UDP);
+ ipv6.setDestinationAddress(SERVER_IP_V6_MCAST.toOctets());
+ ipv6.setSourceAddress(clientGwAddr.toOctets());
+
+ Ethernet eth = new Ethernet();
+ if (relayLevel > 0) {
+ eth.setEtherType(Ethernet.TYPE_IPV6)
+ .setVlanID(vlanId.toShort())
+ .setSourceMACAddress(OUTER_RELAY_MAC)
+ .setDestinationMACAddress(MacAddress.valueOf("33:33:00:01:00:02"))
+ .setPayload(ipv6);
+ } else {
+ eth.setEtherType(Ethernet.TYPE_IPV6)
+ .setVlanID(vlanId.toShort())
+ .setSourceMACAddress(clientMac)
+ .setDestinationMACAddress(MacAddress.valueOf("33:33:00:01:00:02"))
+ .setPayload(ipv6);
+ }
+ this.inPacket = new DefaultInboundPacket(clientCp, eth,
+ ByteBuffer.wrap(eth.serialize()));
+ }
+
+ @Override
+ public InboundPacket inPacket() {
+ return this.inPacket;
+ }
+ }
+
+ /**
+ * Generates DHCP6 REPLY packet.
+ */
+
+ private class TestDhcp6ReplyPacketContext extends PacketContextAdapter {
+ private InboundPacket inPacket;
+
+ public TestDhcp6ReplyPacketContext(ConnectPoint clientCp, MacAddress clientMac,
+ VlanId clientVlan, Ip6Address clientGwAddr,
+ int relayLevel) {
+ super(0, null, null, false);
+
+
+ DHCP6 dhcp6Payload = new DHCP6();
+ buildDhcp6Packet(dhcp6Payload, DHCP6.MsgType.REPLY.value(),
+ IP_FOR_CLIENT_V6,
+ PREFIX_FOR_CLIENT_V6);
+ byte[] interfaceId = buildInterfaceId(clientMac, clientCp);
+ DHCP6 dhcp6 = new DHCP6();
+ buildRelayMsg(dhcp6, DHCP6.MsgType.RELAY_REPL.value(),
+ INTERFACE_IP_V6.ipAddress().getIp6Address(),
+ OUTER_RELAY_IP_V6,
+ (byte) 0, interfaceId,
+ dhcp6Payload);
+
+ DHCP6 dhcp6Parent = null;
+ DHCP6 dhcp6Child = dhcp6;
+ for (int i = 0; i < relayLevel; i++) { // relayLevel 0 : no relay
+ dhcp6Parent = new DHCP6();
+
+ buildRelayMsg(dhcp6Parent, DHCP6.MsgType.RELAY_REPL.value(),
+ INTERFACE_IP_V6.ipAddress().getIp6Address(),
+ OUTER_RELAY_IP_V6,
+ (byte) relayLevel, interfaceId,
+ dhcp6Child);
+
+ dhcp6Child = dhcp6Parent;
+ }
+ if (dhcp6Parent != null) {
+ dhcp6 = dhcp6Parent;
+ }
+
+
+ UDP udp = new UDP();
+ udp.setPayload(dhcp6);
+ udp.setSourcePort(UDP.DHCP_V6_SERVER_PORT);
+ udp.setDestinationPort(UDP.DHCP_V6_CLIENT_PORT);
+ IPv6 ipv6 = new IPv6();
+ ipv6.setPayload(udp);
+ ipv6.setNextHeader(IPv6.PROTOCOL_UDP);
+ ipv6.setDestinationAddress(IP_FOR_CLIENT_V6.toOctets());
+ ipv6.setSourceAddress(SERVER_IP_V6.toOctets());
+ Ethernet eth = new Ethernet();
+ if (relayLevel > 0) {
+ eth.setEtherType(Ethernet.TYPE_IPV6)
+ .setVlanID(SERVER_VLAN.toShort())
+ .setSourceMACAddress(SERVER_MAC)
+ .setDestinationMACAddress(OUTER_RELAY_MAC)
+ .setPayload(ipv6);
+ } else {
+ eth.setEtherType(Ethernet.TYPE_IPV6)
+ .setVlanID(SERVER_VLAN.toShort())
+ .setSourceMACAddress(SERVER_MAC)
+ .setDestinationMACAddress(CLIENT_MAC)
+ .setPayload(ipv6);
+ }
+
+ this.inPacket = new DefaultInboundPacket(SERVER_CONNECT_POINT, eth,
+ ByteBuffer.wrap(eth.serialize()));
+
+ }
+
+ @Override
+ public InboundPacket inPacket() {
+ return this.inPacket;
+ }
+ }
+
}
diff --git a/utils/misc/src/main/java/org/onlab/packet/DHCP6.java b/utils/misc/src/main/java/org/onlab/packet/DHCP6.java
index 924d181..f807000 100644
--- a/utils/misc/src/main/java/org/onlab/packet/DHCP6.java
+++ b/utils/misc/src/main/java/org/onlab/packet/DHCP6.java
@@ -23,8 +23,10 @@
import org.onlab.packet.dhcp.Dhcp6IaAddressOption;
import org.onlab.packet.dhcp.Dhcp6IaNaOption;
import org.onlab.packet.dhcp.Dhcp6IaTaOption;
+import org.onlab.packet.dhcp.Dhcp6IaPdOption;
import org.onlab.packet.dhcp.Dhcp6Option;
import org.onlab.packet.dhcp.Dhcp6RelayOption;
+import org.onlab.packet.dhcp.Dhcp6InterfaceIdOption;
import java.nio.ByteBuffer;
import java.util.List;
@@ -88,7 +90,8 @@
RELAY_MSG((short) 9), AUTH((short) 11), UNICAST((short) 12),
STATUS_CODE((short) 13), RAPID_COMMIT((short) 14), USER_CLASS((short) 15),
VENDOR_CLASS((short) 16), VENDOR_OPTS((short) 17), INTERFACE_ID((short) 18),
- RECONF_MSG((short) 19), RECONF_ACCEPT((short) 20), SUBSCRIBER_ID((short) 38);
+ RECONF_MSG((short) 19), RECONF_ACCEPT((short) 20), IA_PD((short) 25), IAPREFIX((short) 26),
+ SUBSCRIBER_ID((short) 38);
protected short value;
OptionCode(final short value) {
@@ -100,11 +103,15 @@
}
private static final Map<Short, Deserializer<Dhcp6Option>> OPT_DESERIALIZERS =
- ImmutableMap.of(OptionCode.IA_NA.value, Dhcp6IaNaOption.deserializer(),
- OptionCode.IA_TA.value, Dhcp6IaTaOption.deserializer(),
- OptionCode.IAADDR.value, Dhcp6IaAddressOption.deserializer(),
- OptionCode.RELAY_MSG.value, Dhcp6RelayOption.deserializer(),
- OptionCode.CLIENTID.value, Dhcp6ClientIdOption.deserializer());
+ ImmutableMap.<Short, Deserializer<Dhcp6Option>>builder()
+ .put(OptionCode.IA_NA.value, Dhcp6IaNaOption.deserializer())
+ .put(OptionCode.IA_TA.value, Dhcp6IaTaOption.deserializer())
+ .put(OptionCode.IAADDR.value, Dhcp6IaAddressOption.deserializer())
+ .put(OptionCode.RELAY_MSG.value, Dhcp6RelayOption.deserializer())
+ .put(OptionCode.CLIENTID.value, Dhcp6ClientIdOption.deserializer())
+ .put(OptionCode.IA_PD.value, Dhcp6IaPdOption.deserializer())
+ .put(OptionCode.INTERFACE_ID.value, Dhcp6InterfaceIdOption.deserializer())
+ .build();
// general field
private byte msgType; // 1 byte
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaPdOption.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaPdOption.java
new file mode 100644
index 0000000..e76f055
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaPdOption.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.onlab.packet.dhcp;
+
+import com.google.common.collect.Lists;
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * DHCPv6 Identity Association for Prefix Delegation Option.
+ * Based on RFC-3633
+ */
+public final class Dhcp6IaPdOption extends Dhcp6Option {
+ public static final int DEFAULT_LEN = 12;
+ private int iaId;
+ private int t1;
+ private int t2;
+ private List<Dhcp6Option> options;
+
+ @Override
+ public short getCode() {
+ return DHCP6.OptionCode.IA_PD.value();
+ }
+
+ @Override
+ public short getLength() {
+ return (short) (DEFAULT_LEN + options.stream()
+ .mapToInt(opt -> (int) opt.getLength() + Dhcp6Option.DEFAULT_LEN)
+ .sum());
+
+ }
+
+ /**
+ * Gets Identity Association ID.
+ * The unique identifier for this IA_PD; the IAID must
+ * be unique among the identifiers for all of this
+ * requesting router's IA_PDs.
+ *
+ * @return the Identity Association ID
+ */
+ public int getIaId() {
+ return iaId;
+ }
+
+ /**
+ * Sets Identity Association ID.
+ *
+ * @param iaId the Identity Association ID.
+ */
+ public void setIaId(int iaId) {
+ this.iaId = iaId;
+ }
+
+ /**
+ * Gets time 1.
+ * The time at which the requesting router should
+ * contact the delegating router from which the
+ * prefixes in the IA_PD were obtained to extend the
+ * lifetimes of the prefixes delegated to the IA_PD;
+ * T1 is a time duration relative to the current time
+ * expressed in units of seconds.
+ *
+ * @return the value of time 1
+ */
+ public int getT1() {
+ return t1;
+ }
+
+ /**
+ * Sets time 1.
+ *
+ * @param t1 the value of time 1
+ */
+ public void setT1(int t1) {
+ this.t1 = t1;
+ }
+
+ /**
+ * Gets time 2.
+ * The time at which the requesting router should
+ * contact any available delegating router to extend
+ * the lifetimes of the prefixes assigned to the
+ * IA_PD; T2 is a time duration relative to the
+ * current time expressed in units of seconds.
+ *
+ * @return the value of time 2
+ */
+ public int getT2() {
+ return t2;
+ }
+
+ /**
+ * Sets time 2.
+ *
+ * @param t2 the value of time 2
+ */
+ public void setT2(int t2) {
+ this.t2 = t2;
+ }
+
+ /**
+ * Gets sub-options.
+ *
+ * @return sub-options of this option
+ */
+ public List<Dhcp6Option> getOptions() {
+ return options;
+ }
+
+ /**
+ * Sets sub-options.
+ *
+ * @param options the sub-options of this option
+ */
+ public void setOptions(List<Dhcp6Option> options) {
+ this.options = options;
+ }
+
+ /**
+ * Default constructor.
+ */
+ public Dhcp6IaPdOption() {
+ }
+
+ /**
+ * Constructs a DHCPv6 IA PD option with DHCPv6 option.
+ *
+ * @param dhcp6Option the DHCPv6 option
+ */
+ public Dhcp6IaPdOption(Dhcp6Option dhcp6Option) {
+ super(dhcp6Option);
+ }
+
+ /**
+ * Gets deserializer.
+ *
+ * @return the deserializer
+ */
+ public static Deserializer<Dhcp6Option> deserializer() {
+ return (data, offset, length) -> {
+ Dhcp6Option dhcp6Option =
+ Dhcp6Option.deserializer().deserialize(data, offset, length);
+ if (dhcp6Option.getLength() < DEFAULT_LEN) {
+ throw new DeserializationException("Invalid IA PD option data");
+ }
+ Dhcp6IaPdOption iaPdOption = new Dhcp6IaPdOption(dhcp6Option);
+ byte[] optionData = iaPdOption.getData();
+ ByteBuffer bb = ByteBuffer.wrap(optionData);
+ iaPdOption.iaId = bb.getInt();
+ iaPdOption.t1 = bb.getInt();
+ iaPdOption.t2 = bb.getInt();
+
+ iaPdOption.options = Lists.newArrayList();
+ while (bb.remaining() >= Dhcp6Option.DEFAULT_LEN) {
+ Dhcp6Option option;
+ ByteBuffer optByteBuffer = ByteBuffer.wrap(optionData,
+ bb.position(),
+ optionData.length - bb.position());
+ short code = optByteBuffer.getShort();
+ short len = optByteBuffer.getShort();
+ int optLen = UNSIGNED_SHORT_MASK & len;
+ byte[] subOptData = new byte[Dhcp6Option.DEFAULT_LEN + optLen];
+ bb.get(subOptData);
+
+ // TODO: put more sub-options?
+ if (code == DHCP6.OptionCode.IAPREFIX.value()) {
+ option = Dhcp6IaPrefixOption.deserializer()
+ .deserialize(subOptData, 0, subOptData.length);
+ } else {
+ option = Dhcp6Option.deserializer()
+ .deserialize(subOptData, 0, subOptData.length);
+ }
+ iaPdOption.options.add(option);
+ }
+ return iaPdOption;
+ };
+ }
+
+ @Override
+ public byte[] serialize() {
+ int payloadLen = DEFAULT_LEN + options.stream()
+ .mapToInt(opt -> (int) opt.getLength() + Dhcp6Option.DEFAULT_LEN)
+ .sum();
+ int len = Dhcp6Option.DEFAULT_LEN + payloadLen;
+ ByteBuffer bb = ByteBuffer.allocate(len);
+ bb.putShort(DHCP6.OptionCode.IA_PD.value());
+ bb.putShort((short) payloadLen);
+ bb.putInt(iaId);
+ bb.putInt(t1);
+ bb.putInt(t2);
+
+ options.stream().map(Dhcp6Option::serialize).forEach(bb::put);
+ return bb.array();
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * super.hashCode() + Objects.hash(iaId, t1, t2, options);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ final Dhcp6IaPdOption other = (Dhcp6IaPdOption) obj;
+ return Objects.equals(this.iaId, other.iaId)
+ && Objects.equals(this.t1, other.t1)
+ && Objects.equals(this.t2, other.t2)
+ && Objects.equals(this.options, other.options);
+ }
+
+ @Override
+ public String toString() {
+ return getToStringHelper()
+ .add("iaId", iaId)
+ .add("t1", t1)
+ .add("t2", t2)
+ .add("options", options)
+ .toString();
+ }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaPrefixOption.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaPrefixOption.java
new file mode 100644
index 0000000..997455d
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6IaPrefixOption.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.onlab.packet.dhcp;
+
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.Data;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
+import org.onlab.packet.IPacket;
+import org.onlab.packet.Ip6Address;
+
+import java.nio.ByteBuffer;
+import java.util.Objects;
+
+/**
+ * IA Address option for DHCPv6.
+ * Based on RFC-3633.
+ */
+public final class Dhcp6IaPrefixOption extends Dhcp6Option {
+ public static final int DEFAULT_LEN = 25;
+
+ private Ip6Address ip6Prefix;
+ private byte prefixLength;
+ private int preferredLifetime;
+ private int validLifetime;
+ private IPacket options;
+
+ @Override
+ public short getCode() {
+ return DHCP6.OptionCode.IAPREFIX.value();
+ }
+
+ @Override
+ public short getLength() {
+ return (short) (options == null ? DEFAULT_LEN : DEFAULT_LEN + options.serialize().length);
+ }
+
+ /**
+ * Sets IPv6 prefix.
+ *
+ * @param ip6Prefix the IPv6 prefix
+ */
+ public void setIp6Prefix(Ip6Address ip6Prefix) {
+ this.ip6Prefix = ip6Prefix;
+ }
+
+ /**
+ * Sets prefix length.
+ *
+ * @param prefixLength the prefix length
+ */
+ public void setPrefixLength(byte prefixLength) {
+ this.prefixLength = prefixLength;
+ }
+
+ /**
+ * Sets preferred lifetime.
+ *
+ * @param preferredLifetime the preferred lifetime
+ */
+ public void setPreferredLifetime(int preferredLifetime) {
+ this.preferredLifetime = preferredLifetime;
+ }
+
+ /**
+ * Sets valid lifetime.
+ *
+ * @param validLifetime the valid lifetime
+ */
+ public void setValidLifetime(int validLifetime) {
+ this.validLifetime = validLifetime;
+ }
+
+ /**
+ * Sets options data.
+ *
+ * @param options the options data
+ */
+ public void setOptions(IPacket options) {
+ this.options = options;
+ }
+
+ /**
+ * Gets IPv6 address.
+ *
+ * @return the IPv6 address
+ */
+ public Ip6Address getIp6Prefix() {
+ return ip6Prefix;
+ }
+
+ /**
+ * Gets prefix length.
+ *
+ * @return the prefix length
+ */
+ public byte getPrefixLength() {
+ return prefixLength;
+ }
+
+ /**
+ * Gets preferred lifetime.
+ *
+ * @return the preferred lifetime
+ */
+ public int getPreferredLifetime() {
+ return preferredLifetime;
+ }
+
+ /**
+ * Gets valid lifetime.
+ *
+ * @return the valid lifetime
+ */
+ public int getValidLifetime() {
+ return validLifetime;
+ }
+
+ /**
+ * Gets options of IA Address option.
+ *
+ * @return the options data
+ */
+ public IPacket getOptions() {
+ return options;
+ }
+
+ public static Deserializer<Dhcp6Option> deserializer() {
+ return (data, offset, length) -> {
+ Dhcp6IaPrefixOption iaPrefixOption = new Dhcp6IaPrefixOption();
+ Dhcp6Option dhcp6Option =
+ Dhcp6Option.deserializer().deserialize(data, offset, length);
+ iaPrefixOption.setPayload(dhcp6Option.getPayload());
+ if (dhcp6Option.getLength() < DEFAULT_LEN) {
+ throw new DeserializationException("Invalid length of IA prefix option");
+ }
+ ByteBuffer bb = ByteBuffer.wrap(dhcp6Option.getData());
+ iaPrefixOption.preferredLifetime = bb.getInt();
+ iaPrefixOption.validLifetime = bb.getInt();
+ iaPrefixOption.prefixLength = bb.get();
+ byte[] ipv6Pref = new byte[Ip6Address.BYTE_LENGTH];
+ bb.get(ipv6Pref);
+ iaPrefixOption.ip6Prefix = Ip6Address.valueOf(ipv6Pref);
+
+ // options length of IA Address option
+ int optionsLen = dhcp6Option.getLength() - DEFAULT_LEN;
+ if (optionsLen > 0) {
+ byte[] optionsData = new byte[optionsLen];
+ bb.get(optionsData);
+ iaPrefixOption.options =
+ Data.deserializer().deserialize(optionsData, 0, optionsLen);
+ }
+ return iaPrefixOption;
+ };
+ }
+
+ @Override
+ public byte[] serialize() {
+ int payloadLen = options == null ? DEFAULT_LEN : DEFAULT_LEN + options.serialize().length;
+ ByteBuffer bb = ByteBuffer.allocate(payloadLen + Dhcp6Option.DEFAULT_LEN);
+ bb.putShort(DHCP6.OptionCode.IAPREFIX.value());
+ bb.putShort((short) payloadLen);
+ bb.putInt(preferredLifetime);
+ bb.putInt(validLifetime);
+ bb.put(prefixLength);
+ bb.put(ip6Prefix.toOctets());
+ if (options != null) {
+ bb.put(options.serialize());
+ }
+ return bb.array();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), ip6Prefix, prefixLength, preferredLifetime,
+ validLifetime, options);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof Dhcp6IaAddressOption)) {
+ return false;
+ }
+ final Dhcp6IaPrefixOption other = (Dhcp6IaPrefixOption) obj;
+
+ return Objects.equals(getCode(), other.getCode()) &&
+ Objects.equals(getLength(), other.getLength()) &&
+ Objects.equals(preferredLifetime, other.preferredLifetime) &&
+ Objects.equals(validLifetime, other.validLifetime) &&
+ Objects.equals(prefixLength, other.prefixLength) &&
+ Objects.equals(ip6Prefix, other.ip6Prefix) &&
+ Objects.equals(options, other.options);
+ }
+
+ @Override
+ public String toString() {
+ return getToStringHelper()
+ .add("preferredLifetime", preferredLifetime)
+ .add("validLifetime", validLifetime)
+ .add("prefixLength", prefixLength)
+ .add("ip6Address", ip6Prefix)
+ .add("options", options)
+ .toString();
+ }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6InterfaceIdOption.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6InterfaceIdOption.java
new file mode 100644
index 0000000..abb8257
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6InterfaceIdOption.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.onlab.packet.dhcp;
+import org.onlab.packet.MacAddress;
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.Deserializer;
+import org.onlab.packet.DeserializationException;
+
+
+import java.nio.ByteBuffer;
+
+/**
+ * Relay option for DHCPv6.
+ * Based on RFC-3315.
+ */
+public final class Dhcp6InterfaceIdOption extends Dhcp6Option {
+ private MacAddress peerMacAddr;
+ private byte[] inPort;
+ @Override
+ public short getCode() {
+ return DHCP6.OptionCode.INTERFACE_ID.value();
+ }
+
+ @Override
+ public short getLength() {
+ return (short) payload.serialize().length;
+ }
+
+ @Override
+ public byte[] getData() {
+ return this.payload.serialize();
+ }
+
+ /**
+ * Default constructor.
+ */
+ public Dhcp6InterfaceIdOption() {
+ }
+
+ /**
+ * Constructs a DHCPv6 relay option with DHCPv6 option.
+ *
+ * @param dhcp6Option the DHCPv6 option
+ */
+ public Dhcp6InterfaceIdOption(Dhcp6Option dhcp6Option) {
+ super(dhcp6Option);
+ }
+
+ /**
+ * Sets MacAddress address.
+ *
+ * @param macAddress the client peer MacAddress
+ */
+ public void setMacAddress(MacAddress macAddress) {
+ this.peerMacAddr = macAddress;
+ }
+
+ /**
+ * Gets Mac address.
+ *
+ * @return the client peer mac address
+ */
+ public MacAddress getMacAddress() {
+ return peerMacAddr;
+ }
+
+ /**
+ * Sets inPort string.
+ *
+ * @param port the port from which client packet is received
+ */
+ public void setInPort(byte[] port) {
+ this.inPort = port;
+ }
+
+ /**
+ * Gets inPort string.
+ *
+ * @return the port from which client packet is received
+ */
+ public byte[] getInPort() {
+ return inPort;
+ }
+
+ /**
+ * Gets deserializer for DHCPv6 relay option.
+ *
+ * @return the deserializer
+ */
+ public static Deserializer<Dhcp6Option> deserializer() {
+ return (data, offset, len) -> {
+ Dhcp6Option dhcp6Option = Dhcp6Option.deserializer().deserialize(data, offset, len);
+ if (dhcp6Option.getLength() < DEFAULT_LEN) {
+ throw new DeserializationException("Invalid InterfaceIoption data");
+ }
+ Dhcp6InterfaceIdOption interfaceIdOption = new Dhcp6InterfaceIdOption(dhcp6Option);
+ byte[] optionData = interfaceIdOption.getData();
+ if (optionData.length >= 28) {
+ ByteBuffer bb = ByteBuffer.wrap(optionData);
+
+ byte[] macAddr = new byte[MacAddress.MAC_ADDRESS_LENGTH];
+ byte[] port = new byte[21];
+ bb.get(macAddr);
+ bb.get(); // separator
+ bb.get(port);
+ interfaceIdOption.setMacAddress(MacAddress.valueOf(macAddr));
+ interfaceIdOption.setInPort(port);
+ }
+ return interfaceIdOption;
+ };
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("code", getCode())
+ .add("length", getLength())
+ .add("data", payload.toString())
+ .toString();
+ }
+}