/* * Copyright 2015-present Open Networking Laboratory * * 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.dhcp.impl; 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.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.Service; import org.onlab.packet.Ip4Address; import org.onlab.packet.MacAddress; import org.onlab.util.KryoNamespace; import org.onosproject.dhcp.DhcpStore; import org.onosproject.dhcp.IpAssignment; import org.onosproject.net.HostId; import org.onosproject.store.serializers.KryoNamespaces; import org.onosproject.store.service.ConsistentMap; import org.onosproject.store.service.DistributedSet; import org.onosproject.store.service.Serializer; import org.onosproject.store.service.StorageService; import org.onosproject.store.service.Versioned; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; import java.util.Map; import java.util.HashMap; import static org.onosproject.dhcp.IpAssignment.AssignmentStatus.Option_Assigned; import static org.onosproject.dhcp.IpAssignment.AssignmentStatus.Option_RangeNotEnforced; /** * Manages the pool of available IP Addresses in the network and * Remembers the mapping between MAC ID and IP Addresses assigned. */ @Component(immediate = true) @Service public class DistributedDhcpStore implements DhcpStore { private final Logger log = LoggerFactory.getLogger(getClass()); @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected StorageService storageService; private ConsistentMap<HostId, IpAssignment> allocationMap; private DistributedSet<Ip4Address> freeIPPool; private static Ip4Address startIPRange; private static Ip4Address endIPRange; // Hardcoded values are default values. private static int timeoutForPendingAssignments = 60; @Activate protected void activate() { allocationMap = storageService.<HostId, IpAssignment>consistentMapBuilder() .withName("onos-dhcp-assignedIP") .withSerializer(Serializer.using( new KryoNamespace.Builder() .register(KryoNamespaces.API) .register(IpAssignment.class, IpAssignment.AssignmentStatus.class, Date.class) .build("dhcp"))) .build(); freeIPPool = storageService.<Ip4Address>setBuilder() .withName("onos-dhcp-freeIP") .withSerializer(Serializer.using(KryoNamespaces.API)) .build() .asDistributedSet(); log.info("Started"); } @Deactivate protected void deactivate() { log.info("Stopped"); } @Override public Ip4Address suggestIP(HostId hostId, Ip4Address requestedIP) { IpAssignment assignmentInfo; if (allocationMap.containsKey(hostId)) { assignmentInfo = allocationMap.get(hostId).value(); IpAssignment.AssignmentStatus status = assignmentInfo.assignmentStatus(); Ip4Address ipAddr = assignmentInfo.ipAddress(); if (assignmentInfo.assignmentStatus().equals(Option_RangeNotEnforced)) { return assignmentInfo.ipAddress(); } else if (status == Option_Assigned || status == IpAssignment.AssignmentStatus.Option_Requested) { // Client has a currently Active Binding. if (ipWithinRange(ipAddr)) { return ipAddr; } } else if (status == IpAssignment.AssignmentStatus.Option_Expired) { // Client has a Released or Expired Binding. if (freeIPPool.contains(ipAddr)) { assignmentInfo = IpAssignment.builder() .ipAddress(ipAddr) .timestamp(new Date()) .leasePeriod(timeoutForPendingAssignments) .assignmentStatus(IpAssignment.AssignmentStatus.Option_Requested) .build(); if (freeIPPool.remove(ipAddr)) { allocationMap.put(hostId, assignmentInfo); return ipAddr; } } } } else if (requestedIP.toInt() != 0) { // Client has requested an IP. if (freeIPPool.contains(requestedIP)) { assignmentInfo = IpAssignment.builder() .ipAddress(requestedIP) .timestamp(new Date()) .leasePeriod(timeoutForPendingAssignments) .assignmentStatus(IpAssignment.AssignmentStatus.Option_Requested) .build(); if (freeIPPool.remove(requestedIP)) { allocationMap.put(hostId, assignmentInfo); return requestedIP; } } } // Allocate a new IP from the server's pool of available IP. Ip4Address nextIPAddr = fetchNextIP(); if (nextIPAddr != null) { assignmentInfo = IpAssignment.builder() .ipAddress(nextIPAddr) .timestamp(new Date()) .leasePeriod(timeoutForPendingAssignments) .assignmentStatus(IpAssignment.AssignmentStatus.Option_Requested) .build(); allocationMap.put(hostId, assignmentInfo); } return nextIPAddr; } @Override public boolean assignIP(HostId hostId, IpAssignment ipAssignment) { log.trace("Assign IP Called HostId: {}, ipAssignment: {}", hostId, ipAssignment); IpAssignment newAssignment = null; Versioned<IpAssignment> versionedAssignment = allocationMap.get(hostId); Ip4Address requestedIp = ipAssignment.ipAddress(); if (versionedAssignment == null) { // this is new IP assignment of static mapping // dynamic assignment is done in suggestIP if (ipAssignment.assignmentStatus().equals(Option_RangeNotEnforced)) { newAssignment = ipAssignment; } else if (freeIPPool.remove(requestedIp)) { newAssignment = IpAssignment.builder(ipAssignment) .assignmentStatus(Option_Assigned) .timestamp(new Date()) .build(); } else { log.trace("Failed to assign IP for {}", ipAssignment); return false; } log.trace("Assigned {}", newAssignment); return allocationMap.putIfAbsent(hostId, newAssignment) == null; // TODO: handle the case where map changed. } else { // this is lease renew or rebinding // update assignment status and time stamp, and keep the others IpAssignment existingAssignment = versionedAssignment.value(); if (!existingAssignment.ipAddress().equals(requestedIp)) { // return false if existing assignment is not for the // requested host log.trace("Failed to assign IP for {}", ipAssignment); return false; } switch (existingAssignment.assignmentStatus()) { case Option_RangeNotEnforced: newAssignment = IpAssignment.builder(existingAssignment) .timestamp(new Date()) .build(); break; case Option_Expired: if (!freeIPPool.remove(requestedIp)) { // requested IP is expired for this host and reserved to the other host return false; } case Option_Assigned: case Option_Requested: newAssignment = IpAssignment.builder(existingAssignment) .timestamp(new Date()) .assignmentStatus(Option_Assigned) .build(); break; default: break; } log.trace("Assigned {}", newAssignment); return allocationMap.replace(hostId, versionedAssignment.version(), newAssignment); } } @Override public Ip4Address releaseIP(HostId hostId) { if (allocationMap.containsKey(hostId)) { // If the IP has been assigned with Option_RangeNotEnforced, // we do not release the IP address nor remove the host from HostService. // Therefore, if the IP is assigned statically, the IP needs to be released statically. Versioned<IpAssignment> assignmentVersioned = allocationMap.get(hostId); if (Versioned.valueOrNull(assignmentVersioned) != null && assignmentVersioned.value().assignmentStatus().equals(Option_RangeNotEnforced)) { return null; } IpAssignment newAssignment = IpAssignment.builder(allocationMap.get(hostId).value()) .assignmentStatus(IpAssignment.AssignmentStatus.Option_Expired) .build(); Ip4Address freeIP = newAssignment.ipAddress(); allocationMap.put(hostId, newAssignment); if (ipWithinRange(freeIP)) { freeIPPool.add(freeIP); } return freeIP; } return null; } @Override public void setDefaultTimeoutForPurge(int timeInSeconds) { timeoutForPendingAssignments = timeInSeconds; } @Override public Map<HostId, IpAssignment> listAssignedMapping() { Map<HostId, IpAssignment> validMapping = new HashMap<>(); IpAssignment assignment; for (Map.Entry<HostId, Versioned<IpAssignment>> entry: allocationMap.entrySet()) { assignment = entry.getValue().value(); if (assignment.assignmentStatus() == Option_Assigned || assignment.assignmentStatus() == Option_RangeNotEnforced) { validMapping.put(entry.getKey(), assignment); } } return validMapping; } @Override public Map<HostId, IpAssignment> listAllMapping() { Map<HostId, IpAssignment> validMapping = new HashMap<>(); for (Map.Entry<HostId, Versioned<IpAssignment>> entry: allocationMap.entrySet()) { validMapping.put(entry.getKey(), entry.getValue().value()); } return validMapping; } @Override public boolean assignStaticIP(MacAddress macAddress, IpAssignment ipAssignment) { HostId host = HostId.hostId(macAddress); return assignIP(host, ipAssignment); } @Override public boolean removeStaticIP(MacAddress macID) { HostId host = HostId.hostId(macID); if (allocationMap.containsKey(host)) { IpAssignment assignment = allocationMap.get(host).value(); if (assignment.assignmentStatus().equals(Option_RangeNotEnforced)) { allocationMap.remove(host); return true; } Ip4Address freeIP = assignment.ipAddress(); if (assignment.leasePeriod() < 0) { allocationMap.remove(host); if (ipWithinRange(freeIP)) { freeIPPool.add(freeIP); } return true; } } return false; } @Override public Iterable<Ip4Address> getAvailableIPs() { return ImmutableSet.copyOf(freeIPPool); } @Override public void populateIPPoolfromRange(Ip4Address startIP, Ip4Address endIP) { // Clear all entries from previous range. allocationMap.clear(); freeIPPool.clear(); startIPRange = startIP; endIPRange = endIP; int lastIP = endIP.toInt(); Ip4Address nextIP; for (int loopCounter = startIP.toInt(); loopCounter <= lastIP; loopCounter++) { nextIP = Ip4Address.valueOf(loopCounter); freeIPPool.add(nextIP); } log.debug("Updated free IP pool {}:{} size:{}", startIP, endIP, freeIPPool.size()); } @Override public IpAssignment getIpAssignmentFromAllocationMap(HostId hostId) { if (allocationMap.get(hostId) != null) { return allocationMap.get(hostId).value(); } else { return null; } } /** * Fetches the next available IP from the free pool pf IPs. * * @return the next available IP address */ private Ip4Address fetchNextIP() { for (Ip4Address freeIP : freeIPPool) { if (freeIPPool.remove(freeIP)) { return freeIP; } } return null; } /** * Returns true if the given ip is within the range of available IPs. * * @param ip given ip address * @return true if within range, false otherwise */ private boolean ipWithinRange(Ip4Address ip) { if ((ip.toInt() >= startIPRange.toInt()) && (ip.toInt() <= endIPRange.toInt())) { return true; } return false; } }