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 09a118c610..80d29fcb91 100644 --- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerImpl.java +++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerImpl.java @@ -39,8 +39,11 @@ 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.Dhcp6ClientDataOption; +import org.onlab.packet.dhcp.Dhcp6LeaseQueryOption; 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; @@ -102,8 +105,6 @@ import org.slf4j.LoggerFactory; import org.onosproject.net.flow.DefaultTrafficTreatment; import org.onosproject.net.flow.TrafficTreatment; import org.onosproject.dhcprelay.Dhcp6HandlerUtil.InternalPacket; - - import java.nio.ByteBuffer; import java.util.List; import java.util.Collection; @@ -117,6 +118,7 @@ import static org.onosproject.net.flowobjective.Objective.Operation.ADD; import static org.onosproject.net.flowobjective.Objective.Operation.REMOVE; import java.util.concurrent.Semaphore; + @Component @Service @Property(name = "version", value = "6") @@ -139,9 +141,18 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { .matchUdpSrc(TpPort.tpPort(UDP.DHCP_V6_SERVER_PORT)) .matchUdpDst(TpPort.tpPort(UDP.DHCP_V6_SERVER_PORT)) .build(); + // lease query reply is from server to client (no relay in between) - so we need to + // catch that scenario also .. + private static final TrafficSelector LEASE_QUERY_RESPONSE_SELECTOR = DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_IPV6) + .matchIPProtocol(IPv6.PROTOCOL_UDP) + .matchUdpSrc(TpPort.tpPort(UDP.DHCP_V6_SERVER_PORT)) + .matchUdpDst(TpPort.tpPort(UDP.DHCP_V6_CLIENT_PORT)) + .build(); static final Set DHCP_SELECTORS = ImmutableSet.of( CLIENT_SERVER_SELECTOR, - SERVER_RELAY_SELECTOR + SERVER_RELAY_SELECTOR, + LEASE_QUERY_RESPONSE_SELECTOR ); private static Logger log = LoggerFactory.getLogger(Dhcp6HandlerImpl.class); @@ -182,11 +193,8 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { protected ApplicationId appId; protected Multimap ignoredVlans = HashMultimap.create(); private InternalHostListener hostListener = new InternalHostListener(); - private Boolean dhcpFpmEnabled = false; - private Dhcp6HandlerUtil dhcp6HandlerUtil = new Dhcp6HandlerUtil(); - private List defaultServerInfoList = Lists.newArrayList(); private List indirectServerInfoList = Lists.newArrayList(); private class IpAddressInfo { @@ -211,10 +219,12 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { DHCP6.MsgType.RELEASE.value(), DHCP6.MsgType.DECLINE.value(), DHCP6.MsgType.CONFIRM.value(), - DHCP6.MsgType.RELAY_FORW.value()); + DHCP6.MsgType.RELAY_FORW.value(), + DHCP6.MsgType.LEASEQUERY.value()); // SERVER message types public static final Set MSG_TYPE_FROM_SERVER = - ImmutableSet.of(DHCP6.MsgType.RELAY_REPL.value()); + ImmutableSet.of(DHCP6.MsgType.RELAY_REPL.value(), + DHCP6.MsgType.LEASEQUERY_REPLY.value()); @Activate protected void activate() { @@ -267,7 +277,6 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { } processIgnoreVlanRule(deviceId, vlanId, ADD); }); - ignoredVlans.forEach((deviceId, vlanId) -> { if (!config.ignoredVlans().get(deviceId).contains(vlanId)) { // not contains in new config, remove it @@ -287,6 +296,160 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { }); } + public DhcpRecord getDhcpRelayRecordFor(Ip6Address clientAddress) { + + Collection records = dhcpRelayStore.getDhcpRecords(); + DhcpRecord dr = null; + for (DhcpRecord e:records) { + if (e.ip6Address().isPresent()) { + if (e.ip6Address().get().equals(clientAddress)) { + dr = e; + break; + } + } + } + return dr; + } + + public MacAddress findNextHopMacForIp6FromRelayStore(Ip6Address clientAddress, + MacAddress clientMacAddress, VlanId vlanID) { + + DhcpRecord dr = getDhcpRelayRecordFor(clientAddress); + + if (dr != null) { + Optional nextHopTempMac = dr.nextHopTemp(); + if (nextHopTempMac.isPresent()) { + log.info("findNextHopForIp6FromRelayStore " + clientAddress + " got mac " + nextHopTempMac.toString()); + return nextHopTempMac.get(); + } + } else { + log.warn("findNextHopMacForIp6FromRelayStore could NOT find next hop for " + clientAddress); + return null; + } + return null; + } + + public Ip6Address findNextHopIp6FromRelayStore(Ip6Address clientAddress) { + + DhcpRecord dr = getDhcpRelayRecordFor(clientAddress); + if (dr != null) { + Optional nextHopMac = dr.nextHop(); + if (nextHopMac.isPresent()) { + // find the local ip6 from the host store + HostId gwHostId = HostId.hostId(nextHopMac.get(), dr.vlanId()); + Host gwHost = hostService.getHost(gwHostId); + if (gwHost == null) { + log.warn("Can't find next hop host ID {}", gwHostId); + return null; + } + Ip6Address nextHopIp = gwHost.ipAddresses() + .stream() + .filter(IpAddress::isIp6) + .filter(IpAddress::isLinkLocal) + .map(IpAddress::getIp6Address) + .findFirst() + .orElse(null); + + log.info("findNextHopIp6FromRelayStore " + clientAddress + " got mac " + + nextHopMac.toString() + " ip6 " + nextHopIp); + return nextHopIp; + } + } else { + log.warn("findNextHopIp6FromRelayStore could NOT find next hop for " + clientAddress); + return null; + } + return null; + } + + private void setPotentialNextHopForIp6InRelayStore(Ip6Address clientAddress, + VlanId vlanId, MacAddress nh) { + DhcpRecord dr = getDhcpRelayRecordFor(clientAddress); + if (dr != null) { + dr.nextHopTemp(nh); + log.debug("LQ6 potential NH mac " + nh.toString() + " UPDATED in RelayRecord client " + clientAddress); + } else { + log.warn("LQ6 potential NH mac" + nh.toString() + + " NOT FOUND in RelayRecord for client - LQ rejected" + clientAddress); + } + } + + public void handleLeaseQuery6ReplyMsg(PacketContext context, DHCP6 dhcp6Payload) { + ConnectPoint inPort = context.inPacket().receivedFrom(); + log.info("Got LQV6-REPLY on port {}", inPort); + List lopt = dhcp6Payload.getOptions(); + log.info("Options list: {}", lopt); + // find out if this lease is known is + Dhcp6ClientDataOption clientDataOption = dhcp6Payload.getOptions() + .stream() + .filter(opt -> opt instanceof Dhcp6ClientDataOption) + .map(pld -> (Dhcp6ClientDataOption) pld) + .findFirst() + .orElse(null); + + if (clientDataOption == null) { + log.warn("clientDataOption option is not present, " + + "lease is UNKNOWN - not adding any new route..."); + } else { + Dhcp6IaAddressOption aiAddressOption = clientDataOption.getOptions() + .stream() + .filter(opt -> opt instanceof Dhcp6IaAddressOption) + .map(pld -> (Dhcp6IaAddressOption) pld) + .findFirst() + .orElse(null); + + Dhcp6ClientIdOption clientIdOption = clientDataOption.getOptions() + .stream() + .filter(opt -> opt instanceof Dhcp6ClientIdOption) + .map(pld -> (Dhcp6ClientIdOption) pld) + .findFirst() + .orElse(null); + + if (aiAddressOption == null) { + log.warn("clientDataOption from DHCP server does not " + + "contains Dhcp6IaAddressOption for the client - giving up..."); + } else { + Ip6Address clientAddress = aiAddressOption.getIp6Address(); + MacAddress clientMacAddress = MacAddress.valueOf(clientIdOption.getDuid().getLinkLayerAddress()); + Ethernet packet = context.inPacket().parsed(); + VlanId vlanId = VlanId.vlanId(packet.getVlanID()); + MacAddress potentialNextHopMac = + findNextHopMacForIp6FromRelayStore(clientAddress, clientMacAddress, vlanId); + + if (potentialNextHopMac == null) { + log.warn("Can't find next hop host mac for client {} mac:{}/{}", + clientAddress, clientMacAddress, vlanId); + return; + } else { + log.info("Next hop mac for {}/{}/{} is {}", clientAddress, + clientMacAddress, vlanId, potentialNextHopMac.toString()); + } + // search the next hop in the hosts store + HostId gwHostId = HostId.hostId(potentialNextHopMac, vlanId); + Host gwHost = hostService.getHost(gwHostId); + if (gwHost == null) { + log.warn("Can't find next hop host ID {}", gwHostId); + return; + } + Ip6Address nextHopIp = gwHost.ipAddresses() + .stream() + .filter(IpAddress::isIp6) + .filter(IpAddress::isLinkLocal) + .map(IpAddress::getIp6Address) + .findFirst() + .orElse(null); + if (nextHopIp == null) { + log.warn("Can't find IP6 address of next hop {}", gwHost); + return; + } + log.info("client " + clientAddress + " is known !"); + Route routeForIP6 = new Route(Route.Source.STATIC, clientAddress.toIpPrefix(), nextHopIp); + log.debug("updating route of Client for indirectly connected."); + log.debug("client ip: " + clientAddress + ", next hop ip6: " + nextHopIp); + routeStore.updateRoute(routeForIP6); + } + } + } + @Override public void processDhcpPacket(PacketContext context, BasePacket payload) { checkNotNull(payload, "DHCP6 payload can't be null"); @@ -295,7 +458,8 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { Ethernet receivedPacket = context.inPacket().parsed(); if (!configured()) { - log.warn("Missing DHCP6 relay server config. Abort packet processing dhcp6 payload {}", dhcp6Payload); + log.warn("Missing DHCP6 relay server config. " + + "Abort packet processing dhcp6 payload {}", dhcp6Payload); return; } byte msgTypeVal = dhcp6Payload.getMsgType(); @@ -314,22 +478,47 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { return; } - if (MSG_TYPE_FROM_CLIENT.contains(msgTypeVal)) { - + if (msgTypeVal == DHCP6.MsgType.LEASEQUERY.value()) { List ethernetClientPacket = - processDhcp6PacketFromClient(context, receivedPacket, receivingInterfaces); + processLQ6PacketFromClient(context, receivedPacket, receivingInterfaces, dhcp6Payload); for (InternalPacket internalPacket : ethernetClientPacket) { forwardPacket(internalPacket); } - } else if (MSG_TYPE_FROM_SERVER.contains(msgTypeVal)) { - log.debug("calling processDhcp6PacketFromServer with RELAY_REPL {}", msgTypeVal); + } else if (msgTypeVal == DHCP6.MsgType.LEASEQUERY_REPLY.value()) { + + IPv6 clientIpv6 = (IPv6) receivedPacket.getPayload(); + UDP clientUdp = (UDP) clientIpv6.getPayload(); + DHCP6 clientDhcp6 = (DHCP6) clientUdp.getPayload(); + Interface serverInterface = dhcp6HandlerUtil.directlyConnected(clientDhcp6) ? + getServerInterface() : getIndirectServerInterface(); InternalPacket ethernetPacketReply = - processDhcp6PacketFromServer(context, receivedPacket, receivingInterfaces); + dhcp6HandlerUtil.processLQ6PacketFromServer( + defaultServerInfoList, indirectServerInfoList, + serverInterface, interfaceService, + hostService, + context, receivedPacket, receivingInterfaces); if (ethernetPacketReply != null) { forwardPacket(ethernetPacketReply); } + handleLeaseQuery6ReplyMsg(context, dhcp6Payload); } else { - log.warn("Not so fast, packet type {} not supported yet", msgTypeVal); + if (MSG_TYPE_FROM_CLIENT.contains(msgTypeVal)) { + + List ethernetClientPacket = + processDhcp6PacketFromClient(context, receivedPacket, receivingInterfaces); + for (InternalPacket internalPacket : ethernetClientPacket) { + forwardPacket(internalPacket); + } + } else if (MSG_TYPE_FROM_SERVER.contains(msgTypeVal)) { + log.debug("calling processDhcp6PacketFromServer with RELAY_REPL {}", msgTypeVal); + InternalPacket ethernetPacketReply = + processDhcp6PacketFromServer(context, receivedPacket, receivingInterfaces); + if (ethernetPacketReply != null) { + forwardPacket(ethernetPacketReply); + } + } else { + log.warn("Not so fast, packet type {} not supported yet", msgTypeVal); + } } } @@ -352,8 +541,6 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { // Do nothing here } - - //forward the packet to ConnectPoint where the DHCP server is attached. private void forwardPacket(InternalPacket packet) { //send Packetout to dhcp server connectpoint. @@ -369,9 +556,6 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { } // if } - - - /** * extract from dhcp6 packet client ipv6 address of given by dhcp server. * @@ -422,6 +606,7 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { } return ipInfo; } + /** * extract from dhcp6 packet Prefix prefix provided by dhcp server. * @@ -500,6 +685,7 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { return clientIdOption; } + /** * remove host or route and update dhcp relay record attributes. * @@ -661,6 +847,7 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { * @param embeddedDhcp6 the dhcp6 payload within relay * @param srcMac client gw/host macAddress * @param clientInterface client interface + * @param dhcpServerIp6Address DHCP server IP */ private void addHostOrRoute(boolean directConnFlag, ConnectPoint location, DHCP6 dhcp6Relay, DHCP6 embeddedDhcp6, MacAddress srcMac, Interface clientInterface) { @@ -774,11 +961,11 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { if (leafMsgType == DHCP6.MsgType.REPLY.value()) { if (ipInfo != null) { log.debug("IP6 address is being stored into dhcp-relay store."); - log.debug("IP6 address {}", HexString.toHexString(ipInfo.ip6Address.toOctets(), ":")); + log.debug("Client IP6 address {}", HexString.toHexString(ipInfo.ip6Address.toOctets(), ":")); record.ip6Address(ipInfo.ip6Address); record.updateAddrPrefTime(ipInfo.prefTime); record.updateLastIp6Update(); - } else { + } else { log.debug("IP6 address is not returned from server. Maybe only PD is returned."); } if (pdInfo != null) { @@ -811,6 +998,103 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { } } + private List processLQ6PacketFromClient(PacketContext context, + Ethernet clientPacket, + Set clientInterfaces, + DHCP6 dhcp6Payload) { + ConnectPoint inPort = context.inPacket().receivedFrom(); + log.info("Got LQ-REQUEST V6 on port {}", inPort); + List lopt = dhcp6Payload.getOptions(); + log.info("Options list: {}", lopt); + Dhcp6LeaseQueryOption lqoption = dhcp6Payload.getOptions() + .stream() + .filter(opt -> opt instanceof Dhcp6LeaseQueryOption) + .map(pld -> (Dhcp6LeaseQueryOption) pld) + .findFirst() + .orElse(null); + + if (lqoption == null) { + // Can't find dhcp payload + log.warn("Can't find dhcp6 lease query message - aborting"); + return null; + } else { + log.info("dhcp6 lqv6 options found: {}", lqoption); + } + log.warn("LQv6 for " + lqoption.linkAddress.toString() + " comes from " + inPort.toString()); + Ethernet packet = context.inPacket().parsed(); + Ip6Address clientAddress = lqoption.linkAddress; + IPv6 ipv6Packet = (IPv6) packet.getPayload(); + Ip6Address nextHopIp = findNextHopIp6FromRelayStore(clientAddress); + + // 1. only if there is a route to remove - remove it + if (nextHopIp != null) { + Route routeForIP6 = new Route(Route.Source.STATIC, clientAddress.toIpPrefix(), nextHopIp); + log.debug("Removing route of Client " + clientAddress + + " for indirectly connected - next hop ip6 " + nextHopIp); + routeStore.removeRoute(routeForIP6); + } + + // 2. note the potential NH this packet came from in case it's a known lease + // this NH will then be used to build the route + MacAddress potentialNH = packet.getSourceMAC(); + VlanId vlanId = VlanId.vlanId(packet.getVlanID()); + setPotentialNextHopForIp6InRelayStore(clientAddress, vlanId, potentialNH); + + // 3. route this LQ6 to all relevant servers + IPv6 clientIpv6 = (IPv6) clientPacket.getPayload(); + UDP clientUdp = (UDP) clientIpv6.getPayload(); + DHCP6 clientDhcp6 = (DHCP6) clientUdp.getPayload(); + + boolean directConnFlag = dhcp6HandlerUtil.directlyConnected(clientDhcp6); + + ConnectPoint clientConnectionPoint = context.inPacket().receivedFrom(); + VlanId vlanIdInUse = VlanId.vlanId(clientPacket.getVlanID()); + Interface clientInterface = interfaceService.getInterfacesByPort(clientConnectionPoint) + .stream().filter(iface -> dhcp6HandlerUtil.interfaceContainsVlan(iface, vlanIdInUse)) + .findFirst() + .orElse(null); + + List internalPackets = new ArrayList<>(); + List serverInfoList = findValidServerInfo(directConnFlag); + List copyServerInfoList = new ArrayList(serverInfoList); + + for (DhcpServerInfo serverInfo : copyServerInfoList) { + if (!dhcp6HandlerUtil.checkDhcpServerConnPt(directConnFlag, serverInfo)) { + log.warn("Can't get server connect point, ignore"); + continue; + } + DhcpServerInfo newServerInfo = getHostInfoForServerInfo(serverInfo, serverInfoList); + if (newServerInfo == null) { + log.warn("Can't get server interface with host info resolved, ignore"); + continue; + } + + Interface serverInterface = getServerInterface(newServerInfo); + if (serverInterface == null) { + log.warn("Can't get server interface, ignore"); + continue; + } + + + Ethernet etherRouted = (Ethernet) clientPacket.clone(); + MacAddress macFacingServer = serverInterface.mac(); + if (macFacingServer == null) { + log.warn("No MAC address for server Interface {}", serverInterface); + return null; + } + etherRouted.setSourceMACAddress(macFacingServer); + etherRouted.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get()); + InternalPacket internalPacket = + new Dhcp6HandlerUtil().new InternalPacket(etherRouted, + serverInfo.getDhcpServerConnectPoint().get()); + internalPackets.add(internalPacket); + log.debug("Sending LQ to DHCP server {}", newServerInfo.getDhcpServerIp6()); + } + log.debug("num of client packets to send is{}", internalPackets.size()); + + return internalPackets; + } + /** * build the DHCP6 solicit/request packet with gatewayip. * @@ -980,7 +1264,9 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { udpPacket.setDestinationPort(UDP.DHCP_V6_SERVER_PORT); } // add host or route - addHostOrRoute(directConnFlag, clientConnectionPoint, dhcp6Relay, embeddedDhcp6, clientMac, clientInterface); + addHostOrRoute(directConnFlag, clientConnectionPoint, dhcp6Relay, embeddedDhcp6, + clientMac, clientInterface); + udpPacket.setPayload(embeddedDhcp6); udpPacket.resetChecksum(); @@ -989,6 +1275,19 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { return new Dhcp6HandlerUtil().new InternalPacket(etherReply, clientConnectionPoint); } + // Returns the first v6 interface ip out of a set of interfaces or null. + // Checks all interfaces, and ignores v6 interface ips + private Ip6Address getRelayAgentIPv6Address(Set 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 setDhcpFpmEnabled(Boolean enabled) { @@ -1131,7 +1430,6 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { } } } - /** * Handle host removed. * If the host is DHCP server or gateway, unset connect mac and vlan. @@ -1181,6 +1479,65 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { .findFirst() .orElse(null); } + + /** + * Gets Interface facing to the server for default host. + * + * @return the Interface facing to the server; null if not found + */ + private Interface getServerInterface() { + DhcpServerInfo serverInfo; + ConnectPoint dhcpServerConnectPoint; + VlanId dhcpConnectVlan; + + if (!defaultServerInfoList.isEmpty()) { + serverInfo = defaultServerInfoList.get(0); + dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null); + dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null); + } else { + return null; + } + if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) { + log.info("Default DHCP server {} not resolve yet", serverInfo.getDhcpGatewayIp6()); + return null; + } + return interfaceService.getInterfacesByPort(dhcpServerConnectPoint) + .stream() + .filter(iface -> dhcp6HandlerUtil.interfaceContainsVlan(iface, dhcpConnectVlan)) + .findFirst() + .orElse(null); + } + + /** + * Gets Interface facing to the server for indirect hosts. + * Use default server Interface if indirect server not configured. + * + * @return the Interface facing to the server; null if not found + */ + private Interface getIndirectServerInterface() { + DhcpServerInfo serverInfo; + + ConnectPoint indirectDhcpServerConnectPoint; + VlanId indirectDhcpConnectVlan; + + if (!indirectServerInfoList.isEmpty()) { + serverInfo = indirectServerInfoList.get(0); + indirectDhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null); + indirectDhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null); + } else { + return getServerInterface(); + } + if (indirectDhcpServerConnectPoint == null || indirectDhcpConnectVlan == null) { + log.info("Indirect DHCP server {} not resolve yet", serverInfo.getDhcpGatewayIp6()); + return null; + } + return interfaceService.getInterfacesByPort(indirectDhcpServerConnectPoint) + .stream() + .filter(iface -> dhcp6HandlerUtil.interfaceContainsVlan(iface, indirectDhcpConnectVlan)) + .findFirst() + .orElse(null); + } + /** * Checks if serverInfo's host info (mac and vlan) is filled in; if not, fills in. * @@ -1256,15 +1613,16 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { return serverInterface; } - private void requestDhcpPacket(Ip6Address serverIp) { requestServerDhcpPacket(serverIp); requestClientDhcpPacket(serverIp); + requestServerLQPacket(serverIp); } private void cancelDhcpPacket(Ip6Address serverIp) { cancelServerDhcpPacket(serverIp); cancelClientDhcpPacket(serverIp); + cancelServerLQPacket(serverIp); } private void cancelServerDhcpPacket(Ip6Address serverIp) { @@ -1277,6 +1635,16 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { appId); } + private void cancelServerLQPacket(Ip6Address serverIp) { + TrafficSelector serverSelector = + DefaultTrafficSelector.builder(LEASE_QUERY_RESPONSE_SELECTOR) + .matchIPv6Src(serverIp.toIpPrefix()) + .build(); + packetService.cancelPackets(serverSelector, + PacketPriority.CONTROL, + appId); + } + private void requestServerDhcpPacket(Ip6Address serverIp) { TrafficSelector serverSelector = DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR) @@ -1287,6 +1655,16 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { appId); } + private void requestServerLQPacket(Ip6Address serverIp) { + TrafficSelector serverSelector = + DefaultTrafficSelector.builder(LEASE_QUERY_RESPONSE_SELECTOR) + .matchIPv6Src(serverIp.toIpPrefix()) + .build(); + packetService.requestPackets(serverSelector, + PacketPriority.CONTROL, + appId); + } + private void cancelClientDhcpPacket(Ip6Address serverIp) { // Packet comes from relay TrafficSelector indirectClientSelector = @@ -1419,6 +1797,7 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { flowObjectiveService.apply(deviceId, fwd); }); } + /** * Find first ipaddress for a given Host info i.e. mac and vlan. * @@ -1481,6 +1860,7 @@ public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider { } return foundServerInfo; } + /** * Set the dhcp6 lease expiry poll interval value. * diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerUtil.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerUtil.java index 6e47a0daac..17456f98b0 100644 --- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerUtil.java +++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerUtil.java @@ -44,8 +44,11 @@ import org.slf4j.LoggerFactory; import java.util.Set; import java.util.List; import java.util.ArrayList; +import org.onosproject.net.intf.InterfaceService; - +import org.onosproject.net.Host; +import org.onosproject.net.host.HostService; +import org.onosproject.net.HostLocation; import static com.google.common.base.Preconditions.checkNotNull; @@ -68,6 +71,127 @@ public class Dhcp6HandlerUtil { return null; } + /** + * + * process the LQ reply packet from dhcp server. + * + * @param defaultServerInfoList default server list + * @param indirectServerInfoList default indirect server list + * @param serverInterface server interface + * @param interfaceService interface service + * @param hostService host service + * @param context packet context + * @param receivedPacket server ethernet packet + * @param recevingInterfaces set of server side interfaces + * @return a packet ready to be sent to relevant output interface + */ + public InternalPacket processLQ6PacketFromServer( + List defaultServerInfoList, + List indirectServerInfoList, + Interface serverInterface, + InterfaceService interfaceService, + HostService hostService, + PacketContext context, + Ethernet receivedPacket, Set recevingInterfaces) { + // get dhcp6 header. + Ethernet etherReply = (Ethernet) receivedPacket.clone(); + IPv6 ipv6Packet = (IPv6) etherReply.getPayload(); + UDP udpPacket = (UDP) ipv6Packet.getPayload(); + DHCP6 lq6Reply = (DHCP6) udpPacket.getPayload(); + + // TODO: refactor + ConnectPoint receivedFrom = context.inPacket().receivedFrom(); + DeviceId receivedFromDevice = receivedFrom.deviceId(); + DhcpServerInfo serverInfo; + Ip6Address dhcpServerIp = null; + ConnectPoint dhcpServerConnectPoint = null; + MacAddress dhcpConnectMac = null; + VlanId dhcpConnectVlan = null; + Ip6Address dhcpGatewayIp = null; + + // todo: refactor + Ip6Address indirectDhcpServerIp = null; + ConnectPoint indirectDhcpServerConnectPoint = null; + MacAddress indirectDhcpConnectMac = null; + VlanId indirectDhcpConnectVlan = null; + Ip6Address indirectDhcpGatewayIp = null; + Ip6Address indirectRelayAgentIpFromCfg = null; + + if (!defaultServerInfoList.isEmpty()) { + serverInfo = defaultServerInfoList.get(0); + dhcpConnectMac = serverInfo.getDhcpConnectMac().orElse(null); + dhcpGatewayIp = serverInfo.getDhcpGatewayIp6().orElse(null); + dhcpServerIp = serverInfo.getDhcpServerIp6().orElse(null); + dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null); + dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null); + } + + if (!indirectServerInfoList.isEmpty()) { + serverInfo = indirectServerInfoList.get(0); + indirectDhcpConnectMac = serverInfo.getDhcpConnectMac().orElse(null); + indirectDhcpGatewayIp = serverInfo.getDhcpGatewayIp6().orElse(null); + indirectDhcpServerIp = serverInfo.getDhcpServerIp6().orElse(null); + indirectDhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null); + indirectDhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null); + indirectRelayAgentIpFromCfg = serverInfo.getRelayAgentIp6(receivedFromDevice).orElse(null); + } + + Boolean directConnFlag = directlyConnected(lq6Reply); + ConnectPoint inPort = context.inPacket().receivedFrom(); + if ((directConnFlag || (!directConnFlag && indirectDhcpServerIp == null)) + && !inPort.equals(dhcpServerConnectPoint)) { + log.warn("Receiving port {} is not the same as server connect point {} for direct or indirect-null", + inPort, dhcpServerConnectPoint); + return null; + } + + if (!directConnFlag && indirectDhcpServerIp != null && + !inPort.equals(indirectDhcpServerConnectPoint)) { + log.warn("Receiving port {} is not the same as server connect point {} for indirect", + inPort, indirectDhcpServerConnectPoint); + return null; + } + + + Ip6Address nextHopIP = Ip6Address.valueOf(ipv6Packet.getDestinationAddress()); + // use hosts store to find out the next hop mac and connection point + Set hosts = hostService.getHostsByIp(nextHopIP); + Host host; + if (!hosts.isEmpty()) { + host = hosts.iterator().next(); + } else { + log.warn("Host {} is not in store", nextHopIP); + return null; + } + + HostLocation hl = host.location(); + String clientConnectionPointStr = hl.toString(); // iterator().next()); + ConnectPoint clientConnectionPoint = ConnectPoint.deviceConnectPoint(clientConnectionPointStr); + + + VlanId originalPacketVlanId = VlanId.vlanId(etherReply.getVlanID()); + Interface iface; + iface = interfaceService.getInterfacesByPort(clientConnectionPoint) + .stream() + .filter(iface1 -> interfaceContainsVlan(iface1, originalPacketVlanId)) + .findFirst() + .orElse(null); + + etherReply.setSourceMACAddress(iface.mac()); + etherReply.setDestinationMACAddress(host.mac()); + + + // add host or route + //addHostOrRoute(directConnFlag, clientConnectionPoint, lq6Reply, embeddedDhcp6, clientMac, clientInterface); + // workaround for a bug where core sends src port as 547 (server) + udpPacket.setDestinationPort(UDP.DHCP_V6_SERVER_PORT); + udpPacket.setPayload(lq6Reply); + udpPacket.resetChecksum(); + ipv6Packet.setPayload(udpPacket); + etherReply.setPayload(ipv6Packet); + + return new Dhcp6HandlerUtil().new InternalPacket(etherReply, clientConnectionPoint); + } /** * Returns the first interface ip from interface. * @@ -258,7 +382,14 @@ public class Dhcp6HandlerUtil { * @return true if the host is directly connected to the network; false otherwise */ public boolean directlyConnected(DHCP6 dhcp6Payload) { + log.debug("directlyConnected enters"); + if (dhcp6Payload.getMsgType() == DHCP6.MsgType.LEASEQUERY.value() || + dhcp6Payload.getMsgType() == DHCP6.MsgType.LEASEQUERY_REPLY.value()) { + log.debug("directlyConnected false. MsgType {}", dhcp6Payload.getMsgType()); + + return false; + } if (dhcp6Payload.getMsgType() != DHCP6.MsgType.RELAY_FORW.value() && dhcp6Payload.getMsgType() != DHCP6.MsgType.RELAY_REPL.value()) { diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DhcpRecord.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DhcpRecord.java index 44c6c3c665..e4c26736f5 100644 --- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DhcpRecord.java +++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/store/DhcpRecord.java @@ -53,7 +53,6 @@ public class DhcpRecord { private IpPrefix pdPrefix; private DHCP6.MsgType ip6Status; - private long lastSeen; private long lastIp6Update; private long lastPdUpdate; @@ -418,7 +417,6 @@ public class DhcpRecord { newRecord.addrPrefTime = addrPrefTime; newRecord.pdPrefTime = pdPrefTime; newRecord.v6Counters = v6Counters; - return newRecord; } 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 8dbd1dca0d..50b11a16c4 100644 --- a/utils/misc/src/main/java/org/onlab/packet/DHCP6.java +++ b/utils/misc/src/main/java/org/onlab/packet/DHCP6.java @@ -19,11 +19,13 @@ package org.onlab.packet; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; +import org.onlab.packet.dhcp.Dhcp6ClientDataOption; 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.Dhcp6IaPdOption; +import org.onlab.packet.dhcp.Dhcp6LeaseQueryOption; import org.onlab.packet.dhcp.Dhcp6Option; import org.onlab.packet.dhcp.Dhcp6RelayOption; import org.onlab.packet.dhcp.Dhcp6InterfaceIdOption; @@ -60,7 +62,12 @@ public class DHCP6 extends BasePacket { // Relay message types public static final Set RELAY_MSG_TYPES = ImmutableSet.of(MsgType.RELAY_FORW.value, - MsgType.RELAY_REPL.value); + MsgType.RELAY_REPL.value + ); + public static final Set LEASEQUERY_MSG_TYPES = + ImmutableSet.of(MsgType.LEASEQUERY.value, + MsgType.LEASEQUERY_REPLY.value + ); /** * DHCPv6 message type. @@ -70,7 +77,8 @@ public class DHCP6 extends BasePacket { CONFIRM((byte) 4), RENEW((byte) 5), REBIND((byte) 6), REPLY((byte) 7), RELEASE((byte) 8), DECLINE((byte) 9), RECONFIGURE((byte) 10), INFORMATION_REQUEST((byte) 11), - RELAY_FORW((byte) 12), RELAY_REPL((byte) 13); + RELAY_FORW((byte) 12), RELAY_REPL((byte) 13), LEASEQUERY((byte) 14), + LEASEQUERY_REPLY((byte) 15); protected byte value; MsgType(final byte value) { @@ -107,6 +115,10 @@ public class DHCP6 extends BasePacket { return RELAY_FORW; case 13: return RELAY_REPL; + case 14: + return LEASEQUERY; + case 15: + return LEASEQUERY_REPLY; default: return null; } @@ -155,7 +167,8 @@ public class DHCP6 extends BasePacket { 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), IA_PD((short) 25), IAPREFIX((short) 26), - SUBSCRIBER_ID((short) 38); + SUBSCRIBER_ID((short) 38), OPTION_ERO((short) 43), LEASE_QUERY((short) 44), + CLIENT_DATA((short) 45), CLIENT_LT((short) 48); protected short value; OptionCode(final short value) { @@ -175,6 +188,8 @@ public class DHCP6 extends BasePacket { .put(OptionCode.CLIENTID.value, Dhcp6ClientIdOption.deserializer()) .put(OptionCode.IA_PD.value, Dhcp6IaPdOption.deserializer()) .put(OptionCode.INTERFACE_ID.value, Dhcp6InterfaceIdOption.deserializer()) + .put(OptionCode.LEASE_QUERY.value, Dhcp6LeaseQueryOption.deserializer()) + .put(OptionCode.CLIENT_DATA.value, Dhcp6ClientDataOption.deserializer()) .build(); // general field diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6CLTOption.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6CLTOption.java new file mode 100644 index 0000000000..c509ee3d62 --- /dev/null +++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6CLTOption.java @@ -0,0 +1,141 @@ +/* + * 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.DeserializationException; +import org.onlab.packet.Deserializer; + +import java.nio.ByteBuffer; +import java.util.Objects; + +public final class Dhcp6CLTOption extends Dhcp6Option { + public static final int DEFAULT_LEN = 4; + private int clt; // client last transaction time + + @Override + public short getCode() { + return DHCP6.OptionCode.CLIENT_LT.value(); + } + + @Override + public short getLength() { + return (short) (DEFAULT_LEN); + } + + /** + * Gets Client Last Transaction Time. + * + * @return Client Last Transaction Time + */ + public int getClt() { + return clt; + } + + /** + * Sets Identity Association ID. + * + * @param clt the Client Last Transaction Time. + */ + public void setClt(int clt) { + this.clt = clt; + } + + + /** + * Default constructor. + */ + public Dhcp6CLTOption() { + } + + /** + * Constructs a DHCPv6 Client Last Transaction Time option. + * + * @param dhcp6Option the DHCPv6 option + */ + public Dhcp6CLTOption(Dhcp6Option dhcp6Option) { + super(dhcp6Option); + } + + /** + * Gets deserializer. + * + * @return the deserializer + */ + public static Deserializer deserializer() { + return (data, offset, length) -> { + Dhcp6Option dhcp6Option = + Dhcp6Option.deserializer().deserialize(data, offset, length); + if (dhcp6Option.getLength() < DEFAULT_LEN) { + throw new DeserializationException("Invalid CLT option data"); + } + Dhcp6CLTOption cltOption = new Dhcp6CLTOption(dhcp6Option); + byte[] optionData = cltOption.getData(); + ByteBuffer bb = ByteBuffer.wrap(optionData); + cltOption.clt = bb.getInt(); + + return cltOption; + }; + } + + @Override + public byte[] serialize() { + int payloadLen = DEFAULT_LEN; + int len = Dhcp6Option.DEFAULT_LEN + payloadLen; + ByteBuffer bb = ByteBuffer.allocate(len); + bb.putShort(DHCP6.OptionCode.CLIENT_LT.value()); + bb.putShort((short) payloadLen); + bb.putInt(clt); + return bb.array(); + } + + + @Override + public String toString() { + return getToStringHelper() + .add("clt", clt) + .toString(); + } + + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), clt); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Dhcp6CLTOption)) { + return false; + } + if (!super.equals(obj)) { + return false; + } + final Dhcp6CLTOption other = (Dhcp6CLTOption) obj; + + return Objects.equals(getCode(), other.getCode()) && + Objects.equals(getLength(), other.getLength()) && + Objects.equals(clt, other.clt); + } +} diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6ClientDataOption.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6ClientDataOption.java new file mode 100644 index 0000000000..01b4cc3bc9 --- /dev/null +++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6ClientDataOption.java @@ -0,0 +1,161 @@ +/* + * 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.base.MoreObjects; +import com.google.common.collect.Lists; +import org.onlab.packet.DHCP6; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Deserializer; +import org.onlab.packet.Ip6Address; + +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Objects; + +/** + * DHCPv6 Client Data Option. + */ +public final class Dhcp6ClientDataOption extends Dhcp6Option { + private List options; + private Ip6Address clientIaAddress; + public static final int DEFAULT_LEN = 1 + 16; + + public Dhcp6ClientDataOption(Dhcp6Option dhcp6Option) { + super(dhcp6Option); + } + + @Override + public short getCode() { + return DHCP6.OptionCode.CLIENT_DATA.value(); + } + + @Override + public short getLength() { + //return (short) (DEFAULT_LEN + options.stream() + // .mapToInt(opt -> (int) opt.getLength() + Dhcp6Option.DEFAULT_LEN) + // .sum()); + return (short) payload.serialize().length; + } + + @Override + public byte[] getData() { + return payload.serialize(); + } + + public List getOptions() { + return options; + } + + public Ip6Address getIaAddress() { + return clientIaAddress; + } + + public static Deserializer deserializer() { + return (data, offset, length) -> { + Dhcp6Option dhcp6Option = Dhcp6Option.deserializer().deserialize(data, offset, length); + Dhcp6ClientDataOption clientData = new Dhcp6ClientDataOption(dhcp6Option); + + if (dhcp6Option.getLength() < DEFAULT_LEN) { + throw new DeserializationException("Invalid length of Client Id option"); + } + + byte[] optionData = clientData.getData(); + + clientData.options = Lists.newArrayList(); + + ByteBuffer bb = ByteBuffer.wrap(optionData); + + 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.IAADDR.value()) { + option = Dhcp6IaAddressOption.deserializer() + .deserialize(subOptData, 0, subOptData.length); + clientData.clientIaAddress = ((Dhcp6IaAddressOption) option).getIp6Address(); + } else if (code == DHCP6.OptionCode.CLIENTID.value()) { + option = Dhcp6ClientIdOption.deserializer() + .deserialize(subOptData, 0, subOptData.length); + } else if (code == DHCP6.OptionCode.CLIENT_LT.value()) { + option = Dhcp6CLTOption.deserializer() + .deserialize(subOptData, 0, subOptData.length); + } else { + option = Dhcp6Option.deserializer() + .deserialize(subOptData, 0, subOptData.length); + } + clientData.options.add(option); + } + return clientData; + }; + } + + @Override + public byte[] serialize() { + ByteBuffer bb = ByteBuffer.allocate(this.getLength() + Dhcp6Option.DEFAULT_LEN); + bb.putShort(getCode()); + bb.putShort(getLength()); + bb.put(payload.serialize()); + return bb.array(); + } + + + @Override + public String toString() { + return MoreObjects.toStringHelper(getClass()) + .add("code", getCode()) + .add("length", getLength()) + .add("clientAddr", getIaAddress()) + .toString(); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), clientIaAddress, options); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Dhcp6ClientDataOption)) { + return false; + } + if (!super.equals(obj)) { + return false; + } + final Dhcp6ClientDataOption other = (Dhcp6ClientDataOption) obj; + + return Objects.equals(getCode(), other.getCode()) && + Objects.equals(getLength(), other.getLength()) && + Objects.equals(clientIaAddress, other.clientIaAddress) && + Objects.equals(options, other.options); + } +} diff --git a/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6LeaseQueryOption.java b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6LeaseQueryOption.java new file mode 100644 index 0000000000..5bcc8bad77 --- /dev/null +++ b/utils/misc/src/main/java/org/onlab/packet/dhcp/Dhcp6LeaseQueryOption.java @@ -0,0 +1,153 @@ +/* + * 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.base.MoreObjects; +import com.google.common.collect.Lists; +import org.onlab.packet.DHCP6; +import org.onlab.packet.Deserializer; +import org.onlab.packet.Ip6Address; + +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Objects; + +/** + * DHCPv6 Lease Query Option. + */ +public final class Dhcp6LeaseQueryOption extends Dhcp6Option { + + public static final int DEFAULT_LEN = 1 + 16; + //public short QueryType; + public Ip6Address linkAddress; + private List options; + + public Dhcp6LeaseQueryOption(Dhcp6Option dhcp6Option) { + super(dhcp6Option); + } + + @Override + public short getCode() { + return DHCP6.OptionCode.LEASE_QUERY.value(); + } + + @Override + public short getLength() { + //return (short) payload.serialize().length; + return (short) (DEFAULT_LEN + options.stream() + .mapToInt(opt -> (int) opt.getLength() + Dhcp6Option.DEFAULT_LEN) + .sum()); + } + + @Override + public byte[] getData() { + return payload.serialize(); + } + + + public static Deserializer deserializer() { + return (data, offset, length) -> { + Dhcp6Option dhcp6Option = Dhcp6Option.deserializer().deserialize(data, offset, length); + Dhcp6LeaseQueryOption lQ6Option = new Dhcp6LeaseQueryOption(dhcp6Option); + + byte[] optionData = lQ6Option.getData(); + if (optionData.length >= 61) { // 61 is LQ option length + 4 header + ByteBuffer bb = ByteBuffer.wrap(optionData); + // fetch the Query type - just pop the byte from the byte buffer for subsequent parsing... + bb.get(); + byte[] ipv6Addr = new byte[16]; + bb.get(ipv6Addr); + lQ6Option.linkAddress = Ip6Address.valueOf(ipv6Addr); + //int optionsLen = dhcp6Option.getLength() - 1 - 16; // query type (1) + link address (16) + + lQ6Option.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.IAADDR.value()) { + option = Dhcp6IaAddressOption.deserializer() + .deserialize(subOptData, 0, subOptData.length); + } else if (code == DHCP6.OptionCode.ORO.value()) { + option = Dhcp6Option.deserializer() + .deserialize(subOptData, 0, subOptData.length); + } else { + option = Dhcp6Option.deserializer() + .deserialize(subOptData, 0, subOptData.length); + } + lQ6Option.options.add(option); + } + } + return lQ6Option; + }; + } + + @Override + public byte[] serialize() { + ByteBuffer bb = ByteBuffer.allocate(this.getLength() + Dhcp6Option.DEFAULT_LEN); + bb.putShort(getCode()); + bb.putShort(getLength()); + bb.put(payload.serialize()); + return bb.array(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(getClass()) + .add("code", getCode()) + .add("length", getLength()) + .toString(); + } + + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), linkAddress, options); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Dhcp6LeaseQueryOption)) { + return false; + } + if (!super.equals(obj)) { + return false; + } + final Dhcp6LeaseQueryOption other = (Dhcp6LeaseQueryOption) obj; + + return Objects.equals(getCode(), other.getCode()) && + Objects.equals(getLength(), other.getLength()) && + Objects.equals(linkAddress, other.linkAddress) && + Objects.equals(options, other.options); + } +}