package net.floodlightcontroller.dhcpserver;
import java.util.ArrayList;
import org.projectfloodlight.openflow.types.IPv4Address;
import org.projectfloodlight.openflow.types.MacAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.floodlightcontroller.dhcpserver.DHCPBinding;
/**
* The class representing a DHCP Pool.
* This class is essentially a list of DHCPBinding objects containing IP, MAC, and lease status information.
*
* @author Ryan Izard (rizard@g.clemson.edu)
*/
public class DHCPPool {
protected static final Logger log = LoggerFactory.getLogger(DHCPPool.class);;
private volatile static ArrayList<DHCPBinding> DHCP_POOL = new ArrayList<DHCPBinding>();
private volatile int POOL_SIZE;
private volatile int POOL_AVAILABILITY;
private volatile boolean POOL_FULL;
private volatile IPv4Address STARTING_ADDRESS;
private final MacAddress UNASSIGNED_MAC = MacAddress.NONE;
// Need to write this to handle subnets later...
// This assumes startingIPv4Address can handle size addresses
/**
* Constructor for a DHCPPool of DHCPBinding's. Each DHCPBinding object is initialized with a
* null MAC address and the lease is set to inactive (i.e. false).
* @param {@code byte[]} startingIPv4Address: The lowest IP address to lease.
* @param {@code integer} size: (startingIPv4Address + size) is the highest IP address to lease.
* @return none
*/
public DHCPPool(IPv4Address startingIPv4Address, int size) {
int IPv4AsInt = startingIPv4Address.getInt();
this.setPoolSize(size);
this.setPoolAvailability(size);
STARTING_ADDRESS = startingIPv4Address;
for (int i = 0; i < size; i++) {
DHCP_POOL.add(new DHCPBinding(IPv4Address.of(IPv4AsInt + i), UNASSIGNED_MAC));
}
}
public IPv4Address getStartIp() {
return STARTING_ADDRESS;
}
private void setPoolFull(boolean full) {
POOL_FULL = full;
}
private boolean isPoolFull() {
return POOL_FULL;
}
private void setPoolSize(int size) {
POOL_SIZE = size;
}
private int getPoolSize() {
return POOL_SIZE;
}
private int getPoolAvailability() {
return POOL_AVAILABILITY;
}
private void setPoolAvailability(int size) {
POOL_AVAILABILITY = size;
}
/**
* Gets the DHCPBinding object from the DHCPPool containing {@code byte[]} ip
* @param {@code byte[]} ip: The IPv4 address to match in a DHCPBinding
* @return {@code DHCPBinding}: The matching DHCPBinding object or null if ip is not found
*/
public DHCPBinding getDHCPbindingFromIPv4(IPv4Address ip) {
if (ip == null) return null;
for (DHCPBinding binding : DHCP_POOL) {
if (binding.getIPv4Address().equals(ip)) {
return binding;
}
}
return null;
}
/**
* Gets the DHCPBinding object from the DHCPPool containing {@code byte[]} mac
* @param {@code byte[]} mac: The MAC address to match in in a DHCPBinding
* @return {@code DHCPBinding}: The matching DHCPBinding object or null if mac is not found
*/
public DHCPBinding getDHCPbindingFromMAC(MacAddress mac) {
if (mac == null) return null;
for (DHCPBinding binding : DHCP_POOL) {
if (binding.getMACAddress().equals(mac)) {
return binding;
}
}
return null;
}
/**
* Gets the lease status of a particular IPv4 address, {@code byte[]} ip
* @param {@code byte[]} ip: The IPv4 address of which to check the lease status
* @return {@code boolean}: true if lease is active, false if lease is inactive/expired
*/
public boolean isIPv4Leased(IPv4Address ip) {
DHCPBinding binding = this.getDHCPbindingFromIPv4(ip);
if (binding != null) return binding.isActiveLease();
else return false;
}
/**
* Assigns a MAC address to the IP address of the DHCPBinding object in the DHCPPool object.
* This method also sets the lease to active (i.e. true) when the assignment is made.
* @param {@code DHCPBinding} binding: The DHCPBinding object in which to set the MAC
* @param {@code byte[]} mac: The MAC address to set in the DHCPBinding object
* @param {@code long}: The time in seconds for which the lease will be valid
* @return none
*/
public void setDHCPbinding(DHCPBinding binding, MacAddress mac, int time) {
int index = DHCP_POOL.indexOf(binding);
binding.setMACAddress(mac);
binding.setLeaseStatus(true);
this.setPoolAvailability(this.getPoolAvailability() - 1);
DHCP_POOL.set(index, binding);
if (this.getPoolAvailability() == 0) setPoolFull(true);
binding.setLeaseStartTimeSeconds();
binding.setLeaseDurationSeconds(time);
}
/**
* Completely removes the DHCPBinding object with IP address {@code byte[]} ip from the DHCPPool
* @param {@code byte[]} ip: The IP address to remove from the pool. This address will not be available
* for lease after removal.
* @return none
*/
public void removeIPv4FromDHCPPool(IPv4Address ip) {
if (ip == null || getDHCPbindingFromIPv4(ip) == null) return;
if (ip.equals(STARTING_ADDRESS)) {
DHCPBinding lowest = null;
// Locate the lowest address (other than ip), which will be the new starting address
for (DHCPBinding binding : DHCP_POOL) {
if (lowest == null) {
lowest = binding;
} else if (binding.getIPv4Address().getInt() < lowest.getIPv4Address().getInt()
&& !binding.getIPv4Address().equals(ip))
{
lowest = binding;
}
}
// lowest is new starting address
STARTING_ADDRESS = lowest.getIPv4Address();
}
DHCP_POOL.remove(this.getDHCPbindingFromIPv4(ip));
this.setPoolSize(this.getPoolSize() - 1);
this.setPoolAvailability(this.getPoolAvailability() - 1);
if (this.getPoolAvailability() == 0) this.setPoolFull(true);
}
/**
* Adds an IP address to the DHCPPool if the address is not already present. If present, nothing is added to the DHCPPool.
* @param {@code byte[]} ip: The IP address to attempt to add to the DHCPPool
* @return {@code DHCPBinding}: Reference to the DHCPBinding object if successful, null if unsuccessful
*/
public DHCPBinding addIPv4ToDHCPPool(IPv4Address ip) {
DHCPBinding binding = null;
if (this.getDHCPbindingFromIPv4(ip) == null) {
if (ip.getInt() < STARTING_ADDRESS.getInt()) {
STARTING_ADDRESS = ip;
}
binding = new DHCPBinding(ip, null);
DHCP_POOL.add(binding);
this.setPoolSize(this.getPoolSize() + 1);
this.setPoolFull(false);
}
return binding;
}
/**
* Determines if there are available leases in this DHCPPool.
* @return {@code boolean}: true if there are addresses available, false if the DHCPPool is full
*/
public boolean hasAvailableAddresses() {
if (isPoolFull() || getPoolAvailability() == 0) return false;
else return true;
}
/**
* Returns an available address (DHCPBinding) for lease.
* If this MAC is configured for a static/fixed IP, that DHCPBinding will be returned.
* If this MAC has had a lease before and that same lease is available, that DHCPBinding will be returned.
* If not, then an attempt to return an address that has not been active before will be made.
* If there are no addresses that have not been used, then the lowest currently inactive address will be returned.
* If all addresses are being used, then null will be returned.
* @param {@code byte[]): MAC address of the device requesting the lease
* @return {@code DHCPBinding}: Reference to the DHCPBinding object if successful, null if unsuccessful
*/
public DHCPBinding getAnyAvailableLease(MacAddress mac) {
if (isPoolFull()) return null;
DHCPBinding usedBinding = null;
usedBinding = this.getDHCPbindingFromMAC(mac);
if (usedBinding != null) return usedBinding;
for (DHCPBinding binding : DHCP_POOL) {
if (!binding.isActiveLease()
&& binding.getMACAddress().equals(UNASSIGNED_MAC))
{
return binding;
} else if (!binding.isActiveLease() && usedBinding == null && !binding.isStaticIPLease()) {
usedBinding = binding;
}
}
return usedBinding;
}
/**
* Returns a specific available IP address binding for lease. The MAC and IP will be queried
* against the DHCP pool. (1) If the MAC is found in an available, fixed binding, and that binding
* is not for the provided IP, the fixed binding associated with the MAC will be returned. (2) If the
* IP is found in an available, fixed binding, and that binding also contains the MAC address provided,
* then the binding will be returned -- this is true only if the IP and MAC result in the same available,
* fixed binding. (3) If the IP is found in the pool and it is available and not fixed, then its
* binding will be returned. (4) If the IP provided does not match any available entries or is invalid,
* null will be returned. If this is the case, run getAnyAvailableLease(mac) to resolve.
* @param {@code byte[]}: The IP address on which to try and obtain a lease
* @param {@code byte[]}: The MAC address on which to try and obtain a lease.
* @return {@code DHCPBinding}: Reference to the DHCPBinding object if successful, null if unsuccessful.
*/
public DHCPBinding getSpecificAvailableLease(IPv4Address ip, MacAddress mac) {
if (ip == null || mac == null || isPoolFull()) return null;
DHCPBinding binding = this.getDHCPbindingFromIPv4(ip);
DHCPBinding binding2 = this.getDHCPbindingFromMAC(mac);
// For all of the following, the binding is also determined to be inactive:
// If configured, we must return a fixed binding for a MAC address even if it's requesting another IP
if (binding2 != null && !binding2.isActiveLease() && binding2.isStaticIPLease() && binding != binding2) {
if (log != null) log.info("Fixed DHCP entry for MAC trumps requested IP. Returning binding for MAC");
return binding2;
// If configured, we must return a fixed binding for an IP if the binding is fixed to the provided MAC (ideal static request case)
} else if (binding != null && !binding.isActiveLease() && binding.isStaticIPLease() && mac.equals(binding.getMACAddress())) {
if (log != null) log.info("Found matching fixed DHCP entry for IP with MAC. Returning binding for IP with MAC");
return binding;
// The IP and MAC are not a part of a fixed binding, so return the binding of the requested IP
} else if (binding != null && !binding.isActiveLease() && !binding.isStaticIPLease()) {
if (log != null) log.info("No fixed DHCP entry for IP or MAC found. Returning dynamic binding for IP.");
return binding;
// Otherwise, the binding is fixed for both MAC and IP and this MAC does not match either, so we can't return it as available
} else {
if (log != null) log.debug("Invalid IP address request or IP is actively leased...check for any available lease to resolve");
return null;
}
}
/**
* Tries to renew an IP lease.
* @param {@code byte[]}: The IP address on which to try and renew a lease
* @param {@code long}: The time in seconds for which the lease will be valid
* @return {@code DHCPBinding}: True on success, false if unknown IP address
*/
public boolean renewLease(IPv4Address ip, int time) {
DHCPBinding binding = this.getDHCPbindingFromIPv4(ip);
if (binding != null) {
binding.setLeaseStartTimeSeconds();
binding.setLeaseDurationSeconds(time);
binding.setLeaseStatus(true);
return true;
}
return false;
}
/**
* Cancel an IP lease.
* @param {@code byte[]}: The IP address on which to try and cancel a lease
* @return {@code boolean}: True on success, false if unknown IP address
*/
public boolean cancelLeaseOfIPv4(IPv4Address ip) {
DHCPBinding binding = this.getDHCPbindingFromIPv4(ip);
if (binding != null) {
binding.clearLeaseTimes();
binding.setLeaseStatus(false);
this.setPoolAvailability(this.getPoolAvailability() + 1);
this.setPoolFull(false);
return true;
}
return false;
}
/**
* Cancel an IP lease.
* @param {@code byte[]}: The MAC address on which to try and cancel a lease
* @return {@code boolean}: True on success, false if unknown IP address
*/
public boolean cancelLeaseOfMAC(MacAddress mac) {
DHCPBinding binding = getDHCPbindingFromMAC(mac);
if (binding != null) {
binding.clearLeaseTimes();
binding.setLeaseStatus(false);
this.setPoolAvailability(this.getPoolAvailability() + 1);
this.setPoolFull(false);
return true;
}
return false;
}
/**
* Make the addresses of expired leases available and reset the lease times.
* @return {@code ArrayList<DHCPBinding>}: A list of the bindings that are now available
*/
public ArrayList<DHCPBinding> cleanExpiredLeases() {
ArrayList<DHCPBinding> newAvailableLeases = new ArrayList<DHCPBinding>();
for (DHCPBinding binding : DHCP_POOL) {
// isLeaseExpired() automatically excludes configured static leases
if (binding.isLeaseExpired() && binding.isActiveLease()) {
this.cancelLeaseOfIPv4(binding.getIPv4Address());
this.setPoolAvailability(this.getPoolAvailability() + 1);
this.setPoolFull(false);
newAvailableLeases.add(binding);
}
}
return newAvailableLeases;
}
/**
* Used to set a particular IP binding in the pool as a fixed/static IP lease.
* This method does not set the lease as active, but instead reserves that IP
* for only the MAC provided. To set the lease as active, the methods getAnyAvailableLease()
* or getSpecificAvailableLease() will return the correct binding given the same
* MAC provided to this method is used to bind the lease later on.
* @param {@code byte[]}: The IP address to set as static/fixed.
* @param {@code byte[]}: The MAC address to match to the IP address ip when
* an address is requested from the MAC mac
* @return {@code boolean}: True upon success; false upon failure (e.g. no IP found)
*/
public boolean configureFixedIPLease(IPv4Address ip, MacAddress mac) {
DHCPBinding binding = this.getDHCPbindingFromIPv4(ip);
if (binding != null) {
binding.setMACAddress(mac);
binding.setStaticIPLease(true);
binding.setLeaseStatus(false);
return true;
} else {
return false;
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + POOL_AVAILABILITY;
result = prime * result + (POOL_FULL ? 1231 : 1237);
result = prime * result + POOL_SIZE;
result = prime
* result
+ ((STARTING_ADDRESS == null) ? 0 : STARTING_ADDRESS.hashCode());
result = prime * result
+ ((UNASSIGNED_MAC == null) ? 0 : UNASSIGNED_MAC.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DHCPPool other = (DHCPPool) obj;
if (POOL_AVAILABILITY != other.POOL_AVAILABILITY)
return false;
if (POOL_FULL != other.POOL_FULL)
return false;
if (POOL_SIZE != other.POOL_SIZE)
return false;
if (STARTING_ADDRESS == null) {
if (other.STARTING_ADDRESS != null)
return false;
} else if (!STARTING_ADDRESS.equals(other.STARTING_ADDRESS))
return false;
if (UNASSIGNED_MAC == null) {
if (other.UNASSIGNED_MAC != null)
return false;
} else if (!UNASSIGNED_MAC.equals(other.UNASSIGNED_MAC))
return false;
return true;
}
}