REST API support for DHCP Relay
Change-Id: Icfefebd15ff43718493e5223254b23ec02ee0bab
diff --git a/apps/dhcprelay/app/BUCK b/apps/dhcprelay/app/BUCK
new file mode 100644
index 0000000..d698297
--- /dev/null
+++ b/apps/dhcprelay/app/BUCK
@@ -0,0 +1,21 @@
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//lib:JACKSON',
+ '//lib:KRYO',
+ '//lib:org.apache.karaf.shell.console',
+ '//cli:onos-cli',
+ '//core/store/serializers:onos-core-serializers',
+ '//apps/route-service/api:onos-apps-route-service-api',
+ '//apps/routing/fpm/api:onos-apps-routing-fpm-api',
+]
+
+TEST_DEPS = [
+ '//lib:TEST',
+ '//apps/route-service/api:onos-apps-route-service-api-tests',
+ '//core/api:onos-api-tests',
+]
+
+osgi_jar_with_tests (
+ deps = COMPILE_DEPS,
+ test_deps = TEST_DEPS,
+)
diff --git a/apps/dhcprelay/app/BUILD b/apps/dhcprelay/app/BUILD
new file mode 100644
index 0000000..29e4908
--- /dev/null
+++ b/apps/dhcprelay/app/BUILD
@@ -0,0 +1,15 @@
+COMPILE_DEPS = CORE_DEPS + JACKSON + KRYO + CLI + [
+ "//core/store/serializers:onos-core-serializers",
+ "//apps/route-service/api:onos-apps-route-service-api",
+ "//apps/routing/fpm/api:onos-apps-routing-fpm-api",
+]
+
+TEST_DEPS = TEST + [
+ "//apps/route-service/api:onos-apps-route-service-api-tests",
+ "//core/api:onos-api-tests",
+]
+
+osgi_jar_with_tests(
+ test_deps = TEST_DEPS,
+ deps = COMPILE_DEPS,
+)
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
new file mode 100644
index 0000000..982b970
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
@@ -0,0 +1,1983 @@
+/*
+ * 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.onosproject.dhcprelay;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.Sets;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+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.DHCP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.UDP;
+import org.onlab.packet.VlanId;
+import org.onlab.packet.dhcp.CircuitId;
+import org.onlab.packet.dhcp.DhcpOption;
+import org.onlab.packet.dhcp.DhcpRelayAgentOption;
+import org.onlab.util.Tools;
+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.api.DhcpServerInfo;
+import org.onosproject.dhcprelay.config.DhcpServerConfig;
+import org.onosproject.dhcprelay.config.IgnoreDhcpConfig;
+import org.onosproject.dhcprelay.store.DhcpRecord;
+import org.onosproject.dhcprelay.store.DhcpRelayStore;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.behaviour.Pipeliner;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveContext;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostProvider;
+import org.onosproject.net.host.HostProviderRegistry;
+import org.onosproject.net.host.HostProviderService;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.intf.InterfaceService;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteStore;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.InterfaceIpAddress;
+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.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_CircuitID;
+import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
+import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
+import static org.onlab.packet.MacAddress.valueOf;
+import static org.onlab.packet.dhcp.DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.net.flowobjective.Objective.Operation.ADD;
+import static org.onosproject.net.flowobjective.Objective.Operation.REMOVE;
+
+
+@Component
+@Service
+@Property(name = "version", value = "4")
+public class Dhcp4HandlerImpl implements DhcpHandler, HostProvider {
+ public static final String DHCP_V4_RELAY_APP = "org.onosproject.Dhcp4HandlerImpl";
+ public static final ProviderId PROVIDER_ID = new ProviderId("dhcp4", DHCP_V4_RELAY_APP);
+ private static final String BROADCAST_IP = "255.255.255.255";
+ private static final int IGNORE_CONTROL_PRIORITY = PacketPriority.CONTROL.priorityValue() + 1000;
+
+ private static final String LQ_ROUTE_PROPERTY_NAME = "learnRouteFromLeasequery";
+
+ private static final TrafficSelector CLIENT_SERVER_SELECTOR = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPProtocol(IPv4.PROTOCOL_UDP)
+ .matchIPSrc(Ip4Address.ZERO.toIpPrefix())
+ .matchIPDst(Ip4Address.valueOf(BROADCAST_IP).toIpPrefix())
+ .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
+ .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
+ .build();
+ private static final TrafficSelector SERVER_RELAY_SELECTOR = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPProtocol(IPv4.PROTOCOL_UDP)
+ .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
+ .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
+ .build();
+ static final Set<TrafficSelector> DHCP_SELECTORS = ImmutableSet.of(
+ CLIENT_SERVER_SELECTOR,
+ SERVER_RELAY_SELECTOR
+ );
+ private static Logger log = LoggerFactory.getLogger(Dhcp4HandlerImpl.class);
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DhcpRelayStore dhcpRelayStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected RouteStore routeStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InterfaceService interfaceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostProviderRegistry providerRegistry;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected FlowObjectiveService flowObjectiveService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ComponentConfigService cfgService;
+
+ protected HostProviderService providerService;
+ protected ApplicationId appId;
+ protected Multimap<DeviceId, VlanId> ignoredVlans = Multimaps.synchronizedMultimap(HashMultimap.create());
+ private InternalHostListener hostListener = new InternalHostListener();
+
+ private List<DhcpServerInfo> defaultServerInfoList = new CopyOnWriteArrayList<>();
+ private List<DhcpServerInfo> indirectServerInfoList = new CopyOnWriteArrayList<>();
+
+ @Property(name = Dhcp4HandlerImpl.LQ_ROUTE_PROPERTY_NAME, boolValue = false,
+ label = "Enable learning routing information from LQ")
+ private Boolean learnRouteFromLeasequery = Boolean.TRUE;
+
+ private Executor hostEventExecutor = newSingleThreadExecutor(
+ groupedThreads("dhcp4-event-host", "%d", log));
+
+ @Activate
+ protected void activate(ComponentContext context) {
+ cfgService.registerProperties(getClass());
+ modified(context);
+ appId = coreService.registerApplication(DHCP_V4_RELAY_APP);
+ hostService.addListener(hostListener);
+ providerService = providerRegistry.register(this);
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ cfgService.unregisterProperties(getClass(), false);
+ providerRegistry.unregister(this);
+ hostService.removeListener(hostListener);
+ defaultServerInfoList.forEach(this::stopMonitoringIps);
+ defaultServerInfoList.clear();
+ indirectServerInfoList.forEach(this::stopMonitoringIps);
+ indirectServerInfoList.clear();
+ }
+
+ @Modified
+ protected void modified(ComponentContext context) {
+ Dictionary<?, ?> properties = context.getProperties();
+ Boolean flag;
+ flag = Tools.isPropertyEnabled(properties, Dhcp4HandlerImpl.LQ_ROUTE_PROPERTY_NAME);
+ if (flag != null) {
+ learnRouteFromLeasequery = flag;
+ log.info("Learning routes from DHCP leasequery is {}",
+ learnRouteFromLeasequery ? "enabled" : "disabled");
+ }
+ }
+
+ private void stopMonitoringIps(DhcpServerInfo serverInfo) {
+ serverInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
+ hostService.stopMonitoringIp(gatewayIp);
+ });
+ serverInfo.getDhcpServerIp4().ifPresent(serverIp -> {
+ hostService.stopMonitoringIp(serverIp);
+ });
+ }
+
+ @Override
+ public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
+ setDhcpServerConfigs(configs, defaultServerInfoList);
+ }
+
+ @Override
+ public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
+ setDhcpServerConfigs(configs, indirectServerInfoList);
+ }
+
+ @Override
+ public List<DhcpServerInfo> getDefaultDhcpServerInfoList() {
+ return defaultServerInfoList;
+ }
+
+ @Override
+ public List<DhcpServerInfo> getIndirectDhcpServerInfoList() {
+ return indirectServerInfoList;
+ }
+
+ @Override
+ public void updateIgnoreVlanConfig(IgnoreDhcpConfig config) {
+ if (config == null) {
+ ignoredVlans.forEach(((deviceId, vlanId) -> {
+ processIgnoreVlanRule(deviceId, vlanId, REMOVE);
+ }));
+ return;
+ }
+ config.ignoredVlans().forEach((deviceId, vlanId) -> {
+ if (ignoredVlans.get(deviceId).contains(vlanId)) {
+ // don't need to process if it already ignored
+ return;
+ }
+ processIgnoreVlanRule(deviceId, vlanId, ADD);
+ });
+
+ ignoredVlans.forEach((deviceId, vlanId) -> {
+ if (!config.ignoredVlans().get(deviceId).contains(vlanId)) {
+ // not contains in new config, remove it
+ processIgnoreVlanRule(deviceId, vlanId, REMOVE);
+ }
+ });
+ }
+
+ @Override
+ public void removeIgnoreVlanState(IgnoreDhcpConfig config) {
+ if (config == null) {
+ ignoredVlans.clear();
+ return;
+ }
+ config.ignoredVlans().forEach((deviceId, vlanId) -> {
+ ignoredVlans.remove(deviceId, vlanId);
+ });
+ }
+
+ public void setDhcpServerConfigs(Collection<DhcpServerConfig> configs, List<DhcpServerInfo> serverInfoList) {
+ if (configs.size() == 0) {
+ // no config to update
+ return;
+ }
+
+ Boolean isConfigValid = false;
+ for (DhcpServerConfig serverConfig : configs) {
+ if (serverConfig.getDhcpServerIp4().isPresent()) {
+ isConfigValid = true;
+ break;
+ }
+ }
+ if (!isConfigValid) {
+ log.warn("No IP V4 server address found.");
+ return; // No IP V6 address found
+ }
+ // if (!serverInfoList.isEmpty()) {
+ for (DhcpServerInfo oldServerInfo : serverInfoList) {
+ log.info("In for (DhcpServerInfo oldServerInfo : serverInfoList) {");
+ // remove old server info
+ //DhcpServerInfo oldServerInfo = serverInfoList.remove(0);
+
+ // stop monitoring gateway or server
+ oldServerInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
+ hostService.stopMonitoringIp(gatewayIp);
+ });
+ oldServerInfo.getDhcpServerIp4().ifPresent(serverIp -> {
+ hostService.stopMonitoringIp(serverIp);
+ cancelDhcpPacket(serverIp);
+ });
+ }
+
+ // Create new server info according to the config
+ serverInfoList.clear();
+ for (DhcpServerConfig serverConfig : configs) {
+ log.debug("Create new server info according to the config");
+ DhcpServerInfo newServerInfo = new DhcpServerInfo(serverConfig,
+ DhcpServerInfo.Version.DHCP_V4);
+ checkState(newServerInfo.getDhcpServerConnectPoint().isPresent(),
+ "Connect point not exists");
+ checkState(newServerInfo.getDhcpServerIp4().isPresent(),
+ "IP of DHCP server not exists");
+
+ log.debug("DHCP server connect point: {}", newServerInfo.getDhcpServerConnectPoint().orElse(null));
+ log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp4().orElse(null));
+
+ Ip4Address serverIp = newServerInfo.getDhcpServerIp4().get();
+ Ip4Address ipToProbe;
+ if (newServerInfo.getDhcpGatewayIp4().isPresent()) {
+ ipToProbe = newServerInfo.getDhcpGatewayIp4().get();
+ } else {
+ ipToProbe = newServerInfo.getDhcpServerIp4().orElse(null);
+ }
+ log.info("Probe_IP {}", ipToProbe);
+ String hostToProbe = newServerInfo.getDhcpGatewayIp4()
+ .map(ip -> "gateway").orElse("server");
+
+ log.debug("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
+ hostService.startMonitoringIp(ipToProbe);
+
+ Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
+ if (!hosts.isEmpty()) {
+ Host host = hosts.iterator().next();
+ newServerInfo.setDhcpConnectVlan(host.vlan());
+ newServerInfo.setDhcpConnectMac(host.mac());
+ }
+
+ // Add new server info
+ synchronized (this) {
+ //serverInfoList.clear();
+ serverInfoList.add(newServerInfo);
+ }
+
+ requestDhcpPacket(serverIp);
+ }
+ }
+
+ @Override
+ public void processDhcpPacket(PacketContext context, BasePacket payload) {
+ checkNotNull(payload, "DHCP payload can't be null");
+ checkState(payload instanceof DHCP, "Payload is not a DHCP");
+ DHCP dhcpPayload = (DHCP) payload;
+ if (!configured()) {
+ log.warn("Missing default DHCP relay server config. Abort packet processing");
+ return;
+ }
+
+ ConnectPoint inPort = context.inPacket().receivedFrom();
+ checkNotNull(dhcpPayload, "Can't find DHCP payload");
+ Ethernet packet = context.inPacket().parsed();
+ DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
+ .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
+ .map(DhcpOption::getData)
+ .map(data -> DHCP.MsgType.getType(data[0]))
+ .findFirst()
+ .orElse(null);
+ checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
+ 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;
+ }
+ switch (incomingPacketType) {
+ case DHCPDISCOVER:
+ // Add the gateway IP as virtual interface IP for server to understand
+ // the lease to be assigned and forward the packet to dhcp server.
+ List<InternalPacket> ethernetClientPacket =
+ processDhcpPacketFromClient(context, packet, receivingInterfaces);
+ for (InternalPacket internalPacket : ethernetClientPacket) {
+ log.debug("DHCPDISCOVER from {} Forward to server", inPort);
+ writeRequestDhcpRecord(inPort, packet, dhcpPayload);
+ forwardPacket(internalPacket);
+ }
+ break;
+ case DHCPOFFER:
+ //reply to dhcp client.
+ InternalPacket ethernetPacketOffer = processDhcpPacketFromServer(context, packet);
+ if (ethernetPacketOffer != null) {
+ writeResponseDhcpRecord(ethernetPacketOffer.getPacket(), dhcpPayload);
+ sendResponseToClient(ethernetPacketOffer, dhcpPayload);
+ }
+ break;
+ case DHCPREQUEST:
+ // add the gateway ip as virtual interface ip for server to understand
+ // the lease to be assigned and forward the packet to dhcp server.
+ List<InternalPacket> ethernetPacketRequest =
+ processDhcpPacketFromClient(context, packet, receivingInterfaces);
+ for (InternalPacket internalPacket : ethernetPacketRequest) {
+ log.debug("DHCPDISCOVER from {} Forward to server", inPort);
+ writeRequestDhcpRecord(inPort, packet, dhcpPayload);
+ forwardPacket(internalPacket);
+ }
+ break;
+ case DHCPDECLINE:
+ break;
+ case DHCPACK:
+ // reply to dhcp client.
+ InternalPacket ethernetPacketAck = processDhcpPacketFromServer(context, packet);
+ if (ethernetPacketAck != null) {
+ writeResponseDhcpRecord(ethernetPacketAck.getPacket(), dhcpPayload);
+ handleDhcpAck(ethernetPacketAck.getPacket(), dhcpPayload);
+ sendResponseToClient(ethernetPacketAck, dhcpPayload);
+ }
+ break;
+ case DHCPNAK:
+ break;
+ case DHCPRELEASE:
+ // TODO: release the ip address from client
+ break;
+ case DHCPINFORM:
+ break;
+ case DHCPFORCERENEW:
+ break;
+ case DHCPLEASEQUERY:
+ handleLeaseQueryMsg(context, packet, dhcpPayload);
+ break;
+ case DHCPLEASEACTIVE:
+ handleLeaseQueryActivateMsg(packet, dhcpPayload);
+ break;
+ case DHCPLEASEUNASSIGNED:
+ case DHCPLEASEUNKNOWN:
+ handleLeaseQueryUnknown(packet, dhcpPayload);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Checks if this app has been configured.
+ *
+ * @return true if all information we need have been initialized
+ */
+ private boolean configured() {
+ return !defaultServerInfoList.isEmpty();
+ }
+
+ /**
+ * Returns the first interface ip from interface.
+ *
+ * @param iface interface of one connect point
+ * @return the first interface IP; null if not exists an IP address in
+ * these interfaces
+ */
+ private Ip4Address getFirstIpFromInterface(Interface iface) {
+ checkNotNull(iface, "Interface can't be null");
+ return iface.ipAddressesList().stream()
+ .map(InterfaceIpAddress::ipAddress)
+ .filter(IpAddress::isIp4)
+ .map(IpAddress::getIp4Address)
+ .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 getDefaultServerInterface() {
+ return getServerInterface(defaultServerInfoList);
+ }
+
+ /**
+ * 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() {
+ return getServerInterface(indirectServerInfoList);
+ }
+
+ private Interface getServerInterface(List<DhcpServerInfo> serverInfos) {
+ return serverInfos.stream()
+ .findFirst()
+ .map(serverInfo -> {
+ ConnectPoint dhcpServerConnectPoint =
+ serverInfo.getDhcpServerConnectPoint().orElse(null);
+ VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
+ if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
+ return null;
+ }
+ return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
+ .stream()
+ .filter(iface -> interfaceContainsVlan(iface, dhcpConnectVlan))
+ .findFirst()
+ .orElse(null);
+ })
+ .orElse(null);
+ }
+
+ /**
+ * Determind if an Interface contains a vlan id.
+ *
+ * @param iface the Interface
+ * @param vlanId the vlan id
+ * @return true if the Interface contains the vlan id
+ */
+ private boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
+ if (vlanId.equals(VlanId.NONE)) {
+ // untagged packet, check if vlan untagged or vlan native is not NONE
+ return !iface.vlanUntagged().equals(VlanId.NONE) ||
+ !iface.vlanNative().equals(VlanId.NONE);
+ }
+ // tagged packet, check if the interface contains the vlan
+ return iface.vlanTagged().contains(vlanId);
+ }
+
+ private void handleLeaseQueryActivateMsg(Ethernet packet, DHCP dhcpPayload) {
+ log.debug("LQ: Got DHCPLEASEACTIVE packet!");
+
+ if (learnRouteFromLeasequery) {
+ // TODO: release the ip address from client
+ MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
+ VlanId vlanId = VlanId.vlanId(packet.getVlanID());
+ HostId hostId = HostId.hostId(clientMacAddress, vlanId);
+ DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
+
+ if (record == null) {
+ log.warn("Can't find record for host {} when processing DHCPLEASEACTIVE", hostId);
+ return;
+ }
+
+ // need to update routes
+ log.debug("Lease Query for Client results in DHCPLEASEACTIVE - route needs to be modified");
+ // get current route
+ // find the ip of that client with the DhcpRelay store
+
+ Ip4Address clientIP = record.ip4Address().orElse(null);
+ log.debug("LQ: IP of host is " + clientIP.getIp4Address());
+
+ MacAddress nextHopMac = record.nextHop().orElse(null);
+ log.debug("LQ: MAC of resulting *OLD* NH for that host is " + nextHopMac.toString());
+
+ // find the new NH by looking at the src MAC of the dhcp request
+ // from the LQ store
+ MacAddress newNextHopMac = record.nextHopTemp().orElse(null);
+ log.debug("LQ: MAC of resulting *NEW* NH for that host is " + newNextHopMac.toString());
+
+ log.debug("LQ: updating dhcp relay record with new NH");
+ record.nextHop(newNextHopMac);
+
+ // find the next hop IP from its mac
+ HostId gwHostId = HostId.hostId(newNextHopMac, vlanId);
+ Host gwHost = hostService.getHost(gwHostId);
+
+ if (gwHost == null) {
+ log.warn("Can't find gateway for new NH host " + gwHostId);
+ return;
+ }
+
+ Ip4Address nextHopIp = gwHost.ipAddresses()
+ .stream()
+ .filter(IpAddress::isIp4)
+ .map(IpAddress::getIp4Address)
+ .findFirst()
+ .orElse(null);
+
+ if (nextHopIp == null) {
+ log.warn("Can't find IP address of gateway " + gwHost);
+ return;
+ }
+
+ log.debug("LQ: *NEW* NH IP for host is " + nextHopIp.getIp4Address());
+ Route route = new Route(Route.Source.DHCP, clientIP.toIpPrefix(), nextHopIp);
+ routeStore.updateRoute(route);
+ }
+
+ // and forward to client
+ InternalPacket ethernetPacket = processLeaseQueryFromServer(packet);
+ if (ethernetPacket != null) {
+ sendResponseToClient(ethernetPacket, dhcpPayload);
+ }
+ }
+
+ private void handleLeaseQueryMsg(PacketContext context, Ethernet packet, DHCP dhcpPayload) {
+ // If this flag is enabled we expect that DHCPLEASEQUERY-ies are sent from an access concentrator
+ // where queried client is connected to. Otherwise, DHCPLEASEQUERY source may be a separate connected agent
+ if (learnRouteFromLeasequery) {
+ log.debug("LQ: Got DHCPLEASEQUERY packet!");
+ MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
+ log.debug("LQ: got DHCPLEASEQUERY with MAC " + clientMacAddress.toString());
+ // add the client mac (hostid) of this request to a store (the entry will be removed with
+ // the reply sent to the originator)
+ VlanId vlanId = VlanId.vlanId(packet.getVlanID());
+ HostId hId = HostId.hostId(clientMacAddress, vlanId);
+ DhcpRecord record = dhcpRelayStore.getDhcpRecord(hId).orElse(null);
+ if (record != null) {
+ //new NH is to be taken from src mac of LQ packet
+ MacAddress newNextHop = packet.getSourceMAC();
+ record.nextHopTemp(newNextHop);
+ record.ip4Status(dhcpPayload.getPacketType());
+ record.updateLastSeen();
+
+ // do a basic routing of the packet (this is unicast routing
+ // not a relay operation like for other broadcast dhcp packets
+ List<InternalPacket> ethernetPacketRequest = processLeaseQueryFromAgent(context, packet);
+ // and forward to server
+ for (InternalPacket internalPacket : ethernetPacketRequest) {
+ log.debug("LeaseQueryMsg forward to server");
+ forwardPacket(internalPacket);
+ }
+ } else {
+ log.warn("LQ: Error! - DHCP relay record for that client not found - ignoring LQ!");
+ }
+ } else {
+ log.debug("LQ: Got DHCPLEASEQUERY packet!");
+
+ int giaddr = dhcpPayload.getGatewayIPAddress();
+
+ log.debug("DHCPLEASEQUERY giaddr: {} ({}). Originators connectPoint: {}", giaddr,
+ Ip4Address.valueOf(giaddr), context.inPacket().receivedFrom());
+
+ // do a basic routing of the packet (this is unicast routing
+ // not a relay operation like for other broadcast dhcp packets
+ List<InternalPacket> ethernetPacketRequest = processLeaseQueryFromAgent(context, packet);
+ // and forward to server
+ for (InternalPacket internalPacket : ethernetPacketRequest) {
+ log.trace("LeaseQueryMsg forward to server connected to {}", internalPacket.getDestLocation());
+ forwardPacket(internalPacket);
+ }
+ }
+ }
+
+ private void handleLeaseQueryUnknown(Ethernet packet, DHCP dhcpPayload) {
+ log.debug("Lease Query for Client results in DHCPLEASEUNASSIGNED or " +
+ "DHCPLEASEUNKNOWN - removing route & forwarding reply to originator");
+ if (learnRouteFromLeasequery) {
+ MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
+ VlanId vlanId = VlanId.vlanId(packet.getVlanID());
+ HostId hostId = HostId.hostId(clientMacAddress, vlanId);
+ DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
+
+ if (record == null) {
+ log.warn("Can't find record for host {} when handling LQ UNKNOWN/UNASSIGNED message", hostId);
+ return;
+ }
+
+ Ip4Address clientIP = record.ip4Address().orElse(null);
+ log.debug("LQ: IP of host is " + clientIP.getIp4Address());
+
+ // find the new NH by looking at the src MAC of the dhcp request
+ // from the LQ store
+ MacAddress nextHopMac = record.nextHop().orElse(null);
+ log.debug("LQ: MAC of resulting *Existing* NH for that route is " + nextHopMac.toString());
+
+ // find the next hop IP from its mac
+ HostId gwHostId = HostId.hostId(nextHopMac, vlanId);
+ Host gwHost = hostService.getHost(gwHostId);
+
+ if (gwHost == null) {
+ log.warn("Can't find gateway for new NH host " + gwHostId);
+ return;
+ }
+
+ Ip4Address nextHopIp = gwHost.ipAddresses()
+ .stream()
+ .filter(IpAddress::isIp4)
+ .map(IpAddress::getIp4Address)
+ .findFirst()
+ .orElse(null);
+
+ if (nextHopIp == null) {
+ log.warn("Can't find IP address of gateway {}", gwHost);
+ return;
+ }
+
+ log.debug("LQ: *Existing* NH IP for host is " + nextHopIp.getIp4Address() + " removing route for it");
+ Route route = new Route(Route.Source.DHCP, clientIP.toIpPrefix(), nextHopIp);
+ routeStore.removeRoute(route);
+
+ // remove from temp store
+ dhcpRelayStore.removeDhcpRecord(hostId);
+ }
+ // and forward to client
+ InternalPacket ethernetPacket = processLeaseQueryFromServer(packet);
+ if (ethernetPacket != null) {
+ sendResponseToClient(ethernetPacket, dhcpPayload);
+ }
+ }
+
+ /**
+ * Build the DHCP discover/request packet with gateway IP(unicast packet).
+ *
+ * @param context the packet context
+ * @param ethernetPacket the ethernet payload to process
+ * @return processed packet
+ */
+ private List<InternalPacket> processDhcpPacketFromClient(PacketContext context,
+ Ethernet ethernetPacket,
+ Set<Interface> clientInterfaces) {
+ ConnectPoint receivedFrom = context.inPacket().receivedFrom();
+ DeviceId receivedFromDevice = receivedFrom.deviceId();
+ Ip4Address relayAgentIp = null;
+ relayAgentIp = Dhcp4HandlerUtil.getRelayAgentIPv4Address(clientInterfaces);
+ MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
+ if (relayAgentIp == null || relayAgentMac == null) {
+ log.warn("Missing DHCP relay agent interface Ipv4 addr config for "
+ + "packet from client on port: {}. Aborting packet processing",
+ clientInterfaces.iterator().next().connectPoint());
+ return Lists.newArrayList();
+ }
+ log.debug("Multi DHCP V4 processDhcpPacketFromClient on port {}",
+ clientInterfaces.iterator().next().connectPoint());
+
+ // get dhcp header.
+ Ethernet etherReply = (Ethernet) ethernetPacket.clone();
+ IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
+ UDP udpPacket = (UDP) ipv4Packet.getPayload();
+ DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
+
+
+ Ip4Address clientInterfaceIp =
+ interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
+ .stream()
+ .map(Interface::ipAddressesList)
+ .flatMap(Collection::stream)
+ .map(InterfaceIpAddress::ipAddress)
+ .filter(IpAddress::isIp4)
+ .map(IpAddress::getIp4Address)
+ .findFirst()
+ .orElse(null);
+ if (clientInterfaceIp == null) {
+ log.warn("Can't find interface IP for client interface for port {}",
+ context.inPacket().receivedFrom());
+ return Lists.newArrayList();
+ }
+
+ boolean isDirectlyConnected = directlyConnected(dhcpPacket);
+ boolean directConnFlag = directlyConnected(dhcpPacket);
+
+ // Multi DHCP Start
+ ConnectPoint clientConnectionPoint = context.inPacket().receivedFrom();
+ VlanId vlanIdInUse = VlanId.vlanId(ethernetPacket.getVlanID());
+ Interface clientInterface = interfaceService.getInterfacesByPort(clientConnectionPoint)
+ .stream().filter(iface -> Dhcp4HandlerUtil.interfaceContainsVlan(iface, vlanIdInUse))
+ .findFirst()
+ .orElse(null);
+
+ List<InternalPacket> internalPackets = new ArrayList<>();
+ List<DhcpServerInfo> serverInfoList = findValidServerInfo(directConnFlag);
+ List<DhcpServerInfo> copyServerInfoList = new ArrayList<DhcpServerInfo>(serverInfoList);
+
+
+ for (DhcpServerInfo serverInfo : copyServerInfoList) {
+ etherReply = (Ethernet) ethernetPacket.clone();
+ ipv4Packet = (IPv4) etherReply.getPayload();
+ udpPacket = (UDP) ipv4Packet.getPayload();
+ dhcpPacket = (DHCP) udpPacket.getPayload();
+ if (!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;
+ }
+
+ Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
+ MacAddress macFacingServer = serverInterface.mac();
+ log.debug("Interfacing server {} Mac : {} ", ipFacingServer, macFacingServer);
+ if (ipFacingServer == null || macFacingServer == null) {
+ log.warn("No IP address for server Interface {}", serverInterface);
+ continue;
+ }
+
+
+ etherReply.setSourceMACAddress(macFacingServer);
+ // set default info and replace with indirect if available later on.
+ if (newServerInfo.getDhcpConnectMac().isPresent()) {
+ etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
+ }
+ if (newServerInfo.getDhcpConnectVlan().isPresent()) {
+ etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
+ }
+ ipv4Packet.setSourceAddress(ipFacingServer.toInt());
+ ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
+ log.debug("Directly connected {}", isDirectlyConnected);
+ log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp4().get());
+ if (isDirectlyConnected) {
+
+ log.debug("Default DHCP server IP: {}", newServerInfo.getDhcpServerIp4().get());
+ if (newServerInfo.getDhcpConnectMac().isPresent()) {
+ etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
+ }
+ if (newServerInfo.getDhcpConnectVlan().isPresent()) {
+ etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
+ }
+
+ ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
+
+
+ ConnectPoint inPort = context.inPacket().receivedFrom();
+ VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
+ // add connected in port and vlan
+ CircuitId cid = new CircuitId(inPort.toString(), vlanId);
+ byte[] circuitId = cid.serialize();
+ DhcpOption circuitIdSubOpt = new DhcpOption();
+ circuitIdSubOpt
+ .setCode(CIRCUIT_ID.getValue())
+ .setLength((byte) circuitId.length)
+ .setData(circuitId);
+
+ DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
+ newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
+ newRelayAgentOpt.addSubOption(circuitIdSubOpt);
+
+ // Removes END option first
+ List<DhcpOption> options = dhcpPacket.getOptions().stream()
+ .filter(opt -> opt.getCode() != OptionCode_END.getValue())
+ .collect(Collectors.toList());
+
+ // push relay agent option
+ options.add(newRelayAgentOpt);
+
+ // make sure option 255(End) is the last option
+ DhcpOption endOption = new DhcpOption();
+ endOption.setCode(OptionCode_END.getValue());
+ options.add(endOption);
+
+ dhcpPacket.setOptions(options);
+
+ relayAgentIp = serverInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
+
+ // Sets relay agent IP
+ int effectiveRelayAgentIp = relayAgentIp != null ?
+ relayAgentIp.toInt() : clientInterfaceIp.toInt();
+ dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
+ log.debug("In Default, Relay Agent IP {}", effectiveRelayAgentIp);
+ } else {
+ if (!newServerInfo.getDhcpServerIp4().isPresent()) {
+ // do nothing
+ } else if (!newServerInfo.getDhcpConnectMac().isPresent()) {
+ continue;
+ } else {
+ relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
+ // Sets relay agent IP
+ int effectiveRelayAgentIp = relayAgentIp != null ?
+ relayAgentIp.toInt() : clientInterfaceIp.toInt();
+ Ip4Address effectiveRealRealyAgentIP = relayAgentIp != null ?
+ relayAgentIp : clientInterfaceIp;
+ dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
+ ipv4Packet.setSourceAddress(effectiveRealRealyAgentIP.toInt());
+ log.debug("Source IP address set as relay agent IP with value: {}", effectiveRealRealyAgentIP);
+ }
+ }
+
+ // Remove broadcast flag
+ dhcpPacket.setFlags((short) 0);
+
+ udpPacket.setPayload(dhcpPacket);
+ // As a DHCP relay, the source port should be server port( instead
+ // of client port.
+ udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
+ udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
+ ipv4Packet.setPayload(udpPacket);
+ ipv4Packet.setTtl((byte) 64);
+ etherReply.setPayload(ipv4Packet);
+ InternalPacket internalPacket = InternalPacket.internalPacket(etherReply,
+ serverInfo.getDhcpServerConnectPoint().get());
+
+ internalPackets.add(internalPacket);
+ }
+ return internalPackets;
+ }
+
+
+ /**
+ * Do a basic routing for a packet from client (used for LQ processing).
+ *
+ * @param context the packet context
+ * @param ethernetPacket the ethernet payload to process
+ * @return processed packet
+ */
+ private List<InternalPacket> processLeaseQueryFromAgent(PacketContext context,
+ Ethernet ethernetPacket) {
+ ConnectPoint receivedFrom = context.inPacket().receivedFrom();
+ DeviceId receivedFromDevice = receivedFrom.deviceId();
+
+ // get dhcp header.
+ Ethernet etherReply = (Ethernet) ethernetPacket.clone();
+ IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
+ UDP udpPacket = (UDP) ipv4Packet.getPayload();
+ DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
+
+ Ip4Address relayAgentIp;
+
+ Ip4Address clientInterfaceIp =
+ interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
+ .stream()
+ .map(Interface::ipAddressesList)
+ .flatMap(Collection::stream)
+ .map(InterfaceIpAddress::ipAddress)
+ .filter(IpAddress::isIp4)
+ .map(IpAddress::getIp4Address)
+ .findFirst()
+ .orElse(null);
+ if (clientInterfaceIp == null) {
+ log.warn("Can't find interface IP for client interface for port {}",
+ context.inPacket().receivedFrom());
+ return null;
+ }
+
+ boolean isDirectlyConnected = directlyConnected(dhcpPacket);
+ boolean directConnFlag = directlyConnected(dhcpPacket);
+
+ // Multi DHCP Start
+ List<InternalPacket> internalPackets = new ArrayList<>();
+ List<DhcpServerInfo> serverInfoList = findValidServerInfo(directConnFlag);
+ List<DhcpServerInfo> copyServerInfoList = new ArrayList<>(serverInfoList);
+
+ for (DhcpServerInfo serverInfo : copyServerInfoList) {
+ // get dhcp header.
+ etherReply = (Ethernet) ethernetPacket.clone();
+ ipv4Packet = (IPv4) etherReply.getPayload();
+ udpPacket = (UDP) ipv4Packet.getPayload();
+ dhcpPacket = (DHCP) udpPacket.getPayload();
+
+ if (!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;
+ }
+ Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
+ MacAddress macFacingServer = serverInterface.mac();
+ if (ipFacingServer == null || macFacingServer == null) {
+ log.warn("No IP address for server Interface {}", serverInterface);
+ continue;
+ }
+
+ etherReply.setSourceMACAddress(macFacingServer);
+ etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
+ etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
+ ipv4Packet.setSourceAddress(ipFacingServer.toInt());
+ ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
+ if (isDirectlyConnected) {
+ // set default info and replace with indirect if available later on.
+ if (newServerInfo.getDhcpConnectMac().isPresent()) {
+ etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
+ }
+ if (newServerInfo.getDhcpConnectVlan().isPresent()) {
+ etherReply.setVlanID(serverInfo.getDhcpConnectVlan().get().toShort());
+ }
+ if (learnRouteFromLeasequery) {
+ relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
+ // Sets relay agent IP
+ int effectiveRelayAgentIp = relayAgentIp != null ?
+ relayAgentIp.toInt() : clientInterfaceIp.toInt();
+ dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
+ }
+ } else {
+ if (!newServerInfo.getDhcpServerIp4().isPresent()) {
+ //do nothing
+ } else if (!newServerInfo.getDhcpConnectMac().isPresent()) {
+ continue;
+ } else if (learnRouteFromLeasequery) {
+ relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
+ // Sets relay agent IP
+ int effectiveRelayAgentIp = relayAgentIp != null ?
+ relayAgentIp.toInt() : clientInterfaceIp.toInt();
+ dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
+ log.debug("Relay Agent IP {}", relayAgentIp);
+ }
+
+ log.trace("Indirect");
+ }
+
+ // Remove broadcast flag
+ dhcpPacket.setFlags((short) 0);
+
+ udpPacket.setPayload(dhcpPacket);
+ // As a DHCP relay, the source port should be server port( instead
+ // of client port.
+ udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
+ udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
+ ipv4Packet.setPayload(udpPacket);
+ ipv4Packet.setTtl((byte) 64);
+ etherReply.setPayload(ipv4Packet);
+ udpPacket.resetChecksum();
+ ////return etherReply;
+ InternalPacket internalPacket = InternalPacket.internalPacket(etherReply,
+ newServerInfo.getDhcpServerConnectPoint().get());
+
+ internalPackets.add(internalPacket);
+ }
+ log.debug("num of processLeaseQueryFromAgent packets to send is: {}", internalPackets.size());
+
+ return internalPackets;
+ }
+
+
+ /**
+ * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
+ *
+ * @param location the location which DHCP packet comes from
+ * @param ethernet the DHCP packet
+ * @param dhcpPayload the DHCP payload
+ */
+ private void writeRequestDhcpRecord(ConnectPoint location,
+ Ethernet ethernet,
+ DHCP dhcpPayload) {
+ VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
+ MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
+ HostId hostId = HostId.hostId(macAddress, vlanId);
+ DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
+ if (record == null) {
+ record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
+ } else {
+ record = record.clone();
+ }
+ record.addLocation(new HostLocation(location, System.currentTimeMillis()));
+ record.ip4Status(dhcpPayload.getPacketType());
+ record.setDirectlyConnected(directlyConnected(dhcpPayload));
+ if (!directlyConnected(dhcpPayload)) {
+ // Update gateway mac address if the host is not directly connected
+ record.nextHop(ethernet.getSourceMAC());
+ }
+ record.updateLastSeen();
+ dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
+ }
+
+ /**
+ * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
+ *
+ * @param ethernet the DHCP packet
+ * @param dhcpPayload the DHCP payload
+ */
+ private void writeResponseDhcpRecord(Ethernet ethernet,
+ DHCP dhcpPayload) {
+ Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
+ if (!outInterface.isPresent()) {
+ log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
+ return;
+ }
+
+ Interface outIface = outInterface.get();
+ ConnectPoint location = outIface.connectPoint();
+ VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
+ if (vlanId == null) {
+ vlanId = outIface.vlan();
+ }
+ MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
+ HostId hostId = HostId.hostId(macAddress, vlanId);
+ DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
+ if (record == null) {
+ record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
+ } else {
+ record = record.clone();
+ }
+ record.addLocation(new HostLocation(location, System.currentTimeMillis()));
+ if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
+ record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
+ }
+ record.ip4Status(dhcpPayload.getPacketType());
+ record.setDirectlyConnected(directlyConnected(dhcpPayload));
+ record.updateLastSeen();
+ dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
+ }
+
+ /**
+ * Build the DHCP offer/ack with proper client port.
+ *
+ * @param ethernetPacket the original packet comes from server
+ * @return new packet which will send to the client
+ */
+ private InternalPacket processDhcpPacketFromServer(PacketContext context, Ethernet ethernetPacket) {
+ // get dhcp header.
+ Ethernet etherReply = (Ethernet) ethernetPacket.clone();
+ IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
+ UDP udpPacket = (UDP) ipv4Packet.getPayload();
+ DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
+
+ // determine the vlanId of the client host - note that this vlan id
+ // could be different from the vlan in the packet from the server
+ Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
+
+ if (clientInterface == null) {
+ log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
+ return null;
+ }
+ VlanId vlanId;
+ ConnectPoint inPort = context.inPacket().receivedFrom();
+ boolean directConnFlag = directlyConnected(dhcpPayload);
+ DhcpServerInfo foundServerInfo = findServerInfoFromServer(directConnFlag, inPort);
+
+ if (foundServerInfo == null) {
+ log.warn("Cannot find server info for {} server, inPort {}",
+ directConnFlag ? "direct" : "indirect", inPort);
+ return null;
+ } else {
+ if (Dhcp4HandlerUtil.isServerIpEmpty(foundServerInfo)) {
+ log.warn("Cannot find server info's ipaddress");
+ return null;
+ }
+ }
+ if (clientInterface.vlanTagged().isEmpty()) {
+ vlanId = clientInterface.vlan();
+ } else {
+ // might be multiple vlan in same interface
+ vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
+ }
+ if (vlanId == null) {
+ vlanId = VlanId.NONE;
+ }
+ etherReply.setVlanID(vlanId.toShort());
+ etherReply.setSourceMACAddress(clientInterface.mac());
+
+ if (!directlyConnected(dhcpPayload)) {
+ // if client is indirectly connected, try use next hop mac address
+ MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
+ HostId hostId = HostId.hostId(macAddress, vlanId);
+ if (((int) dhcpPayload.getFlags() & 0x8000) == 0x0000) {
+ DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
+ if (record != null) {
+ // if next hop can be found, use mac address of next hop
+ record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
+ } else {
+ // otherwise, discard the packet
+ log.warn("Can't find record for host id {}, discard packet", hostId);
+ return null;
+ }
+ } else {
+ etherReply.setDestinationMACAddress(MacAddress.BROADCAST);
+ }
+ } else {
+ etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
+ }
+
+ // we leave the srcMac from the original packet
+ // figure out the relay agent IP corresponding to the original request
+ Ip4Address ipFacingClient = getFirstIpFromInterface(clientInterface);
+ if (ipFacingClient == null) {
+ log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
+ + "Aborting relay for dhcp packet from server {}",
+ etherReply.getDestinationMAC(), clientInterface.vlan(),
+ ethernetPacket);
+ return null;
+ }
+ // SRC_IP: relay agent IP
+ // DST_IP: offered IP
+ ipv4Packet.setSourceAddress(ipFacingClient.toInt());
+ if (((int) dhcpPayload.getFlags() & 0x8000) == 0x0000) {
+ ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
+ } else {
+ ipv4Packet.setDestinationAddress(BROADCAST_IP);
+ }
+ udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
+ if (directlyConnected(dhcpPayload)) {
+ udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
+ } else {
+ // forward to another dhcp relay
+ // FIXME: Currently we assume the DHCP comes from a L2 relay with
+ // Option 82, this might not work if DHCP message comes from
+ // L3 relay.
+ udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
+ }
+
+ udpPacket.setPayload(dhcpPayload);
+ ipv4Packet.setPayload(udpPacket);
+ etherReply.setPayload(ipv4Packet);
+ return InternalPacket.internalPacket(etherReply, clientInterface.connectPoint());
+ }
+
+ /**
+ * Build the DHCP offer/ack with proper client port.
+ *
+ * @param ethernetPacket the original packet comes from server
+ * @return new packet which will send to the client
+ */
+ private InternalPacket processLeaseQueryFromServer(Ethernet ethernetPacket) {
+ // get dhcp header.
+ Ethernet etherReply = (Ethernet) ethernetPacket.clone();
+ IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
+ UDP udpPacket = (UDP) ipv4Packet.getPayload();
+ DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
+
+ // determine the vlanId of the client host - note that this vlan id
+ // could be different from the vlan in the packet from the server
+ Interface clientInterface = null;
+ MacAddress destinationMac = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
+
+ if (!learnRouteFromLeasequery) {
+ int giaddr = ipv4Packet.getDestinationAddress();
+ IpAddress destinationAddress = Ip4Address.valueOf(giaddr);
+ log.debug("DHCPLEASEQUERYRESP giaddr: {}({})", giaddr, destinationAddress);
+
+ Host destinationHost = hostService.getHostsByIp(destinationAddress).stream().findFirst().orElse(null);
+ if (destinationHost != null) {
+ destinationMac = destinationHost.mac();
+ log.trace("DHCPLEASEQUERYRESP destination mac is: {}", destinationMac);
+ ConnectPoint destinationLocation = destinationHost.location();
+ log.trace("Lookup for client interface by destination location {}", destinationLocation);
+ clientInterface = interfaceService.getInterfacesByPort(destinationLocation)
+ .stream()
+ .filter(iface -> interfaceContainsVlan(iface, VlanId.vlanId(etherReply.getVlanID())))
+ .findFirst()
+ .orElse(null);
+ log.trace("Found Host {} by ip {}", destinationHost, destinationAddress);
+ log.debug("DHCPLEASEQUERYRESP Client interface: {}",
+ (clientInterface != null ? clientInterface : "not resolved"));
+
+ }
+ } else {
+ clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
+ }
+
+ if (clientInterface == null) {
+ log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
+ return null;
+ }
+ VlanId vlanId;
+ if (clientInterface.vlanTagged().isEmpty()) {
+ vlanId = clientInterface.vlan();
+ } else {
+ // might be multiple vlan in same interface
+ vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
+ }
+ if (vlanId == null) {
+ vlanId = VlanId.NONE;
+ }
+ etherReply.setVlanID(vlanId.toShort());
+ etherReply.setSourceMACAddress(clientInterface.mac());
+
+ if (!directlyConnected(dhcpPayload) && learnRouteFromLeasequery) {
+ // if client is indirectly connected, try use next hop mac address
+ MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
+ HostId hostId = HostId.hostId(macAddress, vlanId);
+ DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
+ if (record != null) {
+ // if next hop can be found, use mac address of next hop
+ Optional<MacAddress> nextHop = record.nextHopTemp();
+ if (!nextHop.isPresent()) {
+ nextHop = record.nextHop();
+ }
+ nextHop.ifPresent(etherReply::setDestinationMACAddress);
+ } else {
+ // otherwise, discard the packet
+ log.warn("Can't find record for host id {}, discard packet", hostId);
+ return null;
+ }
+ } else {
+ etherReply.setDestinationMACAddress(destinationMac);
+ }
+
+ udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
+ if (directlyConnected(dhcpPayload)) {
+ udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
+ } else {
+ udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
+ }
+
+ udpPacket.setPayload(dhcpPayload);
+ ipv4Packet.setPayload(udpPacket);
+ etherReply.setPayload(ipv4Packet);
+ udpPacket.resetChecksum();
+
+ return InternalPacket.internalPacket(etherReply, clientInterface.connectPoint());
+ }
+ /**
+ * Extracts VLAN ID from relay agent option.
+ *
+ * @param dhcpPayload the DHCP payload
+ * @return VLAN ID from DHCP payload; null if not exists
+ */
+ private VlanId getVlanIdFromRelayAgentOption(DHCP dhcpPayload) {
+ DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
+ if (option == null) {
+ return null;
+ }
+ DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
+ if (circuitIdSubOption == null) {
+ return null;
+ }
+ try {
+ CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
+ return circuitId.vlanId();
+ } catch (IllegalArgumentException e) {
+ // can't deserialize the circuit ID
+ return null;
+ }
+ }
+
+ /**
+ * Removes DHCP relay agent information option (option 82) from DHCP payload.
+ * Also reset giaddr to 0
+ *
+ * @param ethPacket the Ethernet packet to be processed
+ * @return Ethernet packet processed
+ */
+ private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
+ Ethernet ethernet = (Ethernet) ethPacket.duplicate();
+ IPv4 ipv4 = (IPv4) ethernet.getPayload();
+ UDP udp = (UDP) ipv4.getPayload();
+ DHCP dhcpPayload = (DHCP) udp.getPayload();
+
+ // removes relay agent information option
+ List<DhcpOption> options = dhcpPayload.getOptions();
+ options = options.stream()
+ .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
+ .collect(Collectors.toList());
+ dhcpPayload.setOptions(options);
+ dhcpPayload.setGatewayIPAddress(0);
+
+ udp.setPayload(dhcpPayload);
+ ipv4.setPayload(udp);
+ ethernet.setPayload(ipv4);
+ return ethernet;
+ }
+
+ private boolean isDhcpPacketLeasequery(DHCP dhcpPacket) {
+ switch (dhcpPacket.getPacketType()) {
+ case DHCPLEASEACTIVE:
+ case DHCPLEASEQUERY:
+ case DHCPLEASEUNASSIGNED:
+ case DHCPLEASEUNKNOWN:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Check if the host is directly connected to the network or not.
+ *
+ * @param dhcpPayload the dhcp payload
+ * @return true if the host is directly connected to the network; false otherwise
+ */
+ private boolean directlyConnected(DHCP dhcpPayload) {
+ // leasequery is always indirect
+ if (isDhcpPacketLeasequery(dhcpPayload)) {
+ return false;
+ }
+
+ DhcpRelayAgentOption relayAgentOption =
+ (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
+
+ // Doesn't contains relay option
+ if (relayAgentOption == null) {
+ return true;
+ }
+
+ // check circuit id, if circuit id is invalid, we say it is an indirect host
+ DhcpOption circuitIdOpt = relayAgentOption.getSubOption(CIRCUIT_ID.getValue());
+
+ try {
+ CircuitId.deserialize(circuitIdOpt.getData());
+ return true;
+ } catch (Exception e) {
+ // invalid circuit id
+ return false;
+ }
+ }
+
+
+ /**
+ * Send the DHCP ack to the requester host.
+ * Modify Host or Route store according to the type of DHCP.
+ *
+ * @param ethernetPacketAck the packet
+ * @param dhcpPayload the DHCP data
+ */
+ private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
+ Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
+ if (!outInterface.isPresent()) {
+ log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
+ return;
+ }
+
+ Interface outIface = outInterface.get();
+ HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
+ MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
+ VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
+ if (vlanId == null) {
+ vlanId = outIface.vlan();
+ }
+ HostId hostId = HostId.hostId(macAddress, vlanId);
+ Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
+
+ if (directlyConnected(dhcpPayload)) {
+ // Add to host store if it connect to network directly
+ Set<IpAddress> ips = Sets.newHashSet(ip);
+ Host host = hostService.getHost(hostId);
+
+ Set<HostLocation> hostLocations = Sets.newHashSet(hostLocation);
+ if (host != null) {
+ // Dual homing support:
+ // if host exists, use old locations and new location
+ hostLocations.addAll(host.locations());
+ }
+ HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
+ hostLocations, ips, false);
+ // Add IP address when dhcp server give the host new ip address
+ providerService.hostDetected(hostId, desc, false);
+ } else {
+ // Add to route store if it does not connect to network directly
+ // Get gateway host IP according to host mac address
+ // TODO: remove relay store here
+ DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
+
+ if (record == null) {
+ log.warn("Can't find DHCP record of host {}", hostId);
+ return;
+ }
+
+ MacAddress gwMac = record.nextHop().orElse(null);
+ if (gwMac == null) {
+ log.warn("Can't find gateway mac address from record {}", record);
+ return;
+ }
+
+ HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
+ Host gwHost = hostService.getHost(gwHostId);
+
+ if (gwHost == null) {
+ log.warn("Can't find gateway host {}", gwHostId);
+ return;
+ }
+
+ Ip4Address nextHopIp = gwHost.ipAddresses()
+ .stream()
+ .filter(IpAddress::isIp4)
+ .map(IpAddress::getIp4Address)
+ .findFirst()
+ .orElse(null);
+
+ if (nextHopIp == null) {
+ log.warn("Can't find IP address of gateway {}", gwHost);
+ return;
+ }
+
+ Route route = new Route(Route.Source.DHCP, ip.toIpPrefix(), nextHopIp);
+ routeStore.replaceRoute(route);
+ }
+ }
+
+ /**
+ * Gets output interface of a dhcp packet.
+ * If option 82 exists in the dhcp packet and the option was sent by
+ * ONOS (circuit format is correct), use the connect
+ * point and vlan id from circuit id; otherwise, find host by destination
+ * address and use vlan id from sender (dhcp server).
+ *
+ * @param ethPacket the ethernet packet
+ * @param dhcpPayload the dhcp packet
+ * @return an interface represent the output port and vlan; empty value
+ * if the host or circuit id not found
+ */
+ private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
+ VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
+ DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
+
+ DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
+ try {
+ CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
+ ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
+ VlanId vlanId = circuitId.vlanId();
+ return interfaceService.getInterfacesByPort(connectPoint)
+ .stream()
+ .filter(iface -> interfaceContainsVlan(iface, vlanId))
+ .findFirst();
+ } catch (IllegalArgumentException ex) {
+ // invalid circuit format, didn't sent by ONOS
+ log.debug("Invalid circuit {}, use information from dhcp payload",
+ circuitIdSubOption.getData());
+ }
+
+ // Use Vlan Id from DHCP server if DHCP relay circuit id was not
+ // sent by ONOS or circuit Id can't be parsed
+ // TODO: remove relay store from this method
+ MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
+ VlanId filteredVlanId = getVlanIdFromDhcpRecord(dstMac, originalPacketVlanId);
+ // Get the vlan from the dhcp record
+ if (filteredVlanId == null) {
+ log.debug("not find the matching DHCP record for mac: {} and vlan: {}", dstMac, originalPacketVlanId);
+ return Optional.empty();
+ }
+
+ Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, filteredVlanId));
+ ConnectPoint clientConnectPoint = dhcpRecord
+ .map(DhcpRecord::locations)
+ .orElse(Collections.emptySet())
+ .stream()
+ .reduce((hl1, hl2) -> {
+ // find latest host connect point
+ if (hl1 == null || hl2 == null) {
+ return hl1 == null ? hl2 : hl1;
+ }
+ return hl1.time() > hl2.time() ? hl1 : hl2;
+ })
+ .orElse(null);
+
+ if (clientConnectPoint != null) {
+ return interfaceService.getInterfacesByPort(clientConnectPoint)
+ .stream()
+ .filter(iface -> interfaceContainsVlan(iface, filteredVlanId))
+ .findFirst();
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Get the required vlanId in case the DCHP record has more than one vlanId for a given MAC.
+ *
+ * @param mac MAC address of the DHCP client
+ * @param vlan Expected vlan of the DHCP client
+ */
+ private VlanId getVlanIdFromDhcpRecord(MacAddress mac, VlanId vlan) {
+ // Get all the DHCP records matching with the mac address
+ // If only one entry is present then pick the vlan of that entry
+ // If more then one entry is present then look for an entry with matching vlan
+ // else return null
+ Collection<DhcpRecord> records = dhcpRelayStore.getDhcpRecords();
+ List<DhcpRecord> filteredRecords = new ArrayList<>();
+ for (DhcpRecord e: records) {
+ if (e.macAddress().equals(mac)) {
+ filteredRecords.add(e);
+ }
+ }
+ log.debug("getVlanIdFromDhcpRecord mac: {} vlan: {}", mac, vlan);
+ log.debug("filteredRecords are: {}", filteredRecords);
+ if (filteredRecords.size() == 1) {
+ log.debug("Only one DHCP record entry. Returning back the vlan of that DHCP record: {}", filteredRecords);
+ return filteredRecords.get(0).vlanId();
+ }
+ // Check in the DHCP filtered record for matching vlan
+ for (DhcpRecord e: filteredRecords) {
+ if (e.vlanId().equals(vlan)) {
+ log.debug("Found a matching vlan entry in the DHCP record:{}", e);
+ return vlan;
+ }
+ }
+ // Found nothing return null
+ log.debug("Returning null as no matching or more than one matching entry found");
+ return null;
+
+ }
+
+
+
+ /**
+ * Send the response DHCP to the requester host.
+ *
+ * @param thePacket the packet
+ * @param dhcpPayload the DHCP data
+ */
+ private void sendResponseToClient(InternalPacket thePacket, DHCP dhcpPayload) {
+ checkNotNull(thePacket, "Nothing to send");
+ checkNotNull(thePacket.getPacket(), "Packet to send must not be empty");
+ checkNotNull(thePacket.getDestLocation(), "Packet destination not be empty");
+
+ Ethernet ethPacket = thePacket.getPacket();
+ if (directlyConnected(dhcpPayload)) {
+ ethPacket = removeRelayAgentOption(ethPacket);
+ }
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(thePacket.getDestLocation().port())
+ .build();
+ OutboundPacket o = new DefaultOutboundPacket(
+ thePacket.getDestLocation().deviceId(),
+ treatment,
+ ByteBuffer.wrap(ethPacket.serialize()));
+ if (log.isTraceEnabled()) {
+ log.trace("Relaying packet to DHCP client {} via {}",
+ ethPacket,
+ thePacket.getDestLocation());
+ }
+ packetService.emit(o);
+ }
+
+ @Override
+ public void triggerProbe(Host host) {
+ // Do nothing here
+ }
+
+ @Override
+ public ProviderId id() {
+ return PROVIDER_ID;
+ }
+
+ class InternalHostListener implements HostListener {
+ @Override
+ public void event(HostEvent event) {
+ if (!configured()) {
+ return;
+ }
+ switch (event.type()) {
+ case HOST_ADDED:
+ case HOST_UPDATED:
+ case HOST_MOVED:
+ log.trace("Scheduled host event {}", event);
+ hostEventExecutor.execute(() -> hostUpdated(event.subject()));
+ break;
+ case HOST_REMOVED:
+ log.trace("Scheduled host event {}", event);
+ hostEventExecutor.execute(() -> hostRemoved(event.subject()));
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * 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) {
+ hostUpdated(host, defaultServerInfoList);
+ hostUpdated(host, indirectServerInfoList);
+ }
+
+ private void hostUpdated(Host host, List<DhcpServerInfo> srverInfoList) {
+ srverInfoList.stream().forEach(serverInfo -> {
+ Ip4Address targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
+ Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
+ if (targetIp == null) {
+ targetIp = serverIp;
+ }
+ if (targetIp != null) {
+ if (host.ipAddresses().contains(targetIp)) {
+ serverInfo.setDhcpConnectMac(host.mac());
+ serverInfo.setDhcpConnectVlan(host.vlan());
+ requestDhcpPacket(serverIp);
+ }
+ }
+ });
+ }
+
+
+ /**
+ * 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) {
+ hostRemoved(host, defaultServerInfoList);
+ hostRemoved(host, indirectServerInfoList);
+ }
+
+ private void hostRemoved(Host host, List<DhcpServerInfo> serverInfoList) {
+ serverInfoList.stream().forEach(serverInfo -> {
+ Ip4Address targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
+ Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
+ if (targetIp == null) {
+ targetIp = serverIp;
+ }
+
+ if (targetIp != null) {
+ if (host.ipAddresses().contains(targetIp)) {
+ serverInfo.setDhcpConnectVlan(null);
+ serverInfo.setDhcpConnectMac(null);
+ cancelDhcpPacket(serverIp);
+ }
+ }
+ });
+ }
+
+ private void requestDhcpPacket(Ip4Address serverIp) {
+ requestServerDhcpPacket(serverIp);
+ requestClientDhcpPacket(serverIp);
+ }
+
+ private void cancelDhcpPacket(Ip4Address serverIp) {
+ cancelServerDhcpPacket(serverIp);
+ cancelClientDhcpPacket(serverIp);
+ }
+
+ private void cancelServerDhcpPacket(Ip4Address serverIp) {
+ TrafficSelector serverSelector =
+ DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
+ .matchIPSrc(serverIp.toIpPrefix())
+ .build();
+ packetService.cancelPackets(serverSelector,
+ PacketPriority.CONTROL,
+ appId);
+ }
+
+ private void requestServerDhcpPacket(Ip4Address serverIp) {
+ TrafficSelector serverSelector =
+ DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
+ .matchIPSrc(serverIp.toIpPrefix())
+ .build();
+ packetService.requestPackets(serverSelector,
+ PacketPriority.CONTROL,
+ appId);
+ }
+
+ private void cancelClientDhcpPacket(Ip4Address serverIp) {
+ // Packet comes from relay
+ TrafficSelector indirectClientSelector =
+ DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
+ .matchIPDst(serverIp.toIpPrefix())
+ .build();
+ packetService.cancelPackets(indirectClientSelector,
+ PacketPriority.CONTROL,
+ appId);
+
+ // Packet comes from client
+ packetService.cancelPackets(CLIENT_SERVER_SELECTOR,
+ PacketPriority.CONTROL,
+ appId);
+ }
+
+ private void requestClientDhcpPacket(Ip4Address serverIp) {
+ // Packet comes from relay
+ TrafficSelector indirectClientSelector =
+ DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
+ .matchIPDst(serverIp.toIpPrefix())
+ .build();
+ packetService.requestPackets(indirectClientSelector,
+ PacketPriority.CONTROL,
+ appId);
+
+ // Packet comes from client
+ packetService.requestPackets(CLIENT_SERVER_SELECTOR,
+ PacketPriority.CONTROL,
+ appId);
+ }
+
+ /**
+ * Process the ignore rules.
+ *
+ * @param deviceId the device id
+ * @param vlanId the vlan to be ignored
+ * @param op the operation, ADD to install; REMOVE to uninstall rules
+ */
+ private void processIgnoreVlanRule(DeviceId deviceId, VlanId vlanId, Objective.Operation op) {
+ AtomicInteger installedCount = new AtomicInteger(DHCP_SELECTORS.size());
+ DHCP_SELECTORS.forEach(trafficSelector -> {
+ TrafficSelector selector = DefaultTrafficSelector.builder(trafficSelector)
+ .matchVlanId(vlanId)
+ .build();
+
+ ForwardingObjective.Builder builder = DefaultForwardingObjective.builder()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withSelector(selector)
+ .withPriority(IGNORE_CONTROL_PRIORITY)
+ .withTreatment(DefaultTrafficTreatment.emptyTreatment())
+ .fromApp(appId);
+
+
+ ObjectiveContext objectiveContext = new ObjectiveContext() {
+ @Override
+ public void onSuccess(Objective objective) {
+ log.info("Ignore rule {} (Vlan id {}, device {}, selector {})",
+ op, vlanId, deviceId, selector);
+ int countDown = installedCount.decrementAndGet();
+ if (countDown != 0) {
+ return;
+ }
+ switch (op) {
+ case ADD:
+ ignoredVlans.put(deviceId, vlanId);
+ break;
+ case REMOVE:
+ ignoredVlans.remove(deviceId, vlanId);
+ break;
+ default:
+ log.warn("Unsupported objective operation {}", op);
+ break;
+ }
+ }
+
+ @Override
+ public void onError(Objective objective, ObjectiveError error) {
+ log.warn("Can't {} ignore rule (vlan id {}, selector {}, device {}) due to {}",
+ op, vlanId, selector, deviceId, error);
+ }
+ };
+
+ ForwardingObjective fwd;
+ switch (op) {
+ case ADD:
+ fwd = builder.add(objectiveContext);
+ break;
+ case REMOVE:
+ fwd = builder.remove(objectiveContext);
+ break;
+ default:
+ log.warn("Unsupported objective operation {}", op);
+ return;
+ }
+
+ Device device = deviceService.getDevice(deviceId);
+ if (device == null || !device.is(Pipeliner.class)) {
+ log.warn("Device {} is not available now, wait until device is available", deviceId);
+ return;
+ }
+ flowObjectiveService.apply(deviceId, fwd);
+ });
+ }
+
+ @Override
+ public void setDhcpFpmEnabled(Boolean enabled) {
+ // v4 does not use fpm. Do nothing.
+ }
+ private List<DhcpServerInfo> findValidServerInfo(boolean directConnFlag) {
+ List<DhcpServerInfo> validServerInfo;
+
+ if (directConnFlag || indirectServerInfoList.isEmpty()) {
+ validServerInfo = new ArrayList<DhcpServerInfo>(defaultServerInfoList);
+ } else {
+ validServerInfo = new ArrayList<DhcpServerInfo>(indirectServerInfoList);
+ }
+ return validServerInfo;
+ }
+
+
+ private boolean checkDhcpServerConnPt(boolean directConnFlag,
+ DhcpServerInfo serverInfo) {
+ if (serverInfo.getDhcpServerConnectPoint() == null) {
+ log.warn("DHCP4 server connect point for {} connPt {}",
+ directConnFlag ? "direct" : "indirect", serverInfo.getDhcpServerConnectPoint());
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Checks if serverInfo's host info (mac and vlan) is filled in; if not, fills in.
+ *
+ * @param serverInfo server information
+ * @return newServerInfo if host info can be either found or filled in.
+ */
+ private DhcpServerInfo getHostInfoForServerInfo(DhcpServerInfo serverInfo, List<DhcpServerInfo> sererInfoList) {
+ DhcpServerInfo newServerInfo = null;
+ MacAddress dhcpServerConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
+ VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
+ ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
+
+ if (dhcpServerConnectMac != null && dhcpConnectVlan != null) {
+ newServerInfo = serverInfo;
+ log.debug("DHCP server {} host info found. ConnectPt{} Mac {} vlan {}", serverInfo.getDhcpServerIp4(),
+ dhcpServerConnectPoint, dhcpServerConnectMac, dhcpConnectVlan);
+ } else {
+ log.warn("DHCP server {} not resolve yet connectPt {} mac {} vlan {}", serverInfo.getDhcpServerIp4(),
+ dhcpServerConnectPoint, dhcpServerConnectMac, dhcpConnectVlan);
+
+ Ip4Address ipToProbe;
+ if (serverInfo.getDhcpGatewayIp4().isPresent()) {
+ ipToProbe = serverInfo.getDhcpGatewayIp4().get();
+ } else {
+ ipToProbe = serverInfo.getDhcpServerIp4().orElse(null);
+ }
+ String hostToProbe = serverInfo.getDhcpGatewayIp6()
+ .map(ip -> "gateway").orElse("server");
+
+ log.warn("Dynamically probing to resolve {} IP {}", hostToProbe, ipToProbe);
+ hostService.startMonitoringIp(ipToProbe);
+
+ Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
+ if (!hosts.isEmpty()) {
+ int serverInfoIndex = sererInfoList.indexOf(serverInfo);
+ Host host = hosts.iterator().next();
+ serverInfo.setDhcpConnectVlan(host.vlan());
+ serverInfo.setDhcpConnectMac(host.mac());
+ // replace the serverInfo in the list
+ sererInfoList.set(serverInfoIndex, serverInfo);
+ newServerInfo = serverInfo;
+ log.warn("Dynamically host found host {}", host);
+ } else {
+ log.warn("No host found host ip {} dynamically", ipToProbe);
+ }
+ }
+ return newServerInfo;
+ }
+
+ /**
+ * Gets Interface facing to the server for default host.
+ *
+ * @param serverInfo server information
+ * @return the Interface facing to the server; null if not found
+ */
+ private Interface getServerInterface(DhcpServerInfo serverInfo) {
+ Interface serverInterface = null;
+
+ ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
+ VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
+
+ if (dhcpServerConnectPoint != null && dhcpConnectVlan != null) {
+ serverInterface = interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
+ .stream()
+ .filter(iface -> Dhcp4HandlerUtil.interfaceContainsVlan(iface, dhcpConnectVlan))
+ .findFirst()
+ .orElse(null);
+ } else {
+ log.warn("DHCP server {} not resolve yet connectPoint {} vlan {}", serverInfo.getDhcpServerIp6(),
+ dhcpServerConnectPoint, dhcpConnectVlan);
+ }
+
+ return serverInterface;
+ }
+
+ //forward the packet to ConnectPoint where the DHCP server is attached.
+ private void forwardPacket(InternalPacket packet) {
+ //send Packetout to dhcp server connectpoint.
+ if (packet.getDestLocation() != null) {
+ TrafficTreatment t = DefaultTrafficTreatment.builder()
+ .setOutput(packet.getDestLocation().port()).build();
+ OutboundPacket o = new DefaultOutboundPacket(
+ packet.getDestLocation().deviceId(), t, ByteBuffer.wrap(packet.getPacket().serialize()));
+ if (log.isTraceEnabled()) {
+ log.trace("Relaying packet to destination {}", packet.getDestLocation());
+ }
+ log.debug("packetService.emit(o) to port {}", packet.getDestLocation());
+ packetService.emit(o);
+ }
+ }
+
+
+ private DhcpServerInfo findServerInfoFromServer(boolean directConnFlag, ConnectPoint inPort) {
+ List<DhcpServerInfo> validServerInfoList = findValidServerInfo(directConnFlag);
+ DhcpServerInfo foundServerInfo = null;
+ for (DhcpServerInfo serverInfo : validServerInfoList) {
+ if (inPort.equals(serverInfo.getDhcpServerConnectPoint().get())) {
+ foundServerInfo = serverInfo;
+ log.debug("ServerInfo found for Rcv port {} Server Connect Point {} for {}",
+ inPort, serverInfo.getDhcpServerConnectPoint(), directConnFlag ? "direct" : "indirect");
+ break;
+ }
+ }
+ return foundServerInfo;
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerUtil.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerUtil.java
new file mode 100644
index 0000000..a88bda8
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerUtil.java
@@ -0,0 +1,86 @@
+/*
+ * 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.onosproject.dhcprelay;
+
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.VlanId;
+import org.onlab.util.HexString;
+import org.onosproject.dhcprelay.api.DhcpServerInfo;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.intf.Interface;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.util.Set;
+
+public final class Dhcp4HandlerUtil {
+ private static final Logger log = LoggerFactory.getLogger(Dhcp4HandlerUtil.class);
+
+ private Dhcp4HandlerUtil() {
+ }
+
+ /**
+ * Returns the first v4 interface ip out of a set of interfaces or null.
+ *
+ * @param intfs set of interfaces
+ * @return Ip4Address / null if not present
+ */
+ public static Ip4Address getRelayAgentIPv4Address(Set<Interface> intfs) {
+ for (Interface intf : intfs) {
+ for (InterfaceIpAddress ip : intf.ipAddressesList()) {
+ Ip4Address relayAgentIp = ip.ipAddress().getIp4Address();
+ if (relayAgentIp != null) {
+ return relayAgentIp;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Determind if an Interface contains a vlan id.
+ *
+ * @param iface the Interface
+ * @param vlanId the vlan id
+ * @return true if the Interface contains the vlan id
+ */
+ public static boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
+ if (vlanId.equals(VlanId.NONE)) {
+ // untagged packet, check if vlan untagged or vlan native is not NONE
+ return !iface.vlanUntagged().equals(VlanId.NONE) ||
+ !iface.vlanNative().equals(VlanId.NONE);
+ }
+ // tagged packet, check if the interface contains the vlan
+ return iface.vlanTagged().contains(vlanId);
+ }
+
+ /**
+ * Check if a given server info has v6 ipaddress.
+ *
+ * @param serverInfo server info to check
+ * @return true if server info has v6 ip address; false otherwise
+ */
+ public static boolean isServerIpEmpty(DhcpServerInfo serverInfo) {
+ if (!serverInfo.getDhcpServerIp4().isPresent()) {
+ log.warn("DhcpServerIp not available, use default DhcpServerIp {}",
+ HexString.toHexString(serverInfo.getDhcpServerIp4().get().toOctets()));
+ return true;
+ }
+ return false;
+ }
+}
+
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerImpl.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerImpl.java
new file mode 100644
index 0000000..31dcaaa
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerImpl.java
@@ -0,0 +1,1963 @@
+/*
+ * 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.onosproject.dhcprelay;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.Sets;
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+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.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;
+import org.onlab.packet.dhcp.Dhcp6IaAddressOption;
+import org.onlab.packet.dhcp.Dhcp6IaPrefixOption;
+import org.onlab.packet.dhcp.Dhcp6ClientIdOption;
+import org.onlab.packet.dhcp.Dhcp6Duid;
+import org.onlab.packet.DHCP6.MsgType;
+import org.onlab.util.HexString;
+import org.onlab.util.Tools;
+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.api.DhcpServerInfo;
+import org.onosproject.dhcprelay.config.IgnoreDhcpConfig;
+import org.onosproject.dhcprelay.store.DhcpRelayStore;
+import org.onosproject.dhcprelay.store.DhcpRecord;
+import org.onosproject.dhcprelay.store.DhcpFpmPrefixStore;
+import org.onosproject.dhcprelay.store.DhcpRelayCounters;
+import org.onosproject.dhcprelay.store.DhcpRelayCountersStore;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.routing.fpm.api.FpmRecord;
+import org.onosproject.net.behaviour.Pipeliner;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveContext;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.host.HostProvider;
+import org.onosproject.net.host.HostProviderRegistry;
+import org.onosproject.net.host.HostProviderService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostDescription;
+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.net.packet.PacketPriority;
+import org.onosproject.net.provider.ProviderId;
+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.osgi.service.component.ComponentContext;
+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.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+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")
+public class Dhcp6HandlerImpl implements DhcpHandler, HostProvider {
+ public static final String DHCP_V6_RELAY_APP = "org.onosproject.Dhcp6HandlerImpl";
+ public static final ProviderId PROVIDER_ID = new ProviderId("dhcp6", DHCP_V6_RELAY_APP);
+ private static final int IGNORE_CONTROL_PRIORITY = PacketPriority.CONTROL.priorityValue() + 1000;
+ private String gCount = "global";
+ private static final String LQ_ROUTE_PROPERTY_NAME = "learnRouteFromLeasequery";
+ private static final TrafficSelector CLIENT_SERVER_SELECTOR = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV6)
+ .matchIPProtocol(IPv6.PROTOCOL_UDP)
+ .matchIPv6Src(IpPrefix.IPV6_LINK_LOCAL_PREFIX)
+ .matchIPv6Dst(Ip6Address.ALL_DHCP_RELAY_AGENTS_AND_SERVERS.toIpPrefix())
+ .matchUdpSrc(TpPort.tpPort(UDP.DHCP_V6_CLIENT_PORT))
+ .matchUdpDst(TpPort.tpPort(UDP.DHCP_V6_SERVER_PORT))
+ .build();
+ private static final TrafficSelector SERVER_RELAY_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_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<TrafficSelector> DHCP_SELECTORS = ImmutableSet.of(
+ CLIENT_SERVER_SELECTOR,
+ SERVER_RELAY_SELECTOR,
+ LEASE_QUERY_RESPONSE_SELECTOR
+ );
+ private static Logger log = LoggerFactory.getLogger(Dhcp6HandlerImpl.class);
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DhcpRelayStore dhcpRelayStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DhcpRelayCountersStore dhcpRelayCountersStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected RouteStore routeStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InterfaceService interfaceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostProviderRegistry providerRegistry;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DhcpFpmPrefixStore dhcpFpmPrefixStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected FlowObjectiveService flowObjectiveService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ComponentConfigService cfgService;
+
+ @Property(name = Dhcp6HandlerImpl.LQ_ROUTE_PROPERTY_NAME, boolValue = false,
+ label = "Enable learning routing information from LQ")
+ private Boolean learnRouteFromLeasequery = Boolean.TRUE;
+
+ protected HostProviderService providerService;
+ protected ApplicationId appId;
+ protected Multimap<DeviceId, VlanId> ignoredVlans = Multimaps.synchronizedMultimap(HashMultimap.create());
+ private InternalHostListener hostListener = new InternalHostListener();
+ private Boolean dhcpFpmEnabled = false;
+ private List<DhcpServerInfo> defaultServerInfoList = new CopyOnWriteArrayList<>();
+ private List<DhcpServerInfo> indirectServerInfoList = new CopyOnWriteArrayList<>();
+
+ private Executor hostEventExecutor = newSingleThreadExecutor(
+ groupedThreads("dhcp6-event-host", "%d", log));
+
+ private class IpAddressInfo {
+ Ip6Address ip6Address;
+ long prefTime;
+ }
+ private class PdPrefixInfo {
+ IpPrefix pdPrefix;
+ long prefTime;
+ }
+ protected int dhcp6PollInterval = 24 * 3600; // 24 hr period
+
+ // max 1 thread
+ static Semaphore recordSemaphore = new Semaphore(1);
+
+ // 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(),
+ DHCP6.MsgType.LEASEQUERY.value());
+ // SERVER message types
+ public static final Set<Byte> MSG_TYPE_FROM_SERVER =
+ ImmutableSet.of(DHCP6.MsgType.RELAY_REPL.value(),
+ DHCP6.MsgType.LEASEQUERY_REPLY.value());
+
+ @Activate
+ protected void activate(ComponentContext context) {
+ cfgService.registerProperties(getClass());
+ modified(context);
+ appId = coreService.registerApplication(DHCP_V6_RELAY_APP);
+ providerService = providerRegistry.register(this);
+ hostService.addListener(hostListener);
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ cfgService.unregisterProperties(getClass(), false);
+ providerRegistry.unregister(this);
+ hostService.removeListener(hostListener);
+ defaultServerInfoList.forEach(this::stopMonitoringIps);
+ defaultServerInfoList.clear();
+ indirectServerInfoList.forEach(this::stopMonitoringIps);
+ indirectServerInfoList.clear();
+ }
+
+ @Modified
+ protected void modified(ComponentContext context) {
+ Dictionary<?, ?> properties = context.getProperties();
+ Boolean flag;
+ flag = Tools.isPropertyEnabled(properties, Dhcp6HandlerImpl.LQ_ROUTE_PROPERTY_NAME);
+ if (flag != null) {
+ learnRouteFromLeasequery = flag;
+ log.info("Learning routes from DHCP leasequery is {}",
+ learnRouteFromLeasequery ? "enabled" : "disabled");
+ }
+ }
+
+ private void stopMonitoringIps(DhcpServerInfo serverInfo) {
+ serverInfo.getDhcpGatewayIp6().ifPresent(gatewayIp -> {
+ hostService.stopMonitoringIp(gatewayIp);
+ });
+ serverInfo.getDhcpServerIp6().ifPresent(serverIp -> {
+ hostService.stopMonitoringIp(serverIp);
+ });
+ }
+
+ @Override
+ public List<DhcpServerInfo> getDefaultDhcpServerInfoList() {
+ return defaultServerInfoList;
+ }
+
+ @Override
+ public List<DhcpServerInfo> getIndirectDhcpServerInfoList() {
+ return indirectServerInfoList;
+ }
+
+ @Override
+ public void updateIgnoreVlanConfig(IgnoreDhcpConfig config) {
+ if (config == null) {
+ ignoredVlans.forEach(((deviceId, vlanId) -> {
+ processIgnoreVlanRule(deviceId, vlanId, REMOVE);
+ }));
+ return;
+ }
+ config.ignoredVlans().forEach((deviceId, vlanId) -> {
+ if (ignoredVlans.get(deviceId).contains(vlanId)) {
+ // don't need to process if it already ignored
+ return;
+ }
+ processIgnoreVlanRule(deviceId, vlanId, ADD);
+ });
+ ignoredVlans.forEach((deviceId, vlanId) -> {
+ if (!config.ignoredVlans().get(deviceId).contains(vlanId)) {
+ // not contains in new config, remove it
+ processIgnoreVlanRule(deviceId, vlanId, REMOVE);
+ }
+ });
+ }
+
+ @Override
+ public void removeIgnoreVlanState(IgnoreDhcpConfig config) {
+ if (config == null) {
+ ignoredVlans.clear();
+ return;
+ }
+ config.ignoredVlans().forEach((deviceId, vlanId) -> {
+ ignoredVlans.remove(deviceId, vlanId);
+ });
+ }
+
+ public DhcpRecord getDhcpRelayRecordFor(Ip6Address clientAddress) {
+
+ Collection<DhcpRecord> 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<MacAddress> 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<MacAddress> 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<Dhcp6Option> 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.DHCP, 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");
+ 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 dhcp6 payload {}", dhcp6Payload);
+ return;
+ }
+ byte msgTypeVal = dhcp6Payload.getMsgType();
+ MsgType msgType = DHCP6.MsgType.getType(msgTypeVal);
+ log.debug("msgType is {}", msgType);
+
+ ConnectPoint inPort = context.inPacket().receivedFrom();
+
+ if (inPort == null) {
+ log.warn("incoming 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 (msgTypeVal == DHCP6.MsgType.LEASEQUERY.value()) {
+ List<InternalPacket> ethernetClientPackets =
+ learnRouteFromLeasequery ?
+ processLQ6PacketFromClient(context, receivedPacket, receivingInterfaces, dhcp6Payload) :
+ processDhcp6ForwardOnly(context, receivedPacket, receivingInterfaces, dhcp6Payload);
+ for (InternalPacket internalPacket : ethernetClientPackets) {
+ forwardPacket(internalPacket);
+ }
+ } else if (msgTypeVal == DHCP6.MsgType.LEASEQUERY_REPLY.value() && learnRouteFromLeasequery) {
+ IPv6 clientIpv6 = (IPv6) receivedPacket.getPayload();
+ UDP clientUdp = (UDP) clientIpv6.getPayload();
+ DHCP6 clientDhcp6 = (DHCP6) clientUdp.getPayload();
+ Interface serverInterface = Dhcp6HandlerUtil.directlyConnected(clientDhcp6) ?
+ getServerInterface() : getIndirectServerInterface();
+ InternalPacket ethernetPacketReply =
+ Dhcp6HandlerUtil.processLQ6PacketFromServer(
+ defaultServerInfoList, indirectServerInfoList,
+ serverInterface, interfaceService,
+ hostService,
+ context, receivedPacket, receivingInterfaces);
+ if (ethernetPacketReply != null) {
+ forwardPacket(ethernetPacketReply);
+ }
+ handleLeaseQuery6ReplyMsg(context, dhcp6Payload);
+ } else if (MSG_TYPE_FROM_CLIENT.contains(msgTypeVal)) {
+ List<InternalPacket> 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 {}, {}", receivedPacket, dhcp6Payload);
+ InternalPacket ethernetPacketReply =
+ processDhcp6PacketFromServer(context, receivedPacket, receivingInterfaces);
+ if (ethernetPacketReply != null) {
+ forwardPacket(ethernetPacketReply);
+ }
+ } else {
+ log.warn("Not so fast, packet type {} not supported yet", msgTypeVal);
+ }
+ }
+
+ /**
+ * Checks if this app has been configured.
+ *
+ * @return true if all information we need have been initialized
+ */
+ public boolean configured() {
+ return !defaultServerInfoList.isEmpty();
+ }
+
+ @Override
+ public ProviderId id() {
+ return PROVIDER_ID;
+ }
+
+ @Override
+ public void triggerProbe(Host host) {
+ // 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.
+ if (packet.getDestLocation() != null) {
+ TrafficTreatment t = DefaultTrafficTreatment.builder()
+ .setOutput(packet.getDestLocation().port()).build();
+ OutboundPacket o = new DefaultOutboundPacket(
+ packet.getDestLocation().deviceId(), t, ByteBuffer.wrap(packet.getPacket().serialize()));
+ packetService.emit(o);
+ if (log.isTraceEnabled()) {
+ IPv6 ip6 = (IPv6) packet.getPacket().getPayload();
+ UDP udp = (UDP) ip6.getPayload();
+ DHCP6 dhcp6 = (DHCP6) udp.getPayload();
+ log.trace("Relaying packet to destination {} eth: {} dhcp: {}",
+ packet.getDestLocation(), packet.getPacket(), dhcp6);
+ }
+
+ }
+ }
+
+ /**
+ * extract from dhcp6 packet client ipv6 address of given by dhcp server.
+ *
+ * @param dhcp6 the dhcp6 packet
+ * @return IpAddressInfo IpAddressInfo given by dhcp server, or null if not exists
+ */
+ private IpAddressInfo extractIpAddress(DHCP6 dhcp6) {
+ IpAddressInfo ipInfo = new IpAddressInfo();
+
+ 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 {
+ log.info("No IPv6 address found from iaTaOption {}", iaTaOption);
+ iaAddressOption = Optional.empty();
+ }
+ if (iaAddressOption.isPresent()) {
+ ipInfo.ip6Address = iaAddressOption.get().getIp6Address();
+ ipInfo.prefTime = iaAddressOption.get().getPreferredLifetime();
+ log.debug("Found IPv6 address from iaAddressOption {}", iaAddressOption);
+ } else {
+ log.debug("Can't find IPv6 address from DHCPv6 {}", dhcp6);
+ return null;
+ }
+ return ipInfo;
+ }
+
+ /**
+ * 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 PdPrefixInfo extractPrefix(DHCP6 dhcp6) {
+ log.debug("extractPrefix enters {}", dhcp6);
+
+ // extract prefix
+ PdPrefixInfo pdPrefixInfo = new PdPrefixInfo();
+
+ 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.debug("IA_PD option found {}", iaPdOption);
+
+ iaPrefixOption = iaPdOption.get().getOptions().stream()
+ .filter(opt -> opt instanceof Dhcp6IaPrefixOption)
+ .map(opt -> (Dhcp6IaPrefixOption) opt)
+ .findFirst();
+ } else {
+ log.debug("IA_PD option NOT found");
+
+ iaPrefixOption = Optional.empty();
+ }
+ if (iaPrefixOption.isPresent()) {
+ log.debug("IAPrefix Option within IA_PD option found {}", iaPrefixOption);
+
+ prefixAddress = iaPrefixOption.get().getIp6Prefix();
+ int prefixLen = (int) iaPrefixOption.get().getPrefixLength();
+ log.debug("Prefix length is {} bits", prefixLen);
+ pdPrefixInfo.pdPrefix = IpPrefix.valueOf(prefixAddress, prefixLen);
+ pdPrefixInfo.prefTime = iaPrefixOption.get().getPreferredLifetime();
+ } else {
+ log.debug("Can't find IPv6 prefix from DHCPv6 {}", dhcp6);
+ return null;
+ }
+ return pdPrefixInfo;
+ }
+
+ /**
+ * remove host or route and update dhcp relay record attributes.
+ *
+ * @param directConnFlag flag to show that packet is from directly connected client
+ * @param location client side connect point
+ * @param dhcp6Packet the dhcp6 payload
+ * @param clientPacket client's ethernet packet
+ * @param clientIpv6 client's Ipv6 packet
+ * @param clientInterface client interfaces
+ */
+ private void removeHostOrRoute(boolean directConnFlag, ConnectPoint location,
+ DHCP6 dhcp6Packet,
+ Ethernet clientPacket, IPv6 clientIpv6,
+ Interface clientInterface) {
+ log.debug("removeHostOrRoute enters {}", dhcp6Packet);
+ VlanId vlanId = clientInterface.vlan();
+ MacAddress srcMac = clientPacket.getSourceMAC(); // could be gw or host
+ MacAddress leafClientMac;
+ byte leafMsgType;
+ log.debug("client mac {} client vlan {}", HexString.toHexString(srcMac.toBytes(), ":"), vlanId);
+
+ Dhcp6ClientIdOption clientIdOption = Dhcp6HandlerUtil.extractClientId(directConnFlag, dhcp6Packet);
+ if (clientIdOption != null) {
+ if ((clientIdOption.getDuid().getDuidType() == Dhcp6Duid.DuidType.DUID_LLT) ||
+ (clientIdOption.getDuid().getDuidType() == Dhcp6Duid.DuidType.DUID_LL)) {
+ leafClientMac = MacAddress.valueOf(clientIdOption.getDuid().getLinkLayerAddress());
+ } else {
+ log.warn("Link-Layer Address not supported in CLIENTID option. No DhcpRelay Record created.");
+ dhcpRelayCountersStore.incrementCounter(gCount, DhcpRelayCounters.NO_LINKLOCAL_FAIL);
+ return;
+ }
+ } else {
+ log.warn("CLIENTID option NOT found. Don't create DhcpRelay Record.");
+ dhcpRelayCountersStore.incrementCounter(gCount, DhcpRelayCounters.NO_CLIENTID_FAIL);
+ return;
+ }
+
+ HostId leafHostId = HostId.hostId(leafClientMac, vlanId);
+ DhcpRecord record = dhcpRelayStore.getDhcpRecord(leafHostId).orElse(null);
+ if (record == null) {
+ record = new DhcpRecord(leafHostId);
+ } else {
+ record = record.clone();
+ }
+
+ Boolean isMsgRelease = Dhcp6HandlerUtil.isDhcp6Release(dhcp6Packet);
+ IpAddressInfo ipInfo;
+ PdPrefixInfo pdInfo = null;
+ if (directConnFlag) {
+ // Add to host store if it is connected to network directly
+ ipInfo = extractIpAddress(dhcp6Packet);
+ if (ipInfo != null) {
+ if (isMsgRelease) {
+ HostId hostId = HostId.hostId(srcMac, vlanId);
+ log.debug("remove Host {} ip for directly connected.", hostId.toString());
+ providerService.removeIpFromHost(hostId, ipInfo.ip6Address);
+ }
+ } else {
+ log.debug("ipAddress not found. Do not remove Host {} for directly connected.",
+ HostId.hostId(srcMac, vlanId).toString());
+ }
+ leafMsgType = dhcp6Packet.getMsgType();
+ } else {
+ // Remove from route store if it is not connected to network directly
+ // pick out the first link-local ip address
+ IpAddress nextHopIp = getFirstIpByHost(directConnFlag, srcMac, vlanId);
+ if (nextHopIp == null) {
+ log.warn("Can't find link-local IP address of gateway mac {} vlanId {}", srcMac, vlanId);
+ dhcpRelayCountersStore.incrementCounter(gCount, DhcpRelayCounters.NO_LINKLOCAL_GW);
+ return;
+ }
+
+ DHCP6 leafDhcp = Dhcp6HandlerUtil.getDhcp6Leaf(dhcp6Packet);
+ ipInfo = extractIpAddress(leafDhcp);
+ if (ipInfo == null) {
+ log.debug("ip is null");
+ } else {
+ if (isMsgRelease) {
+ Route routeForIP = new Route(Route.Source.DHCP, ipInfo.ip6Address.toIpPrefix(), nextHopIp);
+ log.debug("removing route of 128 address for indirectly connected.");
+ log.debug("128 ip {}, nexthop {}",
+ HexString.toHexString(ipInfo.ip6Address.toOctets(), ":"),
+ HexString.toHexString(nextHopIp.toOctets(), ":"));
+ routeStore.removeRoute(routeForIP);
+ }
+ }
+
+ pdInfo = extractPrefix(leafDhcp);
+ if (pdInfo == null) {
+ log.debug("ipPrefix is null ");
+ } else {
+ if (isMsgRelease) {
+ Route routeForPrefix = new Route(Route.Source.DHCP, pdInfo.pdPrefix, nextHopIp);
+ log.debug("removing route of PD for indirectly connected.");
+ log.debug("pd ip {}, nexthop {}",
+ HexString.toHexString(pdInfo.pdPrefix.address().toOctets(), ":"),
+ HexString.toHexString(nextHopIp.toOctets(), ":"));
+
+ routeStore.removeRoute(routeForPrefix);
+ if (this.dhcpFpmEnabled) {
+ dhcpFpmPrefixStore.removeFpmRecord(pdInfo.pdPrefix);
+ }
+ }
+ }
+ leafMsgType = leafDhcp.getMsgType();
+ }
+
+ if (isMsgRelease) {
+ log.debug("DHCP6 RELEASE msg.");
+ if (record != null) {
+ if (ipInfo != null) {
+ log.debug("DhcpRelay Record ip6Address is set to null.");
+ record.ip6Address(null);
+ }
+ if (pdInfo != null) {
+ log.debug("DhcpRelay Record pdPrefix is set to null.");
+ }
+
+ if (!record.ip6Address().isPresent() && !record.pdPrefix().isPresent()) {
+ log.warn("IP6 address and IP6 PD both are null. Remove record.");
+ // do not remove a record. Let timer task handler it.
+ //dhcpRelayStore.removeDhcpRecord(HostId.hostId(leafClientMac, vlanId));
+ }
+ }
+ }
+
+ if (record != null) {
+ record.getV6Counters().incrementCounter(Dhcp6HandlerUtil.getMsgTypeStr(leafMsgType));
+ record.addLocation(new HostLocation(location, System.currentTimeMillis()));
+ record.ip6Status(DHCP6.MsgType.getType(leafMsgType));
+ record.setDirectlyConnected(directConnFlag);
+ if (!directConnFlag) {
+ // Update gateway mac address if the host is not directly connected
+ record.nextHop(srcMac);
+ }
+ record.updateLastSeen();
+ }
+ dhcpRelayStore.updateDhcpRecord(leafHostId, record);
+ // TODO Use AtomicInteger for the counters
+ try {
+ recordSemaphore.acquire();
+ try {
+ dhcpRelayCountersStore.incrementCounter(gCount, Dhcp6HandlerUtil.getMsgTypeStr(leafMsgType));
+ } finally {
+ // calling release() after a successful acquire()
+ recordSemaphore.release();
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * add host or route and update dhcp relay record.
+ *
+ * @param directConnFlag flag to show that packet is from directly connected client
+ * @param location client side connect point
+ * @param dhcp6Relay the dhcp6 payload
+ * @param embeddedDhcp6 the dhcp6 payload within relay
+ * @param srcMac client gw/host macAddress
+ * @param clientInterface client interface
+ */
+ private void addHostOrRoute(boolean directConnFlag, ConnectPoint location, DHCP6 dhcp6Relay,
+ DHCP6 embeddedDhcp6, MacAddress srcMac, Interface clientInterface) {
+ log.debug("addHostOrRoute entered.");
+ VlanId vlanId = clientInterface.vlan();
+ Boolean isMsgReply = Dhcp6HandlerUtil.isDhcp6Reply(dhcp6Relay);
+ MacAddress leafClientMac;
+ Byte leafMsgType;
+
+ Dhcp6ClientIdOption clientIdOption = Dhcp6HandlerUtil.extractClientId(directConnFlag, embeddedDhcp6);
+ if (clientIdOption != null) {
+ log.debug("CLIENTID option found {}", clientIdOption);
+ if ((clientIdOption.getDuid().getDuidType() == Dhcp6Duid.DuidType.DUID_LLT) ||
+ (clientIdOption.getDuid().getDuidType() == Dhcp6Duid.DuidType.DUID_LL)) {
+ leafClientMac = MacAddress.valueOf(clientIdOption.getDuid().getLinkLayerAddress());
+ } else {
+ log.warn("Link-Layer Address not supported in CLIENTID option. No DhcpRelay Record created.");
+ dhcpRelayCountersStore.incrementCounter(gCount, DhcpRelayCounters.NO_LINKLOCAL_FAIL);
+ return;
+ }
+ } else {
+ log.warn("CLIENTID option NOT found. No DhcpRelay Record created.");
+ dhcpRelayCountersStore.incrementCounter(gCount, DhcpRelayCounters.NO_CLIENTID_FAIL);
+ return;
+ }
+ HostId leafHostId = HostId.hostId(leafClientMac, vlanId);
+ DhcpRecord record = dhcpRelayStore.getDhcpRecord(leafHostId).orElse(null);
+ if (record == null) {
+ record = new DhcpRecord(HostId.hostId(leafClientMac, vlanId));
+ } else {
+ record = record.clone();
+ }
+
+ IpAddressInfo ipInfo;
+ PdPrefixInfo pdInfo = null;
+ if (directConnFlag) {
+ // Add to host store if it connect to network directly
+ ipInfo = extractIpAddress(embeddedDhcp6);
+ if (ipInfo != null) {
+ if (isMsgReply) {
+ Set<IpAddress> ips = Sets.newHashSet(ipInfo.ip6Address);
+ HostId hostId = HostId.hostId(srcMac, vlanId);
+ Host host = hostService.getHost(hostId);
+ HostLocation hostLocation = new HostLocation(clientInterface.connectPoint(),
+ System.currentTimeMillis());
+ Set<HostLocation> hostLocations = Sets.newHashSet(hostLocation);
+ if (host != null) {
+ // Dual homing support:
+ // if host exists, use old locations and new location
+ hostLocations.addAll(host.locations());
+ }
+ HostDescription desc = new DefaultHostDescription(srcMac, vlanId, hostLocations, ips,
+ false);
+ log.debug("adding Host for directly connected.");
+ log.debug("client mac {} client vlan {} hostlocation {}",
+ HexString.toHexString(srcMac.toBytes(), ":"), vlanId, hostLocation.toString());
+ // Replace the ip when dhcp server give the host new ip address
+ providerService.hostDetected(hostId, desc, false);
+ }
+ } else {
+ log.warn("ipAddress not found. Do not add Host {} for directly connected.",
+ HostId.hostId(srcMac, vlanId).toString());
+ }
+ leafMsgType = embeddedDhcp6.getMsgType();
+ } else {
+ // Add to route store if it does not connect to network directly
+ // pick out the first link-local ip address
+ IpAddress nextHopIp = getFirstIpByHost(directConnFlag, srcMac, vlanId);
+ if (nextHopIp == null) {
+ log.warn("Can't find link-local IP address of gateway mac {} vlanId {}", srcMac, vlanId);
+ dhcpRelayCountersStore.incrementCounter(gCount, DhcpRelayCounters.NO_LINKLOCAL_GW);
+ return;
+ }
+
+ DHCP6 leafDhcp = Dhcp6HandlerUtil.getDhcp6Leaf(embeddedDhcp6);
+ ipInfo = extractIpAddress(leafDhcp);
+ if (ipInfo == null) {
+ log.debug("ip is null");
+ } else {
+ if (isMsgReply) {
+ Route routeForIP = new Route(Route.Source.DHCP, ipInfo.ip6Address.toIpPrefix(), nextHopIp);
+ log.debug("adding Route of 128 address for indirectly connected.");
+ routeStore.replaceRoute(routeForIP);
+ }
+ }
+
+ pdInfo = extractPrefix(leafDhcp);
+ if (pdInfo == null) {
+ log.debug("ipPrefix is null ");
+ } else {
+ if (isMsgReply) {
+ Route routeForPrefix = new Route(Route.Source.DHCP, pdInfo.pdPrefix, nextHopIp);
+ log.debug("adding Route of PD for indirectly connected.");
+ routeStore.replaceRoute(routeForPrefix);
+ if (this.dhcpFpmEnabled) {
+ FpmRecord fpmRecord = new FpmRecord(pdInfo.pdPrefix, nextHopIp, FpmRecord.Type.DHCP_RELAY);
+ dhcpFpmPrefixStore.addFpmRecord(pdInfo.pdPrefix, fpmRecord);
+ }
+ }
+ }
+ leafMsgType = leafDhcp.getMsgType();
+ }
+ if (leafMsgType == DHCP6.MsgType.RELEASE.value() ||
+ (leafMsgType == DHCP6.MsgType.REPLY.value()) && ipInfo == null) {
+ log.warn("DHCP6 RELEASE/REPLY(null ip) from Server. MsgType {}", leafMsgType);
+ //return;
+ }
+
+ record.addLocation(new HostLocation(location, System.currentTimeMillis()));
+
+ if (leafMsgType == DHCP6.MsgType.REPLY.value()) {
+ if (ipInfo != null) {
+ log.debug("IP6 address is being stored into dhcp-relay store.");
+ log.debug("Client IP6 address {}", HexString.toHexString(ipInfo.ip6Address.toOctets(), ":"));
+ record.ip6Address(ipInfo.ip6Address);
+ record.updateAddrPrefTime(ipInfo.prefTime);
+ record.updateLastIp6Update();
+ } else {
+ log.debug("IP6 address is not returned from server. Maybe only PD is returned.");
+ }
+ if (pdInfo != null) {
+ log.debug("IP6 PD address {}",
+ HexString.toHexString(pdInfo.pdPrefix.address().toOctets(), ":"));
+ record.pdPrefix(pdInfo.pdPrefix);
+ record.updatePdPrefTime(pdInfo.prefTime);
+ record.updateLastPdUpdate();
+ } else {
+ log.debug("IP6 PD address is not returned from server. Maybe only IPAddress is returned.");
+ }
+ }
+
+ record.getV6Counters().incrementCounter(Dhcp6HandlerUtil.getMsgTypeStr(leafMsgType));
+ record.ip6Status(DHCP6.MsgType.getType(leafMsgType));
+ record.setDirectlyConnected(directConnFlag);
+ record.updateLastSeen();
+ dhcpRelayStore.updateDhcpRecord(leafHostId, record);
+ // TODO Use AtomicInteger for the counters
+ try {
+ recordSemaphore.acquire();
+ try {
+ dhcpRelayCountersStore.incrementCounter(gCount, Dhcp6HandlerUtil.getMsgTypeStr(leafMsgType));
+ } finally {
+ // calling release() after a successful acquire()
+ recordSemaphore.release();
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private List<InternalPacket> processDhcp6ForwardOnly(PacketContext context,
+ Ethernet clientPacket,
+ Set<Interface> clientInterfaces,
+ DHCP6 dhcpPacket) {
+ ConnectPoint inPort = context.inPacket().receivedFrom();
+ log.trace("Got DHCPv6 on port {}", inPort);
+ boolean directConnFlag = Dhcp6HandlerUtil.directlyConnected(dhcpPacket);
+
+ List<InternalPacket> internalPackets = new ArrayList<>();
+ List<DhcpServerInfo> serverInfoList = findValidServerInfo(directConnFlag);
+
+ for (DhcpServerInfo dhcpServer : serverInfoList) {
+ Interface serverInterface = getServerInterface(dhcpServer);
+ if (serverInterface == null) {
+ log.warn("Can't get server interface, ignore");
+ continue;
+ }
+
+ Ethernet newPacket = Dhcp6HandlerUtil.buildDhcp6PacketFromClient(context,
+ clientPacket, clientInterfaces, dhcpServer, serverInterface);
+ log.trace("Built packet for server {} : {}", dhcpServer, newPacket);
+ internalPackets.add(InternalPacket.internalPacket(newPacket,
+ dhcpServer.getDhcpServerConnectPoint().get()));
+ }
+
+ return internalPackets;
+ }
+
+ private List<InternalPacket> processLQ6PacketFromClient(PacketContext context,
+ Ethernet clientPacket,
+ Set<Interface> clientInterfaces,
+ DHCP6 dhcp6Payload) {
+ ConnectPoint inPort = context.inPacket().receivedFrom();
+ log.info("Got LQ-REQUEST V6 on port {}", inPort);
+ List<Dhcp6Option> 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.DHCP, 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);
+ List<InternalPacket> internalPackets = new ArrayList<>();
+ List<DhcpServerInfo> serverInfoList = findValidServerInfo(directConnFlag);
+ List<DhcpServerInfo> copyServerInfoList = new ArrayList<DhcpServerInfo>(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 =
+ InternalPacket.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.
+ *
+ * @param context packet context
+ * @param clientPacket client ethernet packet
+ * @param clientInterfaces set of client side interfaces
+ */
+ private List<InternalPacket> processDhcp6PacketFromClient(PacketContext context,
+ Ethernet clientPacket,
+ Set<Interface> clientInterfaces) {
+ ConnectPoint receivedFrom = context.inPacket().receivedFrom();
+ Ip6Address relayAgentIp = Dhcp6HandlerUtil.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());
+ dhcpRelayCountersStore.incrementCounter(gCount, DhcpRelayCounters.NO_CLIENT_INTF_MAC);
+ return Lists.newArrayList();
+ }
+
+ 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<InternalPacket> internalPackets = new ArrayList<>();
+ List<DhcpServerInfo> serverInfoList = findValidServerInfo(directConnFlag);
+ List<DhcpServerInfo> copyServerInfoList = new ArrayList<DhcpServerInfo>(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 etherReply = Dhcp6HandlerUtil.buildDhcp6PacketFromClient(context, clientPacket,
+ clientInterfaces, newServerInfo, serverInterface);
+ removeHostOrRoute(directConnFlag, clientConnectionPoint, clientDhcp6, clientPacket,
+ clientIpv6, clientInterface);
+
+ InternalPacket internalPacket = InternalPacket.internalPacket(etherReply,
+ serverInfo.getDhcpServerConnectPoint().get());
+ internalPackets.add(internalPacket);
+ }
+ log.debug("num of client packets to send is{}", internalPackets.size());
+
+ return internalPackets;
+ }
+
+ /**
+ * 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
+ * @return internalPacket toward client
+ */
+ private InternalPacket processDhcp6PacketFromServer(PacketContext context,
+ Ethernet receivedPacket, Set<Interface> recevingInterfaces) {
+ // get dhcp6 header.
+ Ethernet etherReply = receivedPacket.duplicate();
+ IPv6 ipv6Packet = (IPv6) etherReply.getPayload();
+ UDP udpPacket = (UDP) ipv6Packet.getPayload();
+ DHCP6 dhcp6Relay = (DHCP6) udpPacket.getPayload();
+ Boolean directConnFlag = Dhcp6HandlerUtil.directlyConnected(dhcp6Relay);
+
+ DHCP6 embeddedDhcp6 = dhcp6Relay.getOptions().stream()
+ .filter(opt -> opt instanceof Dhcp6RelayOption)
+ .map(BasePacket::getPayload)
+ .map(pld -> (DHCP6) pld)
+ .findFirst()
+ .orElse(null);
+ ConnectPoint inPort = context.inPacket().receivedFrom();
+ DhcpServerInfo foundServerInfo = findServerInfoFromServer(directConnFlag, inPort);
+
+ if (foundServerInfo == null) {
+ log.warn("Cannot find server info for {} server, inPort {}",
+ directConnFlag ? "direct" : "indirect", inPort);
+ dhcpRelayCountersStore.incrementCounter(gCount, DhcpRelayCounters.NO_SERVER_INFO);
+ return null;
+ } else {
+ if (Dhcp6HandlerUtil.isServerIpEmpty(foundServerInfo)) {
+ log.warn("Cannot find server info's ipaddress");
+ dhcpRelayCountersStore.incrementCounter(gCount, DhcpRelayCounters.NO_SERVER_IP6ADDR);
+ return null;
+ }
+ }
+
+ 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...");
+ dhcpRelayCountersStore.incrementCounter(gCount, DhcpRelayCounters.OPTION_MISSING_FAIL);
+ return null;
+ }
+
+ MacAddress peerMac = interfaceIdOption.getMacAddress();
+ String clientConnectionPointStr = new String(interfaceIdOption.getInPort());
+ ConnectPoint clientConnectionPoint = ConnectPoint.deviceConnectPoint(clientConnectionPointStr);
+ VlanId vlanIdInUse = VlanId.vlanId(interfaceIdOption.getVlanId());
+ Interface clientInterface = interfaceService.getInterfacesByPort(clientConnectionPoint)
+ .stream().filter(iface -> Dhcp6HandlerUtil.interfaceContainsVlan(iface, vlanIdInUse))
+ .findFirst().orElse(null);
+ if (clientInterface == null) {
+ log.warn("Cannot get client interface for from packet, abort... vlan {}", vlanIdInUse.toString());
+ dhcpRelayCountersStore.incrementCounter(gCount, DhcpRelayCounters.NO_MATCHING_INTF);
+ return null;
+ }
+ etherReply.setVlanID(vlanIdInUse.toShort());
+
+ MacAddress relayAgentMac = clientInterface.mac();
+ if (relayAgentMac == null) {
+ log.warn("Can not get client interface mac, abort packet..");
+ dhcpRelayCountersStore.incrementCounter(gCount, DhcpRelayCounters.NO_CLIENT_INTF_MAC);
+ return null;
+ }
+ etherReply.setSourceMACAddress(relayAgentMac);
+
+ // find destMac
+ MacAddress clientMac;
+ Ip6Address peerAddress = Ip6Address.valueOf(dhcp6Relay.getPeerAddress());
+ Set<Host> clients = hostService.getHostsByIp(peerAddress);
+ if (clients.isEmpty()) {
+ log.trace("There's no host found for this address {}",
+ HexString.toHexString(dhcp6Relay.getPeerAddress(), ":"));
+ log.trace("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...");
+ dhcpRelayCountersStore.incrementCounter(gCount, DhcpRelayCounters.NO_CLIENT_INTF_MAC);
+ return null;
+ }
+ log.trace("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);
+ }
+
+
+ boolean hostOrRouteAllowed = learnRouteFromLeasequery ||
+ Dhcp6HandlerUtil.getDhcp6LeafMessageType(dhcp6Relay) != MsgType.LEASEQUERY_REPLY;
+ log.debug("Can add host or route: {}", hostOrRouteAllowed);
+
+ if (hostOrRouteAllowed) {
+ // add host or route
+ addHostOrRoute(directConnFlag, clientConnectionPoint, dhcp6Relay, embeddedDhcp6,
+ clientMac, clientInterface);
+ }
+
+ udpPacket.setPayload(embeddedDhcp6);
+ udpPacket.resetChecksum();
+ ipv6Packet.setPayload(udpPacket);
+ etherReply.setPayload(ipv6Packet);
+ return InternalPacket.internalPacket(etherReply, clientConnectionPoint);
+ }
+
+ @Override
+ public void setDhcpFpmEnabled(Boolean enabled) {
+ dhcpFpmEnabled = enabled;
+ }
+
+ @Override
+ public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
+ log.debug("setDefaultDhcpServerConfigs is called.");
+ setDhcpServerConfigs(configs, defaultServerInfoList);
+ }
+
+ @Override
+ public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
+ log.debug("setIndirectDhcpServerConfigs is called.");
+ setDhcpServerConfigs(configs, indirectServerInfoList);
+ }
+
+ public void setDhcpServerConfigs(Collection<DhcpServerConfig> configs, List<DhcpServerInfo> serverInfoList) {
+ log.debug("config size {}.", configs.size());
+
+ 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.
+ Boolean isConfigValid = false;
+ for (DhcpServerConfig serverConfig : configs) {
+ if (serverConfig.getDhcpServerIp6().isPresent()) {
+ isConfigValid = true;
+ break;
+ }
+ }
+ if (!isConfigValid) {
+ log.warn("No IP V6 server address found.");
+ return; // No IP V6 address found
+ }
+ for (DhcpServerInfo oldServerInfo : serverInfoList) {
+ // stop monitoring gateway or server
+ oldServerInfo.getDhcpGatewayIp6().ifPresent(gatewayIp -> {
+ hostService.stopMonitoringIp(gatewayIp);
+ });
+ oldServerInfo.getDhcpServerIp6().ifPresent(serverIp -> {
+ hostService.stopMonitoringIp(serverIp);
+ cancelDhcpPacket(serverIp);
+ });
+ }
+ serverInfoList.clear();
+ for (DhcpServerConfig serverConfig : configs) {
+ // Create new server info according to the config
+ DhcpServerInfo newServerInfo = new DhcpServerInfo(serverConfig,
+ DhcpServerInfo.Version.DHCP_V6);
+ checkState(newServerInfo.getDhcpServerConnectPoint().isPresent(),
+ "Connect point not exists");
+ checkState(newServerInfo.getDhcpServerIp6().isPresent(),
+ "IP of DHCP server not exists");
+
+ log.debug("DHCP server connect point: {}", newServerInfo.getDhcpServerConnectPoint().orElse(null));
+ log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp6().orElse(null));
+
+ Ip6Address serverIp = newServerInfo.getDhcpServerIp6().get();
+ Ip6Address ipToProbe;
+ if (newServerInfo.getDhcpGatewayIp6().isPresent()) {
+ ipToProbe = newServerInfo.getDhcpGatewayIp6().get();
+ } else {
+ ipToProbe = newServerInfo.getDhcpServerIp6().orElse(null);
+ }
+ String hostToProbe = newServerInfo.getDhcpGatewayIp6()
+ .map(ip -> "gateway").orElse("server");
+
+ log.warn("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
+ hostService.startMonitoringIp(ipToProbe);
+
+ Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
+ if (!hosts.isEmpty()) {
+ Host host = hosts.iterator().next();
+ newServerInfo.setDhcpConnectVlan(host.vlan());
+ newServerInfo.setDhcpConnectMac(host.mac());
+ log.warn("Host found host {}", host);
+
+ } else {
+ log.warn("No host found host ip {}", ipToProbe);
+ }
+ // Add new server info
+ synchronized (this) {
+ serverInfoList.add(newServerInfo);
+ }
+ if (!hosts.isEmpty()) {
+ requestDhcpPacket(serverIp);
+ }
+ }
+ }
+
+ class InternalHostListener implements HostListener {
+ @Override
+ public void event(HostEvent event) {
+ switch (event.type()) {
+ case HOST_ADDED:
+ case HOST_UPDATED:
+ case HOST_MOVED:
+ log.trace("Scheduled host event {}", event);
+ hostEventExecutor.execute(() -> hostUpdated(event.subject()));
+ break;
+ case HOST_REMOVED:
+ log.trace("Scheduled host event {}", event);
+ hostEventExecutor.execute(() -> hostRemoved(event.subject()));
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * 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) {
+ hostUpdated(host, defaultServerInfoList);
+ hostUpdated(host, indirectServerInfoList);
+ }
+
+ private void hostUpdated(Host host, List<DhcpServerInfo> serverInfoList) {
+ serverInfoList.stream().forEach(serverInfo -> {
+ Ip6Address serverIp = serverInfo.getDhcpServerIp6().orElse(null);
+ Ip6Address targetIp = serverInfo.getDhcpGatewayIp6().orElse(null);
+
+ if (targetIp == null) {
+ targetIp = serverIp;
+ }
+ if (targetIp != null) {
+ if (host.ipAddresses().contains(targetIp)) {
+ serverInfo.setDhcpConnectMac(host.mac());
+ serverInfo.setDhcpConnectVlan(host.vlan());
+ requestDhcpPacket(serverIp);
+ }
+ }
+ });
+ }
+ /**
+ * 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) {
+ hostRemoved(host, defaultServerInfoList);
+ hostRemoved(host, indirectServerInfoList);
+ }
+
+ private void hostRemoved(Host host, List<DhcpServerInfo> serverInfoList) {
+ serverInfoList.stream().forEach(serverInfo -> {
+ Ip6Address serverIp = serverInfo.getDhcpServerIp6().orElse(null);
+ Ip6Address targetIp = serverInfo.getDhcpGatewayIp6().orElse(null);
+
+ if (targetIp == null) {
+ targetIp = serverIp;
+ }
+ if (targetIp != null) {
+ if (host.ipAddresses().contains(targetIp)) {
+ serverInfo.setDhcpConnectVlan(null);
+ serverInfo.setDhcpConnectMac(null);
+ cancelDhcpPacket(serverIp);
+ }
+ }
+ });
+ }
+
+ /**
+ * 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.
+ *
+ * @param serverInfo server information
+ * @return newServerInfo if host info can be either found or filled in.
+ */
+ private DhcpServerInfo getHostInfoForServerInfo(DhcpServerInfo serverInfo, List<DhcpServerInfo> sererInfoList) {
+ DhcpServerInfo newServerInfo = null;
+ MacAddress dhcpServerConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
+ VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
+ ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
+
+ if (dhcpServerConnectMac != null && dhcpConnectVlan != null) {
+ newServerInfo = serverInfo;
+ log.debug("DHCP server {} host info found. ConnectPt{} Mac {} vlan {}", serverInfo.getDhcpServerIp6(),
+ dhcpServerConnectPoint, dhcpServerConnectMac, dhcpConnectVlan);
+ } else {
+ log.warn("DHCP server {} not resolve yet connectPt {} mac {} vlan {}", serverInfo.getDhcpServerIp6(),
+ dhcpServerConnectPoint, dhcpServerConnectMac, dhcpConnectVlan);
+
+ Ip6Address ipToProbe;
+ if (serverInfo.getDhcpGatewayIp6().isPresent()) {
+ ipToProbe = serverInfo.getDhcpGatewayIp6().get();
+ } else {
+ ipToProbe = serverInfo.getDhcpServerIp6().orElse(null);
+ }
+ String hostToProbe = serverInfo.getDhcpGatewayIp6()
+ .map(ip -> "gateway").orElse("server");
+
+ log.info("Dynamically probing to resolve {} IP {}", hostToProbe, ipToProbe);
+ hostService.startMonitoringIp(ipToProbe);
+
+ Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
+ if (!hosts.isEmpty()) {
+ int serverInfoIndex = sererInfoList.indexOf(serverInfo);
+ Host host = hosts.iterator().next();
+ serverInfo.setDhcpConnectVlan(host.vlan());
+ serverInfo.setDhcpConnectMac(host.mac());
+ // replace the serverInfo in the list
+ sererInfoList.set(serverInfoIndex, serverInfo);
+ newServerInfo = serverInfo;
+ log.warn("Dynamically host found host {}", host);
+ } else {
+ log.warn("No host found host ip {} dynamically", ipToProbe);
+ }
+ }
+ return newServerInfo;
+ }
+
+ /**
+ * Gets Interface facing to the server for default host.
+ *
+ * @param serverInfo server information
+ * @return the Interface facing to the server; null if not found
+ */
+ private Interface getServerInterface(DhcpServerInfo serverInfo) {
+ Interface serverInterface = null;
+
+ ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
+ VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
+
+ if (dhcpServerConnectPoint != null && dhcpConnectVlan != null) {
+ serverInterface = interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
+ .stream()
+ .filter(iface -> Dhcp6HandlerUtil.interfaceContainsVlan(iface, dhcpConnectVlan))
+ .findFirst()
+ .orElse(null);
+ } else {
+ log.warn("DHCP server {} not resolve yet connectPoint {} vlan {}", serverInfo.getDhcpServerIp6(),
+ dhcpServerConnectPoint, dhcpConnectVlan);
+ }
+
+ 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) {
+ TrafficSelector serverSelector =
+ DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
+ .matchIPv6Src(serverIp.toIpPrefix())
+ .build();
+ packetService.cancelPackets(serverSelector,
+ PacketPriority.CONTROL,
+ 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)
+ .matchIPv6Src(serverIp.toIpPrefix())
+ .build();
+ packetService.requestPackets(serverSelector,
+ PacketPriority.CONTROL,
+ 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 =
+ DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
+ .matchIPv6Dst(serverIp.toIpPrefix())
+ .build();
+ packetService.cancelPackets(indirectClientSelector,
+ PacketPriority.CONTROL,
+ appId);
+ indirectClientSelector =
+ DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
+ .matchIPv6Dst(Ip6Address.ALL_DHCP_RELAY_AGENTS_AND_SERVERS.toIpPrefix())
+ .build();
+ packetService.cancelPackets(indirectClientSelector,
+ PacketPriority.CONTROL,
+ appId);
+ indirectClientSelector =
+ DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
+ .matchIPv6Dst(Ip6Address.ALL_DHCP_SERVERS.toIpPrefix())
+ .build();
+ packetService.cancelPackets(indirectClientSelector,
+ PacketPriority.CONTROL,
+ appId);
+
+ // Packet comes from client
+ packetService.cancelPackets(CLIENT_SERVER_SELECTOR,
+ PacketPriority.CONTROL,
+ appId);
+ }
+
+ private void requestClientDhcpPacket(Ip6Address serverIp) {
+ // Packet comes from relay
+ TrafficSelector indirectClientSelector =
+ DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
+ .matchIPv6Dst(serverIp.toIpPrefix())
+ .build();
+ packetService.requestPackets(indirectClientSelector,
+ PacketPriority.CONTROL,
+ appId);
+ indirectClientSelector =
+ DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
+ .matchIPv6Dst(Ip6Address.ALL_DHCP_RELAY_AGENTS_AND_SERVERS.toIpPrefix())
+ .build();
+ packetService.requestPackets(indirectClientSelector,
+ PacketPriority.CONTROL,
+ appId);
+ indirectClientSelector =
+ DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
+ .matchIPv6Dst(Ip6Address.ALL_DHCP_SERVERS.toIpPrefix())
+ .build();
+ packetService.requestPackets(indirectClientSelector,
+ PacketPriority.CONTROL,
+ appId);
+
+ // Packet comes from client
+ packetService.requestPackets(CLIENT_SERVER_SELECTOR,
+ PacketPriority.CONTROL,
+ appId);
+ }
+
+ /**
+ * Process the ignore rules.
+ *
+ * @param deviceId the device id
+ * @param vlanId the vlan to be ignored
+ * @param op the operation, ADD to install; REMOVE to uninstall rules
+ */
+ private void processIgnoreVlanRule(DeviceId deviceId, VlanId vlanId, Objective.Operation op) {
+ AtomicInteger installedCount = new AtomicInteger(DHCP_SELECTORS.size());
+ DHCP_SELECTORS.forEach(trafficSelector -> {
+ TrafficSelector selector = DefaultTrafficSelector.builder(trafficSelector)
+ .matchVlanId(vlanId)
+ .build();
+
+ ForwardingObjective.Builder builder = DefaultForwardingObjective.builder()
+ .withFlag(ForwardingObjective.Flag.VERSATILE)
+ .withSelector(selector)
+ .withPriority(IGNORE_CONTROL_PRIORITY)
+ .withTreatment(DefaultTrafficTreatment.emptyTreatment())
+ .fromApp(appId);
+
+
+ ObjectiveContext objectiveContext = new ObjectiveContext() {
+ @Override
+ public void onSuccess(Objective objective) {
+ log.info("Ignore rule {} (Vlan id {}, device {}, selector {})",
+ op, vlanId, deviceId, selector);
+ int countDown = installedCount.decrementAndGet();
+ if (countDown != 0) {
+ return;
+ }
+ switch (op) {
+ case ADD:
+ ignoredVlans.put(deviceId, vlanId);
+ break;
+ case REMOVE:
+ ignoredVlans.remove(deviceId, vlanId);
+ break;
+ default:
+ log.warn("Unsupported objective operation {}", op);
+ break;
+ }
+ }
+
+ @Override
+ public void onError(Objective objective, ObjectiveError error) {
+ log.warn("Can't {} ignore rule (vlan id {}, selector {}, device {}) due to {}",
+ op, vlanId, selector, deviceId, error);
+ }
+ };
+
+ ForwardingObjective fwd;
+ switch (op) {
+ case ADD:
+ fwd = builder.add(objectiveContext);
+ break;
+ case REMOVE:
+ fwd = builder.remove(objectiveContext);
+ break;
+ default:
+ log.warn("Unsupported objective operation {}", op);
+ return;
+ }
+
+ Device device = deviceService.getDevice(deviceId);
+ if (device == null || !device.is(Pipeliner.class)) {
+ log.warn("Device {} is not available now, wait until device is available", deviceId);
+ return;
+ }
+ flowObjectiveService.apply(deviceId, fwd);
+ });
+ }
+
+ /**
+ * Find first ipaddress for a given Host info i.e. mac and vlan.
+ *
+ * @param clientMac client mac
+ * @param vlanId packet's vlan
+ * @return next-hop link-local ipaddress for a given host
+ */
+ private IpAddress getFirstIpByHost(Boolean directConnFlag, MacAddress clientMac, VlanId vlanId) {
+ IpAddress nextHopIp;
+ // pick out the first link-local ip address
+ HostId gwHostId = HostId.hostId(clientMac, vlanId);
+ Host gwHost = hostService.getHost(gwHostId);
+ if (gwHost == null) {
+ log.warn("Can't find gateway host for hostId {}", gwHostId);
+ return null;
+ }
+ if (directConnFlag) {
+ nextHopIp = gwHost.ipAddresses()
+ .stream()
+ .filter(IpAddress::isIp6)
+ .map(IpAddress::getIp6Address)
+ .findFirst()
+ .orElse(null);
+ } else {
+ nextHopIp = gwHost.ipAddresses()
+ .stream()
+ .filter(IpAddress::isIp6)
+ .filter(ip6 -> ip6.isLinkLocal())
+ .map(IpAddress::getIp6Address)
+ .findFirst()
+ .orElse(null);
+ }
+ return nextHopIp;
+ }
+
+ private List<DhcpServerInfo> findValidServerInfo(boolean directConnFlag) {
+ List<DhcpServerInfo> validServerInfo;
+
+ if (directConnFlag || indirectServerInfoList.isEmpty()) {
+ validServerInfo = new ArrayList<DhcpServerInfo>(defaultServerInfoList);
+ } else {
+ validServerInfo = new ArrayList<DhcpServerInfo>(indirectServerInfoList);
+ }
+ return validServerInfo;
+ }
+
+ private DhcpServerInfo findServerInfoFromServer(boolean directConnFlag, ConnectPoint inPort) {
+ List<DhcpServerInfo> validServerInfoList = findValidServerInfo(directConnFlag);
+ DhcpServerInfo foundServerInfo = null;
+ for (DhcpServerInfo serverInfo : validServerInfoList) {
+ if (inPort.equals(serverInfo.getDhcpServerConnectPoint().get())) {
+ foundServerInfo = serverInfo;
+ log.debug("ServerInfo found for Rcv port {} Server Connect Point {} for {}",
+ inPort, serverInfo.getDhcpServerConnectPoint(), directConnFlag ? "direct" : "indirect");
+ break;
+ }
+ }
+ return foundServerInfo;
+ }
+
+ /**
+ * Set the dhcp6 lease expiry poll interval value.
+ *
+ * @param val poll interval value in seconds
+ */
+ @Override
+ public void setDhcp6PollInterval(int val) {
+ dhcp6PollInterval = val;
+ }
+
+ /**
+ * get the dhcp6 lease expiry poll interval value.
+ * This is a private function
+ * @return poll interval value in seconds
+ */
+ private int getDhcp6PollInterval() {
+ return dhcp6PollInterval;
+ }
+
+ /**
+ * Find lease-expired ipaddresses and pd prefixes.
+ * Removing host/route/fpm entries.
+ */
+ public void timeTick() {
+ long currentTime = System.currentTimeMillis();
+ Collection<DhcpRecord> records = dhcpRelayStore.getDhcpRecords();
+
+ log.debug("timeTick called currenttime {} records num {} ", currentTime, records.size());
+
+ records.forEach(record -> {
+ boolean addrOrPdRemoved = false;
+ DHCP6.MsgType ip6Status = record.ip6Status().orElse(null);
+ if (ip6Status == null) {
+ log.debug("record is not valid v6 record.");
+ return;
+ }
+
+ if ((currentTime - record.getLastIp6Update()) >
+ ((record.addrPrefTime() + getDhcp6PollInterval() / 2) * 1000)) {
+ // remove ipaddress from host/route table
+ IpAddress ip = record.ip6Address().orElse(null);
+ if (ip != null) {
+ if (record.directlyConnected()) {
+ providerService.removeIpFromHost(HostId.hostId(record.macAddress(),
+ record.vlanId()), ip);
+ } else {
+ MacAddress gwMac = record.nextHop().orElse(null);
+ if (gwMac == null) {
+ log.warn("Can't find gateway mac address from record {} for ip6Addr", record);
+ return;
+ }
+ IpAddress nextHopIp = getFirstIpByHost(record.directlyConnected(),
+ gwMac,
+ record.vlanId());
+ Route route = new Route(Route.Source.DHCP, ip.toIpPrefix(), nextHopIp);
+ routeStore.removeRoute(route);
+ }
+ record.updateAddrPrefTime(0);
+ record.ip6Address(null);
+ addrOrPdRemoved = true;
+ dhcpRelayStore.updateDhcpRecord(HostId.hostId(record.macAddress(),
+ record.vlanId()), record);
+ log.warn("IP6 address is set to null. delta {} lastUpdate {} addrPrefTime {}",
+ (currentTime - record.getLastIp6Update()), record.getLastIp6Update(),
+ record.addrPrefTime());
+ }
+ }
+ if ((currentTime - record.getLastPdUpdate()) >
+ ((record.pdPrefTime() + getDhcp6PollInterval() / 2) * 1000)) {
+ // remove PD from route/fpm table
+ IpPrefix pdIpPrefix = record.pdPrefix().orElse(null);
+ if (pdIpPrefix != null) {
+ if (record.directlyConnected()) {
+ providerService.removeIpFromHost(HostId.hostId(record.macAddress(), record.vlanId()),
+ pdIpPrefix.address().getIp6Address());
+ } else {
+ MacAddress gwMac = record.nextHop().orElse(null);
+ if (gwMac == null) {
+ log.warn("Can't find gateway mac address from record {} for PD prefix", record);
+ return;
+ }
+ IpAddress nextHopIp = getFirstIpByHost(record.directlyConnected(),
+ gwMac,
+ record.vlanId());
+ Route route = new Route(Route.Source.DHCP, pdIpPrefix, nextHopIp);
+ routeStore.removeRoute(route);
+ if (this.dhcpFpmEnabled) {
+ dhcpFpmPrefixStore.removeFpmRecord(pdIpPrefix);
+ }
+ }
+ record.updatePdPrefTime(0);
+ record.pdPrefix(null);
+ addrOrPdRemoved = true;
+ dhcpRelayStore.updateDhcpRecord(HostId.hostId(record.macAddress(),
+ record.vlanId()), record);
+ log.warn("PD prefix is set to null.delta {} pdPrefTime {}",
+ (currentTime - record.getLastPdUpdate()), record.pdPrefTime());
+ }
+ }
+ if (addrOrPdRemoved &&
+ !record.ip6Address().isPresent() && !record.pdPrefix().isPresent()) {
+ log.warn("ip6Status {} IP6 address and IP6 PD both are null. Remove record.", ip6Status);
+ dhcpRelayStore.removeDhcpRecord(HostId.hostId(record.macAddress(), record.vlanId()));
+ }
+ }
+ );
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerUtil.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerUtil.java
new file mode 100644
index 0000000..05cc231
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/Dhcp6HandlerUtil.java
@@ -0,0 +1,648 @@
+/*
+ * 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.onosproject.dhcprelay;
+
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.DHCP6.MsgType;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.packet.dhcp.Dhcp6ClientIdOption;
+import org.onlab.packet.dhcp.Dhcp6RelayOption;
+import org.onlab.packet.dhcp.Dhcp6Option;
+
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.UDP;
+
+import org.onlab.util.HexString;
+import org.onosproject.dhcprelay.api.DhcpServerInfo;
+import org.onosproject.dhcprelay.store.DhcpRelayCounters;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.DeviceId;
+
+import org.slf4j.Logger;
+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;
+
+public final class Dhcp6HandlerUtil {
+
+ private static final Logger log = LoggerFactory.getLogger(Dhcp6HandlerUtil.class);
+
+ private Dhcp6HandlerUtil() {
+ }
+
+ // Returns the first v6 interface ip out of a set of interfaces or null.
+ // Checks all interfaces, and ignores v6 interface ips
+ public static 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;
+ }
+
+ /**
+ * Returns the first interface ip from interface.
+ *
+ * @param iface interface of one connect point
+ * @return the first interface IP; null if not exists an IP address in
+ * these interfaces
+ */
+ private static Ip6Address getFirstIpFromInterface(Interface iface) {
+ checkNotNull(iface, "Interface can't be null");
+ return iface.ipAddressesList().stream()
+ .map(InterfaceIpAddress::ipAddress)
+ .filter(IpAddress::isIp6)
+ .map(IpAddress::getIp6Address)
+ .findFirst()
+ .orElse(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 static InternalPacket processLQ6PacketFromServer(
+ List<DhcpServerInfo> defaultServerInfoList,
+ List<DhcpServerInfo> indirectServerInfoList,
+ Interface serverInterface,
+ InterfaceService interfaceService,
+ HostService hostService,
+ PacketContext context,
+ Ethernet receivedPacket, Set<Interface> 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 || 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<Host> 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());
+
+ // 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 InternalPacket.internalPacket(etherReply, clientConnectionPoint);
+ }
+
+ /**
+ * 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
+ */
+ public static DHCP6 dhcp6PacketFromRelayPacket(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
+ */
+ public static 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.debug("Expected dhcp6 within relay pkt, but no dhcp6 leaf found.");
+ break;
+ }
+ }
+ return dhcp6Child;
+ }
+
+ /**
+ * Determine DHCP message type (direct DHCPv6 or wrapped into relay messages).
+ *
+ * @param relayPacket {@link DHCP6} packet to be parsed
+ * @return {@link DHCP6.MsgType} contained message type of dhcpv6 packet/relay-message
+ */
+ public static DHCP6.MsgType getDhcp6LeafMessageType(DHCP6 relayPacket) {
+ checkNotNull(relayPacket);
+ DHCP6 dhcp6Child = getDhcp6Leaf(relayPacket);
+ return DHCP6.MsgType.getType(dhcp6Child != null ? dhcp6Child.getMsgType() : relayPacket.getMsgType());
+ }
+
+ /**
+ * check if DHCP6 relay-reply is reply.
+ *
+ * @param relayPacket dhcp6 relay-reply
+ * @return boolean relay-reply contains ack
+ */
+ public static boolean isDhcp6Reply(DHCP6 relayPacket) {
+ 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
+ */
+ public static boolean isDhcp6Release(DHCP6 dhcp6Payload) {
+ 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;
+ }
+ }
+ }
+
+
+ /**
+ * convert dhcp6 msgType to String.
+ *
+ * @param msgTypeVal msgType byte of dhcp6 packet
+ * @return String string value of dhcp6 msg type
+ */
+ public static String getMsgTypeStr(byte msgTypeVal) {
+ MsgType msgType = DHCP6.MsgType.getType(msgTypeVal);
+ return DHCP6.MsgType.getMsgTypeStr(msgType);
+ }
+
+ /**
+ * find the string of dhcp6 leaf packets's msg type.
+ *
+ * @param directConnFlag boolean value indicating direct/indirect connection
+ * @param dhcp6Packet dhcp6 packet
+ * @return String string value of dhcp6 leaf packet msg type
+ */
+ public static String findLeafMsgType(boolean directConnFlag, DHCP6 dhcp6Packet) {
+ if (directConnFlag) {
+ return getMsgTypeStr(dhcp6Packet.getMsgType());
+ } else {
+ DHCP6 leafDhcp = getDhcp6Leaf(dhcp6Packet);
+ if (leafDhcp != null) {
+ return getMsgTypeStr(leafDhcp.getMsgType());
+ } else {
+ return DhcpRelayCounters.INVALID_PACKET;
+ }
+ }
+ }
+
+ /**
+ * Determind if an Interface contains a vlan id.
+ *
+ * @param iface the Interface
+ * @param vlanId the vlan id
+ * @return true if the Interface contains the vlan id
+ */
+ public static boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
+ if (vlanId.equals(VlanId.NONE)) {
+ // untagged packet, check if vlan untagged or vlan native is not NONE
+ return !iface.vlanUntagged().equals(VlanId.NONE) ||
+ !iface.vlanNative().equals(VlanId.NONE);
+ }
+ // tagged packet, check if the interface contains the vlan
+ return iface.vlanTagged().contains(vlanId);
+ }
+
+ /**
+ * 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
+ */
+ public static 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()) {
+ 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()
+ && dhcp6Payload2.getMsgType() != MsgType.LEASEQUERY_REPLY.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.debug("directlyConnected true.");
+ return true;
+ }
+ }
+ /**
+ * Check if a given server info has v6 ipaddress.
+ *
+ * @param serverInfo server info to check
+ * @return true if server info has v6 ip address; false otherwise
+ */
+ public static boolean isServerIpEmpty(DhcpServerInfo serverInfo) {
+ if (!serverInfo.getDhcpServerIp6().isPresent()) {
+ log.warn("DhcpServerIp not available, use default DhcpServerIp {}",
+ HexString.toHexString(serverInfo.getDhcpServerIp6().get().toOctets()));
+ return true;
+ }
+ return false;
+ }
+
+ private static boolean isConnectMacEmpty(DhcpServerInfo serverInfo, Set<Interface> clientInterfaces) {
+ if (!serverInfo.getDhcpConnectMac().isPresent()) {
+ log.warn("DHCP6 {} not yet resolved .. Aborting DHCP "
+ + "packet processing from client on port: {}",
+ !serverInfo.getDhcpGatewayIp6().isPresent() ? "server IP " + serverInfo.getDhcpServerIp6()
+ : "gateway IP " + serverInfo.getDhcpGatewayIp6(),
+ clientInterfaces.iterator().next().connectPoint());
+ return true;
+ }
+ return false;
+ }
+
+ private static boolean isRelayAgentIpFromCfgEmpty(DhcpServerInfo serverInfo, DeviceId receivedFromDevice) {
+ if (!serverInfo.getRelayAgentIp6(receivedFromDevice).isPresent()) {
+ log.warn("indirect connection: relayAgentIp NOT availale from config file! Use dynamic.");
+ return true;
+ }
+ return false;
+ }
+
+ private static Dhcp6Option getInterfaceIdIdOption(PacketContext context, Ethernet clientPacket) {
+ 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[] vlanIdBytes = new byte[2];
+ vlanIdBytes[0] = (byte) (clientPacket.getVlanID() & 0xff);
+ vlanIdBytes[1] = (byte) ((clientPacket.getVlanID() >> 8) & 0xff);
+ byte[] interfaceIdBytes = new byte[clientSoureMacBytes.length +
+ inPortStringBytes.length + vlanIdBytes.length];
+ log.debug("Length: interfaceIdBytes {} clientSoureMacBytes {} inPortStringBytes {} vlan {}",
+ interfaceIdBytes.length, clientSoureMacBytes.length, inPortStringBytes.length,
+ vlanIdBytes.length);
+
+ System.arraycopy(clientSoureMacBytes, 0, interfaceIdBytes, 0, clientSoureMacBytes.length);
+ System.arraycopy(inPortStringBytes, 0, interfaceIdBytes, clientSoureMacBytes.length,
+ inPortStringBytes.length);
+ System.arraycopy(vlanIdBytes, 0, interfaceIdBytes,
+ clientSoureMacBytes.length + inPortStringBytes.length,
+ vlanIdBytes.length);
+ interfaceId.setData(interfaceIdBytes);
+ interfaceId.setLength((short) interfaceIdBytes.length);
+ log.debug("interfaceId write srcMac {} portString {}",
+ HexString.toHexString(clientSoureMacBytes, ":"), inPortString);
+ return interfaceId;
+ }
+
+ private static void addDhcp6OptionsFromClient(List<Dhcp6Option> options, byte[] dhcp6PacketByte,
+ PacketContext context, Ethernet clientPacket) {
+ 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
+ Dhcp6Option interfaceId = getInterfaceIdIdOption(context, clientPacket);
+ options.add(interfaceId);
+ }
+
+ /**
+ * build the DHCP6 solicit/request packet with gatewayip.
+ *
+ * @param context packet context
+ * @param clientPacket client ethernet packet
+ * @param clientInterfaces set of client side interfaces
+ * @param serverInfo target server which a packet is generated for
+ * @param serverInterface target server interface
+ * @return ethernet packet with dhcp6 packet info
+ */
+ public static Ethernet buildDhcp6PacketFromClient(PacketContext context, Ethernet clientPacket,
+ Set<Interface> clientInterfaces, DhcpServerInfo serverInfo,
+ Interface serverInterface) {
+ ConnectPoint receivedFrom = context.inPacket().receivedFrom();
+ DeviceId receivedFromDevice = receivedFrom.deviceId();
+
+ 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;
+ }
+ IPv6 clientIpv6 = (IPv6) clientPacket.getPayload();
+ UDP clientUdp = (UDP) clientIpv6.getPayload();
+ DHCP6 clientDhcp6 = (DHCP6) clientUdp.getPayload();
+ boolean directConnFlag = directlyConnected(clientDhcp6);
+
+ Ip6Address serverIpFacing = getFirstIpFromInterface(serverInterface);
+ if (serverIpFacing == null || serverInterface.mac() == null) {
+ log.warn("No IP v6 address for server Interface {}", serverInterface);
+ return null;
+ }
+
+ Ethernet etherReply = clientPacket.duplicate();
+ etherReply.setSourceMACAddress(serverInterface.mac());
+
+ // set default info and replace with indirect if available later on.
+ if (serverInfo.getDhcpConnectMac().isPresent()) {
+ etherReply.setDestinationMACAddress(serverInfo.getDhcpConnectMac().get());
+ }
+ if (serverInfo.getDhcpConnectVlan().isPresent()) {
+ etherReply.setVlanID(serverInfo.getDhcpConnectVlan().get().toShort());
+ }
+ IPv6 ipv6Packet = (IPv6) etherReply.getPayload();
+ byte[] peerAddress = clientIpv6.getSourceAddress();
+ ipv6Packet.setSourceAddress(serverIpFacing.toOctets());
+ ipv6Packet.setDestinationAddress(serverInfo.getDhcpServerIp6().get().toOctets());
+ UDP udpPacket = (UDP) ipv6Packet.getPayload();
+ udpPacket.setSourcePort(UDP.DHCP_V6_SERVER_PORT);
+ DHCP6 dhcp6Packet = (DHCP6) udpPacket.getPayload();
+ byte[] dhcp6PacketByte = dhcp6Packet.serialize();
+
+ DHCP6 dhcp6Relay = new DHCP6();
+
+ dhcp6Relay.setMsgType(DHCP6.MsgType.RELAY_FORW.value());
+
+ if (directConnFlag) {
+ dhcp6Relay.setLinkAddress(relayAgentIp.toOctets());
+ } else {
+ if (isServerIpEmpty(serverInfo)) {
+ log.warn("indirect DhcpServerIp empty... use default server ");
+ } else {
+ // Indirect case, replace destination to indirect dhcp server if exist
+ // Check if mac is obtained for valid server ip
+ if (isConnectMacEmpty(serverInfo, clientInterfaces)) {
+ log.warn("indirect Dhcp ConnectMac empty ...");
+ return null;
+ }
+ etherReply.setDestinationMACAddress(serverInfo.getDhcpConnectMac().get());
+ etherReply.setVlanID(serverInfo.getDhcpConnectVlan().get().toShort());
+ ipv6Packet.setDestinationAddress(serverInfo.getDhcpServerIp6().get().toOctets());
+ }
+ if (isRelayAgentIpFromCfgEmpty(serverInfo, receivedFromDevice)) {
+ log.debug("indirect connection: relayAgentIp NOT availale from config file! Use dynamic. {}",
+ HexString.toHexString(relayAgentIp.toOctets(), ":"));
+ serverIpFacing = relayAgentIp;
+ } else {
+ serverIpFacing = serverInfo.getRelayAgentIp6(receivedFromDevice).get();
+ }
+ log.debug("Source IP address set as relay agent IP with value: {}", serverIpFacing);
+ dhcp6Relay.setLinkAddress(serverIpFacing.toOctets());
+ ipv6Packet.setSourceAddress(serverIpFacing.toOctets());
+ }
+ // peer address: address of the client or relay agent from which the message to be relayed was received.
+ dhcp6Relay.setPeerAddress(peerAddress);
+ // directly connected case, hop count is zero; otherwise, hop count + 1
+ if (directConnFlag) {
+ dhcp6Relay.setHopCount((byte) 0);
+ } else {
+ dhcp6Relay.setHopCount((byte) (dhcp6Packet.getHopCount() + 1));
+ }
+
+ List<Dhcp6Option> options = new ArrayList<>();
+ addDhcp6OptionsFromClient(options, dhcp6PacketByte, context, clientPacket);
+ dhcp6Relay.setOptions(options);
+ udpPacket.setPayload(dhcp6Relay);
+ udpPacket.resetChecksum();
+ ipv6Packet.setPayload(udpPacket);
+ ipv6Packet.setHopLimit((byte) 64);
+ etherReply.setPayload(ipv6Packet);
+
+ return etherReply;
+ }
+
+ /**
+ * build the DHCP6 solicit/request packet with gatewayip.
+ *
+ * @param directConnFlag flag indicating if packet is from direct client or not
+ * @param serverInfo server to check its connect point
+ * @return boolean true if serverInfo is found; false otherwise
+ */
+ public static boolean checkDhcpServerConnPt(boolean directConnFlag,
+ DhcpServerInfo serverInfo) {
+ if (serverInfo.getDhcpServerConnectPoint() == null) {
+ log.warn("DHCP6 server connect point for {} connPt {}",
+ directConnFlag ? "direct" : "indirect", serverInfo.getDhcpServerConnectPoint());
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * extract from dhcp6 packet ClientIdOption.
+ *
+ * @param directConnFlag directly connected host
+ * @param dhcp6Payload the dhcp6 payload
+ * @return Dhcp6ClientIdOption clientIdOption, or null if not exists.
+ */
+ static Dhcp6ClientIdOption extractClientId(Boolean directConnFlag, DHCP6 dhcp6Payload) {
+ Dhcp6ClientIdOption clientIdOption;
+
+ if (directConnFlag) {
+ clientIdOption = dhcp6Payload.getOptions()
+ .stream()
+ .filter(opt -> opt instanceof Dhcp6ClientIdOption)
+ .map(opt -> (Dhcp6ClientIdOption) opt)
+ .findFirst()
+ .orElse(null);
+ } else {
+ DHCP6 leafDhcp = Dhcp6HandlerUtil.getDhcp6Leaf(dhcp6Payload);
+ clientIdOption = leafDhcp.getOptions()
+ .stream()
+ .filter(opt -> opt instanceof Dhcp6ClientIdOption)
+ .map(opt -> (Dhcp6ClientIdOption) opt)
+ .findFirst()
+ .orElse(null);
+ }
+
+ return clientIdOption;
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java
new file mode 100644
index 0000000..2b404d9
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/DhcpRelayManager.java
@@ -0,0 +1,676 @@
+/*
+ * Copyright 2016-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.onosproject.dhcprelay;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Streams;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+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.ARP;
+import org.onlab.packet.DHCP;
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.IPacket;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.UDP;
+import org.onlab.packet.VlanId;
+import org.onlab.util.Tools;
+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.api.DhcpRelayService;
+import org.onosproject.dhcprelay.api.DhcpServerInfo;
+import org.onosproject.dhcprelay.config.DefaultDhcpRelayConfig;
+import org.onosproject.dhcprelay.config.DhcpServerConfig;
+import org.onosproject.dhcprelay.config.EnableDhcpFpmConfig;
+import org.onosproject.dhcprelay.config.IndirectDhcpRelayConfig;
+import org.onosproject.dhcprelay.config.IgnoreDhcpConfig;
+import org.onosproject.dhcprelay.store.DhcpRecord;
+import org.onosproject.dhcprelay.store.DhcpRelayStore;
+import org.onosproject.dhcprelay.store.DhcpFpmPrefixStore;
+import org.onosproject.routing.fpm.api.FpmRecord;
+import org.onosproject.net.Device;
+import org.onosproject.net.Host;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.intf.InterfaceService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.HostId;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import static org.onlab.util.Tools.groupedThreads;
+
+
+import com.google.common.collect.ImmutableSet;
+
+import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
+
+/**
+ * DHCP Relay Agent Application Component.
+ */
+@Component(immediate = true)
+@Service
+public class DhcpRelayManager implements DhcpRelayService {
+ public static final String DHCP_RELAY_APP = "org.onosproject.dhcprelay";
+ public static final String ROUTE_STORE_IMPL =
+ "org.onosproject.routeservice.store.RouteStoreImpl";
+
+ private static final TrafficSelector ARP_SELECTOR = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_ARP)
+ .build();
+ private final Logger log = LoggerFactory.getLogger(getClass());
+ private final InternalConfigListener cfgListener = new InternalConfigListener();
+
+ private final Set<ConfigFactory> factories = ImmutableSet.of(
+ new ConfigFactory<ApplicationId, DefaultDhcpRelayConfig>(APP_SUBJECT_FACTORY,
+ DefaultDhcpRelayConfig.class,
+ DefaultDhcpRelayConfig.KEY,
+ true) {
+ @Override
+ public DefaultDhcpRelayConfig createConfig() {
+ return new DefaultDhcpRelayConfig();
+ }
+ },
+ new ConfigFactory<ApplicationId, IndirectDhcpRelayConfig>(APP_SUBJECT_FACTORY,
+ IndirectDhcpRelayConfig.class,
+ IndirectDhcpRelayConfig.KEY,
+ true) {
+ @Override
+ public IndirectDhcpRelayConfig createConfig() {
+ return new IndirectDhcpRelayConfig();
+ }
+ },
+ new ConfigFactory<ApplicationId, IgnoreDhcpConfig>(APP_SUBJECT_FACTORY,
+ IgnoreDhcpConfig.class,
+ IgnoreDhcpConfig.KEY,
+ true) {
+ @Override
+ public IgnoreDhcpConfig createConfig() {
+ return new IgnoreDhcpConfig();
+ }
+ },
+ new ConfigFactory<ApplicationId, EnableDhcpFpmConfig>(APP_SUBJECT_FACTORY,
+ EnableDhcpFpmConfig.class,
+ EnableDhcpFpmConfig.KEY,
+ false) {
+ @Override
+ public EnableDhcpFpmConfig createConfig() {
+ return new EnableDhcpFpmConfig();
+ }
+ }
+ );
+
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NetworkConfigRegistry cfgService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InterfaceService interfaceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DhcpRelayStore dhcpRelayStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ComponentConfigService compCfgService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DhcpFpmPrefixStore dhcpFpmPrefixStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY,
+ target = "(version=4)")
+ protected DhcpHandler v4Handler;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY,
+ target = "(version=6)")
+ protected DhcpHandler v6Handler;
+
+ @Property(name = "arpEnabled", boolValue = true,
+ label = "Enable Address resolution protocol")
+ protected boolean arpEnabled = true;
+
+ @Property(name = "dhcpPollInterval", intValue = 24 * 3600,
+ label = "dhcp relay poll interval")
+ protected int dhcpPollInterval = 24 * 3600;
+
+ @Property(name = "dhcpFpmEnabled", boolValue = false,
+ label = "Enable DhcpRelay Fpm")
+ protected boolean dhcpFpmEnabled = false;
+
+ private ScheduledExecutorService timerExecutor;
+
+ protected DeviceListener deviceListener = new InternalDeviceListener();
+ private DhcpRelayPacketProcessor dhcpRelayPacketProcessor = new DhcpRelayPacketProcessor();
+ private ApplicationId appId;
+
+ /**
+ * One second timer.
+ */
+ class Dhcp6Timer implements Runnable {
+ @Override
+ public void run() {
+ v6Handler.timeTick();
+ }
+ };
+
+ @Activate
+ protected void activate(ComponentContext context) {
+ //start the dhcp relay agent
+ appId = coreService.registerApplication(DHCP_RELAY_APP);
+
+ cfgService.addListener(cfgListener);
+ factories.forEach(cfgService::registerConfigFactory);
+ //update the dhcp server configuration.
+ updateConfig();
+
+ //add the packet processor
+ packetService.addProcessor(dhcpRelayPacketProcessor, PacketProcessor.director(0));
+
+ timerExecutor = Executors.newScheduledThreadPool(1,
+ groupedThreads("dhcpRelay",
+ "config-reloader-%d", log));
+ timerExecutor.scheduleAtFixedRate(new Dhcp6Timer(),
+ 0,
+ dhcpPollInterval,
+ TimeUnit.SECONDS);
+
+ modified(context);
+
+ // Enable distribute route store
+ compCfgService.preSetProperty(ROUTE_STORE_IMPL,
+ "distributed", Boolean.TRUE.toString());
+ compCfgService.registerProperties(getClass());
+
+ deviceService.addListener(deviceListener);
+
+
+
+ log.info("DHCP-RELAY Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ cfgService.removeListener(cfgListener);
+ factories.forEach(cfgService::unregisterConfigFactory);
+ packetService.removeProcessor(dhcpRelayPacketProcessor);
+ cancelArpPackets();
+ compCfgService.unregisterProperties(getClass(), false);
+ deviceService.removeListener(deviceListener);
+ timerExecutor.shutdown();
+
+ log.info("DHCP-RELAY Stopped");
+ }
+
+ @Modified
+ protected void modified(ComponentContext context) {
+ Dictionary<?, ?> properties = context.getProperties();
+ Boolean flag;
+
+ flag = Tools.isPropertyEnabled(properties, "arpEnabled");
+ if (flag != null) {
+ arpEnabled = flag;
+ log.info("Address resolution protocol is {}",
+ arpEnabled ? "enabled" : "disabled");
+ }
+
+ if (arpEnabled) {
+ requestArpPackets();
+ } else {
+ cancelArpPackets();
+ }
+
+ int intervalVal = Tools.getIntegerProperty(properties, "dhcpPollInterval");
+ log.info("DhcpRelay poll interval new {} old {}", intervalVal, dhcpPollInterval);
+ if (intervalVal != dhcpPollInterval) {
+ timerExecutor.shutdown();
+ dhcpPollInterval = intervalVal;
+ timerExecutor = Executors.newScheduledThreadPool(1,
+ groupedThreads("dhcpRelay",
+ "config-reloader-%d", log));
+ timerExecutor.scheduleAtFixedRate(new Dhcp6Timer(),
+ 0,
+ dhcpPollInterval > 1 ? dhcpPollInterval : 1,
+ TimeUnit.SECONDS);
+ v6Handler.setDhcp6PollInterval(dhcpPollInterval);
+ }
+
+ flag = Tools.isPropertyEnabled(properties, "dhcpFpmEnabled");
+ if (flag != null) {
+ boolean oldValue = dhcpFpmEnabled;
+ dhcpFpmEnabled = flag;
+ log.info("DhcpRelay FPM is {}",
+ dhcpFpmEnabled ? "enabled" : "disabled");
+
+ if (dhcpFpmEnabled && !oldValue) {
+ log.info("Dhcp Fpm is enabled.");
+ processDhcpFpmRoutes(true);
+ }
+ if (!dhcpFpmEnabled && oldValue) {
+ log.info("Dhcp Fpm is disabled.");
+ processDhcpFpmRoutes(false);
+ }
+ v6Handler.setDhcpFpmEnabled(dhcpFpmEnabled);
+ }
+ }
+
+ private static List<TrafficSelector> buildClientDhcpSelectors() {
+ return Streams.concat(Dhcp4HandlerImpl.DHCP_SELECTORS.stream(),
+ Dhcp6HandlerImpl.DHCP_SELECTORS.stream())
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Updates DHCP relay app configuration.
+ */
+ private void updateConfig() {
+ DefaultDhcpRelayConfig defaultConfig =
+ cfgService.getConfig(appId, DefaultDhcpRelayConfig.class);
+ IndirectDhcpRelayConfig indirectConfig =
+ cfgService.getConfig(appId, IndirectDhcpRelayConfig.class);
+ IgnoreDhcpConfig ignoreDhcpConfig =
+ cfgService.getConfig(appId, IgnoreDhcpConfig.class);
+
+ if (defaultConfig != null) {
+ updateConfig(defaultConfig);
+ }
+ if (indirectConfig != null) {
+ updateConfig(indirectConfig);
+ }
+ if (ignoreDhcpConfig != null) {
+ updateConfig(ignoreDhcpConfig);
+ }
+ }
+
+ /**
+ * Updates DHCP relay app configuration with given configuration.
+ *
+ * @param config the configuration ot update
+ */
+ protected void updateConfig(Config config) {
+ if (config instanceof IndirectDhcpRelayConfig) {
+ IndirectDhcpRelayConfig indirectConfig = (IndirectDhcpRelayConfig) config;
+ v4Handler.setIndirectDhcpServerConfigs(indirectConfig.dhcpServerConfigs());
+ v6Handler.setIndirectDhcpServerConfigs(indirectConfig.dhcpServerConfigs());
+ } else if (config instanceof DefaultDhcpRelayConfig) {
+ DefaultDhcpRelayConfig defaultConfig = (DefaultDhcpRelayConfig) config;
+ v4Handler.setDefaultDhcpServerConfigs(defaultConfig.dhcpServerConfigs());
+ v6Handler.setDefaultDhcpServerConfigs(defaultConfig.dhcpServerConfigs());
+ }
+ if (config instanceof IgnoreDhcpConfig) {
+ v4Handler.updateIgnoreVlanConfig((IgnoreDhcpConfig) config);
+ v6Handler.updateIgnoreVlanConfig((IgnoreDhcpConfig) config);
+ }
+ }
+
+ protected void removeConfig(Config config) {
+ if (config instanceof IndirectDhcpRelayConfig) {
+ v4Handler.setIndirectDhcpServerConfigs(Collections.emptyList());
+ v6Handler.setIndirectDhcpServerConfigs(Collections.emptyList());
+ } else if (config instanceof DefaultDhcpRelayConfig) {
+ v4Handler.setDefaultDhcpServerConfigs(Collections.emptyList());
+ v6Handler.setDefaultDhcpServerConfigs(Collections.emptyList());
+ }
+ if (config instanceof IgnoreDhcpConfig) {
+ v4Handler.updateIgnoreVlanConfig(null);
+ v6Handler.updateIgnoreVlanConfig(null);
+ }
+ }
+
+ private void processDhcpFpmRoutes(Boolean add) {
+ // needs to restore/remove fpm
+ }
+
+ public boolean isDhcpFpmEnabled() {
+ return dhcpFpmEnabled;
+ }
+
+ /**
+ * Request ARP packet in via PacketService.
+ */
+ private void requestArpPackets() {
+ packetService.requestPackets(ARP_SELECTOR, PacketPriority.CONTROL, appId);
+ }
+
+ /**
+ * Cancel requested ARP packets in via packet service.
+ */
+ private void cancelArpPackets() {
+ packetService.cancelPackets(ARP_SELECTOR, PacketPriority.CONTROL, appId);
+ }
+
+ @Override
+ public Optional<DhcpRecord> getDhcpRecord(HostId hostId) {
+ return dhcpRelayStore.getDhcpRecord(hostId);
+ }
+
+ @Override
+ public Collection<DhcpRecord> getDhcpRecords() {
+ return dhcpRelayStore.getDhcpRecords();
+ }
+ @Override
+ public void updateDhcpRecord(HostId hostId, DhcpRecord dhcpRecord) {
+ dhcpRelayStore.updateDhcpRecord(hostId, dhcpRecord);
+ }
+ @Override
+ public Optional<MacAddress> getDhcpServerMacAddress() {
+ // TODO: depreated it
+ DefaultDhcpRelayConfig config = cfgService.getConfig(appId, DefaultDhcpRelayConfig.class);
+ DhcpServerConfig serverConfig = config.dhcpServerConfigs().get(0);
+ Ip4Address serverip = serverConfig.getDhcpServerIp4().get();
+ return hostService.getHostsByIp(serverip)
+ .stream()
+ .map(Host::mac)
+ .findFirst();
+ }
+
+ @Override
+ public List<DhcpServerInfo> getDefaultDhcpServerInfoList() {
+ return ImmutableList.<DhcpServerInfo>builder()
+ .addAll(v4Handler.getDefaultDhcpServerInfoList())
+ .addAll(v6Handler.getDefaultDhcpServerInfoList())
+ .build();
+ }
+
+ @Override
+ public List<DhcpServerInfo> getIndirectDhcpServerInfoList() {
+ return ImmutableList.<DhcpServerInfo>builder()
+ .addAll(v4Handler.getIndirectDhcpServerInfoList())
+ .addAll(v6Handler.getIndirectDhcpServerInfoList())
+ .build();
+ }
+
+ /**
+ * Gets DHCP data from a packet.
+ *
+ * @param packet the packet
+ * @return the DHCP data; empty if it is not a DHCP packet
+ */
+ private Optional<DHCP> findDhcp(Ethernet packet) {
+ return Stream.of(packet)
+ .filter(Objects::nonNull)
+ .map(Ethernet::getPayload)
+ .filter(p -> p instanceof IPv4)
+ .map(IPacket::getPayload)
+ .filter(Objects::nonNull)
+ .filter(p -> p instanceof UDP)
+ .map(IPacket::getPayload)
+ .filter(Objects::nonNull)
+ .filter(p -> p instanceof DHCP)
+ .map(p -> (DHCP) p)
+ .findFirst();
+ }
+
+ /**
+ * Gets DHCPv6 data from a packet.
+ *
+ * @param packet the packet
+ * @return the DHCPv6 data; empty if it is not a DHCPv6 packet
+ */
+ private Optional<DHCP6> findDhcp6(Ethernet packet) {
+ return Stream.of(packet)
+ .filter(Objects::nonNull)
+ .map(Ethernet::getPayload)
+ .filter(p -> p instanceof IPv6)
+ .map(IPacket::getPayload)
+ .filter(Objects::nonNull)
+ .filter(p -> p instanceof UDP)
+ .map(IPacket::getPayload)
+ .filter(Objects::nonNull)
+ .filter(p -> p instanceof DHCP6)
+ .map(p -> (DHCP6) p)
+ .findFirst();
+ }
+
+
+ private class DhcpRelayPacketProcessor implements PacketProcessor {
+
+ @Override
+ public void process(PacketContext context) {
+ // process the packet and get the payload
+ Ethernet packet = context.inPacket().parsed();
+ if (packet == null) {
+ return;
+ }
+
+ findDhcp(packet).ifPresent(dhcpPayload -> {
+ v4Handler.processDhcpPacket(context, dhcpPayload);
+ });
+
+ findDhcp6(packet).ifPresent(dhcp6Payload -> {
+ v6Handler.processDhcpPacket(context, dhcp6Payload);
+ });
+
+ if (packet.getEtherType() == Ethernet.TYPE_ARP && arpEnabled) {
+ ARP arpPacket = (ARP) packet.getPayload();
+ VlanId vlanId = VlanId.vlanId(packet.getVlanID());
+ Set<Interface> interfaces = interfaceService.
+ getInterfacesByPort(context.inPacket().receivedFrom());
+ //ignore the packets if dhcp server interface is not configured on onos.
+ if (interfaces.isEmpty()) {
+ log.warn("server virtual interface not configured");
+ return;
+ }
+ if ((arpPacket.getOpCode() != ARP.OP_REQUEST)) {
+ // handle request only
+ return;
+ }
+ MacAddress interfaceMac = interfaces.stream()
+ .filter(iface -> iface.vlan().equals(vlanId))
+ .map(Interface::mac)
+ .filter(mac -> !mac.equals(MacAddress.NONE))
+ .findFirst()
+ .orElse(MacAddress.ONOS);
+ if (interfaceMac == null) {
+ // can't find interface mac address
+ return;
+ }
+ processArpPacket(context, packet, interfaceMac);
+ }
+ }
+
+ /**
+ * Processes the ARP Payload and initiates a reply to the client.
+ *
+ * @param context the packet context
+ * @param packet the ethernet payload
+ * @param replyMac mac address to be replied
+ */
+ private void processArpPacket(PacketContext context, Ethernet packet, MacAddress replyMac) {
+ ARP arpPacket = (ARP) packet.getPayload();
+ ARP arpReply = arpPacket.duplicate();
+ arpReply.setOpCode(ARP.OP_REPLY);
+
+ arpReply.setTargetProtocolAddress(arpPacket.getSenderProtocolAddress());
+ arpReply.setTargetHardwareAddress(arpPacket.getSenderHardwareAddress());
+ arpReply.setSenderProtocolAddress(arpPacket.getTargetProtocolAddress());
+ arpReply.setSenderHardwareAddress(replyMac.toBytes());
+
+ // Ethernet Frame.
+ Ethernet ethReply = new Ethernet();
+ ethReply.setSourceMACAddress(replyMac.toBytes());
+ ethReply.setDestinationMACAddress(packet.getSourceMAC());
+ ethReply.setEtherType(Ethernet.TYPE_ARP);
+ ethReply.setVlanID(packet.getVlanID());
+ ethReply.setPayload(arpReply);
+
+ ConnectPoint targetPort = context.inPacket().receivedFrom();
+ TrafficTreatment t = DefaultTrafficTreatment.builder()
+ .setOutput(targetPort.port()).build();
+ OutboundPacket o = new DefaultOutboundPacket(
+ targetPort.deviceId(), t, ByteBuffer.wrap(ethReply.serialize()));
+ if (log.isTraceEnabled()) {
+ log.trace("Relaying ARP packet {} to {}", packet, targetPort);
+ }
+ packetService.emit(o);
+ }
+ }
+
+ /**
+ * Listener for network config events.
+ */
+ private class InternalConfigListener implements NetworkConfigListener {
+ @Override
+ public void event(NetworkConfigEvent event) {
+ switch (event.type()) {
+ case CONFIG_UPDATED:
+ case CONFIG_ADDED:
+ event.config().ifPresent(config -> {
+ updateConfig(config);
+ log.info("{} updated", config.getClass().getSimpleName());
+ });
+ break;
+ case CONFIG_REMOVED:
+ event.prevConfig().ifPresent(config -> {
+ removeConfig(config);
+ log.info("{} removed", config.getClass().getSimpleName());
+ });
+ break;
+ case CONFIG_REGISTERED:
+ case CONFIG_UNREGISTERED:
+ break;
+ default:
+ log.warn("Unsupported event type {}", event.type());
+ break;
+ }
+ }
+
+ @Override
+ public boolean isRelevant(NetworkConfigEvent event) {
+ if (event.configClass().equals(DefaultDhcpRelayConfig.class) ||
+ event.configClass().equals(IndirectDhcpRelayConfig.class) ||
+ event.configClass().equals(IgnoreDhcpConfig.class)) {
+ return true;
+ }
+ log.debug("Ignore irrelevant event class {}", event.configClass().getName());
+ return false;
+ }
+ }
+
+ private class InternalDeviceListener implements DeviceListener {
+
+ @Override
+ public void event(DeviceEvent event) {
+ Device device = event.subject();
+ switch (event.type()) {
+ case DEVICE_ADDED:
+ updateIgnoreVlanConfigs();
+ break;
+ case DEVICE_AVAILABILITY_CHANGED:
+ deviceAvailabilityChanged(device);
+ default:
+ break;
+ }
+ }
+
+ private void deviceAvailabilityChanged(Device device) {
+ if (deviceService.isAvailable(device.id())) {
+ updateIgnoreVlanConfigs();
+ } else {
+ removeIgnoreVlanState();
+ }
+ }
+
+ private void updateIgnoreVlanConfigs() {
+ IgnoreDhcpConfig config = cfgService.getConfig(appId, IgnoreDhcpConfig.class);
+ v4Handler.updateIgnoreVlanConfig(config);
+ v6Handler.updateIgnoreVlanConfig(config);
+ }
+
+ private void removeIgnoreVlanState() {
+ IgnoreDhcpConfig config = cfgService.getConfig(appId, IgnoreDhcpConfig.class);
+ v4Handler.removeIgnoreVlanState(config);
+ v6Handler.removeIgnoreVlanState(config);
+ }
+ }
+
+
+
+ public Optional<FpmRecord> getFpmRecord(IpPrefix prefix) {
+ return dhcpFpmPrefixStore.getFpmRecord(prefix);
+ }
+
+ public Collection<FpmRecord> getFpmRecords() {
+ return dhcpFpmPrefixStore.getFpmRecords();
+ }
+
+ @Override
+ public void addFpmRecord(IpPrefix prefix, FpmRecord fpmRecord) {
+ dhcpFpmPrefixStore.addFpmRecord(prefix, fpmRecord);
+ }
+
+ @Override
+ public Optional<FpmRecord> removeFpmRecord(IpPrefix prefix) {
+ return dhcpFpmPrefixStore.removeFpmRecord(prefix);
+ }
+
+
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/InternalPacket.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/InternalPacket.java
new file mode 100644
index 0000000..70d4ae2
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/InternalPacket.java
@@ -0,0 +1,52 @@
+/*
+ * 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.onosproject.dhcprelay;
+
+import org.onlab.packet.Ethernet;
+import org.onosproject.net.ConnectPoint;
+
+/**
+ * Container for Ethernet packet and destination port.
+ */
+final class InternalPacket {
+ private Ethernet packet;
+ private ConnectPoint destLocation;
+
+ private InternalPacket(Ethernet newPacket, ConnectPoint newLocation) {
+ packet = newPacket;
+ destLocation = newLocation;
+ }
+
+ public Ethernet getPacket() {
+ return packet;
+ }
+
+ public ConnectPoint getDestLocation() {
+ return destLocation;
+ }
+
+ /**
+ * Build {@link InternalPacket} object instance.
+ *
+ * @param newPacket {@link Ethernet} packet to be sent
+ * @param newLocation {@link ConnectPoint} packet destination
+ * @return new instance of {@link InternalPacket} class
+ */
+ public static InternalPacket internalPacket(Ethernet newPacket, ConnectPoint newLocation) {
+ return new InternalPacket(newPacket, newLocation);
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/api/DhcpHandler.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/api/DhcpHandler.java
new file mode 100644
index 0000000..3de3ced
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/api/DhcpHandler.java
@@ -0,0 +1,199 @@
+/*
+ * 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.onosproject.dhcprelay.api;
+
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.dhcprelay.config.DhcpServerConfig;
+import org.onosproject.dhcprelay.config.IgnoreDhcpConfig;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.packet.PacketContext;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * DHCP relay handler.
+ */
+public interface DhcpHandler {
+ /**
+ * Process the DHCP packet before sending to server or client.
+ *
+ * @param context the packet context
+ * @param dhcpPayload the DHCP payload
+ */
+ void processDhcpPacket(PacketContext context, BasePacket dhcpPayload);
+
+ /**
+ * Gets DHCP server IP.
+ *
+ * @return IP address of DHCP server; empty value if not exist
+ * @deprecated 1.12 get the address from config service
+ */
+ @Deprecated
+ default Optional<IpAddress> getDhcpServerIp() {
+ throw new UnsupportedOperationException("Method deprecated");
+ }
+
+ /**
+ * Gets DHCP gateway IP.
+ *
+ * @return IP address of DHCP gateway; empty value if not exist
+ * @deprecated 1.12 get the address from config service
+ */
+ @Deprecated
+ default Optional<IpAddress> getDhcpGatewayIp() {
+ throw new UnsupportedOperationException("Method deprecated");
+ }
+
+ /**
+ * Gets DHCP connect Mac address.
+ *
+ * @return the connect Mac address of server or gateway
+ * @deprecated 1.12 get host mac from host service
+ */
+ @Deprecated
+ default Optional<MacAddress> getDhcpConnectMac() {
+ throw new UnsupportedOperationException("Method deprecated");
+ }
+
+ /**
+ * Sets DHCP gateway IP.
+ *
+ * @param dhcpGatewayIp the DHCP gateway IP
+ * @deprecated 1.12 use setDefaultDhcpServerConfigs or setindirectDhcpServerConfigs
+ */
+ @Deprecated
+ default void setDhcpGatewayIp(IpAddress dhcpGatewayIp) {
+ throw new UnsupportedOperationException("Method deprecated");
+ }
+
+ /**
+ * Sets DHCP connect vlan.
+ *
+ * @param dhcpConnectVlan the DHCP connect vlan
+ * @deprecated 1.12 use setDefaultDhcpServerConfigs or setindirectDhcpServerConfigs
+ */
+ @Deprecated
+ default void setDhcpConnectVlan(VlanId dhcpConnectVlan) {
+ throw new UnsupportedOperationException("Method deprecated");
+ }
+
+ /**
+ * Sets DHCP connect Mac address.
+ *
+ * @param dhcpConnectMac the connect Mac address
+ * @deprecated 1.12 use setDefaultDhcpServerConfigs or setindirectDhcpServerConfigs
+ */
+ @Deprecated
+ default void setDhcpConnectMac(MacAddress dhcpConnectMac) {
+ throw new UnsupportedOperationException("Method deprecated");
+ }
+
+ /**
+ * Sets DHCP server connect point.
+ *
+ * @param dhcpServerConnectPoint the server connect point
+ * @deprecated 1.12 use setDefaultDhcpServerConfigs or setindirectDhcpServerConfigs
+ */
+ @Deprecated
+ default void setDhcpServerConnectPoint(ConnectPoint dhcpServerConnectPoint) {
+ throw new UnsupportedOperationException("Method deprecated");
+ }
+
+ /**
+ * Sets DHCP server IP.
+ *
+ * @param dhcpServerIp the DHCP server IP
+ * @deprecated 1.12 use setDefaultDhcpServerConfigs or setindirectDhcpServerConfigs
+ */
+ @Deprecated
+ default void setDhcpServerIp(IpAddress dhcpServerIp) {
+ throw new UnsupportedOperationException("Method deprecated");
+ }
+
+ /**
+ * Gets list of default DHCP server information.
+ *
+ * @return list of default DHCP server information
+ */
+ default List<DhcpServerInfo> getDefaultDhcpServerInfoList() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Gets list of indirect DHCP server information.
+ *
+ * @return list of indirect DHCP server information
+ */
+ default List<DhcpServerInfo> getIndirectDhcpServerInfoList() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Sets DHCP server config for default case.
+ *
+ * @param configs the config
+ */
+ void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs);
+
+ /**
+ * Sets DHCP server config for indirect case.
+ *
+ * @param configs the config
+ */
+ void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs);
+
+ /**
+ * Push IgnoreDhcpConfig to the handler.
+ *
+ * @param config the config
+ */
+ void updateIgnoreVlanConfig(IgnoreDhcpConfig config);
+
+ /**
+ * Remove internal state for IgnoreDhcp.
+ *
+ * @param config the config
+ */
+ void removeIgnoreVlanState(IgnoreDhcpConfig config);
+
+ /**
+ * Hander for Dhcp expiration poll timer.
+ *
+ */
+ default void timeTick() { }
+
+ /**
+ * Update Dhcp expiration poll timer value.
+ *
+ * @param val the timer interval value
+ */
+ default void setDhcp6PollInterval(int val) { }
+
+ /**
+ * Sets DHCP FPM Enable state.
+ *
+ * @param dhcpFpmFlag flag indicating dhcpFpmEnable state
+ */
+ default void setDhcpFpmEnabled(Boolean dhcpFpmFlag) { }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/api/DhcpRelayService.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/api/DhcpRelayService.java
new file mode 100644
index 0000000..5eb8d9e
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/api/DhcpRelayService.java
@@ -0,0 +1,114 @@
+/*
+ * 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.onosproject.dhcprelay.api;
+
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.dhcprelay.store.DhcpRecord;
+import org.onosproject.routing.fpm.api.FpmRecord;
+import org.onosproject.net.HostId;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+public interface DhcpRelayService {
+ /**
+ * Gets DHCP record for specific host id (mac + vlan).
+ *
+ * @param hostId the id of host
+ * @return the DHCP record of the host
+ */
+ Optional<DhcpRecord> getDhcpRecord(HostId hostId);
+
+ /**
+ * Gets all DHCP records from store.
+ *
+ * @return all DHCP records from store
+ */
+ Collection<DhcpRecord> getDhcpRecords();
+
+ /**
+ * Updates DHCP record for specific host id (mac + vlan).
+ *
+ * @param hostId the id of host
+ * @param dhcpRecord the DHCP record of the host
+ */
+ void updateDhcpRecord(HostId hostId, DhcpRecord dhcpRecord);
+
+ /**
+ * Gets mac address of DHCP server.
+ *
+ * @return the mac address of DHCP server; empty if not exist
+ * @deprecated 1.12, use get DHCP server configs method
+ */
+ @Deprecated
+ Optional<MacAddress> getDhcpServerMacAddress();
+
+ /**
+ * Gets list of default DHCP server information.
+ *
+ * @return list of default DHCP server information
+ */
+ List<DhcpServerInfo> getDefaultDhcpServerInfoList();
+
+ /**
+ * Gets list of indirect DHCP server information.
+ *
+ * @return list of indirect DHCP server information
+ */
+ List<DhcpServerInfo> getIndirectDhcpServerInfoList();
+
+ /**
+ * Add DHCP FPM record to store.
+ *
+ * @param prefix the prefix
+ * @param fpmRecord the fpmRecord
+ */
+ void addFpmRecord(IpPrefix prefix, FpmRecord fpmRecord);
+
+ /**
+ * Delete DHCP FPM record from store.
+ *
+ * @param prefix the prefix
+ * @return DHCP record from store; empty value if it does not exist.
+ */
+ Optional<FpmRecord> removeFpmRecord(IpPrefix prefix);
+
+ /**
+ * Gets PD route record for specific prefix.
+ *
+ * @param prefix PD prefix
+ * @return the PD route record from store
+ */
+ Optional<FpmRecord> getFpmRecord(IpPrefix prefix);
+
+ /**
+ * Gets all PD route records from store.
+ *
+ * @return all PD records from store
+ */
+ Collection<FpmRecord> getFpmRecords();
+
+ /**
+ * Determine if DHCP FPM feature is enabled or not.
+ *
+ * @return boolean value
+ */
+ public boolean isDhcpFpmEnabled();
+
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/api/DhcpServerInfo.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/api/DhcpServerInfo.java
new file mode 100644
index 0000000..74328ec
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/api/DhcpServerInfo.java
@@ -0,0 +1,154 @@
+/*
+ * 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.onosproject.dhcprelay.api;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.collect.Maps;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.dhcprelay.config.DhcpServerConfig;
+
+import java.util.Optional;
+
+/**
+ * class contains DHCP server information.
+ */
+public class DhcpServerInfo extends DhcpServerConfig {
+ public enum Version {
+ DHCP_V4,
+ DHCP_V6
+ }
+ private MacAddress dhcpConnectMac;
+ private VlanId dhcpConnectVlan;
+ private Version version;
+
+ /**
+ * Creates DHCP server information from config.
+ *
+ * @param config DHCP server config
+ * @param version DHCP version for the server
+ */
+ public DhcpServerInfo(DhcpServerConfig config, Version version) {
+ this.relayAgentIps = Maps.newHashMap(config.getRelayAgentIps());
+ this.connectPoint = config.getDhcpServerConnectPoint().orElse(null);
+ this.version = version;
+
+ switch (version) {
+ case DHCP_V4:
+ this.serverIp4Addr = config.getDhcpServerIp4().orElse(null);
+ this.gatewayIp4Addr = config.getDhcpGatewayIp4().orElse(null);
+ break;
+ case DHCP_V6:
+ this.serverIp6Addr = config.getDhcpServerIp6().orElse(null);
+ this.gatewayIp6Addr = config.getDhcpGatewayIp6().orElse(null);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Sets DHCP server or gateway mac address.
+ *
+ * @param dhcpConnectMac the mac address
+ */
+ public void setDhcpConnectMac(MacAddress dhcpConnectMac) {
+ this.dhcpConnectMac = dhcpConnectMac;
+ }
+
+ /**
+ * Sets DHCP server or gateway vlan id.
+ *
+ * @param dhcpConnectVlan the vlan id
+ */
+ public void setDhcpConnectVlan(VlanId dhcpConnectVlan) {
+ this.dhcpConnectVlan = dhcpConnectVlan;
+ }
+
+ /**
+ * Gets DHCP server or gateway mac address.
+ *
+ * @return the mac address
+ */
+ public Optional<MacAddress> getDhcpConnectMac() {
+ return Optional.ofNullable(dhcpConnectMac);
+ }
+
+ /**
+ * Gets DHCP server or gateway vlan id.
+ *
+ * @return the vlan id.
+ */
+ public Optional<VlanId> getDhcpConnectVlan() {
+ return Optional.ofNullable(dhcpConnectVlan);
+ }
+
+ /**
+ * Get DHCP version of the DHCP server.
+ *
+ * @return the version; can be DHCP_V4 or DHCP_V6
+ */
+ public Version getVersion() {
+ return version;
+ }
+
+ @Override
+ public String toString() {
+ MoreObjects.ToStringHelper toStringHelper = MoreObjects.toStringHelper(this);
+ toStringHelper
+ .add("dhcpConnectMac", dhcpConnectMac)
+ .add("dhcpConnectVlan", dhcpConnectVlan)
+ .add("connectPoint", connectPoint)
+ .add("version", version);
+ switch (version) {
+ case DHCP_V4:
+ toStringHelper
+ .add("serverIp4Addr", serverIp4Addr)
+ .add("gatewayIp4Addr", gatewayIp4Addr);
+ break;
+ case DHCP_V6:
+ toStringHelper
+ .add("serverIp6Addr", serverIp6Addr)
+ .add("gatewayIp6Addr", gatewayIp6Addr);
+ break;
+ default:
+ break;
+ }
+ return toStringHelper.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof DhcpServerInfo)) {
+ return false;
+ }
+ DhcpServerInfo that = (DhcpServerInfo) o;
+ return super.equals(o) &&
+ Objects.equal(dhcpConnectMac, that.dhcpConnectMac) &&
+ Objects.equal(dhcpConnectVlan, that.dhcpConnectVlan) &&
+ version == that.version;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(super.hashCode(), dhcpConnectMac, dhcpConnectVlan, version);
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/api/package-info.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/api/package-info.java
new file mode 100644
index 0000000..fa239e8
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/api/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ *
+ */
+
+/**
+ * APIs for DHCP relay application.
+ */
+package org.onosproject.dhcprelay.api;
\ No newline at end of file
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpFpmAddCommand.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpFpmAddCommand.java
new file mode 100644
index 0000000..fd886b9
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpFpmAddCommand.java
@@ -0,0 +1,56 @@
+/*
+ * 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.onosproject.dhcprelay.cli;
+
+//import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.apache.karaf.shell.commands.Argument;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.IpAddress;
+import org.onosproject.routing.fpm.api.FpmRecord;
+import org.onosproject.dhcprelay.api.DhcpRelayService;
+
+/**
+ * Prints Dhcp FPM Routes information.
+ */
+@Command(scope = "onos", name = "dhcp-fpm-add",
+ description = "Add DHCP FPM prefix in dhcp-fpm-store.")
+public class DhcpFpmAddCommand extends AbstractShellCommand {
+
+ private static final DhcpRelayService DHCP_RELAY_SERVICE = get(DhcpRelayService.class);
+
+ @Argument(index = 0, name = "prefix",
+ description = "prefix",
+ required = true, multiValued = false)
+ String prefixString = null;
+
+ @Argument(index = 1, name = "next hop",
+ description = "next hop",
+ required = true, multiValued = false)
+ String nextHopString = null;
+
+ @Override
+ protected void execute() {
+
+ IpPrefix prefix = IpPrefix.valueOf(prefixString);
+ IpAddress nextHop = IpAddress.valueOf(nextHopString);
+ FpmRecord record = new FpmRecord(prefix, nextHop, FpmRecord.Type.DHCP_RELAY);
+
+ DHCP_RELAY_SERVICE.addFpmRecord(prefix, record);
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpFpmDeleteCommand.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpFpmDeleteCommand.java
new file mode 100644
index 0000000..244bcf8
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpFpmDeleteCommand.java
@@ -0,0 +1,45 @@
+/*
+ * 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.onosproject.dhcprelay.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.apache.karaf.shell.commands.Argument;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.dhcprelay.api.DhcpRelayService;
+
+/**
+ * Prints Dhcp FPM Routes information.
+ */
+@Command(scope = "onos", name = "dhcp-fpm-delete",
+ description = "delete DHCP FPM prefix in dhcp-fpm-store")
+public class DhcpFpmDeleteCommand extends AbstractShellCommand {
+
+ private static final DhcpRelayService DHCP_RELAY_SERVICE = get(DhcpRelayService.class);
+
+ @Argument(index = 0, name = "prefix",
+ description = "prefix",
+ required = true, multiValued = false)
+ String prefixString = null;
+
+ @Override
+ protected void execute() {
+ IpPrefix prefix = IpPrefix.valueOf(prefixString);
+
+ DHCP_RELAY_SERVICE.removeFpmRecord(prefix);
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpFpmRoutesCommand.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpFpmRoutesCommand.java
new file mode 100644
index 0000000..9aacac3
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpFpmRoutesCommand.java
@@ -0,0 +1,56 @@
+/*
+ * 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.onosproject.dhcprelay.cli;
+
+//import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+
+import org.onosproject.dhcprelay.api.DhcpRelayService;
+import org.onosproject.routing.fpm.api.FpmRecord;
+
+import java.util.Collection;
+
+/**
+ * Prints Dhcp FPM Routes information.
+ */
+@Command(scope = "onos", name = "dhcp-fpm-routes",
+ description = "DHCP FPM routes cli.")
+public class DhcpFpmRoutesCommand extends AbstractShellCommand {
+ private static final String NO_RECORDS = "No DHCP FPM Route record found";
+ private static final String HEADER = "DHCP FPM Routes records :";
+ private static final String ROUTE = "prefix=%s, next-hop=%s";
+
+
+ private static final DhcpRelayService DHCP_RELAY_SERVICE = get(DhcpRelayService.class);
+
+ @Override
+ protected void execute() {
+
+ print("Dhcp Fpm Feature is %s !", DHCP_RELAY_SERVICE.isDhcpFpmEnabled() ? "enabled" : "disabled");
+ print("\n");
+ Collection<FpmRecord> records = DHCP_RELAY_SERVICE.getFpmRecords();
+ if (records.isEmpty()) {
+ print(NO_RECORDS);
+ return;
+ }
+ print(HEADER);
+ records.forEach(record -> print(ROUTE,
+ record.ipPrefix(),
+ record.nextHop()));
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayAggCountersCommand.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayAggCountersCommand.java
new file mode 100644
index 0000000..a91a53a
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayAggCountersCommand.java
@@ -0,0 +1,82 @@
+/*
+ * 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.onosproject.dhcprelay.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.dhcprelay.api.DhcpRelayService;
+import org.onosproject.dhcprelay.store.DhcpRelayCounters;
+import org.onosproject.dhcprelay.store.DhcpRelayCountersStore;
+
+
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Prints Dhcp FPM Routes information.
+ */
+@Command(scope = "onos", name = "dhcp-relay-agg-counters",
+ description = "DHCP Relay Aggregate Counters cli.")
+public class DhcpRelayAggCountersCommand extends AbstractShellCommand {
+ @Argument(index = 0, name = "reset",
+ description = "reset counters or not",
+ required = false, multiValued = false)
+ String reset = null;
+
+ private static final String HEADER = "DHCP Relay Aggregate Counters :";
+ private static final String GCOUNT = "global";
+ private static final DhcpRelayService DHCP_RELAY_SERVICE = get(DhcpRelayService.class);
+
+ @Override
+ protected void execute() {
+ boolean toResetFlag;
+
+ if (reset != null) {
+ if (reset.equals("reset") || reset.equals("[reset]")) {
+ toResetFlag = true;
+ } else {
+ print("Last parameter is [reset]");
+ return;
+ }
+ } else {
+ toResetFlag = false;
+ }
+
+ print(HEADER);
+
+ DhcpRelayCountersStore counterStore = AbstractShellCommand.get(DhcpRelayCountersStore.class);
+
+ Optional<DhcpRelayCounters> perClassCounters = counterStore.getCounters(GCOUNT);
+
+ if (perClassCounters.isPresent()) {
+ Map<String, Integer> counters = perClassCounters.get().getCounters();
+ if (counters.size() > 0) {
+ counters.forEach((name, value) -> {
+ print("%-30s ............................ %-4d packets", name, value);
+ });
+ } else {
+ print("No counter for {}", GCOUNT);
+ }
+
+ if (toResetFlag) {
+ counterStore.resetCounters(GCOUNT);
+
+ }
+ }
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayCommand.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayCommand.java
new file mode 100644
index 0000000..90816b0
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayCommand.java
@@ -0,0 +1,277 @@
+/*
+ * 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.onosproject.dhcprelay.cli;
+
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.util.Tools;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.dhcprelay.store.DhcpRelayCounters;
+import org.onosproject.dhcprelay.api.DhcpServerInfo;
+import org.onosproject.dhcprelay.api.DhcpRelayService;
+import org.onosproject.dhcprelay.store.DhcpRecord;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.host.HostService;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.Map;
+
+
+/**
+ * Prints DHCP server and DHCP relay status.
+ */
+@Command(scope = "onos", name = "dhcp-relay", description = "DHCP relay app cli.")
+public class DhcpRelayCommand extends AbstractShellCommand {
+ @Argument(index = 0, name = "counter",
+ description = "shows counter values",
+ required = false, multiValued = false)
+ String counter = null;
+
+ @Argument(index = 1, name = "reset",
+ description = "reset counters or not",
+ required = false, multiValued = false)
+ String reset = null;
+
+
+
+ private static final String CONUTER_HEADER = "DHCP Relay Counters :";
+ private static final String COUNTER_HOST = "Counters for id=%s/%s, locations=%s%s";
+
+
+ private static final String HEADER = "DHCP relay records ([D]: Directly connected):";
+ private static final String NO_RECORDS = "No DHCP relay record found";
+ private static final String HOST = "id=%s/%s, locations=%s%s, last-seen=%s, IPv4=%s, IPv6=%s";
+ private static final String DHCP_SERVER_GW = "DHCP Server: %s, %s via %s (Mac: %s)";
+ private static final String DHCP_SERVER = "DHCP Server: %s, %s (Mac: %s)";
+ private static final String MISSING_SERVER_CFG = "DHCP Server info not available";
+ private static final String DIRECTLY = "[D]";
+ private static final String EMPTY = "";
+ private static final String NA = "N/A";
+ private static final String STATUS_FMT = "[%s, %s]";
+ private static final String STATUS_FMT_NH = "[%s via %s, %s]";
+ private static final String STATUS_FMT_V6 = "[%s %d, %d ms %s %d %d ms, %s]";
+ private static final String STATUS_FMT_V6_NH = "[%s %d %d ms, %s %d %d ms via %s, %s]";
+ private static final String DEFAULT_SERVERS = "Default DHCP servers:";
+ private static final String INDIRECT_SERVERS = "Indirect DHCP servers:";
+
+ private static final DhcpRelayService DHCP_RELAY_SERVICE = get(DhcpRelayService.class);
+ private static final HostService HOST_SERVICE = get(HostService.class);
+
+
+ @Override
+ protected void execute() {
+ List<DhcpServerInfo> defaultDhcpServerInfoList = DHCP_RELAY_SERVICE.getDefaultDhcpServerInfoList();
+ List<DhcpServerInfo> indirectDhcpServerInfoList = DHCP_RELAY_SERVICE.getIndirectDhcpServerInfoList();
+
+ if (defaultDhcpServerInfoList.isEmpty() && indirectDhcpServerInfoList.isEmpty()) {
+ print(MISSING_SERVER_CFG);
+ return;
+ }
+
+ if (!defaultDhcpServerInfoList.isEmpty()) {
+ print(DEFAULT_SERVERS);
+ listServers(defaultDhcpServerInfoList);
+ }
+ if (!indirectDhcpServerInfoList.isEmpty()) {
+ print(INDIRECT_SERVERS);
+ listServers(indirectDhcpServerInfoList);
+ }
+
+ // DHCP records
+ Collection<DhcpRecord> records = DHCP_RELAY_SERVICE.getDhcpRecords();
+ if (records.isEmpty()) {
+ print(NO_RECORDS);
+ return;
+ }
+
+ // Handle display of counters
+ boolean toResetFlag;
+
+ if (counter != null) {
+ if (counter.equals("counter") || counter.equals("[counter]")) {
+ print(CONUTER_HEADER);
+ } else {
+ print("first parameter is [counter]");
+ return;
+ }
+ if (reset != null) {
+ if (reset.equals("reset") || reset.equals("[reset]")) {
+ toResetFlag = true;
+ } else {
+ print("Last parameter is [reset]");
+ return;
+ }
+ } else {
+ toResetFlag = false;
+ }
+
+ records.forEach(record -> {
+ print(COUNTER_HOST, record.macAddress(),
+ record.vlanId(),
+ record.locations(),
+ record.directlyConnected() ? DIRECTLY : EMPTY);
+ DhcpRelayCounters v6Counters = record.getV6Counters();
+ Map<String, Integer> countersMap = v6Counters.getCounters();
+ countersMap.forEach((name, value) -> {
+ print("%-30s ............................ %-4d packets", name, value);
+ });
+ if (toResetFlag) {
+ v6Counters.resetCounters();
+ record.updateLastSeen();
+ DHCP_RELAY_SERVICE.updateDhcpRecord(HostId.hostId(record.macAddress(), record.vlanId()), record);
+ }
+ });
+
+
+ return;
+ }
+
+
+ // Handle display of records
+
+ print(HEADER);
+ records.forEach(record -> print(HOST,
+ record.macAddress(),
+ record.vlanId(),
+ record.locations(),
+ record.directlyConnected() ? DIRECTLY : EMPTY,
+ Tools.timeAgo(record.lastSeen()),
+ ip4State(record),
+ ip6State(record)));
+ }
+
+ private void listServers(List<DhcpServerInfo> dhcpServerInfoList) {
+ dhcpServerInfoList.forEach(dhcpServerInfo -> {
+ String connectPoint = dhcpServerInfo.getDhcpServerConnectPoint()
+ .map(Object::toString).orElse(NA);
+ String serverMac = dhcpServerInfo.getDhcpConnectMac()
+ .map(Object::toString).orElse(NA);
+ String gatewayAddress;
+ String serverIp;
+
+ switch (dhcpServerInfo.getVersion()) {
+ case DHCP_V4:
+ gatewayAddress = dhcpServerInfo.getDhcpGatewayIp4()
+ .map(Object::toString).orElse(null);
+ serverIp = dhcpServerInfo.getDhcpServerIp4()
+ .map(Object::toString).orElse(NA);
+ break;
+ case DHCP_V6:
+ gatewayAddress = dhcpServerInfo.getDhcpGatewayIp6()
+ .map(Object::toString).orElse(null);
+ serverIp = dhcpServerInfo.getDhcpServerIp6()
+ .map(Object::toString).orElse(NA);
+ break;
+ default:
+ return;
+ }
+ if (gatewayAddress != null) {
+ print(DHCP_SERVER_GW, connectPoint, serverIp, gatewayAddress, serverMac);
+ } else {
+ print(DHCP_SERVER, connectPoint, serverIp, serverMac);
+ }
+ });
+ }
+
+ private String ip4State(DhcpRecord record) {
+ String nextHopIp = findNextHopIp(IpAddress::isIp4,
+ record.nextHop().orElse(null),
+ record.vlanId());
+ return ipState(record.ip4Address().map(Object::toString).orElse(NA),
+ record.ip4Status().map(Object::toString).orElse(NA),
+ record.directlyConnected(),
+ nextHopIp);
+ }
+
+ private String ip6State(DhcpRecord record) {
+ String nextHopIp = findNextHopIp6(IpAddress::isIp6,
+ record.nextHop().orElse(null),
+ record.vlanId());
+
+ if (record.directlyConnected()) {
+ return String.format(STATUS_FMT_V6,
+ record.ip6Address().map(Object::toString).orElse(NA),
+ record.addrPrefTime(),
+ record.getLastIp6Update(),
+ record.pdPrefix().map(Object::toString).orElse(NA),
+ record.pdPrefTime(),
+ record.getLastPdUpdate(),
+ record.ip6Status().map(Object::toString).orElse(NA));
+ } else {
+ return String.format(STATUS_FMT_V6_NH,
+ record.ip6Address().map(Object::toString).orElse(NA),
+ record.addrPrefTime(),
+ record.getLastIp6Update(),
+ record.pdPrefix().map(Object::toString).orElse(NA),
+ record.pdPrefTime(),
+ record.getLastPdUpdate(),
+ nextHopIp,
+ record.ip6Status().map(Object::toString).orElse(NA));
+ }
+ }
+
+ private String ipState(String ipAddress, String status,
+ boolean directlyConnected,
+ String nextHopIp) {
+ if (directlyConnected) {
+ return String.format(STATUS_FMT, ipAddress, status);
+ } else {
+ return String.format(STATUS_FMT_NH, ipAddress, nextHopIp, status);
+ }
+ }
+
+ private String findNextHopIp(Predicate<IpAddress> ipFilter, MacAddress nextHopMac, VlanId vlanId) {
+ if (ipFilter == null || nextHopMac == null || vlanId == null) {
+ return NA;
+ }
+
+ Host host = HOST_SERVICE.getHost(HostId.hostId(nextHopMac, vlanId));
+ if (host == null) {
+ return NA;
+ }
+ return host.ipAddresses().stream()
+ .filter(ipFilter)
+ .filter(ip -> !ip.isLinkLocal())
+ .map(Object::toString)
+ .findFirst()
+ .orElse(NA);
+ }
+
+ private String findNextHopIp6(Predicate<IpAddress> ipFilter, MacAddress nextHopMac, VlanId vlanId) {
+ if (ipFilter == null || nextHopMac == null || vlanId == null) {
+ return NA;
+ }
+
+ Host host = HOST_SERVICE.getHost(HostId.hostId(nextHopMac, vlanId));
+ if (host == null) {
+ return NA;
+ }
+ return host.ipAddresses().stream()
+ .filter(ipFilter)
+ .filter(ip -> ip.isLinkLocal())
+ .map(Object::toString)
+ .findFirst()
+ .orElse(NA);
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayCounterCompleter.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayCounterCompleter.java
new file mode 100644
index 0000000..8b024a5
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayCounterCompleter.java
@@ -0,0 +1,39 @@
+/*
+ * 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.onosproject.dhcprelay.cli;
+
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+
+import java.util.List;
+import java.util.SortedSet;
+
+/**
+ * Dhcp Relay counter completer.
+ */
+public class DhcpRelayCounterCompleter implements Completer {
+
+ @Override
+ public int complete(String buffer, int cursor, List<String> candidates) {
+ // Delegate string completer
+ StringsCompleter delegate = new StringsCompleter();
+ SortedSet<String> strings = delegate.getStrings();
+ strings.add("counter");
+
+ // Now let the completer do the work for figuring out what to offer.
+ return delegate.complete(buffer, cursor, candidates);
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayResetCompleter.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayResetCompleter.java
new file mode 100644
index 0000000..3eb4404
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/DhcpRelayResetCompleter.java
@@ -0,0 +1,39 @@
+/*
+ * 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.onosproject.dhcprelay.cli;
+
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+
+import java.util.List;
+import java.util.SortedSet;
+
+/**
+ * Dhcp Relay reset completer.
+ */
+public class DhcpRelayResetCompleter implements Completer {
+
+ @Override
+ public int complete(String buffer, int cursor, List<String> candidates) {
+ // Delegate string completer
+ StringsCompleter delegate = new StringsCompleter();
+ SortedSet<String> strings = delegate.getStrings();
+ strings.add("reset");
+
+ // Now let the completer do the work for figuring out what to offer.
+ return delegate.complete(buffer, cursor, candidates);
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/package-info.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/package-info.java
new file mode 100644
index 0000000..2a983fd
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Command line interface for DHCP relay application.
+ */
+package org.onosproject.dhcprelay.cli;
\ No newline at end of file
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/config/DefaultDhcpRelayConfig.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/config/DefaultDhcpRelayConfig.java
new file mode 100644
index 0000000..daf97bf
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/config/DefaultDhcpRelayConfig.java
@@ -0,0 +1,45 @@
+/*
+ * 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.onosproject.dhcprelay.config;
+
+import com.google.common.collect.Lists;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.config.Config;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * DHCP Relay Config class for default use case (directly connected hosts).
+ */
+public class DefaultDhcpRelayConfig extends Config<ApplicationId> {
+ public static final String KEY = "default";
+
+ @Override
+ public boolean isValid() {
+ // check if all configs are valid
+ AtomicBoolean valid = new AtomicBoolean(true);
+ array.forEach(config -> valid.compareAndSet(true, DhcpServerConfig.isValid(config)));
+ return valid.get();
+ }
+
+ public List<DhcpServerConfig> dhcpServerConfigs() {
+ List<DhcpServerConfig> configs = Lists.newArrayList();
+ array.forEach(node -> configs.add(new DhcpServerConfig(node)));
+ return configs;
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/config/DhcpServerConfig.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/config/DhcpServerConfig.java
new file mode 100644
index 0000000..d60c6b0
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/config/DhcpServerConfig.java
@@ -0,0 +1,276 @@
+/*
+ * 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.onosproject.dhcprelay.config;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.base.Objects;
+import com.google.common.collect.Maps;
+import org.apache.commons.lang3.tuple.Pair;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpAddress;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.slf4j.Logger;
+
+import java.util.Map;
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * DHCP server configuration.
+ */
+public class DhcpServerConfig {
+ private final Logger log = getLogger(getClass());
+
+ private static final String DHCP_CONNECT_POINT = "dhcpServerConnectPoint";
+ private static final String DHCP_SERVER_IP = "serverIps";
+ private static final String DHCP_GATEWAY_IP = "gatewayIps";
+ private static final String RELAY_AGENT_IP = "relayAgentIps";
+ private static final String IPV4 = "ipv4";
+ private static final String IPV6 = "ipv6";
+
+ protected ConnectPoint connectPoint;
+ protected Ip4Address serverIp4Addr;
+ protected Ip4Address gatewayIp4Addr;
+ protected Ip6Address serverIp6Addr;
+ protected Ip6Address gatewayIp6Addr;
+ protected Map<DeviceId, Pair<Ip4Address, Ip6Address>> relayAgentIps = Maps.newHashMap();
+
+ protected DhcpServerConfig() {
+ // empty config not allowed here
+ }
+
+ public DhcpServerConfig(JsonNode config) {
+ if (!config.has(DHCP_CONNECT_POINT)) {
+ // connect point doesn't exist
+ throw new IllegalArgumentException("Missing " + DHCP_CONNECT_POINT);
+ }
+ connectPoint = ConnectPoint.deviceConnectPoint(config.path(DHCP_CONNECT_POINT).asText());
+
+ if (!config.has(DHCP_SERVER_IP)) {
+ // server ip doesn't exist
+ throw new IllegalArgumentException("Missing " + DHCP_SERVER_IP);
+ }
+ ArrayNode serverIps = (ArrayNode) config.path(DHCP_SERVER_IP);
+ serverIps.forEach(node -> {
+ if (node.isTextual()) {
+ IpAddress ip = IpAddress.valueOf(node.asText());
+ if (ip.isIp4() && serverIp4Addr == null) {
+ try {
+ serverIp4Addr = ip.getIp4Address();
+ } catch (IllegalArgumentException iae) {
+ log.warn("Invalid IPv4 address {} found in DHCP server config. Ignored.", ip.toString());
+ }
+ }
+ if (ip.isIp6() && serverIp6Addr == null) {
+ try {
+ serverIp6Addr = ip.getIp6Address();
+ } catch (IllegalArgumentException iae) {
+ log.warn("Invalid IPv6 address {} found in DHCP server config. Ignored.", ip.toString());
+ }
+ }
+ }
+ });
+
+ if (config.has(DHCP_GATEWAY_IP)) {
+ ArrayNode gatewayIps = (ArrayNode) config.path(DHCP_GATEWAY_IP);
+ gatewayIps.forEach(node -> {
+ if (node.isTextual()) {
+ IpAddress ip = IpAddress.valueOf(node.asText());
+ if (ip.isIp4() && gatewayIp4Addr == null) {
+ try {
+ gatewayIp4Addr = ip.getIp4Address();
+ } catch (IllegalArgumentException iae) {
+ log.warn("Invalid IPv4 address {} found in DHCP gateway config. Ignored.", ip.toString());
+ }
+ }
+ if (ip.isIp6() && gatewayIp6Addr == null) {
+ try {
+ gatewayIp6Addr = ip.getIp6Address();
+ } catch (IllegalArgumentException iae) {
+ log.warn("Invalid IPv6 address {} found in DHCP gateway config. Ignored.", ip.toString());
+ }
+ }
+ }
+ });
+ }
+ if (config.has(RELAY_AGENT_IP)) {
+ JsonNode relayAgentIpsNode = config.path(RELAY_AGENT_IP);
+ relayAgentIpsNode.fields().forEachRemaining(e -> {
+ DeviceId deviceId = DeviceId.deviceId(e.getKey());
+ JsonNode ips = e.getValue();
+ Ip4Address ipv4 = null;
+ Ip6Address ipv6 = null;
+ if (ips.has(IPV4)) {
+ String ipv4Str = ips.get(IPV4).asText();
+ try {
+ ipv4 = Ip4Address.valueOf(ipv4Str);
+ } catch (IllegalArgumentException iae) {
+ log.warn("Invalid IPv4 address {} found in DHCP relay config. Ignored.", ipv4Str);
+ }
+ }
+ if (ips.has(IPV6)) {
+ String ipv6Str = ips.get(IPV6).asText();
+ try {
+ ipv6 = Ip6Address.valueOf(ipv6Str);
+ } catch (IllegalArgumentException iae) {
+ log.warn("Invalid IPv6 address {} found in DHCP relay config. Ignored.", ipv6Str);
+ }
+ }
+ relayAgentIps.put(deviceId, Pair.of(ipv4, ipv6));
+ });
+ }
+
+ checkNotNull(connectPoint, "Connect point of DHCP server can't be null");
+ checkState(serverIp4Addr != null || serverIp6Addr != null,
+ "Should exist at least one server IP for DHCPv4 or DHCPv6");
+
+ }
+
+ /**
+ * Verify a json config is a valid DHCP server config.
+ *
+ * @param jsonConfig the json config
+ * @return true if valid; false otherwise
+ */
+ public static boolean isValid(JsonNode jsonConfig) {
+ return jsonConfig.has(DHCP_CONNECT_POINT) && jsonConfig.has(DHCP_SERVER_IP);
+ }
+
+ /**
+ * Returns the dhcp server connect point.
+ *
+ * @return dhcp server connect point
+ */
+ public Optional<ConnectPoint> getDhcpServerConnectPoint() {
+ return Optional.ofNullable(connectPoint);
+ }
+
+ /**
+ * Returns the IPv4 address of DHCP server.
+ *
+ * @return IPv4 address of server; empty value if not set
+ */
+ public Optional<Ip4Address> getDhcpServerIp4() {
+ return Optional.ofNullable(serverIp4Addr);
+ }
+
+ /**
+ * Returns the optional IPv4 address of dhcp gateway, if configured.
+ * This option is typically used if the dhcp server is not directly attached
+ * to a switch; For example, the dhcp server may be reached via an external
+ * gateway connected to the dhcpserverConnectPoint.
+ *
+ * @return IPv4 address of gateway; empty value if not set
+ */
+ public Optional<Ip4Address> getDhcpGatewayIp4() {
+ return Optional.ofNullable(gatewayIp4Addr);
+ }
+
+ /**
+ * Returns the IPv6 address of DHCP server.
+ *
+ * @return IPv6 address of server ; empty value if not set
+ */
+ public Optional<Ip6Address> getDhcpServerIp6() {
+ return Optional.ofNullable(serverIp6Addr);
+ }
+
+ /**
+ * Returns the optional IPv6 address of dhcp gateway, if configured.
+ * This option is typically used if the dhcp server is not directly attached
+ * to a switch; For example, the dhcp server may be reached via an external
+ * gateway connected to the dhcpserverConnectPoint.
+ *
+ * @return IPv6 address of gateway; empty value if not set
+ */
+ public Optional<Ip6Address> getDhcpGatewayIp6() {
+ return Optional.ofNullable(gatewayIp6Addr);
+ }
+
+ /**
+ * Returns the optional IPv4 address for relay agent for given device,
+ * if configured.
+ * This option is used if we want to replace the giaddr field in DHCPv4
+ * payload.
+ *
+ * @param deviceId the device
+ * @return the giaddr; empty value if not set
+ */
+ public Optional<Ip4Address> getRelayAgentIp4(DeviceId deviceId) {
+ Pair<Ip4Address, Ip6Address> relayAgentIp = relayAgentIps.get(deviceId);
+ if (relayAgentIp == null) {
+ return Optional.empty();
+ }
+ return Optional.ofNullable(relayAgentIp.getLeft());
+ }
+
+ /**
+ * Returns the optional IPv6 address for relay agent for given device,
+ * if configured.
+ * This option is used if we want to replace the link-address field in DHCPv6
+ * payload.
+ *
+ * @param deviceId the device
+ * @return the link-addr; empty value if not set
+ */
+ public Optional<Ip6Address> getRelayAgentIp6(DeviceId deviceId) {
+ Pair<Ip4Address, Ip6Address> relayAgentIp = relayAgentIps.get(deviceId);
+ if (relayAgentIp == null) {
+ return Optional.empty();
+ }
+ return Optional.ofNullable(relayAgentIp.getRight());
+ }
+
+ /**
+ * Gets all relay agent ips and device mapping.
+ *
+ * @return the mapping
+ */
+ public Map<DeviceId, Pair<Ip4Address, Ip6Address>> getRelayAgentIps() {
+ return relayAgentIps;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ DhcpServerConfig that = (DhcpServerConfig) o;
+ return Objects.equal(connectPoint, that.connectPoint) &&
+ Objects.equal(serverIp4Addr, that.serverIp4Addr) &&
+ Objects.equal(gatewayIp4Addr, that.gatewayIp4Addr) &&
+ Objects.equal(serverIp6Addr, that.serverIp6Addr) &&
+ Objects.equal(gatewayIp6Addr, that.gatewayIp6Addr) &&
+ Objects.equal(relayAgentIps, that.relayAgentIps);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(connectPoint, serverIp4Addr, gatewayIp4Addr,
+ serverIp6Addr, gatewayIp6Addr, relayAgentIps);
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/config/EnableDhcpFpmConfig.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/config/EnableDhcpFpmConfig.java
new file mode 100644
index 0000000..6eb9cf5
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/config/EnableDhcpFpmConfig.java
@@ -0,0 +1,46 @@
+/*
+ * 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.onosproject.dhcprelay.config;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.config.Config;
+
+
+/**
+ * Dhcp Fpm Config.
+ */
+public class EnableDhcpFpmConfig extends Config<ApplicationId> {
+ public static final String KEY = "dhcpFpm";
+ private static final String DHCP_FPM_ENABLE = "enabled";
+
+ @Override
+ public boolean isValid() {
+ if (!hasFields(DHCP_FPM_ENABLE)) {
+ return false;
+ }
+ return isBoolean(DHCP_FPM_ENABLE, FieldPresence.OPTIONAL);
+ }
+
+ /**
+ * Returns whether Dhcp Fpm is enabled.
+ *
+ * @return true if enabled, otherwise false
+ */
+ public boolean getDhcpFpmEnable() {
+ return object.path(DHCP_FPM_ENABLE).asBoolean(false);
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/config/IgnoreDhcpConfig.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/config/IgnoreDhcpConfig.java
new file mode 100644
index 0000000..b4b8adb
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/config/IgnoreDhcpConfig.java
@@ -0,0 +1,56 @@
+/*
+ * 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.onosproject.dhcprelay.config;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.config.Config;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class IgnoreDhcpConfig extends Config<ApplicationId> {
+ public static final String KEY = "ignoreDhcp";
+ private static final String DEVICE_ID = "deviceId";
+ private static final String VLAN_ID = "vlan";
+
+ @Override
+ public boolean isValid() {
+ AtomicBoolean valid = new AtomicBoolean(true);
+ if (array == null) {
+ return false;
+ }
+ array.forEach(node -> {
+ valid.compareAndSet(true, node.has(DEVICE_ID) && node.has(VLAN_ID));
+ });
+ return valid.get();
+ }
+
+ public Multimap<DeviceId, VlanId> ignoredVlans() {
+ Multimap<DeviceId, VlanId> ignored = ArrayListMultimap.create();
+
+ array.forEach(node -> {
+ DeviceId deviceId = DeviceId.deviceId(node.get(DEVICE_ID).asText());
+ VlanId vlanId = VlanId.vlanId((short) node.get(VLAN_ID).asInt());
+ ignored.put(deviceId, vlanId);
+ });
+
+ return ignored;
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/config/IndirectDhcpRelayConfig.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/config/IndirectDhcpRelayConfig.java
new file mode 100644
index 0000000..02e76b2
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/config/IndirectDhcpRelayConfig.java
@@ -0,0 +1,25 @@
+/*
+ * 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.onosproject.dhcprelay.config;
+
+/**
+ * DHCP Relay Config class for indirect use case (indirectly connected hosts).
+ */
+public class IndirectDhcpRelayConfig extends DefaultDhcpRelayConfig {
+ public static final String KEY = "indirect";
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/config/package-info.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/config/package-info.java
new file mode 100644
index 0000000..b9ca580
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/config/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ *
+ */
+
+/**
+ * Configuration utility for DHCP relay app.
+ */
+package org.onosproject.dhcprelay.config;
\ No newline at end of file
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/package-info.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/package-info.java
new file mode 100644
index 0000000..20d1a7a
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-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.
+ */
+
+/**
+ * DHCP-RELAY application.
+ */
+package org.onosproject.dhcprelay;
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpFpmPrefixStore.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpFpmPrefixStore.java
new file mode 100644
index 0000000..694b048
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpFpmPrefixStore.java
@@ -0,0 +1,51 @@
+/*
+ * 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.onosproject.dhcprelay.store;
+import org.onosproject.routing.fpm.api.FpmRecord;
+import org.onosproject.routing.fpm.api.FpmPrefixStore;
+
+
+
+import org.onlab.packet.IpPrefix;
+import java.util.Optional;
+
+
+
+/**
+ * Interface to store DhcpFpm records.
+ */
+
+public interface DhcpFpmPrefixStore extends FpmPrefixStore {
+
+
+ /**
+ * Add a dhcp fpm record.
+ *
+ * @param prefix the route prefix in the advertisement
+ * @param fpmRecord the route for fpm
+ **/
+ public void addFpmRecord(IpPrefix prefix, FpmRecord fpmRecord);
+
+ /**
+ * Remove a dhcp fpm entry
+ * and return the removed record; return empty value if not exists.
+ *
+ * @param prefix the route prefix in the advertisement
+ * @return none
+ **/
+ public Optional<FpmRecord> removeFpmRecord(IpPrefix prefix);
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpFpmRecord.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpFpmRecord.java
new file mode 100644
index 0000000..81eadb7
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpFpmRecord.java
@@ -0,0 +1,85 @@
+/*
+ * 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.onosproject.dhcprelay.store;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import java.util.Objects;
+import com.google.common.base.MoreObjects;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A class to define a Dhcp Fpm record.
+ */
+public class DhcpFpmRecord {
+
+ private IpPrefix prefix;
+ private IpAddress nextHop;
+
+ public DhcpFpmRecord(IpPrefix prefix, IpAddress nextHop) {
+ checkNotNull(prefix, "prefix cannot be null");
+ checkNotNull(nextHop, "ipAddress cannot be null");
+
+ this.prefix = prefix;
+ this.nextHop = nextHop;
+ }
+
+ /**
+ * Gets IP prefix of record.
+ *
+ * @return the IP prefix
+ */
+ public IpPrefix ipPrefix() {
+ return prefix;
+ }
+
+ /**
+ * Gets IP address of record.
+ *
+ * @return the IP address
+ */
+ public IpAddress nextHop() {
+ return nextHop;
+ }
+
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(prefix, nextHop);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof DhcpFpmRecord)) {
+ return false;
+ }
+ DhcpFpmRecord that = (DhcpFpmRecord) obj;
+ return Objects.equals(prefix, that.prefix) &&
+ Objects.equals(nextHop, that.nextHop);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("prefix", prefix)
+ .add("ipAddress", nextHop)
+ .toString();
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpRecord.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpRecord.java
new file mode 100644
index 0000000..f0beff9
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpRecord.java
@@ -0,0 +1,483 @@
+/*
+ * 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.onosproject.dhcprelay.store;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.Sets;
+import org.onlab.packet.DHCP;
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A class to record DHCP from DHCP relay application.
+ */
+public class DhcpRecord {
+ private final Set<HostLocation> locations;
+ private final MacAddress macAddress;
+ private final VlanId vlanId;
+ private MacAddress nextHop;
+ // this will hold the potential next hop change in case
+ // of successfull LQ from another NH for a certain IP
+ private MacAddress nextHopTemp;
+
+ private Ip4Address ip4Address;
+ private DHCP.MsgType ip4Status;
+
+ private Ip6Address ip6Address;
+ private IpPrefix pdPrefix;
+ private DHCP6.MsgType ip6Status;
+
+ private long lastSeen;
+ private long lastIp6Update;
+ private long lastPdUpdate;
+
+ private boolean directlyConnected;
+ private long addrPrefTime;
+ private long pdPrefTime;
+ private DhcpRelayCounters v6Counters;
+
+
+ /**
+ * Creates a DHCP record for a host (mac + vlan).
+ *
+ * @param hostId the host id for the host
+ */
+ public DhcpRecord(HostId hostId) {
+ checkNotNull(hostId, "Host id can't be null");
+
+ this.locations = Sets.newHashSet();
+ this.macAddress = hostId.mac();
+ this.vlanId = hostId.vlanId();
+ this.lastSeen = System.currentTimeMillis();
+ this.directlyConnected = false;
+ this.v6Counters = new DhcpRelayCounters();
+ }
+
+ /**
+ * Gets host locations.
+ *
+ * @return the locations of host
+ */
+ public Set<HostLocation> locations() {
+ return locations;
+ }
+
+ /**
+ * Adds a location to record.
+ *
+ * @param location the location
+ * @return the DHCP record
+ */
+ public DhcpRecord addLocation(HostLocation location) {
+ if (locations.contains(location)) {
+ locations.remove(location);
+ }
+ locations.add(location);
+ return this;
+ }
+
+ /**
+ * Removes a location from record.
+ *
+ * @param location the location
+ * @return the DHCP record
+ */
+ public DhcpRecord removeLocation(HostLocation location) {
+ locations.remove(location);
+ return this;
+ }
+
+ /**
+ * Gets host mac address of this record.
+ *
+ * @return the host mac address
+ */
+ public MacAddress macAddress() {
+ return macAddress;
+ }
+
+ /**
+ * Gets host vlan id of this record.
+ *
+ * @return the host id.
+ */
+ public VlanId vlanId() {
+ return vlanId;
+ }
+
+ /**
+ * Gets IPv4 address which assigned to the host.
+ *
+ * @return the IP address assigned to the host
+ */
+ public Optional<Ip4Address> ip4Address() {
+ return Optional.ofNullable(ip4Address);
+ }
+
+ /**
+ * Sets IPv4 address.
+ *
+ * @param ip4Address the IPv4 address
+ * @return the DHCP record
+ */
+ public DhcpRecord ip4Address(Ip4Address ip4Address) {
+ this.ip4Address = ip4Address;
+ return this;
+ }
+
+ /**
+ * Gets IPv6 address which assigned to the host.
+ *
+ * @return the IP address assigned to the host
+ */
+ public Optional<Ip6Address> ip6Address() {
+ return Optional.ofNullable(ip6Address);
+ }
+
+ /**
+ * Sets IPv6 address.
+ *
+ * @param ip6Address the IPv6 address
+ * @return the DHCP record
+ */
+ public DhcpRecord ip6Address(Ip6Address ip6Address) {
+ this.ip6Address = ip6Address;
+ return this;
+ }
+
+ /**
+ * Gets IPv6 PD address which assigned to the host.
+ *
+ * @return the PD IP address assigned to the host
+ */
+ public Optional<IpPrefix> pdPrefix() {
+ return Optional.ofNullable(pdPrefix);
+ }
+
+ /**
+ * Sets IPv6 PD address.
+ *
+ * @param pdPrefix the IPv6 PD address
+ * @return the DHCP record
+ */
+ public DhcpRecord pdPrefix(IpPrefix pdPrefix) {
+ this.pdPrefix = pdPrefix;
+ return this;
+ }
+
+ /**
+ * Gets the latest time this record updated.
+ *
+ * @return the last time host send or receive DHCP packet
+ */
+ public long lastSeen() {
+ return lastSeen;
+ }
+
+ /**
+ * Updates the update time of this record.
+ *
+ * @return the DHCP record
+ */
+ public DhcpRecord updateLastSeen() {
+ lastSeen = System.currentTimeMillis();
+ return this;
+ }
+
+ /**
+ * Gets the latest time this record updated with ip6 Address.
+ *
+ * @return the last time received DHCP packet provide ip6 Address
+ */
+ public long getLastIp6Update() {
+ return lastIp6Update;
+ }
+
+ /**
+ * Updates the update time of this record is given ip6 Address.
+ *
+ * @return the DHCP record
+ */
+ public DhcpRecord updateLastIp6Update() {
+ lastIp6Update = System.currentTimeMillis();
+ return this;
+ }
+
+ /**
+ * Gets the latest time this record updated with pd Prefix.
+ *
+ * @return the last time received DHCP packet provide pd Prefix
+ */
+ public long getLastPdUpdate() {
+ return lastPdUpdate;
+ }
+
+ /**
+ * Updates the update time of this record is given pd Prefix.
+ *
+ * @return the DHCP record
+ */
+ public DhcpRecord updateLastPdUpdate() {
+ lastPdUpdate = System.currentTimeMillis();
+ return this;
+ }
+
+ /**
+ * Gets the IP Address preferred time for this record.
+ *
+ * @return the preferred lease time for this ip address
+ */
+ public long addrPrefTime() {
+ return addrPrefTime;
+ }
+
+ /**
+ * Updates the IP Address preferred time of this record.
+ *
+ * @param prefTime preferred liftme
+ * @return the DHCP record
+ */
+ public DhcpRecord updateAddrPrefTime(long prefTime) {
+ addrPrefTime = prefTime;
+ return this;
+ }
+
+ /**
+ * Gets the PD Prefix preferred time for this record.
+ *
+ * @return the preferred lease time for this PD prefix
+ */
+ public long pdPrefTime() {
+ return pdPrefTime;
+ }
+
+ /**
+ * Updates the PD Prefix preferred time of this record.
+ *
+ * @param prefTime preferred liftme
+ * @return the DHCP record
+ */
+ public DhcpRecord updatePdPrefTime(long prefTime) {
+ pdPrefTime = prefTime;
+ return this;
+ }
+
+ /**
+ * Indicated that the host is direct connected to the network or not.
+ *
+ * @return true if the host is directly connected to the network; false otherwise
+ */
+ public boolean directlyConnected() {
+ return directlyConnected;
+ }
+
+ /**
+ * Sets the flag which indicated that the host is directly connected to the
+ * network.
+ *
+ * @param directlyConnected the flag to set
+ * @return the DHCP record
+ */
+ public DhcpRecord setDirectlyConnected(boolean directlyConnected) {
+ this.directlyConnected = directlyConnected;
+ return this;
+ }
+
+ /**
+ * Gets the DHCPv4 status of this record.
+ *
+ * @return the DHCPv4 status; empty if not exists
+ */
+ public Optional<DHCP.MsgType> ip4Status() {
+ return Optional.ofNullable(ip4Status);
+ }
+
+ /**
+ * Sets status of DHCPv4.
+ *
+ * @param ip4Status the status
+ * @return the DHCP record
+ */
+ public DhcpRecord ip4Status(DHCP.MsgType ip4Status) {
+ this.ip4Status = ip4Status;
+ return this;
+ }
+
+ /**
+ * Gets the DHCPv6 status of this record.
+ *
+ * @return the DHCPv6 status; empty if not exists
+ */
+ public Optional<DHCP6.MsgType> ip6Status() {
+ return Optional.ofNullable(ip6Status);
+ }
+
+ /**
+ * Sets status of DHCPv6.
+ *
+ * @param ip6Status the DHCPv6 status
+ * @return the DHCP record
+ */
+ public DhcpRecord ip6Status(DHCP6.MsgType ip6Status) {
+ this.ip6Status = ip6Status;
+ return this;
+ }
+
+ /**
+ * Gets nextHop mac address.
+ *
+ * @return the IPv4 nextHop mac address; empty if not exists
+ */
+ public Optional<MacAddress> nextHop() {
+ return Optional.ofNullable(nextHop);
+ }
+
+ /**
+ * Sets nextHop mac address.
+ *
+ * @param nextHop the IPv4 nextHop mac address
+ * @return the DHCP record
+ */
+ public DhcpRecord nextHop(MacAddress nextHop) {
+ this.nextHop = nextHop;
+ return this;
+ }
+
+ /**
+ * Gets temporary nextHop mac address.
+ *
+ * @return the IPv4 nextHop mac address; empty if not exists
+ */
+ public Optional<MacAddress> nextHopTemp() {
+ return Optional.ofNullable(nextHopTemp);
+ }
+
+ /**
+ * Sets temporary nextHop mac address.
+ *
+ * @param nextHop the IPv4 nextHop mac address
+ * @return the DHCP record
+ */
+ public DhcpRecord nextHopTemp(MacAddress nextHop) {
+ this.nextHopTemp = nextHop;
+ return this;
+ }
+
+ /**
+ * Gets dhcp relay counters.
+ *
+ * @return the counter object
+ */
+ public DhcpRelayCounters getV6Counters() {
+ return v6Counters;
+ }
+
+ /**
+ * Clone this DHCP record.
+ *
+ * @return the DHCP record which cloned
+ */
+ public DhcpRecord clone() {
+ DhcpRecord newRecord = new DhcpRecord(HostId.hostId(macAddress, vlanId));
+ locations.forEach(newRecord::addLocation);
+ newRecord.directlyConnected = directlyConnected;
+ newRecord.nextHop = nextHop;
+ newRecord.nextHopTemp = nextHopTemp;
+ newRecord.ip4Address = ip4Address;
+ newRecord.ip4Status = ip4Status;
+ newRecord.ip6Address = ip6Address;
+ newRecord.pdPrefix = pdPrefix;
+ newRecord.ip6Status = ip6Status;
+ newRecord.lastSeen = lastSeen;
+ newRecord.lastIp6Update = lastIp6Update;
+ newRecord.lastPdUpdate = lastPdUpdate;
+ newRecord.addrPrefTime = addrPrefTime;
+ newRecord.pdPrefTime = pdPrefTime;
+ newRecord.v6Counters = v6Counters;
+ return newRecord;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(locations, macAddress, vlanId, ip4Address, ip4Status,
+ nextHop, nextHopTemp, ip6Address, pdPrefix, ip6Status, lastSeen,
+ lastIp6Update, lastPdUpdate, addrPrefTime, pdPrefTime, v6Counters);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof DhcpRecord)) {
+ return false;
+ }
+ DhcpRecord that = (DhcpRecord) obj;
+ return Objects.equals(locations, that.locations) &&
+ Objects.equals(macAddress, that.macAddress) &&
+ Objects.equals(vlanId, that.vlanId) &&
+ Objects.equals(ip4Address, that.ip4Address) &&
+ Objects.equals(ip4Status, that.ip4Status) &&
+ Objects.equals(nextHop, that.nextHop) &&
+ Objects.equals(nextHopTemp, that.nextHopTemp) &&
+ Objects.equals(ip6Address, that.ip6Address) &&
+ Objects.equals(pdPrefix, that.pdPrefix) &&
+ Objects.equals(ip6Status, that.ip6Status) &&
+ Objects.equals(lastSeen, that.lastSeen) &&
+ Objects.equals(lastIp6Update, that.lastIp6Update) &&
+ Objects.equals(lastPdUpdate, that.lastPdUpdate) &&
+ Objects.equals(directlyConnected, that.directlyConnected) &&
+ Objects.equals(addrPrefTime, that.addrPrefTime) &&
+ Objects.equals(pdPrefTime, that.pdPrefTime) &&
+ Objects.equals(v6Counters, that.v6Counters);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("locations", locations)
+ .add("macAddress", macAddress)
+ .add("vlanId", vlanId)
+ .add("ip4Address", ip4Address)
+ .add("ip4State", ip4Status)
+ .add("nextHop", nextHop)
+ .add("nextHopTemp", nextHopTemp)
+ .add("ip6Address", ip6Address)
+ .add("pdPrefix", pdPrefix)
+ .add("ip6State", ip6Status)
+ .add("lastSeen", lastSeen)
+ .add("lastIp6Update", lastIp6Update)
+ .add("lastPdUpdate", lastPdUpdate)
+ .add("directlyConnected", directlyConnected)
+ .add("addPrefTime", addrPrefTime)
+ .add("pdPrefTime", pdPrefTime)
+ .add("v6Counters", v6Counters)
+ .toString();
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayCounters.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayCounters.java
new file mode 100644
index 0000000..2a69c21
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayCounters.java
@@ -0,0 +1,110 @@
+/*
+ * 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.onosproject.dhcprelay.store;
+
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public class DhcpRelayCounters {
+ // common counters
+
+ // IpV6 specific counters
+ public static final String SOLICIT = "SOLICIT";
+ public static final String ADVERTISE = "ADVERTISE";
+ public static final String REQUEST = "REQUEST";
+ public static final String CONFIRM = "CONFIRM";
+ public static final String RENEW = "RENEW";
+ public static final String REBIND = "REBIND";
+ public static final String REPLY = "REPLY";
+ public static final String RELEASE = "RELEASE";
+ public static final String DECLINE = "DECLINE";
+ public static final String RECONFIGURE = "RECONFIGURE";
+ public static final String INFORMATION_REQUEST = "INFORMATION_REQUEST";
+ public static final String RELAY_FORW = "RELAY_FORW";
+ public static final String RELAY_REPL = "RELAY_REPL";
+
+ public static final String NO_LINKLOCAL_GW = "No link-local in Gateway";
+ public static final String NO_LINKLOCAL_FAIL = "No link-local in CLIENT_ID";
+ public static final String NO_CLIENTID_FAIL = "No CLIENT_ID Found";
+ public static final String SVR_CFG_FAIL = "Server Config Error";
+ public static final String OPTION_MISSING_FAIL = "Expected Option missing";
+ public static final String NO_MATCHING_INTF = "No matching Inteface";
+ public static final String NO_CLIENT_INTF_MAC = "No client interface mac";
+ public static final String NO_SERVER_INFO = "No Server info found";
+ public static final String NO_SERVER_IP6ADDR = "No Server ip6 addr found";
+
+ public static final String INVALID_PACKET = "Invalid Packet";
+
+ public static final Set<String> SUPPORTED_COUNTERS =
+ ImmutableSet.of(SOLICIT, ADVERTISE, REQUEST, CONFIRM, RENEW,
+ REBIND, REPLY, RELEASE, DECLINE, RECONFIGURE,
+ INFORMATION_REQUEST, RELAY_FORW, RELAY_REPL,
+ NO_LINKLOCAL_GW, NO_LINKLOCAL_FAIL, NO_CLIENTID_FAIL, SVR_CFG_FAIL, OPTION_MISSING_FAIL,
+ NO_MATCHING_INTF, NO_CLIENT_INTF_MAC, NO_SERVER_INFO, NO_SERVER_IP6ADDR,
+ INVALID_PACKET);
+
+ // TODO Use AtomicInteger for the counters
+ private Map<String, Integer> countersMap = new ConcurrentHashMap<>();
+ public long lastUpdate;
+
+ public void resetCounters() {
+ countersMap.forEach((name, value) -> {
+ countersMap.put(name, 0);
+ });
+ }
+ public boolean incrementCounter(String name) {
+ boolean counterValid = false;
+ if (SUPPORTED_COUNTERS.contains(name)) {
+ Integer counter = countersMap.get(name);
+ if (counter != null) {
+ counter = counter + 1;
+ countersMap.put(name, counter);
+ } else {
+ // this is the first time
+ countersMap.put(name, 1);
+ }
+ lastUpdate = System.currentTimeMillis();
+ counterValid = true;
+ }
+ return counterValid;
+ }
+ public Map<String, Integer> getCounters() {
+ return countersMap;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(countersMap, lastUpdate);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof DhcpRelayCounters)) {
+ return false;
+ }
+ DhcpRelayCounters that = (DhcpRelayCounters) obj;
+ return Objects.equals(countersMap, that.countersMap) &&
+ Objects.equals(lastUpdate, that.lastUpdate);
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayCountersStore.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayCountersStore.java
new file mode 100644
index 0000000..3534871
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayCountersStore.java
@@ -0,0 +1,65 @@
+/*
+ * 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.onosproject.dhcprelay.store;
+
+
+import java.util.Optional;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Stores DHCP Relay Counters records.
+ */
+public interface DhcpRelayCountersStore {
+
+ /**
+ * Creates or updates DHCP record for specific host id (mac + vlan).
+ *
+ * @param counterClass class of counters (direct, indirect, global)
+ * @param counterName name of counter
+ */
+ void incrementCounter(String counterClass, String counterName);
+
+ /**
+ * Gets the DHCP counter record for a given counter class.
+ *
+ * @param counterClass the class of counters (direct, indirect, global)
+ * @return the DHCP counter record for a given counter class; empty if record not exists
+ */
+ Optional<DhcpRelayCounters> getCounters(String counterClass);
+
+ /**
+ * Gets all classes of DHCP counters record from store.
+ *
+ * @return all classes of DHCP counters records from store
+ */
+ Set<Map.Entry<String, DhcpRelayCounters>> getAllCounters();
+
+ /**
+ * Resets counter value for a given counter class.
+ *
+ * @param counterClass the class of counters (direct, indirect, global)
+ */
+ void resetCounters(String counterClass);
+
+ /**
+ * Resets counter value for a all counter classes.
+ *
+ */
+ void resetAllCounters();
+
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayStore.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayStore.java
new file mode 100644
index 0000000..e26d99e
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayStore.java
@@ -0,0 +1,61 @@
+/*
+ * 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.onosproject.dhcprelay.store;
+
+import org.onosproject.net.HostId;
+import org.onosproject.store.Store;
+import org.onosproject.store.StoreDelegate;
+
+import java.util.Collection;
+import java.util.Optional;
+
+/**
+ * Stores DHCP records which relay-ed by DHCP relay application.
+ */
+public interface DhcpRelayStore extends Store<DhcpRelayStoreEvent, StoreDelegate<DhcpRelayStoreEvent>> {
+
+ /**
+ * Creates or updates DHCP record for specific host id (mac + vlan).
+ *
+ * @param hostId the id of host
+ * @param dhcpRecord the DHCP record to update
+ */
+ void updateDhcpRecord(HostId hostId, DhcpRecord dhcpRecord);
+
+ /**
+ * Gets DHCP record for specific host id (mac + vlan).
+ *
+ * @param hostId the id of host
+ * @return the DHCP record of the host; empty if record not exists
+ */
+ Optional<DhcpRecord> getDhcpRecord(HostId hostId);
+
+ /**
+ * Gets all DHCP records from store.
+ *
+ * @return all DHCP records from store
+ */
+ Collection<DhcpRecord> getDhcpRecords();
+
+ /**
+ * Removes record for specific host id (mac + vlan).
+ *
+ * @param hostId the id of host
+ * @return the DHCP record of the host; empty if record not exists
+ */
+ Optional<DhcpRecord> removeDhcpRecord(HostId hostId);
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayStoreEvent.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayStoreEvent.java
new file mode 100644
index 0000000..03f8b6a
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DhcpRelayStoreEvent.java
@@ -0,0 +1,51 @@
+/*
+ * 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.onosproject.dhcprelay.store;
+
+
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Event class for DHCP relay store.
+ */
+public class DhcpRelayStoreEvent extends AbstractEvent<DhcpRelayStoreEvent.Type, DhcpRecord> {
+
+ /**
+ * Types of the event.
+ */
+ public enum Type {
+ /**
+ * A DHCP record has been created or updated.
+ */
+ UPDATED,
+
+ /**
+ * A DHCP record has been removed.
+ */
+ REMOVED
+ }
+
+ /**
+ * Creates a DHCP relay store event by given information.
+ *
+ * @param type the type of event
+ * @param subject the DHCP record of this event
+ */
+ public DhcpRelayStoreEvent(Type type, DhcpRecord subject) {
+ super(type, subject);
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DistributedDhcpRelayCountersStore.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DistributedDhcpRelayCountersStore.java
new file mode 100644
index 0000000..4b7b26c
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DistributedDhcpRelayCountersStore.java
@@ -0,0 +1,130 @@
+/*
+ * 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.onosproject.dhcprelay.store;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.onosproject.store.serializers.KryoNamespaces;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import org.onosproject.store.service.Versioned;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Component(immediate = true)
+@Service
+public class DistributedDhcpRelayCountersStore implements DhcpRelayCountersStore {
+ private static final KryoNamespace.Builder APP_KYRO = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(DhcpRelayCounters.class);
+
+ private Logger log = LoggerFactory.getLogger(getClass());
+ private ConsistentMap<String, DhcpRelayCounters> counters;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+
+ @Activate
+ protected void activated() {
+ ApplicationId appId = coreService.getAppId("org.onosproject.Dhcp6HandlerImpl");
+ counters = storageService.<String, DhcpRelayCounters>consistentMapBuilder()
+ .withSerializer(Serializer.using(APP_KYRO.build()))
+ .withName("Dhcp-Relay-Counters")
+ .withApplicationId(appId)
+ .withPurgeOnUninstall()
+ .build();
+ }
+
+ @Deactivate
+ protected void deactivated() {
+ counters.destroy().join();
+ }
+ @Override
+ public void incrementCounter(String coutnerClass, String counterName) {
+ DhcpRelayCounters countersRecord;
+
+ Versioned<DhcpRelayCounters> vCounters = counters.get(coutnerClass);
+ if (vCounters == null) {
+ countersRecord = new DhcpRelayCounters();
+ } else {
+ countersRecord = vCounters.value();
+ }
+ countersRecord.incrementCounter(counterName);
+ counters.put(coutnerClass, countersRecord);
+ }
+
+ @Override
+ public Set<Map.Entry<String, DhcpRelayCounters>> getAllCounters() {
+ final Set<Map.Entry<String, DhcpRelayCounters>> result =
+ new HashSet<Map.Entry<String, DhcpRelayCounters>>();
+ Set<Map.Entry<String, Versioned<DhcpRelayCounters>>> tmpCounters = counters.entrySet();
+ tmpCounters.forEach(entry -> {
+ String key = entry.getKey();
+ DhcpRelayCounters value = entry.getValue().value();
+ ConcurrentHashMap<String, DhcpRelayCounters> newMap = new ConcurrentHashMap();
+ newMap.put(key, value);
+
+ for (Map.Entry m: newMap.entrySet()) {
+ result.add(m);
+ }
+ });
+ return result;
+ }
+ @Override
+ public Optional<DhcpRelayCounters> getCounters(String counterClass) {
+ DhcpRelayCounters countersRecord;
+ checkNotNull(counterClass, "counter class can't be null");
+ Versioned<DhcpRelayCounters> vCounters = counters.get(counterClass);
+ if (vCounters == null) {
+ return Optional.empty();
+ }
+ return Optional.of(vCounters.value());
+ }
+ @Override
+ public void resetAllCounters() {
+ counters.clear();
+ }
+
+ @Override
+ public void resetCounters(String counterClass) {
+ checkNotNull(counterClass, "counter class can't be null");
+ DhcpRelayCounters countersRecord = counters.get(counterClass).value();
+ countersRecord.resetCounters();
+ counters.put(counterClass, countersRecord);
+ }
+
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DistributedDhcpRelayStore.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DistributedDhcpRelayStore.java
new file mode 100644
index 0000000..884d9cc
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DistributedDhcpRelayStore.java
@@ -0,0 +1,152 @@
+/*
+ * 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.onosproject.dhcprelay.store;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+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.DHCP;
+import org.onlab.packet.DHCP6;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.net.HostId;
+import org.onosproject.store.StoreDelegate;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMapEvent;
+import org.onosproject.store.service.EventuallyConsistentMapListener;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.WallClockTimestamp;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Distributed DHCP relay store.
+ */
+@Component(immediate = true)
+@Service
+public class DistributedDhcpRelayStore implements DhcpRelayStore {
+ private static final KryoNamespace APP_KRYO = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(DhcpRecord.class)
+ .register(DHCP.MsgType.class)
+ .register(DHCP6.MsgType.class)
+ .register(DhcpRelayCounters.class)
+ .build();
+
+ private Logger log = getLogger(getClass());
+ private StoreDelegate<DhcpRelayStoreEvent> delegate;
+ private EventuallyConsistentMap<HostId, DhcpRecord> dhcpRecords;
+ private EventuallyConsistentMapListener<HostId, DhcpRecord> listener;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Activate
+ protected void activated() {
+ dhcpRecords = storageService.<HostId, DhcpRecord>eventuallyConsistentMapBuilder()
+ .withName("DHCP-Relay-Records")
+ .withTimestampProvider((hostId, record) -> {
+ if (record != null) {
+ return new WallClockTimestamp(record.lastSeen());
+ } else {
+ return new WallClockTimestamp();
+ }
+ })
+ .withSerializer(APP_KRYO)
+ .build();
+ listener = new InternalMapListener();
+ dhcpRecords.addListener(listener);
+ }
+
+ @Deactivate
+ protected void deactivated() {
+ dhcpRecords.removeListener(listener);
+ dhcpRecords.destroy().join();
+ }
+
+ @Override
+ public void setDelegate(StoreDelegate<DhcpRelayStoreEvent> delegate) {
+ checkNotNull(delegate, "Delegate can't be null");
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void unsetDelegate(StoreDelegate<DhcpRelayStoreEvent> delegate) {
+ this.delegate = null;
+ }
+
+ @Override
+ public boolean hasDelegate() {
+ return delegate != null;
+ }
+
+ @Override
+ public void updateDhcpRecord(HostId hostId, DhcpRecord dhcpRecord) {
+ checkNotNull(hostId, "Host id can't be null");
+ checkNotNull(dhcpRecord, "DHCP record can't be null");
+ dhcpRecords.put(hostId, dhcpRecord);
+ }
+
+ @Override
+ public Optional<DhcpRecord> getDhcpRecord(HostId hostId) {
+ checkNotNull(hostId, "Host id can't be null");
+ return Optional.ofNullable(dhcpRecords.get(hostId));
+ }
+
+ @Override
+ public Collection<DhcpRecord> getDhcpRecords() {
+ return dhcpRecords.values();
+ }
+
+ @Override
+ public Optional<DhcpRecord> removeDhcpRecord(HostId hostId) {
+ checkNotNull(hostId, "Host id can't be null");
+ return Optional.ofNullable(dhcpRecords.remove(hostId));
+ }
+
+ /**
+ * Internal map listener for DHCP records map.
+ */
+ private class InternalMapListener implements EventuallyConsistentMapListener<HostId, DhcpRecord> {
+ @Override
+ public void event(EventuallyConsistentMapEvent<HostId, DhcpRecord> event) {
+ DhcpRelayStoreEvent.Type eventType;
+ switch (event.type()) {
+ case PUT:
+ eventType = DhcpRelayStoreEvent.Type.UPDATED;
+ break;
+ case REMOVE:
+ eventType = DhcpRelayStoreEvent.Type.REMOVED;
+ break;
+ default:
+ log.warn("Unknown event type {}", event.type());
+ return;
+ }
+ if (delegate != null) {
+ delegate.notify(new DhcpRelayStoreEvent(eventType, event.value()));
+ }
+ }
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DistributedFpmPrefixStore.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DistributedFpmPrefixStore.java
new file mode 100644
index 0000000..bc95d0b
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/DistributedFpmPrefixStore.java
@@ -0,0 +1,167 @@
+/*
+ * 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.onosproject.dhcprelay.store;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.felix.scr.annotations.Property;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.store.StoreDelegate;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMapEvent;
+import org.onosproject.store.service.EventuallyConsistentMapListener;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.WallClockTimestamp;
+import org.onosproject.routing.fpm.api.FpmRecord;
+import org.onosproject.routing.fpm.api.FpmPrefixStoreEvent;
+
+
+
+
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.Optional;
+
+
+
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Persistent Fpm Prefix Store with Listener.
+ */
+@Component(immediate = true)
+@Property(name = "fpm_type", value = "DHCP")
+@Service
+public class DistributedFpmPrefixStore implements DhcpFpmPrefixStore {
+
+ private static final KryoNamespace APP_KRYO = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(FpmRecord.class)
+ .register(FpmRecord.Type.class)
+ .build();
+
+ private Logger log = getLogger(getClass());
+ private StoreDelegate<FpmPrefixStoreEvent> delegate;
+ private EventuallyConsistentMap<IpPrefix, FpmRecord> dhcpFpmRecords;
+ private EventuallyConsistentMapListener<IpPrefix, FpmRecord> listener;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Activate
+ protected void activated() {
+ dhcpFpmRecords = storageService.<IpPrefix, FpmRecord>eventuallyConsistentMapBuilder()
+ .withName("DHCP-FPM-Records")
+ .withTimestampProvider((k, v) -> new WallClockTimestamp())
+ .withSerializer(APP_KRYO)
+ .withPersistence()
+ .build();
+ listener = new InternalMapListener();
+ dhcpFpmRecords.addListener(listener);
+ }
+
+ @Deactivate
+ protected void deactivated() {
+ dhcpFpmRecords.removeListener(listener);
+ dhcpFpmRecords.destroy().join();
+ }
+
+ @Override
+ public void setDelegate(StoreDelegate<FpmPrefixStoreEvent> delegate) {
+ checkNotNull(delegate, "Delegate can't be null");
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void unsetDelegate(StoreDelegate<FpmPrefixStoreEvent> delegate) {
+ this.delegate = null;
+ }
+
+ @Override
+ public boolean hasDelegate() {
+ return delegate != null;
+ }
+
+ @Override
+ public Optional<FpmRecord> getFpmRecord(IpPrefix prefix) {
+ checkNotNull(prefix, "Prefix can't be null");
+ return Optional.ofNullable(dhcpFpmRecords.get(prefix));
+ }
+
+ @Override
+ public Collection<FpmRecord> getFpmRecords() {
+ return dhcpFpmRecords.values();
+ }
+
+ /**
+ * Add a dhcp fpm entry.
+ *
+ * @param prefix the route prefix in the advertisement
+ * @param fpmRecord the route for fpm
+ **/
+ @Override
+ public void addFpmRecord(IpPrefix prefix, FpmRecord fpmRecord) {
+ checkNotNull(prefix, "Prefix can't be null");
+ checkNotNull(fpmRecord, "Fpm record can't be null");
+ dhcpFpmRecords.put(prefix, fpmRecord);
+ }
+
+ /**
+ * Remove a dhcp fpm entry.
+ *
+ * @param prefix the route prefix in the advertisement
+ * @return none
+ **/
+ @Override
+ public Optional<FpmRecord> removeFpmRecord(IpPrefix prefix) {
+ checkNotNull(prefix, "Prefix can't be null");
+ return Optional.ofNullable(dhcpFpmRecords.remove(prefix));
+ }
+
+ /**
+ * Internal map listener for Fpm records map.
+ */
+ private class InternalMapListener implements EventuallyConsistentMapListener<IpPrefix, FpmRecord> {
+ @Override
+ public void event(EventuallyConsistentMapEvent<IpPrefix, FpmRecord> event) {
+ FpmPrefixStoreEvent.Type eventType;
+ switch (event.type()) {
+ case PUT:
+ eventType = FpmPrefixStoreEvent.Type.ADD;
+ break;
+ case REMOVE:
+ eventType = FpmPrefixStoreEvent.Type.REMOVE;
+ break;
+ default:
+ log.warn("Unknown event type {}", event.type());
+ return;
+ }
+ if (delegate != null) {
+ delegate.notify(new FpmPrefixStoreEvent(eventType, event.value()));
+ }
+ }
+ }
+}
diff --git a/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/package-info.java b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/package-info.java
new file mode 100644
index 0000000..105854f
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/java/org/onosproject/dhcprelay/store/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Store of DHCP relay application.
+ */
+package org.onosproject.dhcprelay.store;
\ No newline at end of file
diff --git a/apps/dhcprelay/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/dhcprelay/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..c0a4b6f
--- /dev/null
+++ b/apps/dhcprelay/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,44 @@
+<!--
+ ~ Copyright 2014-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.
+ -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+ <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+ <command>
+ <action class="org.onosproject.dhcprelay.cli.DhcpRelayCommand"/>
+ <completers>
+ <ref component-id="dhcpRelayCounterCompleter"/>
+ <ref component-id="dhcpRelayResetCompleter"/>
+ </completers>
+ </command>
+ <command>
+ <action class="org.onosproject.dhcprelay.cli.DhcpFpmRoutesCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.dhcprelay.cli.DhcpFpmAddCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.dhcprelay.cli.DhcpFpmDeleteCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.dhcprelay.cli.DhcpRelayAggCountersCommand"/>
+ <completers>
+ <ref component-id="dhcpRelayResetCompleter"/>
+ </completers>
+ </command>
+ </command-bundle>
+ <bean id="dhcpRelayCounterCompleter" class="org.onosproject.dhcprelay.cli.DhcpRelayCounterCompleter"/>
+ <bean id="dhcpRelayResetCompleter" class="org.onosproject.dhcprelay.cli.DhcpRelayResetCompleter"/>
+</blueprint>
diff --git a/apps/dhcprelay/app/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java b/apps/dhcprelay/app/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
new file mode 100644
index 0000000..9d86606
--- /dev/null
+++ b/apps/dhcprelay/app/src/test/java/org/onosproject/dhcprelay/DhcpRelayManagerTest.java
@@ -0,0 +1,1573 @@
+/*
+ * 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.onosproject.dhcprelay;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
+import com.google.common.io.Resources;
+import org.apache.commons.io.Charsets;
+import org.apache.commons.lang3.tuple.Pair;
+import org.easymock.Capture;
+import org.easymock.CaptureType;
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.ARP;
+import org.onlab.packet.DHCP;
+import org.onlab.packet.DeserializationException;
+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;
+import org.onlab.packet.VlanId;
+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.onlab.packet.dhcp.Dhcp6ClientIdOption;
+import org.onlab.packet.dhcp.Dhcp6Duid;
+import org.onosproject.dhcprelay.store.DhcpRelayStore;
+import org.onosproject.dhcprelay.store.DhcpRecord;
+import org.onosproject.dhcprelay.store.DhcpRelayStoreEvent;
+import org.onosproject.dhcprelay.store.DhcpRelayCounters;
+import org.onosproject.dhcprelay.store.DhcpRelayCountersStore;
+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.config.DefaultDhcpRelayConfig;
+import org.onosproject.dhcprelay.config.DhcpServerConfig;
+import org.onosproject.dhcprelay.config.IgnoreDhcpConfig;
+import org.onosproject.dhcprelay.config.IndirectDhcpRelayConfig;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.behaviour.Pipeliner;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.host.HostProviderService;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.intf.InterfaceServiceAdapter;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteStoreAdapter;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.packet.DefaultInboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketContextAdapter;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketServiceAdapter;
+import org.onosproject.store.StoreDelegate;
+
+
+
+
+import org.osgi.service.component.ComponentContext;
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.IPv6;
+
+import java.io.IOException;
+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;
+import java.util.stream.Collectors;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+import static org.onosproject.dhcprelay.DhcpRelayManager.DHCP_RELAY_APP;
+
+public class DhcpRelayManagerTest {
+ private static final short VLAN_LEN = 2;
+ private static final short SEPARATOR_LEN = 1;
+ private static final String CONFIG_FILE_PATH = "dhcp-relay.json";
+ private static final DeviceId DEV_1_ID = DeviceId.deviceId("of:0000000000000001");
+ 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 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");
+ private static final MacAddress CLIENT_IFACE_MAC = MacAddress.valueOf("00:00:00:00:11:01");
+ private static final HostLocation CLIENT_LOCATION = new HostLocation(CLIENT_CP, 0);
+ private static final HostId CLIENT_HOST_ID = HostId.hostId(CLIENT_MAC, CLIENT_VLAN);
+ private static final Ip6Address CLIENT_LL_IP_V6 = Ip6Address.valueOf("fe80::200:00ff:fe00:0001");
+ private static final Host EXISTS_HOST = new DefaultHost(Dhcp4HandlerImpl.PROVIDER_ID,
+ CLIENT_HOST_ID, CLIENT_MAC, CLIENT_VLAN,
+ CLIENT_LOCATION, ImmutableSet.of(CLIENT_LL_IP_V6));
+ private static final Interface CLIENT_INTERFACE = createInterface("C1",
+ CLIENT_CP,
+ INTERFACE_IPS,
+ CLIENT_IFACE_MAC,
+ CLIENT_VLAN,
+ null);
+
+
+
+ // Dual homing test
+ private static final ConnectPoint CLIENT_DH_CP = ConnectPoint.deviceConnectPoint("of:0000000000000001/3");
+ private static final HostLocation CLIENT_DH_LOCATION = new HostLocation(CLIENT_DH_CP, 0);
+ private static final Interface CLIENT_DH_INTERFACE = createInterface("C1-DH",
+ CLIENT_DH_CP,
+ INTERFACE_IPS,
+ CLIENT_IFACE_MAC,
+ CLIENT_VLAN,
+ null);
+
+
+ // DHCP client 2 (will send with option 82, so the vlan should equals to vlan from server)
+ private static final MacAddress CLIENT2_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+ private static final VlanId CLIENT2_VLAN = VlanId.NONE;
+ private static final VlanId CLIENT2_VLAN_NATIVE = VlanId.vlanId("20");
+ private static final ConnectPoint CLIENT2_CP = ConnectPoint.deviceConnectPoint("of:0000000000000001/2");
+ private static final MacAddress CLIENT2_IFACE_MAC = MacAddress.valueOf("00:00:00:00:11:01");
+ private static final Interface CLIENT2_INTERFACE = createInterface("C2",
+ CLIENT2_CP,
+ INTERFACE_IPS,
+ CLIENT2_IFACE_MAC,
+ CLIENT2_VLAN,
+ CLIENT2_VLAN_NATIVE);
+ private static final VlanId CLIENT_BOGUS_VLAN = VlanId.vlanId("108");
+
+ // Outer relay information
+ private static final Ip4Address OUTER_RELAY_IP = Ip4Address.valueOf("10.0.6.253");
+ private static final Ip6Address OUTER_RELAY_IP_V6 = Ip6Address.valueOf("2001:db8:1::4");
+ private static final Ip6Address OUTER_RELAY_LL_IP_V6 = Ip6Address.valueOf("fe80::200:0102:0304:0501");
+ private static final Set<IpAddress> OUTER_RELAY_IPS = ImmutableSet.of(OUTER_RELAY_IP,
+ OUTER_RELAY_IP_V6,
+ OUTER_RELAY_LL_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");
+ private static final HostLocation OUTER_REPLAY_HL = new HostLocation(OUTER_RELAY_CP, 0);
+ private static final HostId OUTER_RELAY_HOST_ID = HostId.hostId(OUTER_RELAY_MAC, OUTER_RELAY_VLAN);
+ private static final Host OUTER_RELAY_HOST = new DefaultHost(Dhcp4HandlerImpl.PROVIDER_ID,
+ OUTER_RELAY_HOST_ID,
+ OUTER_RELAY_MAC,
+ OUTER_RELAY_VLAN,
+ OUTER_REPLAY_HL,
+ OUTER_RELAY_IPS);
+
+ // DHCP Server
+ private static final MacAddress SERVER_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+ private static final VlanId SERVER_VLAN = VlanId.NONE;
+ private static final VlanId SERVER_VLAN_NATIVE = VlanId.vlanId("10");
+ private static final ConnectPoint SERVER_CONNECT_POINT =
+ 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 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(Dhcp4HandlerImpl.PROVIDER_ID,
+ SERVER_HOST_ID,
+ SERVER_MAC,
+ SERVER_VLAN,
+ SERVER_LOCATION,
+ DHCP_SERVER_IPS);
+ private static final MacAddress SERVER_IFACE_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+ private static final Interface SERVER_INTERFACE = createInterface("SERVER",
+ SERVER_CONNECT_POINT,
+ INTERFACE_IPS,
+ SERVER_IFACE_MAC,
+ SERVER_VLAN,
+ SERVER_VLAN_NATIVE);
+
+ // Relay agent config
+ private static final Ip4Address RELAY_AGENT_IP = Ip4Address.valueOf("10.0.4.254");
+
+ private static final List<TrafficSelector> DHCP_SELECTORS = buildClientDhcpSelectors();
+
+ // 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,
+ SERVER_INTERFACE,
+ CLIENT_DH_INTERFACE
+ );
+ private static final String NON_ONOS_CID = "Non-ONOS circuit ID";
+ private static final VlanId IGNORED_VLAN = VlanId.vlanId("100");
+ private static final int IGNORE_CONTROL_PRIORITY = PacketPriority.CONTROL.priorityValue() + 1000;
+
+ private DhcpRelayManager manager;
+ private MockPacketService packetService;
+ private MockRouteStore mockRouteStore;
+ private MockDhcpRelayStore mockDhcpRelayStore;
+ private MockDhcpRelayCountersStore mockDhcpRelayCountersStore;
+ private HostProviderService mockHostProviderService;
+ private FlowObjectiveService flowObjectiveService;
+ private DeviceService deviceService;
+ private Dhcp4HandlerImpl v4Handler;
+ private Dhcp6HandlerImpl v6Handler;
+
+ private static Interface createInterface(String name, ConnectPoint connectPoint,
+ List<InterfaceIpAddress> interfaceIps,
+ MacAddress macAddress,
+ VlanId vlanId,
+ VlanId vlanNative) {
+
+ if (vlanId.equals(VlanId.NONE)) {
+ return new Interface(name, connectPoint, interfaceIps, macAddress, vlanId,
+ null, null, vlanNative);
+ } else {
+ return new Interface(name, connectPoint, interfaceIps, macAddress, vlanId,
+ null, ImmutableSet.of(vlanId), null);
+ }
+ }
+
+ @Before
+ public void setup() {
+ manager = new DhcpRelayManager();
+ manager.cfgService = createNiceMock(NetworkConfigRegistry.class);
+
+ expect(manager.cfgService.getConfig(APP_ID, DefaultDhcpRelayConfig.class))
+ .andReturn(CONFIG)
+ .anyTimes();
+
+ expect(manager.cfgService.getConfig(APP_ID, IndirectDhcpRelayConfig.class))
+ .andReturn(CONFIG_INDIRECT)
+ .anyTimes();
+
+ manager.coreService = createNiceMock(CoreService.class);
+ expect(manager.coreService.registerApplication(anyString()))
+ .andReturn(APP_ID).anyTimes();
+
+ manager.hostService = createNiceMock(HostService.class);
+
+ expect(manager.hostService.getHostsByIp(OUTER_RELAY_IP_V6))
+ .andReturn(ImmutableSet.of(OUTER_RELAY_HOST)).anyTimes();
+ expect(manager.hostService.getHostsByIp(SERVER_IP))
+ .andReturn(ImmutableSet.of(SERVER_HOST)).anyTimes();
+ expect(manager.hostService.getHostsByIp(SERVER_IP_V6))
+ .andReturn(ImmutableSet.of(SERVER_HOST)).anyTimes();
+ expect(manager.hostService.getHostsByIp(GATEWAY_IP))
+ .andReturn(ImmutableSet.of(SERVER_HOST)).anyTimes();
+ expect(manager.hostService.getHostsByIp(GATEWAY_IP_V6))
+ .andReturn(ImmutableSet.of(SERVER_HOST)).anyTimes();
+ expect(manager.hostService.getHostsByIp(CLIENT_LL_IP_V6))
+ .andReturn(ImmutableSet.of(EXISTS_HOST)).anyTimes();
+
+ expect(manager.hostService.getHost(OUTER_RELAY_HOST_ID)).andReturn(OUTER_RELAY_HOST).anyTimes();
+
+ packetService = new MockPacketService();
+ manager.packetService = packetService;
+ manager.compCfgService = createNiceMock(ComponentConfigService.class);
+ deviceService = createNiceMock(DeviceService.class);
+
+ Device device = createNiceMock(Device.class);
+ expect(device.is(Pipeliner.class)).andReturn(true).anyTimes();
+
+ expect(deviceService.getDevice(DEV_1_ID)).andReturn(device).anyTimes();
+ expect(deviceService.getDevice(DEV_2_ID)).andReturn(device).anyTimes();
+ replay(deviceService, device);
+
+ mockRouteStore = new MockRouteStore();
+ mockDhcpRelayStore = new MockDhcpRelayStore();
+ mockDhcpRelayCountersStore = new MockDhcpRelayCountersStore();
+
+ manager.dhcpRelayStore = mockDhcpRelayStore;
+
+ manager.deviceService = deviceService;
+
+ manager.interfaceService = new MockInterfaceService();
+ flowObjectiveService = EasyMock.niceMock(FlowObjectiveService.class);
+ mockHostProviderService = createNiceMock(HostProviderService.class);
+ v4Handler = new Dhcp4HandlerImpl();
+ v4Handler.providerService = mockHostProviderService;
+ v4Handler.dhcpRelayStore = mockDhcpRelayStore;
+ v4Handler.hostService = manager.hostService;
+ v4Handler.interfaceService = manager.interfaceService;
+ v4Handler.packetService = manager.packetService;
+ v4Handler.routeStore = mockRouteStore;
+ v4Handler.coreService = createNiceMock(CoreService.class);
+ v4Handler.flowObjectiveService = flowObjectiveService;
+ v4Handler.appId = TestApplicationId.create(Dhcp4HandlerImpl.DHCP_V4_RELAY_APP);
+ v4Handler.deviceService = deviceService;
+ manager.v4Handler = v4Handler;
+
+ v6Handler = new Dhcp6HandlerImpl();
+ v6Handler.dhcpRelayStore = mockDhcpRelayStore;
+ v6Handler.dhcpRelayCountersStore = mockDhcpRelayCountersStore;
+ v6Handler.hostService = manager.hostService;
+ v6Handler.interfaceService = manager.interfaceService;
+ v6Handler.packetService = manager.packetService;
+ v6Handler.routeStore = mockRouteStore;
+ v6Handler.providerService = mockHostProviderService;
+ v6Handler.coreService = createNiceMock(CoreService.class);
+ v6Handler.flowObjectiveService = flowObjectiveService;
+ v6Handler.appId = TestApplicationId.create(Dhcp6HandlerImpl.DHCP_V6_RELAY_APP);
+ v6Handler.deviceService = deviceService;
+ manager.v6Handler = v6Handler;
+
+ // properties
+ Dictionary<String, Object> dictionary = createNiceMock(Dictionary.class);
+ expect(dictionary.get("arpEnabled")).andReturn(true).anyTimes();
+ expect(dictionary.get("dhcpPollInterval")).andReturn(120).anyTimes();
+ ComponentContext context = createNiceMock(ComponentContext.class);
+ expect(context.getProperties()).andReturn(dictionary).anyTimes();
+
+ replay(manager.cfgService, manager.coreService, manager.hostService,
+ manager.compCfgService, dictionary, context);
+ manager.activate(context);
+ }
+
+ @After
+ public void tearDown() {
+ manager.deactivate();
+ }
+
+ /**
+ * Relay a DHCP packet without option 82.
+ * Should add new host to host store after dhcp ack.
+ */
+ @Test
+ public void relayDhcpWithoutAgentInfo() {
+ replay(mockHostProviderService);
+ // send request
+ packetService.processPacket(new TestDhcpRequestPacketContext(CLIENT_MAC,
+ CLIENT_VLAN,
+ CLIENT_CP,
+ INTERFACE_IP.ipAddress().getIp4Address(),
+ false));
+ // won't trigger the host provider service
+ verify(mockHostProviderService);
+ reset(mockHostProviderService);
+
+ assertEquals(0, mockRouteStore.routes.size());
+
+ HostId expectHostId = HostId.hostId(CLIENT_MAC, CLIENT_VLAN);
+ Capture<HostDescription> capturedHostDesc = newCapture();
+ mockHostProviderService.hostDetected(eq(expectHostId), capture(capturedHostDesc), eq(false));
+ replay(mockHostProviderService);
+ // send ack
+ packetService.processPacket(new TestDhcpAckPacketContext(CLIENT_CP, CLIENT_MAC,
+ CLIENT_VLAN, INTERFACE_IP.ipAddress().getIp4Address(),
+ false));
+ verify(mockHostProviderService);
+ assertEquals(0, mockRouteStore.routes.size());
+
+ HostDescription host = capturedHostDesc.getValue();
+ assertEquals(false, host.configured());
+ assertEquals(CLIENT_CP.deviceId(), host.location().elementId());
+ assertEquals(CLIENT_CP.port(), host.location().port());
+ assertEquals(1, host.ipAddress().size());
+ assertEquals(IP_FOR_CLIENT, host.ipAddress().iterator().next());
+ }
+
+ /**
+ * Relay a DHCP packet with option 82 (Indirectly connected host).
+ */
+ @Test
+ public void relayDhcpWithAgentInfo() {
+ replay(mockHostProviderService);
+ // Assume outer dhcp relay agent exists in store already
+ // send request
+ packetService.processPacket(new TestDhcpRequestPacketContext(CLIENT2_MAC,
+ CLIENT2_VLAN,
+ CLIENT2_CP,
+ INTERFACE_IP.ipAddress().getIp4Address(),
+ true));
+ // No routes
+ assertEquals(0, mockRouteStore.routes.size());
+ // send ack
+ packetService.processPacket(new TestDhcpAckPacketContext(CLIENT2_CP,
+ CLIENT2_MAC,
+ CLIENT2_VLAN,
+ INTERFACE_IP.ipAddress().getIp4Address(),
+ true));
+
+ // won't trigger the host provider service
+ verify(mockHostProviderService);
+ reset(mockHostProviderService);
+
+ assertEquals(1, mockRouteStore.routes.size());
+
+ Route route = mockRouteStore.routes.get(0);
+ assertEquals(OUTER_RELAY_IP, route.nextHop());
+ assertEquals(IP_FOR_CLIENT.toIpPrefix(), route.prefix());
+ assertEquals(Route.Source.DHCP, route.source());
+ }
+
+ @Test
+ public void testWithRelayAgentConfig() throws DeserializationException {
+ manager.v4Handler
+ .setDefaultDhcpServerConfigs(ImmutableList.of(new MockDhcpServerConfig(RELAY_AGENT_IP)));
+ manager.v4Handler
+ .setIndirectDhcpServerConfigs(ImmutableList.of(new MockDhcpServerConfig(RELAY_AGENT_IP)));
+ packetService.processPacket(new TestDhcpRequestPacketContext(CLIENT2_MAC,
+ CLIENT2_VLAN,
+ CLIENT2_CP,
+ INTERFACE_IP.ipAddress().getIp4Address(),
+ true));
+ OutboundPacket outPacket = packetService.emittedPacket;
+ byte[] outData = outPacket.data().array();
+ Ethernet eth = Ethernet.deserializer().deserialize(outData, 0, outData.length);
+ IPv4 ip = (IPv4) eth.getPayload();
+ UDP udp = (UDP) ip.getPayload();
+ DHCP dhcp = (DHCP) udp.getPayload();
+ assertEquals(RELAY_AGENT_IP.toInt(), dhcp.getGatewayIPAddress());
+ }
+
+ @Test
+ public void testArpRequest() throws Exception {
+ packetService.processPacket(new TestArpRequestPacketContext(CLIENT_INTERFACE));
+ OutboundPacket outboundPacket = packetService.emittedPacket;
+ byte[] outPacketData = outboundPacket.data().array();
+ Ethernet eth = Ethernet.deserializer().deserialize(outPacketData, 0, outPacketData.length);
+
+ assertEquals(eth.getEtherType(), Ethernet.TYPE_ARP);
+ ARP arp = (ARP) eth.getPayload();
+ assertArrayEquals(arp.getSenderHardwareAddress(), CLIENT_INTERFACE.mac().toBytes());
+ }
+
+ /**
+ * Ignores specific vlans from specific devices if config.
+ *
+ * @throws Exception the exception from this test
+ */
+ @Test
+ public void testIgnoreVlan() throws Exception {
+ ObjectMapper om = new ObjectMapper();
+ JsonNode json = om.readTree(Resources.getResource(CONFIG_FILE_PATH));
+ IgnoreDhcpConfig config = new IgnoreDhcpConfig();
+ json = json.path("apps").path(DHCP_RELAY_APP).path(IgnoreDhcpConfig.KEY);
+ config.init(APP_ID, IgnoreDhcpConfig.KEY, json, om, null);
+
+ Capture<Objective> capturedFromDev1 = newCapture(CaptureType.ALL);
+ flowObjectiveService.apply(eq(DEV_1_ID), capture(capturedFromDev1));
+ expectLastCall().times(DHCP_SELECTORS.size());
+ Capture<Objective> capturedFromDev2 = newCapture(CaptureType.ALL);
+ flowObjectiveService.apply(eq(DEV_2_ID), capture(capturedFromDev2));
+ expectLastCall().times(DHCP_SELECTORS.size());
+ replay(flowObjectiveService);
+ manager.updateConfig(config);
+ verify(flowObjectiveService);
+
+ List<Objective> objectivesFromDev1 = capturedFromDev1.getValues();
+ List<Objective> objectivesFromDev2 = capturedFromDev2.getValues();
+
+ assertTrue(objectivesFromDev1.containsAll(objectivesFromDev2));
+ assertTrue(objectivesFromDev2.containsAll(objectivesFromDev1));
+
+ for (int index = 0; index < objectivesFromDev1.size(); index++) {
+ TrafficSelector selector =
+ DefaultTrafficSelector.builder(DHCP_SELECTORS.get(index))
+ .matchVlanId(IGNORED_VLAN)
+ .build();
+ ForwardingObjective fwd = (ForwardingObjective) objectivesFromDev1.get(index);
+ assertEquals(selector, fwd.selector());
+ assertEquals(DefaultTrafficTreatment.emptyTreatment(), fwd.treatment());
+ assertEquals(IGNORE_CONTROL_PRIORITY, fwd.priority());
+ assertEquals(ForwardingObjective.Flag.VERSATILE, fwd.flag());
+ assertEquals(Objective.Operation.ADD, fwd.op());
+ fwd.context().ifPresent(ctx -> {
+ ctx.onSuccess(fwd);
+ });
+ }
+ objectivesFromDev2.forEach(obj -> obj.context().ifPresent(ctx -> ctx.onSuccess(obj)));
+ assertEquals(2, v4Handler.ignoredVlans.size());
+ assertEquals(2, v6Handler.ignoredVlans.size());
+ }
+
+ /**
+ * "IgnoreVlan" policy should be removed when the config removed.
+ */
+ @Test
+ public void testRemoveIgnoreVlan() {
+ v4Handler.ignoredVlans.put(DEV_1_ID, IGNORED_VLAN);
+ v4Handler.ignoredVlans.put(DEV_2_ID, IGNORED_VLAN);
+ v6Handler.ignoredVlans.put(DEV_1_ID, IGNORED_VLAN);
+ v6Handler.ignoredVlans.put(DEV_2_ID, IGNORED_VLAN);
+ IgnoreDhcpConfig config = new IgnoreDhcpConfig();
+
+ Capture<Objective> capturedFromDev1 = newCapture(CaptureType.ALL);
+ flowObjectiveService.apply(eq(DEV_1_ID), capture(capturedFromDev1));
+ expectLastCall().times(DHCP_SELECTORS.size());
+ Capture<Objective> capturedFromDev2 = newCapture(CaptureType.ALL);
+ flowObjectiveService.apply(eq(DEV_2_ID), capture(capturedFromDev2));
+ expectLastCall().times(DHCP_SELECTORS.size());
+ replay(flowObjectiveService);
+ manager.removeConfig(config);
+ verify(flowObjectiveService);
+
+ List<Objective> objectivesFromDev1 = capturedFromDev1.getValues();
+ List<Objective> objectivesFromDev2 = capturedFromDev2.getValues();
+
+ assertTrue(objectivesFromDev1.containsAll(objectivesFromDev2));
+ assertTrue(objectivesFromDev2.containsAll(objectivesFromDev1));
+
+ for (int index = 0; index < objectivesFromDev1.size(); index++) {
+ TrafficSelector selector =
+ DefaultTrafficSelector.builder(DHCP_SELECTORS.get(index))
+ .matchVlanId(IGNORED_VLAN)
+ .build();
+ ForwardingObjective fwd = (ForwardingObjective) objectivesFromDev1.get(index);
+ assertEquals(selector, fwd.selector());
+ assertEquals(DefaultTrafficTreatment.emptyTreatment(), fwd.treatment());
+ assertEquals(IGNORE_CONTROL_PRIORITY, fwd.priority());
+ assertEquals(ForwardingObjective.Flag.VERSATILE, fwd.flag());
+ assertEquals(Objective.Operation.REMOVE, fwd.op());
+ fwd.context().ifPresent(ctx -> {
+ ctx.onSuccess(fwd);
+ });
+ }
+ objectivesFromDev2.forEach(obj -> obj.context().ifPresent(ctx -> ctx.onSuccess(obj)));
+ assertEquals(0, v4Handler.ignoredVlans.size());
+ assertEquals(0, v6Handler.ignoredVlans.size());
+ }
+
+ /**
+ * Should ignore ignore rules installation when device not available.
+ */
+ @Test
+ public void testIgnoreUnknownDevice() throws IOException {
+ reset(manager.deviceService);
+ Device device = createNiceMock(Device.class);
+ expect(device.is(Pipeliner.class)).andReturn(true).anyTimes();
+
+ expect(manager.deviceService.getDevice(DEV_1_ID)).andReturn(device).anyTimes();
+ expect(manager.deviceService.getDevice(DEV_2_ID)).andReturn(null).anyTimes();
+
+ ObjectMapper om = new ObjectMapper();
+ JsonNode json = om.readTree(Resources.getResource(CONFIG_FILE_PATH));
+ IgnoreDhcpConfig config = new IgnoreDhcpConfig();
+ json = json.path("apps").path(DHCP_RELAY_APP).path(IgnoreDhcpConfig.KEY);
+ config.init(APP_ID, IgnoreDhcpConfig.KEY, json, om, null);
+
+ Capture<Objective> capturedFromDev1 = newCapture(CaptureType.ALL);
+ flowObjectiveService.apply(eq(DEV_1_ID), capture(capturedFromDev1));
+ expectLastCall().times(DHCP_SELECTORS.size());
+ replay(flowObjectiveService, manager.deviceService, device);
+
+ manager.updateConfig(config);
+ capturedFromDev1.getValues().forEach(obj -> obj.context().ifPresent(ctx -> ctx.onSuccess(obj)));
+
+ assertEquals(1, v4Handler.ignoredVlans.size());
+ assertEquals(1, v6Handler.ignoredVlans.size());
+ }
+
+ /**
+ * Should try install ignore rules when device comes up.
+ */
+ @Test
+ public void testInstallIgnoreRuleWhenDeviceComesUp() throws IOException {
+ ObjectMapper om = new ObjectMapper();
+ JsonNode json = om.readTree(Resources.getResource(CONFIG_FILE_PATH));
+ IgnoreDhcpConfig config = new IgnoreDhcpConfig();
+ json = json.path("apps").path(DHCP_RELAY_APP).path(IgnoreDhcpConfig.KEY);
+ config.init(APP_ID, IgnoreDhcpConfig.KEY, json, om, null);
+
+ reset(manager.cfgService, flowObjectiveService, manager.deviceService);
+ expect(manager.cfgService.getConfig(APP_ID, IgnoreDhcpConfig.class))
+ .andReturn(config).anyTimes();
+
+ Device device = createNiceMock(Device.class);
+ expect(device.is(Pipeliner.class)).andReturn(true).anyTimes();
+ expect(device.id()).andReturn(DEV_1_ID).anyTimes();
+ expect(manager.deviceService.getDevice(DEV_1_ID)).andReturn(device).anyTimes();
+ DeviceEvent event = new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, device);
+ Capture<Objective> capturedFromDev1 = newCapture(CaptureType.ALL);
+ flowObjectiveService.apply(eq(DEV_1_ID), capture(capturedFromDev1));
+ expectLastCall().times(DHCP_SELECTORS.size());
+ replay(manager.cfgService, flowObjectiveService, manager.deviceService, device);
+ manager.deviceListener.event(event);
+ capturedFromDev1.getValues().forEach(obj -> obj.context().ifPresent(ctx -> ctx.onSuccess(obj)));
+ assertEquals(1, v4Handler.ignoredVlans.size());
+ assertEquals(1, v6Handler.ignoredVlans.size());
+ }
+
+ /**
+ * Relay a DHCP6 packet without relay option
+ * Note: Should add new host to host store after dhcp ack.
+ */
+ @Test
+ public void relayDhcp6WithoutAgentInfo() {
+ replay(mockHostProviderService);
+ // send request
+ packetService.processPacket(new TestDhcp6RequestPacketContext(DHCP6.MsgType.REQUEST.value(),
+ CLIENT_MAC,
+ CLIENT_VLAN,
+ CLIENT_CP,
+ INTERFACE_IP_V6.ipAddress().getIp6Address(),
+ 0));
+
+ verify(mockHostProviderService);
+ reset(mockHostProviderService);
+ assertEquals(0, mockRouteStore.routes.size());
+
+ Capture<HostDescription> capturedHostDesc = newCapture();
+ mockHostProviderService.hostDetected(eq(HostId.hostId(CLIENT_MAC, CLIENT_VLAN)),
+ capture(capturedHostDesc), eq(false));
+ replay(mockHostProviderService);
+ // send reply
+ packetService.processPacket(new TestDhcp6ReplyPacketContext(DHCP6.MsgType.REPLY.value(),
+ CLIENT_CP, CLIENT_MAC,
+ CLIENT_VLAN,
+ INTERFACE_IP_V6.ipAddress().getIp6Address(),
+ 0, false, CLIENT_VLAN));
+ verify(mockHostProviderService);
+ assertEquals(0, mockRouteStore.routes.size());
+
+ HostDescription host = capturedHostDesc.getValue();
+ assertEquals(CLIENT_VLAN, host.vlan());
+ assertEquals(CLIENT_CP.deviceId(), host.location().elementId());
+ assertEquals(CLIENT_CP.port(), host.location().port());
+ assertEquals(1, host.ipAddress().size());
+ assertEquals(IP_FOR_CLIENT_V6, host.ipAddress().iterator().next());
+
+ // send release
+ packetService.processPacket(new TestDhcp6RequestPacketContext(DHCP6.MsgType.RELEASE.value(),
+ CLIENT_MAC,
+ CLIENT_VLAN,
+ CLIENT_CP,
+ INTERFACE_IP_V6.ipAddress().getIp6Address(),
+ 0));
+
+ assertEquals(null, manager.hostService.getHost(HostId.hostId(CLIENT_MAC, CLIENT_VLAN)));
+ }
+
+ /**
+ * Relay a DHCP6 packet with Relay Message opion (Indirectly connected host).
+ */
+ @Test
+ public void relayDhcp6WithAgentInfo() {
+ replay(mockHostProviderService);
+ // Assume outer dhcp6 relay agent exists in store already
+ // send request
+ packetService.processPacket(new TestDhcp6RequestPacketContext(DHCP6.MsgType.REQUEST.value(),
+ CLIENT2_MAC,
+ CLIENT2_VLAN,
+ CLIENT2_CP,
+ OUTER_RELAY_IP_V6,
+ 1));
+
+ assertEquals(0, mockRouteStore.routes.size());
+
+ // send reply
+ packetService.processPacket(new TestDhcp6ReplyPacketContext(DHCP6.MsgType.REPLY.value(), CLIENT2_CP,
+ CLIENT2_MAC,
+ CLIENT2_VLAN,
+ OUTER_RELAY_IP_V6,
+ 1, false, CLIENT2_VLAN));
+
+ // won't trigger the host provider service
+ verify(mockHostProviderService);
+ reset(mockHostProviderService);
+ assertEquals(2, mockRouteStore.routes.size()); // ipAddress and prefix
+
+ Route aRoute = mockRouteStore.routes.stream()
+ .filter(rt -> rt.prefix().contains(IP_FOR_CLIENT_V6))
+ .findFirst()
+ .orElse(null);
+ assertNotEquals(null, aRoute);
+
+ aRoute = mockRouteStore.routes.stream()
+ .filter(rt -> rt.prefix().contains(PREFIX_FOR_CLIENT_V6))
+ .findFirst()
+ .orElse(null);
+ assertNotEquals(null, aRoute);
+
+ // send release msg
+ packetService.processPacket(new TestDhcp6RequestPacketContext(DHCP6.MsgType.RELEASE.value(),
+ CLIENT2_MAC,
+ CLIENT2_VLAN,
+ CLIENT2_CP,
+ OUTER_RELAY_IP_V6,
+ 1));
+
+ aRoute = mockRouteStore.routes.stream()
+ .filter(rt -> rt.prefix().contains(IP_FOR_CLIENT_V6))
+ .findFirst()
+ .orElse(null);
+ assertEquals(null, aRoute);
+
+ aRoute = mockRouteStore.routes.stream()
+ .filter(rt -> rt.prefix().contains(PREFIX_FOR_CLIENT_V6))
+ .findFirst()
+ .orElse(null);
+ assertEquals(null, aRoute);
+
+ assertEquals(0, mockRouteStore.routes.size());
+
+ }
+
+ /**
+ * Relay a DHCP6 packet with Relay Message opion (Indirectly connected host) and server responded
+ * with vlan differnt from client interface vlan.
+ */
+ @Test
+ public void relayDhcp6WithAgentInfoWrongVlan() {
+ replay(mockHostProviderService);
+ // Assume outer dhcp6 relay agent exists in store already
+ // send request
+ packetService.processPacket(new TestDhcp6RequestPacketContext(DHCP6.MsgType.REQUEST.value(),
+ CLIENT2_MAC,
+ CLIENT2_VLAN,
+ CLIENT2_CP,
+ INTERFACE_IP_V6.ipAddress().getIp6Address(),
+ 1));
+
+ assertEquals(0, mockRouteStore.routes.size());
+
+ // send reply
+ packetService.processPacket(new TestDhcp6ReplyPacketContext(DHCP6.MsgType.REPLY.value(),
+ CLIENT2_CP,
+ CLIENT2_MAC,
+ CLIENT2_VLAN,
+ INTERFACE_IP_V6.ipAddress().getIp6Address(),
+ 1, true,
+ CLIENT_BOGUS_VLAN // mismatch
+ ));
+
+ // won't trigger the host provider service
+ verify(mockHostProviderService);
+ reset(mockHostProviderService);
+ assertEquals(0, mockRouteStore.routes.size()); // ipAddress and prefix
+
+ }
+
+
+ @Test
+ public void testDhcp4DualHome() {
+ PacketContext packetContext =
+ new TestDhcpAckPacketContext(CLIENT_DH_CP, CLIENT_MAC, CLIENT_VLAN,
+ INTERFACE_IP.ipAddress().getIp4Address(),
+ false);
+ reset(manager.hostService);
+ expect(manager.hostService.getHost(CLIENT_HOST_ID)).andReturn(EXISTS_HOST).anyTimes();
+ Capture<HostDescription> capturedHostDesc = newCapture();
+ mockHostProviderService.hostDetected(eq(CLIENT_HOST_ID), capture(capturedHostDesc), eq(false));
+ replay(mockHostProviderService, manager.hostService);
+ packetService.processPacket(packetContext);
+ verify(mockHostProviderService);
+
+ HostDescription hostDesc = capturedHostDesc.getValue();
+ Set<HostLocation> hostLocations = hostDesc.locations();
+ assertEquals(2, hostLocations.size());
+ assertTrue(hostLocations.contains(CLIENT_LOCATION));
+ assertTrue(hostLocations.contains(CLIENT_DH_LOCATION));
+ }
+
+ @Test
+ public void testDhcp6DualHome() {
+ PacketContext packetContext =
+ new TestDhcp6ReplyPacketContext(DHCP6.MsgType.REPLY.value(),
+ CLIENT_DH_CP, CLIENT_MAC, CLIENT_VLAN,
+ INTERFACE_IP_V6.ipAddress().getIp6Address(),
+ 0, false, CLIENT_VLAN);
+ reset(manager.hostService);
+ expect(manager.hostService.getHostsByIp(CLIENT_LL_IP_V6)).andReturn(ImmutableSet.of(EXISTS_HOST)).anyTimes();
+
+ // FIXME: currently DHCPv6 has a bug, we can't get correct vlan of client......
+ // XXX: The vlan relied from DHCP6 handler might be wrong, do hack here
+ HostId hostId = HostId.hostId(CLIENT_MAC, VlanId.NONE);
+ expect(manager.hostService.getHost(hostId)).andReturn(EXISTS_HOST).anyTimes();
+
+ // XXX: sometimes this will work, sometimes not
+ expect(manager.hostService.getHost(CLIENT_HOST_ID)).andReturn(EXISTS_HOST).anyTimes();
+
+ Capture<HostDescription> capturedHostDesc = newCapture();
+
+ // XXX: also a hack here
+ mockHostProviderService.hostDetected(eq(hostId), capture(capturedHostDesc), eq(false));
+ expectLastCall().anyTimes();
+
+ mockHostProviderService.hostDetected(eq(CLIENT_HOST_ID), capture(capturedHostDesc), eq(false));
+ expectLastCall().anyTimes();
+ replay(mockHostProviderService, manager.hostService);
+ packetService.processPacket(packetContext);
+ verify(mockHostProviderService);
+
+ HostDescription hostDesc = capturedHostDesc.getValue();
+ Set<HostLocation> hostLocations = hostDesc.locations();
+ assertEquals(2, hostLocations.size());
+ assertTrue(hostLocations.contains(CLIENT_LOCATION));
+ assertTrue(hostLocations.contains(CLIENT_DH_LOCATION));
+ }
+
+ private static class MockDefaultDhcpRelayConfig extends DefaultDhcpRelayConfig {
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public List<DhcpServerConfig> dhcpServerConfigs() {
+ return ImmutableList.of(new MockDhcpServerConfig(null));
+ }
+ }
+
+ 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;
+
+ /**
+ * Create mocked version DHCP server config.
+ *
+ * @param relayAgentIp the relay agent Ip config; null if we don't need it
+ */
+ public MockDhcpServerConfig(Ip4Address relayAgentIp) {
+ this.relayAgentIp = relayAgentIp;
+ this.relayAgentIps.put(DEV_1_ID, Pair.of(relayAgentIp, null));
+ this.relayAgentIps.put(DEV_2_ID, Pair.of(relayAgentIp, null));
+ }
+
+ @Override
+ public Optional<Ip4Address> getRelayAgentIp4(DeviceId deviceId) {
+ return Optional.ofNullable(this.relayAgentIps.get(deviceId).getLeft());
+ }
+
+ @Override
+ public Optional<ConnectPoint> getDhcpServerConnectPoint() {
+ return Optional.of(SERVER_CONNECT_POINT);
+ }
+
+ @Override
+ 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 MockRouteStore extends RouteStoreAdapter {
+ private List<Route> routes = Lists.newArrayList();
+
+ @Override
+ public void updateRoute(Route route) {
+ routes.add(route);
+ }
+
+ @Override
+ public void removeRoute(Route route) {
+ routes.remove(route);
+ }
+
+ public void replaceRoute(Route route) {
+ routes.remove(route);
+ routes.add(route);
+ }
+ }
+
+ private class MockInterfaceService extends InterfaceServiceAdapter {
+
+ @Override
+ public Set<Interface> getInterfaces() {
+ return INTERFACES;
+ }
+
+ @Override
+ public Set<Interface> getInterfacesByIp(IpAddress ip) {
+ return INTERFACES.stream()
+ .filter(iface -> {
+ return iface.ipAddressesList().stream()
+ .anyMatch(ifaceIp -> ifaceIp.ipAddress().equals(ip));
+ })
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<Interface> getInterfacesByPort(ConnectPoint port) {
+ return INTERFACES.stream()
+ .filter(iface -> iface.connectPoint().equals(port))
+ .collect(Collectors.toSet());
+ }
+ }
+
+ private class MockDhcpRelayStore implements DhcpRelayStore {
+ StoreDelegate<DhcpRelayStoreEvent> delegate;
+ private Map<HostId, DhcpRecord> records = Maps.newHashMap();
+
+ @Override
+ public void updateDhcpRecord(HostId hostId, DhcpRecord dhcpRecord) {
+ records.put(hostId, dhcpRecord);
+ DhcpRelayStoreEvent event = new DhcpRelayStoreEvent(DhcpRelayStoreEvent.Type.UPDATED,
+ dhcpRecord);
+ if (delegate != null) {
+ delegate.notify(event);
+ }
+ }
+
+ @Override
+ public Optional<DhcpRecord> getDhcpRecord(HostId hostId) {
+ return Optional.ofNullable(records.get(hostId));
+ }
+
+ @Override
+ public Collection<DhcpRecord> getDhcpRecords() {
+ return records.values();
+ }
+
+ @Override
+ public Optional<DhcpRecord> removeDhcpRecord(HostId hostId) {
+ DhcpRecord dhcpRecord = records.remove(hostId);
+ if (dhcpRecord != null) {
+ DhcpRelayStoreEvent event = new DhcpRelayStoreEvent(DhcpRelayStoreEvent.Type.REMOVED,
+ dhcpRecord);
+ if (delegate != null) {
+ delegate.notify(event);
+ }
+ }
+ return Optional.ofNullable(dhcpRecord);
+ }
+
+ @Override
+ public void setDelegate(StoreDelegate<DhcpRelayStoreEvent> delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void unsetDelegate(StoreDelegate<DhcpRelayStoreEvent> delegate) {
+ this.delegate = null;
+ }
+
+ @Override
+ public boolean hasDelegate() {
+ return this.delegate != null;
+ }
+ }
+
+ private class MockDhcpRelayCountersStore implements DhcpRelayCountersStore {
+ private Map<String, DhcpRelayCounters> counters = Maps.newHashMap();
+
+ public void incrementCounter(String coutnerClass, String counterName) {
+ DhcpRelayCounters countersRecord;
+
+ DhcpRelayCounters classCounters = counters.get(coutnerClass);
+ if (classCounters == null) {
+ classCounters = new DhcpRelayCounters();
+ }
+ classCounters.incrementCounter(counterName);
+ counters.put(coutnerClass, classCounters);
+ }
+
+ @Override
+ public Set<Map.Entry<String, DhcpRelayCounters>> getAllCounters() {
+ return counters.entrySet();
+ }
+
+ @Override
+ public Optional<DhcpRelayCounters> getCounters(String counterClass) {
+ DhcpRelayCounters classCounters = counters.get(counterClass);
+ if (classCounters == null) {
+ return Optional.empty();
+ }
+ return Optional.of(classCounters);
+ }
+
+ @Override
+ public void resetAllCounters() {
+ counters.clear();
+ }
+
+ @Override
+ public void resetCounters(String counterClass) {
+ DhcpRelayCounters classCounters = counters.get(counterClass);
+ classCounters.resetCounters();
+ counters.put(counterClass, classCounters);
+ }
+ }
+
+
+ private class MockPacketService extends PacketServiceAdapter {
+ Set<PacketProcessor> packetProcessors = Sets.newHashSet();
+ OutboundPacket emittedPacket;
+
+ @Override
+ public void addProcessor(PacketProcessor processor, int priority) {
+ packetProcessors.add(processor);
+ }
+
+ public void processPacket(PacketContext packetContext) {
+ packetProcessors.forEach(p -> p.process(packetContext));
+ }
+
+ @Override
+ public void emit(OutboundPacket packet) {
+ this.emittedPacket = packet;
+ }
+ }
+
+
+
+ /**
+ * Generates DHCP REQUEST packet.
+ */
+ private class TestDhcpRequestPacketContext extends PacketContextAdapter {
+
+
+ private InboundPacket inPacket;
+
+ public TestDhcpRequestPacketContext(MacAddress clientMac, VlanId vlanId,
+ ConnectPoint clientCp,
+ Ip4Address clientGwAddr,
+ boolean withNonOnosRelayInfo) {
+ super(0, null, null, false);
+ byte[] dhcpMsgType = new byte[1];
+ dhcpMsgType[0] = (byte) DHCP.MsgType.DHCPREQUEST.getValue();
+
+ DhcpOption dhcpOption = new DhcpOption();
+ dhcpOption.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
+ dhcpOption.setData(dhcpMsgType);
+ dhcpOption.setLength((byte) 1);
+ DhcpOption endOption = new DhcpOption();
+ endOption.setCode(DHCP.DHCPOptionCode.OptionCode_END.getValue());
+
+ DHCP dhcp = new DHCP();
+ dhcp.setHardwareType(DHCP.HWTYPE_ETHERNET);
+ dhcp.setHardwareAddressLength((byte) 6);
+ dhcp.setClientHardwareAddress(clientMac.toBytes());
+ if (withNonOnosRelayInfo) {
+ DhcpRelayAgentOption relayOption = new DhcpRelayAgentOption();
+ DhcpOption circuitIdOption = new DhcpOption();
+ CircuitId circuitId = new CircuitId("Custom option", VlanId.NONE);
+ byte[] cid = circuitId.serialize();
+ circuitIdOption.setCode(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
+ circuitIdOption.setLength((byte) cid.length);
+ circuitIdOption.setData(cid);
+ relayOption.setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue());
+ relayOption.addSubOption(circuitIdOption);
+ dhcp.setOptions(ImmutableList.of(dhcpOption, relayOption, endOption));
+ dhcp.setGatewayIPAddress(OUTER_RELAY_IP.getIp4Address().toInt());
+ } else {
+ dhcp.setOptions(ImmutableList.of(dhcpOption, endOption));
+ }
+
+
+ UDP udp = new UDP();
+ udp.setPayload(dhcp);
+ udp.setSourcePort(UDP.DHCP_CLIENT_PORT);
+ udp.setDestinationPort(UDP.DHCP_SERVER_PORT);
+
+ IPv4 ipv4 = new IPv4();
+ ipv4.setPayload(udp);
+ ipv4.setDestinationAddress(SERVER_IP.toInt());
+ ipv4.setSourceAddress(clientGwAddr.toInt());
+
+ Ethernet eth = new Ethernet();
+ if (withNonOnosRelayInfo) {
+ eth.setEtherType(Ethernet.TYPE_IPV4)
+ .setVlanID(vlanId.toShort())
+ .setSourceMACAddress(OUTER_RELAY_MAC)
+ .setDestinationMACAddress(MacAddress.BROADCAST)
+ .setPayload(ipv4);
+ } else {
+ eth.setEtherType(Ethernet.TYPE_IPV4)
+ .setVlanID(vlanId.toShort())
+ .setSourceMACAddress(clientMac)
+ .setDestinationMACAddress(MacAddress.BROADCAST)
+ .setPayload(ipv4);
+ }
+
+ this.inPacket = new DefaultInboundPacket(clientCp, eth,
+ ByteBuffer.wrap(eth.serialize()));
+ }
+
+ @Override
+ public InboundPacket inPacket() {
+ return this.inPacket;
+ }
+ }
+
+ /**
+ * Generates DHCP ACK packet.
+ */
+ private class TestDhcpAckPacketContext extends PacketContextAdapter {
+ private InboundPacket inPacket;
+
+ public TestDhcpAckPacketContext(ConnectPoint clientCp, MacAddress clientMac,
+ VlanId clientVlan, Ip4Address clientGwAddr,
+ boolean withNonOnosRelayInfo) {
+ super(0, null, null, false);
+
+ byte[] dhcpMsgType = new byte[1];
+ dhcpMsgType[0] = (byte) DHCP.MsgType.DHCPACK.getValue();
+
+ DhcpOption dhcpOption = new DhcpOption();
+ dhcpOption.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
+ dhcpOption.setData(dhcpMsgType);
+ dhcpOption.setLength((byte) 1);
+
+ DhcpOption endOption = new DhcpOption();
+ endOption.setCode(DHCP.DHCPOptionCode.OptionCode_END.getValue());
+
+ DHCP dhcp = new DHCP();
+ if (withNonOnosRelayInfo) {
+ DhcpRelayAgentOption relayOption = new DhcpRelayAgentOption();
+ DhcpOption circuitIdOption = new DhcpOption();
+ String circuitId = NON_ONOS_CID;
+ byte[] cid = circuitId.getBytes(Charsets.US_ASCII);
+ circuitIdOption.setCode(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
+ circuitIdOption.setLength((byte) cid.length);
+ circuitIdOption.setData(cid);
+ relayOption.setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue());
+ relayOption.addSubOption(circuitIdOption);
+ dhcp.setOptions(ImmutableList.of(dhcpOption, relayOption, endOption));
+ dhcp.setGatewayIPAddress(OUTER_RELAY_IP.getIp4Address().toInt());
+ } else {
+ CircuitId cid = new CircuitId(clientCp.toString(), clientVlan);
+ byte[] circuitId = cid.serialize();
+ DhcpOption circuitIdSubOption = new DhcpOption();
+ circuitIdSubOption.setCode(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
+ circuitIdSubOption.setData(circuitId);
+ circuitIdSubOption.setLength((byte) circuitId.length);
+
+ DhcpRelayAgentOption relayInfoOption = new DhcpRelayAgentOption();
+ relayInfoOption.setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue());
+ relayInfoOption.addSubOption(circuitIdSubOption);
+ dhcp.setOptions(ImmutableList.of(dhcpOption, relayInfoOption, endOption));
+ dhcp.setGatewayIPAddress(clientGwAddr.toInt());
+ }
+ dhcp.setHardwareType(DHCP.HWTYPE_ETHERNET);
+ dhcp.setHardwareAddressLength((byte) 6);
+ dhcp.setClientHardwareAddress(clientMac.toBytes());
+ dhcp.setYourIPAddress(IP_FOR_CLIENT.toInt());
+
+ UDP udp = new UDP();
+ udp.setPayload(dhcp);
+ udp.setSourcePort(UDP.DHCP_SERVER_PORT);
+ udp.setDestinationPort(UDP.DHCP_CLIENT_PORT);
+ IPv4 ipv4 = new IPv4();
+ ipv4.setPayload(udp);
+ ipv4.setDestinationAddress(IP_FOR_CLIENT.toString());
+ ipv4.setSourceAddress(SERVER_IP.toString());
+ Ethernet eth = new Ethernet();
+ if (withNonOnosRelayInfo) {
+ eth.setEtherType(Ethernet.TYPE_IPV4)
+ .setVlanID(SERVER_VLAN.toShort())
+ .setSourceMACAddress(SERVER_MAC)
+ .setDestinationMACAddress(OUTER_RELAY_MAC)
+ .setPayload(ipv4);
+ } else {
+ eth.setEtherType(Ethernet.TYPE_IPV4)
+ .setVlanID(SERVER_VLAN.toShort())
+ .setSourceMACAddress(SERVER_MAC)
+ .setDestinationMACAddress(CLIENT_MAC)
+ .setPayload(ipv4);
+ }
+
+ this.inPacket = new DefaultInboundPacket(SERVER_CONNECT_POINT, eth,
+ ByteBuffer.wrap(eth.serialize()));
+
+ }
+
+ @Override
+ public InboundPacket inPacket() {
+ return this.inPacket;
+ }
+ }
+
+ private class TestArpRequestPacketContext extends PacketContextAdapter {
+ private InboundPacket inPacket;
+
+ public TestArpRequestPacketContext(Interface fromInterface) {
+ super(0, null, null, false);
+ ARP arp = new ARP();
+ arp.setOpCode(ARP.OP_REQUEST);
+
+ IpAddress targetIp = fromInterface.ipAddressesList().get(0).ipAddress();
+ arp.setTargetProtocolAddress(targetIp.toOctets());
+ arp.setTargetHardwareAddress(MacAddress.BROADCAST.toBytes());
+ arp.setSenderHardwareAddress(MacAddress.NONE.toBytes());
+ arp.setSenderProtocolAddress(Ip4Address.valueOf(0).toOctets());
+ arp.setHardwareAddressLength((byte) MacAddress.MAC_ADDRESS_LENGTH);
+ Ethernet eth = new Ethernet();
+ eth.setEtherType(Ethernet.TYPE_ARP);
+ eth.setSourceMACAddress(MacAddress.NONE);
+ eth.setDestinationMACAddress(MacAddress.BROADCAST);
+ eth.setVlanID(fromInterface.vlan().toShort());
+ eth.setPayload(arp);
+
+ this.inPacket = new DefaultInboundPacket(fromInterface.connectPoint(), eth,
+ ByteBuffer.wrap(eth.serialize()));
+ }
+
+ @Override
+ public InboundPacket inPacket() {
+ 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);
+
+ Dhcp6ClientIdOption clientIdOption = new Dhcp6ClientIdOption();
+ Dhcp6Duid dhcp6Duip = new Dhcp6Duid();
+ dhcp6Duip.setDuidType(Dhcp6Duid.DuidType.DUID_LLT);
+ dhcp6Duip.setHardwareType((short) 0x01); // Ethernet
+ dhcp6Duip.setDuidTime(1234);
+ dhcp6Duip.setLinkLayerAddress(CLIENT_MAC.toBytes());
+ clientIdOption.setDuid(dhcp6Duip);
+
+ 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(clientIdOption);
+ 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);
+ byte[] optionData = interfaceIdOption.getData();
+ ByteBuffer bb = ByteBuffer.wrap(interfaceIdBytes);
+
+ byte[] macAddr = new byte[MacAddress.MAC_ADDRESS_LENGTH];
+ byte[] port = new byte[optionData.length - MacAddress.MAC_ADDRESS_LENGTH -
+ VLAN_LEN - SEPARATOR_LEN * 2];
+ short vlan;
+ bb.get(macAddr);
+ bb.get(); // separator
+ bb.get(port);
+ bb.get(); // separator
+ vlan = bb.getShort();
+ interfaceIdOption.setMacAddress(MacAddress.valueOf(macAddr));
+ interfaceIdOption.setInPort(port);
+ interfaceIdOption.setVlanId(vlan);
+
+ 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, short vlanId, ConnectPoint clientCp) {
+ String inPortString = "-" + clientCp.toString() + ":";
+ byte[] clientSoureMacBytes = clientMac.toBytes();
+ byte[] inPortStringBytes = inPortString.getBytes();
+ byte[] vlanIdBytes = new byte[2];
+ vlanIdBytes[0] = (byte) ((vlanId >> 8) & 0xff); // high-order byte first
+ vlanIdBytes[1] = (byte) (vlanId & 0xff);
+ byte[] interfaceIdBytes = new byte[clientSoureMacBytes.length + inPortStringBytes.length + vlanIdBytes.length];
+
+ System.arraycopy(clientSoureMacBytes, 0, interfaceIdBytes, 0, clientSoureMacBytes.length);
+ System.arraycopy(inPortStringBytes, 0, interfaceIdBytes, clientSoureMacBytes.length, inPortStringBytes.length);
+ System.arraycopy(vlanIdBytes, 0, interfaceIdBytes, clientSoureMacBytes.length + inPortStringBytes.length,
+ vlanIdBytes.length);
+
+ return interfaceIdBytes;
+ }
+
+ private static List<TrafficSelector> buildClientDhcpSelectors() {
+ return Streams.concat(Dhcp4HandlerImpl.DHCP_SELECTORS.stream(),
+ Dhcp6HandlerImpl.DHCP_SELECTORS.stream())
+ .collect(Collectors.toList());
+ }
+
+ private class TestDhcp6RequestPacketContext extends PacketContextAdapter {
+
+
+ private InboundPacket inPacket;
+
+
+ public TestDhcp6RequestPacketContext(byte msgType, 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, msgType,
+ IP_FOR_CLIENT_V6,
+ msgType == DHCP6.MsgType.REQUEST.value() ? PREFIX_FOR_ZERO : PREFIX_FOR_CLIENT_V6);
+ DHCP6 dhcp6Parent = null;
+ DHCP6 dhcp6Child = dhcp6Payload;
+ for (int i = 0; i < relayLevel; i++) {
+ dhcp6Parent = new DHCP6();
+ byte[] interfaceId = buildInterfaceId(clientMac, vlanId.toShort(), 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, msgType,
+ IP_FOR_CLIENT_V6,
+ msgType == DHCP6.MsgType.REQUEST.value() ? PREFIX_FOR_ZERO : PREFIX_FOR_CLIENT_V6);
+ }
+
+ 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(OUTER_RELAY_VLAN.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(byte msgType, ConnectPoint clientCp, MacAddress clientMac,
+ VlanId clientVlan, Ip6Address clientGwAddr,
+ int relayLevel, boolean overWriteFlag, VlanId overWriteVlan) {
+ super(0, null, null, false);
+
+
+ DHCP6 dhcp6Payload = new DHCP6();
+ buildDhcp6Packet(dhcp6Payload, msgType,
+ IP_FOR_CLIENT_V6,
+ PREFIX_FOR_CLIENT_V6);
+ byte[] interfaceId = null;
+ if (relayLevel > 0) {
+ interfaceId = buildInterfaceId(OUTER_RELAY_MAC,
+ overWriteFlag ? overWriteVlan.toShort() : OUTER_RELAY_VLAN.toShort(),
+ OUTER_RELAY_CP);
+ } else {
+ interfaceId = buildInterfaceId(clientMac, clientVlan.toShort(), clientCp);
+ }
+ DHCP6 dhcp6 = new DHCP6();
+ buildRelayMsg(dhcp6, DHCP6.MsgType.RELAY_REPL.value(),
+ INTERFACE_IP_V6.ipAddress().getIp6Address(),
+ CLIENT_LL_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/apps/dhcprelay/app/src/test/java/org/onosproject/dhcprelay/config/DhcpRelayConfigTest.java b/apps/dhcprelay/app/src/test/java/org/onosproject/dhcprelay/config/DhcpRelayConfigTest.java
new file mode 100644
index 0000000..c743247
--- /dev/null
+++ b/apps/dhcprelay/app/src/test/java/org/onosproject/dhcprelay/config/DhcpRelayConfigTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.onosproject.dhcprelay.config;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.io.Resources;
+import org.junit.Test;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+
+import java.io.IOException;
+
+import static org.junit.Assert.*;
+import static org.onosproject.dhcprelay.DhcpRelayManager.DHCP_RELAY_APP;
+
+/**
+ * Tests for DHCP relay app configuration.
+ */
+public class DhcpRelayConfigTest {
+ private static final String CONFIG_FILE_PATH = "dhcp-relay.json";
+ private static final String INVALID_CONFIG_FILE_PATH = "invalid-dhcp-relay.json";
+ private static final ApplicationId APP_ID = new TestApplicationId("DhcpRelayTest");
+ private static final ConnectPoint DEFAULT_CONNECT_POINT = ConnectPoint.deviceConnectPoint("of:0000000000000002/2");
+ private static final Ip4Address DEFAULT_SERVER_IP = Ip4Address.valueOf("172.168.10.2");
+ private static final Ip4Address DEFAULT_GATEWAY_IP = Ip4Address.valueOf("192.168.10.254");
+ private static final Ip6Address DEFAULT_SERVER_IP_V6 = Ip6Address.valueOf("2000::200:1");
+ private static final Ip6Address DEFAULT_GATEWAY_IP_V6 = Ip6Address.valueOf("1000::100:1");
+ private static final ConnectPoint INDIRECT_CONNECT_POINT = ConnectPoint.deviceConnectPoint("of:0000000000000002/3");
+ private static final Ip4Address INDIRECT_SERVER_IP = Ip4Address.valueOf("172.168.10.3");
+
+ @Test
+ public void testDefaultConfig() throws IOException {
+ ObjectMapper om = new ObjectMapper();
+ JsonNode json = om.readTree(Resources.getResource(CONFIG_FILE_PATH));
+ DefaultDhcpRelayConfig config = new DefaultDhcpRelayConfig();
+ json = json.path("apps").path(DHCP_RELAY_APP).path(DefaultDhcpRelayConfig.KEY);
+ config.init(APP_ID, DefaultDhcpRelayConfig.KEY, json, om, null);
+
+ assertEquals(1, config.dhcpServerConfigs().size());
+ DhcpServerConfig serverConfig = config.dhcpServerConfigs().get(0);
+ assertEquals(DEFAULT_CONNECT_POINT, serverConfig.getDhcpServerConnectPoint().orElse(null));
+ assertEquals(DEFAULT_SERVER_IP, serverConfig.getDhcpServerIp4().orElse(null));
+ assertEquals(DEFAULT_GATEWAY_IP, serverConfig.getDhcpGatewayIp4().orElse(null));
+ assertEquals(DEFAULT_SERVER_IP_V6, serverConfig.getDhcpServerIp6().orElse(null));
+ assertEquals(DEFAULT_GATEWAY_IP_V6, serverConfig.getDhcpGatewayIp6().orElse(null));
+ }
+
+ @Test
+ public void testIndirectConfig() throws IOException {
+ ObjectMapper om = new ObjectMapper();
+ JsonNode json = om.readTree(Resources.getResource(CONFIG_FILE_PATH));
+ IndirectDhcpRelayConfig config = new IndirectDhcpRelayConfig();
+ json = json.path("apps").path(DHCP_RELAY_APP).path(IndirectDhcpRelayConfig.KEY);
+ config.init(APP_ID, IndirectDhcpRelayConfig.KEY, json, om, null);
+
+ assertEquals(1, config.dhcpServerConfigs().size());
+ DhcpServerConfig serverConfig = config.dhcpServerConfigs().get(0);
+ assertEquals(INDIRECT_CONNECT_POINT, serverConfig.getDhcpServerConnectPoint().orElse(null));
+ assertEquals(INDIRECT_SERVER_IP, serverConfig.getDhcpServerIp4().orElse(null));
+ assertNull(serverConfig.getDhcpGatewayIp4().orElse(null));
+ assertNull(serverConfig.getDhcpServerIp6().orElse(null));
+ assertNull(serverConfig.getDhcpGatewayIp6().orElse(null));
+ }
+
+ @Test
+ public void testInvalidConfig() throws IOException {
+ ObjectMapper om = new ObjectMapper();
+ JsonNode json = om.readTree(Resources.getResource(INVALID_CONFIG_FILE_PATH));
+ DefaultDhcpRelayConfig config = new DefaultDhcpRelayConfig();
+ json = json.path("apps").path(DHCP_RELAY_APP).path(DefaultDhcpRelayConfig.KEY);
+ config.init(APP_ID, DefaultDhcpRelayConfig.KEY, json, om, null);
+ assertFalse(config.isValid());
+ }
+}
diff --git a/apps/dhcprelay/app/src/test/java/org/onosproject/dhcprelay/config/IgnoreDhcpConfigTest.java b/apps/dhcprelay/app/src/test/java/org/onosproject/dhcprelay/config/IgnoreDhcpConfigTest.java
new file mode 100644
index 0000000..3712c06
--- /dev/null
+++ b/apps/dhcprelay/app/src/test/java/org/onosproject/dhcprelay/config/IgnoreDhcpConfigTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.onosproject.dhcprelay.config;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.io.Resources;
+import org.junit.Test;
+import org.onlab.packet.VlanId;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.dhcprelay.DhcpRelayManager.DHCP_RELAY_APP;
+
+public class IgnoreDhcpConfigTest {
+ private static final String CONFIG_FILE_PATH = "dhcp-relay.json";
+ private static final ApplicationId APP_ID = new TestApplicationId("DhcpRelayTest");
+ private static final DeviceId DEV_1_ID = DeviceId.deviceId("of:0000000000000001");
+ private static final DeviceId DEV_2_ID = DeviceId.deviceId("of:0000000000000002");
+ private static final VlanId IGNORED_VLAN = VlanId.vlanId("100");
+ @Test
+ public void testIgnoredDhcpConfig() throws IOException {
+ ObjectMapper om = new ObjectMapper();
+ JsonNode json = om.readTree(Resources.getResource(CONFIG_FILE_PATH));
+ IgnoreDhcpConfig config = new IgnoreDhcpConfig();
+ json = json.path("apps").path(DHCP_RELAY_APP).path(IgnoreDhcpConfig.KEY);
+ config.init(APP_ID, IgnoreDhcpConfig.KEY, json, om, null);
+
+ assertEquals(2, config.ignoredVlans().size());
+ Collection<VlanId> vlanForDev1 = config.ignoredVlans().get(DEV_1_ID);
+ Collection<VlanId> vlanForDev2 = config.ignoredVlans().get(DEV_2_ID);
+
+ assertEquals(1, vlanForDev1.size());
+ assertEquals(1, vlanForDev2.size());
+
+ assertTrue(vlanForDev1.contains(IGNORED_VLAN));
+ assertTrue(vlanForDev2.contains(IGNORED_VLAN));
+ }
+}
diff --git a/apps/dhcprelay/app/src/test/java/org/onosproject/dhcprelay/store/DhcpRecordTest.java b/apps/dhcprelay/app/src/test/java/org/onosproject/dhcprelay/store/DhcpRecordTest.java
new file mode 100644
index 0000000..7aaf65b
--- /dev/null
+++ b/apps/dhcprelay/app/src/test/java/org/onosproject/dhcprelay/store/DhcpRecordTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.onosproject.dhcprelay.store;
+
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.packet.DHCP;
+import org.onlab.packet.DHCP6;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+
+import java.util.Optional;
+
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit test for DHCP record.
+ */
+public class DhcpRecordTest {
+ private static final MacAddress MAC = MacAddress.valueOf("1a:1a:1a:1a:1a:1a");
+ private static final VlanId VLAN = VlanId.vlanId("100");
+ private static final HostId HOST_ID = HostId.hostId(MAC, VLAN);
+ private static final HostLocation HL1 =
+ new HostLocation(ConnectPoint.deviceConnectPoint("of:0000000000000001/1"), 0);
+ private static final HostLocation HL2 =
+ new HostLocation(ConnectPoint.deviceConnectPoint("of:0000000000000001/2"), 0);
+ private static final Ip4Address IP4ADDR = Ip4Address.valueOf("10.0.2.1");
+ private static final MacAddress GW_MAC = MacAddress.valueOf("00:00:00:00:04:01");
+ private static final Ip6Address IP6ADDR = Ip6Address.valueOf("2001::1");
+
+ /**
+ * Test creating a DHCP relay record.
+ */
+ @Test
+ public void testCreateRecord() {
+ DhcpRecord record = new DhcpRecord(HOST_ID)
+ .addLocation(HL1)
+ .addLocation(HL2)
+ .ip4Address(IP4ADDR)
+ .nextHop(GW_MAC)
+ .ip4Status(DHCP.MsgType.DHCPACK)
+ .ip6Address(IP6ADDR)
+ .ip6Status(DHCP6.MsgType.REPLY)
+ .setDirectlyConnected(true);
+
+ assertThat(record.locations().size(), is(2));
+ assertThat(record.locations(), containsInAnyOrder(HL1, HL2));
+ assertThat(record.ip4Address(), is(Optional.of(IP4ADDR)));
+ assertThat(record.nextHop(), is(Optional.of(GW_MAC)));
+ assertThat(record.ip4Status(), is(Optional.of(DHCP.MsgType.DHCPACK)));
+ assertThat(record.ip6Address(), is(Optional.of(IP6ADDR)));
+ assertThat(record.ip6Status(), is(Optional.of(DHCP6.MsgType.REPLY)));
+ assertThat(record.directlyConnected(), is(true));
+
+ DhcpRecord record2 = new DhcpRecord(HOST_ID)
+ .nextHop(GW_MAC)
+ .addLocation(HL2)
+ .ip6Address(IP6ADDR)
+ .addLocation(HL1)
+ .ip6Status(DHCP6.MsgType.REPLY)
+ .ip4Address(IP4ADDR)
+ .ip4Status(DHCP.MsgType.DHCPACK)
+ .setDirectlyConnected(true);
+
+ TestUtils.setField(record, "lastSeen", 0);
+ TestUtils.setField(record2, "lastSeen", 0);
+ TestUtils.setField(record, "addrPrefTime", 0);
+ TestUtils.setField(record2, "addrPrefTime", 0);
+ TestUtils.setField(record, "pdPrefTime", 0);
+ TestUtils.setField(record2, "pdPrefTime", 0);
+ TestUtils.setField(record, "v6Counter", null);
+ TestUtils.setField(record2, "v6Counter", null);
+
+ assertThat(record, equalTo(record2));
+ assertThat(record.hashCode(), equalTo(record2.hashCode()));
+ }
+
+ /**
+ * Test clone a DHCP record.
+ */
+ @Test
+ public void testCloneRecord() {
+ DhcpRecord record = new DhcpRecord(HOST_ID)
+ .addLocation(HL1)
+ .addLocation(HL2)
+ .ip4Address(IP4ADDR)
+ .nextHop(GW_MAC)
+ .ip4Status(DHCP.MsgType.DHCPACK)
+ .ip6Address(IP6ADDR)
+ .ip6Status(DHCP6.MsgType.REPLY)
+ .setDirectlyConnected(true);
+ DhcpRecord clonedRecord = record.clone();
+ assertEquals(record, clonedRecord);
+ }
+}
diff --git a/apps/dhcprelay/app/src/test/java/org/onosproject/dhcprelay/store/DistributedDhcpRelayStoreTest.java b/apps/dhcprelay/app/src/test/java/org/onosproject/dhcprelay/store/DistributedDhcpRelayStoreTest.java
new file mode 100644
index 0000000..81491d0
--- /dev/null
+++ b/apps/dhcprelay/app/src/test/java/org/onosproject/dhcprelay/store/DistributedDhcpRelayStoreTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.onosproject.dhcprelay.store;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.store.service.TestStorageService;
+
+import java.util.Collection;
+import java.util.concurrent.CompletableFuture;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.onlab.packet.DHCP.MsgType.DHCPREQUEST;
+
+public class DistributedDhcpRelayStoreTest {
+ private static final ConnectPoint CP = ConnectPoint.deviceConnectPoint("of:1/1");
+ private static final MacAddress MAC = MacAddress.valueOf("00:00:00:00:00:01");
+ private static final VlanId VLAN_ID = VlanId.vlanId("100");
+ private static final HostId HOST_ID = HostId.hostId(MAC, VLAN_ID);
+ private static final Ip4Address IP = Ip4Address.valueOf("192.168.1.10");
+ private static final MacAddress GW_MAC = MacAddress.valueOf("00:00:00:00:01:01");
+ private DistributedDhcpRelayStore store;
+
+ @Before
+ public void setup() {
+ store = new DistributedDhcpRelayStore();
+ store.storageService = new TestStorageService();
+ store.activated();
+ }
+
+ @After
+ public void teerDown() {
+ store.deactivated();
+ }
+
+ /**
+ * Puts and removes a record, should received UPDATED and REMOVED event.
+ */
+ @Test
+ public void testPutAndRemoveRecord() {
+ // dhcp request, no IP
+ HostId hostId = HostId.hostId(MAC, VLAN_ID);
+ DhcpRecord record = new DhcpRecord(hostId);
+ record.addLocation(new HostLocation(CP, System.currentTimeMillis()));
+ record.setDirectlyConnected(true);
+ record.nextHop(GW_MAC);
+ record.ip4Status(DHCPREQUEST);
+
+ CompletableFuture<DhcpRelayStoreEvent> recordComplete = new CompletableFuture<>();
+ store.setDelegate(recordComplete::complete);
+ store.updateDhcpRecord(HOST_ID, record);
+ DhcpRelayStoreEvent event = recordComplete.join();
+ assertEquals(record, event.subject());
+ assertEquals(DhcpRelayStoreEvent.Type.UPDATED, event.type());
+ DhcpRecord recordInStore = store.getDhcpRecord(HOST_ID).orElse(null);
+ assertNotNull(recordInStore);
+ assertEquals(record, recordInStore);
+ Collection<DhcpRecord> recordsInStore = store.getDhcpRecords();
+ assertEquals(1, recordsInStore.size());
+ assertEquals(record, recordsInStore.iterator().next());
+
+ // dhcp request, with IP
+ record = new DhcpRecord(hostId);
+ record.addLocation(new HostLocation(CP, System.currentTimeMillis()));
+ record.setDirectlyConnected(true);
+ record.ip4Address(IP);
+ record.nextHop(GW_MAC);
+ record.ip4Status(DHCPREQUEST);
+
+ recordComplete = new CompletableFuture<>();
+ store.setDelegate(recordComplete::complete);
+ store.updateDhcpRecord(HOST_ID, record);
+ event = recordComplete.join();
+ DhcpRecord subject = event.subject();
+ assertEquals(record.locations(), subject.locations());
+ assertEquals(record.vlanId(), subject.vlanId());
+ assertEquals(record.macAddress(), subject.macAddress());
+ assertEquals(record.ip4Address(), subject.ip4Address());
+ assertEquals(record.nextHop(), subject.nextHop());
+ assertEquals(record.ip4Status(), subject.ip4Status());
+ assertEquals(record.ip6Address(), subject.ip6Address());
+ assertEquals(record.ip6Status(), subject.ip6Status());
+ assertEquals(record.directlyConnected(), subject.directlyConnected());
+
+ assertEquals(DhcpRelayStoreEvent.Type.UPDATED, event.type());
+ recordInStore = store.getDhcpRecord(HOST_ID).orElse(null);
+ assertNotNull(recordInStore);
+ assertEquals(record.locations(), recordInStore.locations());
+ assertEquals(record.vlanId(), recordInStore.vlanId());
+ assertEquals(record.macAddress(), recordInStore.macAddress());
+ assertEquals(record.ip4Address(), recordInStore.ip4Address());
+ assertEquals(record.nextHop(), recordInStore.nextHop());
+ assertEquals(record.ip4Status(), recordInStore.ip4Status());
+ assertEquals(record.ip6Address(), recordInStore.ip6Address());
+ assertEquals(record.ip6Status(), recordInStore.ip6Status());
+ assertEquals(record.directlyConnected(), recordInStore.directlyConnected());
+ recordsInStore = store.getDhcpRecords();
+ assertEquals(1, recordsInStore.size());
+
+ // removes record
+ recordComplete = new CompletableFuture<>();
+ store.setDelegate(recordComplete::complete);
+ DhcpRecord removedRecord = store.removeDhcpRecord(HOST_ID).orElse(null);
+ assertEquals(record.locations(), removedRecord.locations());
+ assertEquals(record.vlanId(), removedRecord.vlanId());
+ assertEquals(record.macAddress(), removedRecord.macAddress());
+ assertEquals(record.ip4Address(), removedRecord.ip4Address());
+ assertEquals(record.nextHop(), removedRecord.nextHop());
+ assertEquals(record.ip4Status(), removedRecord.ip4Status());
+ assertEquals(record.ip6Address(), removedRecord.ip6Address());
+ assertEquals(record.ip6Status(), removedRecord.ip6Status());
+ assertEquals(record.directlyConnected(), removedRecord.directlyConnected());
+ event = recordComplete.join();
+ assertEquals(record, event.subject());
+ assertEquals(DhcpRelayStoreEvent.Type.REMOVED, event.type());
+ recordInStore = store.getDhcpRecord(HOST_ID).orElse(null);
+ assertNull(recordInStore);
+ recordsInStore = store.getDhcpRecords();
+ assertEquals(0, recordsInStore.size());
+ }
+}
diff --git a/apps/dhcprelay/app/src/test/resources/dhcp-relay.json b/apps/dhcprelay/app/src/test/resources/dhcp-relay.json
new file mode 100644
index 0000000..fe5da9c
--- /dev/null
+++ b/apps/dhcprelay/app/src/test/resources/dhcp-relay.json
@@ -0,0 +1,39 @@
+{
+ "apps": {
+ "org.onosproject.dhcprelay" : {
+ "default": [
+ {
+ "dhcpServerConnectPoint": "of:0000000000000002/2",
+ "serverIps": ["172.168.10.2", "2000::200:1"],
+ "gatewayIps": ["192.168.10.254", "1000::100:1"],
+ "relayAgentIps": {
+ "of:0000000000000001": {
+ "ipv4": "10.0.0.10",
+ "ipv6": "2000::10"
+ },
+ "of:0000000000000002": {
+ "ipv4": "10.0.1.10",
+ "ipv6": "2000::1:10"
+ }
+ }
+ }
+ ],
+ "indirect": [
+ {
+ "dhcpServerConnectPoint": "of:0000000000000002/3",
+ "serverIps": ["172.168.10.3"],
+ "relayAgentIps": {
+ "of:0000000000000001": {
+ "ipv4": "10.0.0.10",
+ "ipv6": "2000::10"
+ }
+ }
+ }
+ ],
+ "ignoreDhcp": [
+ {"deviceId": "of:0000000000000001", "vlan": 100},
+ {"deviceId": "of:0000000000000002", "vlan": 100}
+ ]
+ }
+ }
+}
diff --git a/apps/dhcprelay/app/src/test/resources/invalid-dhcp-relay.json b/apps/dhcprelay/app/src/test/resources/invalid-dhcp-relay.json
new file mode 100644
index 0000000..2c67eaa
--- /dev/null
+++ b/apps/dhcprelay/app/src/test/resources/invalid-dhcp-relay.json
@@ -0,0 +1,12 @@
+{
+ "apps": {
+ "org.onosproject.dhcprelay" : {
+ "default": [
+ {
+ "dhcpServerConnectPoint": "of:0000000000000002/2",
+ "gatewayIps": ["192.168.10.254", "1000::100:1"]
+ }
+ ]
+ }
+ }
+}