package net.floodlightcontroller.dhcpserver; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.projectfloodlight.openflow.protocol.OFMessage; import org.projectfloodlight.openflow.protocol.OFPacketIn; import org.projectfloodlight.openflow.protocol.OFPacketOut; import org.projectfloodlight.openflow.protocol.OFType; import org.projectfloodlight.openflow.protocol.action.OFAction; import org.projectfloodlight.openflow.types.EthType; import org.projectfloodlight.openflow.types.IPv4Address; import org.projectfloodlight.openflow.types.IpProtocol; import org.projectfloodlight.openflow.types.MacAddress; import org.projectfloodlight.openflow.types.OFBufferId; import org.projectfloodlight.openflow.types.OFPort; import org.projectfloodlight.openflow.types.OFVlanVidMatch; import org.projectfloodlight.openflow.types.VlanVid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.internal.IOFSwitchService; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.module.FloodlightModuleException; import net.floodlightcontroller.core.module.IFloodlightModule; import net.floodlightcontroller.core.module.IFloodlightService; import net.floodlightcontroller.core.types.NodePortTuple; import net.floodlightcontroller.dhcpserver.DHCPInstance.DHCPInstanceBuilder; import net.floodlightcontroller.packet.DHCP.DHCPOptionCode; import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.packet.IPv4; import net.floodlightcontroller.packet.UDP; import net.floodlightcontroller.packet.DHCP; import net.floodlightcontroller.packet.DHCPOption; import net.floodlightcontroller.util.OFMessageUtils; /** * SDN DHCP Server * @author Ryan Izard, rizard@g.clemson.edu, ryan.izard@bigswitch.com * * The Floodlight Module implementing a DHCP DHCPServer. * This module uses {@code DHCPPool} to manage DHCP leases. * It intercepts any DHCP/BOOTP requests from connected hosts and * handles the replies. The configuration file: * * floodlight/src/main/resources/floodlightdefault.properties * * contains the DHCP options and parameters that can be set for a single * subnet. Multiple subnets can be configured with the REST API. * * To allow all DHCP request messages to be sent to the controller, * the DHCPSwitchFlowSetter module (in this same package) and the * Forwarding module (loaded by default) should also be loaded in * Floodlight. When the first DHCP request is received on a particular * port of an OpenFlow switch, the request will by default be sent to * the control plane to the controller for processing. The DHCPServer * module will intercept the message before it makes it to the Forwarding * module and process the packet. Now, because we don't want to hog all * the DHCP messages (in case there is another module that is using them) * we forward the packets down to other modules using Command.CONTINUE. * As a side effect, the forwarding module will insert flows in the OF * switch for our DHCP traffic even though we've already processed it. * In order to allow all future DHCP messages from that same port to be * sent to the controller (and not follow the Forwarding module's flows), * we need to proactively insert flows for all DHCP client traffic on * UDP port 67 to the controller. These flows will allow all DHCP traffic * to be intercepted on that same port and sent to the DHCP server running * on the Floodlight controller. * * On a traditional DHCP server, the machine is configured with different * NICs, each with their own statically-assigned IP address/subnet/mask. * The DHCP server matches the network information of each NIC with the DHCP * server's configured subnets and answers the requests accordingly. To * mirror this behavior on a OF network, we can differentiate between subnets * based on a device's attachment point. We can assign subnets for a device * per OpenFlow switch or per port per switch. * * I welcome any feedback or suggestions for improvement! * * */ public class DHCPServer implements IOFMessageListener, IFloodlightModule, IDHCPService { protected static final Logger log = LoggerFactory.getLogger(DHCPServer.class); protected static IFloodlightProviderService floodlightProvider; protected static IOFSwitchService switchService; private static Map<String, DHCPInstance> instances; private static volatile boolean enabled = false; /* * The garbage collector service for the DHCP server. It handles * expired leases by adding the IPs back to the address pool. */ private static ScheduledThreadPoolExecutor leasePoliceDispatcher; private static Runnable leasePolicePatrol; /* Variables set from floodlightdefault.properties */ private static long DHCP_SERVER_LEASE_POLICE_PATROL_PERIOD_SECONDS; /* end floodlightdefault.properties variables */ /** * DHCP messages are either: * REQUEST (client --0x01--> server) * or REPLY (server --0x02--> client) */ public static byte DHCP_OPCODE_REQUEST = intToBytes(1)[0]; public static byte DHCP_OPCODE_REPLY = intToBytes(2)[0]; /** * DHCP REQUEST messages are either of type: * DISCOVER (0x01) * REQUEST (0x03) * DECLINE (0x04) * RELEASE (0x07) * or INFORM (0x08) * DHCP REPLY messages are either of type: * OFFER (0x02) * ACK (0x05) * or NACK (0x06) **/ public static byte[] DHCP_MSG_TYPE_DISCOVER = intToBytesSizeOne(1); public static byte[] DHCP_MSG_TYPE_OFFER = intToBytesSizeOne(2); public static byte[] DHCP_MSG_TYPE_REQUEST = intToBytesSizeOne(3); public static byte[] DHCP_MSG_TYPE_DECLINE = intToBytesSizeOne(4); public static byte[] DHCP_MSG_TYPE_ACK = intToBytesSizeOne(5); public static byte[] DHCP_MSG_TYPE_NACK = intToBytesSizeOne(6); public static byte[] DHCP_MSG_TYPE_RELEASE = intToBytesSizeOne(7); public static byte[] DHCP_MSG_TYPE_INFORM = intToBytesSizeOne(8); /** * DHCP messages contain options requested by the client and * provided by the server. The options requested by the client are * provided in a list (option 0x37 below) and the server elects to * answer some or all of these options and may provide additional * options as necessary for the DHCP client to obtain a lease. * OPTION NAME HEX DEC * Subnet Mask 0x01 1 * Router IP 0x03 3 * DNS Server IP 0x06 6 * Domain Name 0x0F 15 * IP Forwarding 0x13 19 * Broadcast IP 0x1C 28 * NTP Server IP 0x2A 42 * NetBios Name IP 0x2C 44 * NetBios DDS IP 0x2D 45 * NetBios Node Type 0x2E 46 * NetBios Scope ID 0x2F 47 * Requested IP 0x32 50 * Lease Time (s) 0x33 51 * Msg Type (above) 0x35 53 * DHCP Server IP 0x36 54 * Option List (this) 0x37 55 * Renewal Time (s) 0x3A 58 * Rebind Time (s) 0x3B 59 * End Option List 0xFF 255 * * NetBios options are not currently implemented in this server but can be added * via the configuration file. **/ public static byte DHCP_REQ_PARAM_OPTION_CODE_SN = intToBytes(1)[0]; public static byte DHCP_REQ_PARAM_OPTION_CODE_ROUTER = intToBytes(3)[0]; public static byte DHCP_REQ_PARAM_OPTION_CODE_DNS = intToBytes(6)[0]; public static byte DHCP_REQ_PARAM_OPTION_CODE_DN = intToBytes(15)[0]; public static byte DHCP_REQ_PARAM_OPTION_CODE_IP_FORWARDING = intToBytes(19)[0]; public static byte DHCP_REQ_PARAM_OPTION_CODE_BROADCAST_IP = intToBytes(28)[0]; public static byte DHCP_REQ_PARAM_OPTION_CODE_NTP_IP = intToBytes(42)[0]; public static byte DHCP_REQ_PARAM_OPTION_CODE_NET_BIOS_NAME_IP = intToBytes(44)[0]; public static byte DHCP_REQ_PARAM_OPTION_CODE_NET_BIOS_DDS_IP = intToBytes(45)[0]; public static byte DHCP_REQ_PARAM_OPTION_CODE_NET_BIOS_NODE_TYPE = intToBytes(46)[0]; public static byte DHCP_REQ_PARAM_OPTION_CODE_NET_BIOS_SCOPE_ID = intToBytes(47)[0]; public static byte DHCP_REQ_PARAM_OPTION_CODE_REQUESTED_IP = intToBytes(50)[0]; public static byte DHCP_REQ_PARAM_OPTION_CODE_LEASE_TIME = intToBytes(51)[0]; public static byte DHCP_REQ_PARAM_OPTION_CODE_MSG_TYPE = intToBytes(53)[0]; public static byte DHCP_REQ_PARAM_OPTION_CODE_DHCP_SERVER = intToBytes(54)[0]; public static byte DHCP_REQ_PARAM_OPTION_CODE_REQUESTED_PARAMTERS = intToBytes(55)[0]; public static byte DHCP_REQ_PARAM_OPTION_CODE_RENEWAL_TIME = intToBytes(58)[0]; public static byte DHCP_REQ_PARAM_OPTION_CODE_REBIND_TIME = intToBytes(59)[0]; public static byte DHCP_REQ_PARAM_OPTION_CODE_END = intToBytes(255)[0]; // Used for composing DHCP REPLY messages public static final MacAddress BROADCAST_MAC = MacAddress.BROADCAST; public static final IPv4Address BROADCAST_IP = IPv4Address.NO_MASK; /* no_mask is all 1's */ public static final IPv4Address UNASSIGNED_IP = IPv4Address.FULL_MASK; /* full_mask is all 0's */ @Override public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); l.add(IFloodlightProviderService.class); return l; } @Override public void init(FloodlightModuleContext context) throws FloodlightModuleException { floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class); switchService = context.getServiceImpl(IOFSwitchService.class); instances = new HashMap<String, DHCPInstance>(); } @Override public void startUp(FloodlightModuleContext context) { floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this); // Read our config options for the DHCP DHCPServer Map<String, String> configOptions = context.getConfigParams(this); DHCPInstanceBuilder instanceBuilder = DHCPInstance.createBuilder(); try { instanceBuilder.setSubnetMask(IPv4Address.of(configOptions.get("subnet-mask"))) .setStartIp(IPv4Address.of(configOptions.get("lower-ip-range"))) .setStopIp(IPv4Address.of(configOptions.get("upper-ip-range"))) .setBroadcastIp(IPv4Address.of(configOptions.get("broadcast-address"))) .setRouterIp(IPv4Address.of(configOptions.get("router"))) .setDomainName(configOptions.get("domain-name")) .setLeaseTimeSec(Integer.parseInt(configOptions.get("default-lease-time"))) .setIpForwarding(Boolean.parseBoolean(configOptions.get("ip-forwarding"))) .setServer(MacAddress.of(configOptions.get("controller-mac")), IPv4Address.of(configOptions.get("controller-ip"))); DHCP_SERVER_LEASE_POLICE_PATROL_PERIOD_SECONDS = Long.parseLong(configOptions.get("lease-gc-period")); // NetBios and other options can be added to this function here as needed in the future } catch(IllegalArgumentException ex) { log.error("Incorrect DHCP Server configuration options", ex); throw ex; } catch(NullPointerException ex) { log.error("Incorrect DHCP Server configuration options", ex); throw ex; } // Any addresses that need to be set as static/fixed can be permanently added to the pool with a set MAC String staticAddresses = configOptions.get("reserved-static-addresses"); if (staticAddresses != null) { String[] macIpCouples = staticAddresses.split("\\s*;\\s*"); int i; String[] macIpSplit; int ipPos, macPos; for (i = 0; i < macIpCouples.length; i++) { macIpSplit = macIpCouples[i].split("\\s*,\\s*"); // Determine which element is the MAC and which is the IP // i.e. which order have they been typed in in the config file? if (macIpSplit[0].length() > macIpSplit[1].length()) { macPos = 0; ipPos = 1; } else { macPos = 1; ipPos = 0; } instanceBuilder.addReservedStaticAddress(MacAddress.of(macIpSplit[macPos]), IPv4Address.of(macIpSplit[ipPos])); log.info("Configured fixed address of {} for device {}", IPv4Address.of(macIpSplit[ipPos]).toString(), MacAddress.of(macIpSplit[macPos]).toString()); } } /* * Separate the servers in the comma-delimited list, * otherwise the client will get incorrect option information */ String dnses = configOptions.get("domain-name-servers"); if (dnses != null) { List<IPv4Address> dnsIps = new ArrayList<IPv4Address>(); for (String dnsIp : dnses.split("\\s*,\\s*")) { dnsIps.add(IPv4Address.of(dnsIp)); } instanceBuilder.setDnsIps(dnsIps); } String ntps = configOptions.get("ntp-servers"); if (ntps != null) { List<IPv4Address> ntpIps = new ArrayList<IPv4Address>(); for (String ntpIp : ntps.split("\\s*,\\s*")) { ntpIps.add(IPv4Address.of(ntpIp)); } instanceBuilder.setNtpIps(ntpIps); } DHCPInstance instance = instanceBuilder.build(); instances.put(instance.getName(), instance); // Monitor bindings for expired leases and clean them up leasePoliceDispatcher = new ScheduledThreadPoolExecutor(1); leasePolicePatrol = new DHCPLeasePolice(); leasePoliceDispatcher.scheduleAtFixedRate(leasePolicePatrol, 10, DHCP_SERVER_LEASE_POLICE_PATROL_PERIOD_SECONDS, TimeUnit.SECONDS); String enable = configOptions.get("enable"); if (enable != null && !enable.isEmpty() && (enable.toLowerCase().contains("true") || enable.toLowerCase().contains("yes") || enable.toLowerCase().contains("yep") ) ) { enable(); } else { disable(); } } @Override public Collection<Class<? extends IFloodlightService>> getModuleServices() { Collection<Class<? extends IFloodlightService>> s = new HashSet<Class<? extends IFloodlightService>>(); s.add(IDHCPService.class); return s; } @Override public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { Map<Class<? extends IFloodlightService>, IFloodlightService> m = new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); m.put(IDHCPService.class, this); return m; } @Override public String getName() { return "dhcpserver"; } @Override public boolean isCallbackOrderingPrereq(OFType type, String name) { return false; } @Override public boolean isCallbackOrderingPostreq(OFType type, String name) { // We will rely on forwarding to forward out any DHCP packets that are not // destined for our DHCP server. This is to allow an environment where // multiple DHCP servers operate cooperatively if (type == OFType.PACKET_IN && name.equals("forwarding")) { return true; } else { return false; } } public static byte[] intToBytes(int integer) { byte[] bytes = new byte[4]; bytes[3] = (byte) (integer >> 24); bytes[2] = (byte) (integer >> 16); bytes[1] = (byte) (integer >> 8); bytes[0] = (byte) (integer); return bytes; } public static byte[] intToBytesSizeOne(int integer) { byte[] bytes = new byte[1]; bytes[0] = (byte) (integer); return bytes; } public void sendDHCPOffer(DHCPInstance inst, IOFSwitch sw, OFPort inPort, MacAddress chaddr, IPv4Address dstIPAddr, IPv4Address yiaddr, IPv4Address giaddr, int xid, ArrayList<Byte> requestOrder) { // Compose DHCP OFFER /** (2) DHCP Offer * -- UDP src port = 67 * -- UDP dst port = 68 * -- IP src addr = DHCP DHCPServer's IP * -- IP dst addr = 255.255.255.255 * -- Opcode = 0x02 * -- XID = transactionX * -- ciaddr = blank * -- yiaddr = offer IP * -- siaddr = DHCP DHCPServer IP * -- giaddr = blank * -- chaddr = Client's MAC * -- Options: * -- Option 53 = DHCP Offer * -- Option 1 = SN Mask IP * -- Option 3 = Router IP * -- Option 51 = Lease time (s) * -- Option 54 = DHCP DHCPServer IP * -- Option 6 = DNS servers **/ OFPacketOut.Builder DHCPOfferPacket = sw.getOFFactory().buildPacketOut(); DHCPOfferPacket.setBufferId(OFBufferId.NO_BUFFER); Ethernet ethDHCPOffer = new Ethernet(); ethDHCPOffer.setSourceMACAddress(inst.getServerMac()); ethDHCPOffer.setDestinationMACAddress(chaddr); ethDHCPOffer.setEtherType(EthType.IPv4); IPv4 ipv4DHCPOffer = new IPv4(); if (dstIPAddr.equals(IPv4Address.NONE)) { ipv4DHCPOffer.setDestinationAddress(BROADCAST_IP); } else { // Client has IP and dhcpc must have crashed ipv4DHCPOffer.setDestinationAddress(dstIPAddr); } ipv4DHCPOffer.setSourceAddress(inst.getServerIp()); ipv4DHCPOffer.setProtocol(IpProtocol.UDP); ipv4DHCPOffer.setTtl((byte) 64); UDP udpDHCPOffer = new UDP(); udpDHCPOffer.setDestinationPort(UDP.DHCP_CLIENT_PORT); udpDHCPOffer.setSourcePort(UDP.DHCP_SERVER_PORT); DHCP dhcpDHCPOffer = new DHCP(); dhcpDHCPOffer.setOpCode(DHCP_OPCODE_REPLY); dhcpDHCPOffer.setHardwareType((byte) 1); dhcpDHCPOffer.setHardwareAddressLength((byte) 6); dhcpDHCPOffer.setHops((byte) 0); dhcpDHCPOffer.setTransactionId(xid); dhcpDHCPOffer.setSeconds((short) 0); dhcpDHCPOffer.setFlags((short) 0); dhcpDHCPOffer.setClientIPAddress(UNASSIGNED_IP); dhcpDHCPOffer.setYourIPAddress(yiaddr); dhcpDHCPOffer.setServerIPAddress(inst.getServerIp()); dhcpDHCPOffer.setGatewayIPAddress(giaddr); dhcpDHCPOffer.setClientHardwareAddress(chaddr); List<DHCPOption> dhcpOfferOptions = new ArrayList<DHCPOption>(); DHCPOption newOption; newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_MSG_TYPE); newOption.setData(DHCP_MSG_TYPE_OFFER); newOption.setLength((byte) 1); dhcpOfferOptions.add(newOption); for (Byte specificRequest : requestOrder) { if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_SN) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_SN); newOption.setData(inst.getSubnetMask().getBytes()); newOption.setLength((byte) 4); dhcpOfferOptions.add(newOption); } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_ROUTER) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_ROUTER); newOption.setData(inst.getRouterIp().getBytes()); newOption.setLength((byte) 4); dhcpOfferOptions.add(newOption); } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_DN) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_DN); newOption.setData(inst.getDomainName().getBytes()); newOption.setLength((byte) inst.getDomainName().getBytes().length); dhcpOfferOptions.add(newOption); } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_DNS) { if (!inst.getDnsIps().isEmpty()) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_DNS); ByteBuffer bb = ByteBuffer.allocate(inst.getDnsIps().size() * 4 /* sizeof(IPv4Address) */); /* exact size */ for (IPv4Address ip : inst.getDnsIps()) { bb.put(ip.getBytes()); } bb.flip(); newOption.setData(bb.array()); //TODO verify this newOption.setLength((byte) bb.array().length); dhcpOfferOptions.add(newOption); } else { log.warn("Client asked for DNS servers, but we didn't have any for instance {}", inst.getName()); } } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_BROADCAST_IP) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_BROADCAST_IP); newOption.setData(inst.getBroadcastIp().getBytes()); newOption.setLength((byte) 4); dhcpOfferOptions.add(newOption); } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_DHCP_SERVER) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_DHCP_SERVER); newOption.setData(inst.getServerIp().getBytes()); newOption.setLength((byte) 4); dhcpOfferOptions.add(newOption); } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_LEASE_TIME) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_LEASE_TIME); newOption.setData(intToBytes(inst.getLeaseTimeSec())); newOption.setLength((byte) 4); dhcpOfferOptions.add(newOption); } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_NTP_IP) { if (!inst.getNtpIps().isEmpty()) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_NTP_IP); ByteBuffer bb = ByteBuffer.allocate(inst.getNtpIps().size() * 4); /* exact size */ for (IPv4Address ip : inst.getNtpIps()) { bb.put(ip.getBytes()); } bb.flip(); newOption.setData(bb.array()); //TODO verify this newOption.setLength((byte) bb.array().length); dhcpOfferOptions.add(newOption); } else { log.warn("Client asked for NTP servers, but we didn't have any for instance {}", inst.getName()); } } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_REBIND_TIME) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_REBIND_TIME); newOption.setData(intToBytes(inst.getRebindTimeSec())); newOption.setLength((byte) 4); dhcpOfferOptions.add(newOption); } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_RENEWAL_TIME) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_RENEWAL_TIME); newOption.setData(intToBytes(inst.getRenewalTimeSec())); newOption.setLength((byte) 4); dhcpOfferOptions.add(newOption); } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_IP_FORWARDING) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_IP_FORWARDING); newOption.setData(intToBytesSizeOne(inst.getIpForwarding() ? 1 : 0)); newOption.setLength((byte) 1); dhcpOfferOptions.add(newOption); } else { //log.debug("Setting specific request for OFFER failed"); } } newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_END); newOption.setLength((byte) 0); dhcpOfferOptions.add(newOption); dhcpDHCPOffer.setOptions(dhcpOfferOptions); ethDHCPOffer.setPayload(ipv4DHCPOffer.setPayload(udpDHCPOffer.setPayload(dhcpDHCPOffer))); DHCPOfferPacket.setInPort(OFPort.ANY); List<OFAction> actions = new ArrayList<OFAction>(1); actions.add(sw.getOFFactory().actions().output(inPort, 0xffFFffFF)); DHCPOfferPacket.setActions(actions); DHCPOfferPacket.setData(ethDHCPOffer.serialize()); log.debug("Sending DHCP OFFER"); sw.write(DHCPOfferPacket.build()); } public void sendDHCPAck(DHCPInstance inst, IOFSwitch sw, OFPort inPort, MacAddress chaddr, IPv4Address dstIPAddr, IPv4Address yiaddr, IPv4Address giaddr, int xid, ArrayList<Byte> requestOrder) { /** (4) DHCP ACK * -- UDP src port = 67 * -- UDP dst port = 68 * -- IP src addr = DHCP DHCPServer's IP * -- IP dst addr = 255.255.255.255 * -- Opcode = 0x02 * -- XID = transactionX * -- ciaddr = blank * -- yiaddr = offer IP * -- siaddr = DHCP DHCPServer IP * -- giaddr = blank * -- chaddr = Client's MAC * -- Options: * -- Option 53 = DHCP ACK * -- Option 1 = SN Mask IP * -- Option 3 = Router IP * -- Option 51 = Lease time (s) * -- Option 54 = DHCP DHCPServer IP * -- Option 6 = DNS servers **/ OFPacketOut.Builder DHCPACKPacket = sw.getOFFactory().buildPacketOut(); DHCPACKPacket.setBufferId(OFBufferId.NO_BUFFER); Ethernet ethDHCPAck = new Ethernet(); ethDHCPAck.setSourceMACAddress(inst.getServerMac()); ethDHCPAck.setDestinationMACAddress(chaddr); ethDHCPAck.setEtherType(EthType.IPv4); IPv4 ipv4DHCPAck = new IPv4(); if (dstIPAddr.equals(IPv4Address.NONE)) { ipv4DHCPAck.setDestinationAddress(BROADCAST_IP); } else { // Client has IP and dhclient must have crashed ipv4DHCPAck.setDestinationAddress(dstIPAddr); } ipv4DHCPAck.setSourceAddress(inst.getServerIp()); ipv4DHCPAck.setProtocol(IpProtocol.UDP); ipv4DHCPAck.setTtl((byte) 64); UDP udpDHCPAck = new UDP(); udpDHCPAck.setDestinationPort(UDP.DHCP_CLIENT_PORT); udpDHCPAck.setSourcePort(UDP.DHCP_SERVER_PORT); DHCP dhcpDHCPAck = new DHCP(); dhcpDHCPAck.setOpCode(DHCP_OPCODE_REPLY); dhcpDHCPAck.setHardwareType((byte) 1); dhcpDHCPAck.setHardwareAddressLength((byte) 6); dhcpDHCPAck.setHops((byte) 0); dhcpDHCPAck.setTransactionId(xid); dhcpDHCPAck.setSeconds((short) 0); dhcpDHCPAck.setFlags((short) 0); dhcpDHCPAck.setClientIPAddress(UNASSIGNED_IP); dhcpDHCPAck.setYourIPAddress(yiaddr); dhcpDHCPAck.setServerIPAddress(inst.getServerIp()); dhcpDHCPAck.setGatewayIPAddress(giaddr); dhcpDHCPAck.setClientHardwareAddress(chaddr); List<DHCPOption> dhcpAckOptions = new ArrayList<DHCPOption>(); DHCPOption newOption; newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_MSG_TYPE); newOption.setData(DHCP_MSG_TYPE_ACK); newOption.setLength((byte) 1); dhcpAckOptions.add(newOption); for (Byte specificRequest : requestOrder) { if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_SN) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_SN); newOption.setData(inst.getSubnetMask().getBytes()); newOption.setLength((byte) 4); dhcpAckOptions.add(newOption); } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_ROUTER) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_ROUTER); newOption.setData(inst.getRouterIp().getBytes()); newOption.setLength((byte) 4); dhcpAckOptions.add(newOption); } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_DN) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_DN); newOption.setData(inst.getDomainName().getBytes()); newOption.setLength((byte) inst.getDomainName().getBytes().length); dhcpAckOptions.add(newOption); } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_DNS) { if (!inst.getDnsIps().isEmpty()) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_DNS); ByteBuffer bb = ByteBuffer.allocate(inst.getDnsIps().size() * 4); /* exact size */ for (IPv4Address ip : inst.getDnsIps()) { bb.put(ip.getBytes()); } bb.flip(); newOption.setData(bb.array()); //TODO verify this newOption.setLength((byte) bb.array().length); dhcpAckOptions.add(newOption); } else { log.warn("Client asked for DNS servers, but we didn't have any for instance {}", inst.getName()); } } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_BROADCAST_IP) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_BROADCAST_IP); newOption.setData(inst.getBroadcastIp().getBytes()); newOption.setLength((byte) 4); dhcpAckOptions.add(newOption); } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_DHCP_SERVER) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_DHCP_SERVER); newOption.setData(inst.getServerIp().getBytes()); newOption.setLength((byte) 4); dhcpAckOptions.add(newOption); } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_LEASE_TIME) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_LEASE_TIME); newOption.setData(intToBytes(inst.getLeaseTimeSec())); newOption.setLength((byte) 4); dhcpAckOptions.add(newOption); } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_NTP_IP) { if (!inst.getNtpIps().isEmpty()) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_NTP_IP); ByteBuffer bb = ByteBuffer.allocate(inst.getNtpIps().size() * 4); /* exact size */ for (IPv4Address ip : inst.getNtpIps()) { bb.put(ip.getBytes()); } bb.flip(); newOption.setData(bb.array()); //TODO verify this newOption.setLength((byte) bb.array().length); dhcpAckOptions.add(newOption); } else { log.warn("Client asked for NTP servers, but we didn't have any for instance {}", inst.getName()); } } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_REBIND_TIME) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_REBIND_TIME); newOption.setData(intToBytes(inst.getRebindTimeSec())); newOption.setLength((byte) 4); dhcpAckOptions.add(newOption); } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_RENEWAL_TIME) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_RENEWAL_TIME); newOption.setData(intToBytes(inst.getRenewalTimeSec())); newOption.setLength((byte) 4); dhcpAckOptions.add(newOption); } else if (specificRequest.byteValue() == DHCP_REQ_PARAM_OPTION_CODE_IP_FORWARDING) { newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_IP_FORWARDING); newOption.setData(intToBytesSizeOne(inst.getIpForwarding() ? 1 : 0)); newOption.setLength((byte) 1); dhcpAckOptions.add(newOption); }else { log.debug("Setting specific request for ACK failed"); } } newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_END); newOption.setLength((byte) 0); dhcpAckOptions.add(newOption); dhcpDHCPAck.setOptions(dhcpAckOptions); ethDHCPAck.setPayload(ipv4DHCPAck.setPayload(udpDHCPAck.setPayload(dhcpDHCPAck))); DHCPACKPacket.setInPort(OFPort.ANY); List<OFAction> actions = new ArrayList<OFAction>(1); actions.add(sw.getOFFactory().actions().output(inPort, 0xffFFffFF)); DHCPACKPacket.setActions(actions); DHCPACKPacket.setData(ethDHCPAck.serialize()); log.debug("Sending DHCP ACK"); sw.write(DHCPACKPacket.build()); } public void sendDHCPNack(DHCPInstance inst, IOFSwitch sw, OFPort inPort, MacAddress chaddr, IPv4Address giaddr, int xid) { OFPacketOut.Builder DHCPOfferPacket = sw.getOFFactory().buildPacketOut(); DHCPOfferPacket.setBufferId(OFBufferId.NO_BUFFER); Ethernet ethDHCPOffer = new Ethernet(); ethDHCPOffer.setSourceMACAddress(inst.getServerMac()); ethDHCPOffer.setDestinationMACAddress(chaddr); ethDHCPOffer.setEtherType(EthType.IPv4); IPv4 ipv4DHCPOffer = new IPv4(); ipv4DHCPOffer.setDestinationAddress(BROADCAST_IP); ipv4DHCPOffer.setSourceAddress(inst.getServerIp()); ipv4DHCPOffer.setProtocol(IpProtocol.UDP); ipv4DHCPOffer.setTtl((byte) 64); UDP udpDHCPOffer = new UDP(); udpDHCPOffer.setDestinationPort(UDP.DHCP_CLIENT_PORT); udpDHCPOffer.setSourcePort(UDP.DHCP_SERVER_PORT); DHCP dhcpDHCPOffer = new DHCP(); dhcpDHCPOffer.setOpCode(DHCP_OPCODE_REPLY); dhcpDHCPOffer.setHardwareType((byte) 1); dhcpDHCPOffer.setHardwareAddressLength((byte) 6); dhcpDHCPOffer.setHops((byte) 0); dhcpDHCPOffer.setTransactionId(xid); dhcpDHCPOffer.setSeconds((short) 0); dhcpDHCPOffer.setFlags((short) 0); dhcpDHCPOffer.setClientIPAddress(UNASSIGNED_IP); dhcpDHCPOffer.setYourIPAddress(UNASSIGNED_IP); dhcpDHCPOffer.setServerIPAddress(inst.getServerIp()); dhcpDHCPOffer.setGatewayIPAddress(giaddr); dhcpDHCPOffer.setClientHardwareAddress(chaddr); List<DHCPOption> dhcpOfferOptions = new ArrayList<DHCPOption>(); DHCPOption newOption; newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_MSG_TYPE); newOption.setData(DHCP_MSG_TYPE_NACK); newOption.setLength((byte) 1); dhcpOfferOptions.add(newOption); newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_DHCP_SERVER); newOption.setData(inst.getServerIp().getBytes()); newOption.setLength((byte) 4); dhcpOfferOptions.add(newOption); newOption = new DHCPOption(); newOption.setCode(DHCP_REQ_PARAM_OPTION_CODE_END); newOption.setLength((byte) 0); dhcpOfferOptions.add(newOption); dhcpDHCPOffer.setOptions(dhcpOfferOptions); ethDHCPOffer.setPayload(ipv4DHCPOffer.setPayload(udpDHCPOffer.setPayload(dhcpDHCPOffer))); DHCPOfferPacket.setInPort(OFPort.ANY); List<OFAction> actions = new ArrayList<OFAction>(1); actions.add(sw.getOFFactory().actions().output(inPort, 0xffFFffFF)); DHCPOfferPacket.setActions(actions); DHCPOfferPacket.setData(ethDHCPOffer.serialize()); log.info("Sending DHCP NACK"); sw.write(DHCPOfferPacket.build()); } public ArrayList<Byte> getRequestedParameters(DHCP DHCPPayload, boolean isInform) { ArrayList<Byte> requestOrder = new ArrayList<Byte>(); byte[] requests = DHCPPayload.getOption(DHCPOptionCode.OptionCode_RequestedParameters).getData(); boolean requestedLeaseTime = false; boolean requestedRebindTime = false; boolean requestedRenewTime = false; for (byte specificRequest : requests) { if (specificRequest == DHCP_REQ_PARAM_OPTION_CODE_SN) { requestOrder.add(DHCP_REQ_PARAM_OPTION_CODE_SN); } else if (specificRequest == DHCP_REQ_PARAM_OPTION_CODE_ROUTER) { requestOrder.add(DHCP_REQ_PARAM_OPTION_CODE_ROUTER); } else if (specificRequest == DHCP_REQ_PARAM_OPTION_CODE_DN) { requestOrder.add(DHCP_REQ_PARAM_OPTION_CODE_DN); } else if (specificRequest == DHCP_REQ_PARAM_OPTION_CODE_DNS) { requestOrder.add(DHCP_REQ_PARAM_OPTION_CODE_DNS); } else if (specificRequest == DHCP_REQ_PARAM_OPTION_CODE_LEASE_TIME) { requestOrder.add(DHCP_REQ_PARAM_OPTION_CODE_LEASE_TIME); requestedLeaseTime = true; } else if (specificRequest == DHCP_REQ_PARAM_OPTION_CODE_DHCP_SERVER) { requestOrder.add(DHCP_REQ_PARAM_OPTION_CODE_DHCP_SERVER); } else if (specificRequest == DHCP_REQ_PARAM_OPTION_CODE_BROADCAST_IP) { requestOrder.add(DHCP_REQ_PARAM_OPTION_CODE_BROADCAST_IP); } else if (specificRequest == DHCP_REQ_PARAM_OPTION_CODE_NTP_IP) { requestOrder.add(DHCP_REQ_PARAM_OPTION_CODE_NTP_IP); } else if (specificRequest == DHCP_REQ_PARAM_OPTION_CODE_REBIND_TIME) { requestOrder.add(DHCP_REQ_PARAM_OPTION_CODE_REBIND_TIME); requestedRebindTime = true; } else if (specificRequest == DHCP_REQ_PARAM_OPTION_CODE_RENEWAL_TIME) { requestOrder.add(DHCP_REQ_PARAM_OPTION_CODE_RENEWAL_TIME); requestedRenewTime = true; } else if (specificRequest == DHCP_REQ_PARAM_OPTION_CODE_IP_FORWARDING) { requestOrder.add(DHCP_REQ_PARAM_OPTION_CODE_IP_FORWARDING); log.debug("requested IP FORWARDING"); } else { //log.debug("Requested option 0x" + Byte.toString(specificRequest) + " not available"); } } // We need to add these in regardless if the request list includes them if (!isInform) { if (!requestedLeaseTime) { requestOrder.add(DHCP_REQ_PARAM_OPTION_CODE_LEASE_TIME); log.debug("added option LEASE TIME"); } if (!requestedRenewTime) { requestOrder.add(DHCP_REQ_PARAM_OPTION_CODE_RENEWAL_TIME); log.debug("added option RENEWAL TIME"); } if (!requestedRebindTime) { requestOrder.add(DHCP_REQ_PARAM_OPTION_CODE_REBIND_TIME); log.debug("added option REBIND TIME"); } } return requestOrder; } @Override public net.floodlightcontroller.core.IListener.Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { OFPacketIn pi = (OFPacketIn) msg; OFPort inPort = OFMessageUtils.getInPort(pi); OFVlanVidMatch vlan = OFMessageUtils.getVlan(pi); DHCPInstance instance = getInstance(new NodePortTuple(sw.getId(), inPort)); if (instance == null) { log.debug("Could not locate DHCP instance for DPID {}, port {}. Checking VLAN next", sw.getId(), inPort); instance = getInstance(vlan.getVlanVid()); } if (instance == null) { log.error("Could not locate DHCP instance for DPID {}, port {}, VLAN {}", new Object[] { sw.getId(), inPort, }); return Command.CONTINUE; } if (!instance.getPool().hasAvailableAddresses()) { log.info("DHCP Pool is full! Consider increasing the pool size."); return Command.CONTINUE; } Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD); if (eth.getEtherType() == EthType.IPv4) { /* shallow compare is okay for EthType */ log.debug("Got IPv4 Packet"); IPv4 IPv4Payload = (IPv4) eth.getPayload(); IPv4Address IPv4SrcAddr = IPv4Payload.getSourceAddress(); if (IPv4Payload.getProtocol() == IpProtocol.UDP) { /* shallow compare also okay for IpProtocol */ log.debug("Got UDP Packet"); UDP UDPPayload = (UDP) IPv4Payload.getPayload(); if ((UDPPayload.getDestinationPort().equals(UDP.DHCP_SERVER_PORT) /* TransportPort must be deep though */ || UDPPayload.getDestinationPort().equals(UDP.DHCP_CLIENT_PORT)) && (UDPPayload.getSourcePort().equals(UDP.DHCP_SERVER_PORT) || UDPPayload.getSourcePort().equals(UDP.DHCP_CLIENT_PORT))) { log.debug("Got DHCP Packet"); // This is a DHCP packet that we need to process DHCP DHCPPayload = (DHCP) UDPPayload.getPayload(); /* DHCP/IPv4 Header Information */ int xid = 0; IPv4Address yiaddr = IPv4Address.NONE; IPv4Address giaddr = IPv4Address.NONE; MacAddress chaddr = null; IPv4Address desiredIPAddr = null; ArrayList<Byte> requestOrder = new ArrayList<Byte>(); if (DHCPPayload.getOpCode() == DHCP_OPCODE_REQUEST) { /** * (1) DHCP Discover * -- UDP src port = 68 * -- UDP dst port = 67 * -- IP src addr = 0.0.0.0 * -- IP dst addr = 255.255.255.255 * -- Opcode = 0x01 * -- XID = transactionX * -- All addresses blank: * -- ciaddr (client IP) * -- yiaddr (your IP) * -- siaddr (DHCPServer IP) * -- giaddr (GW IP) * -- chaddr = Client's MAC * -- Options: * -- Option 53 = DHCP Discover * -- Option 50 = possible IP request * -- Option 55 = parameter request list * -- (1) SN Mask * -- (3) Router * -- (15) Domain Name * -- (6) DNS **/ if (Arrays.equals(DHCPPayload.getOption(DHCP.DHCPOptionCode.OptionCode_MessageType).getData(), DHCP_MSG_TYPE_DISCOVER)) { log.debug("DHCP DISCOVER Received"); xid = DHCPPayload.getTransactionId(); yiaddr = DHCPPayload.getYourIPAddress(); // Will have GW IP if a relay agent was used giaddr = DHCPPayload.getGatewayIPAddress(); chaddr = DHCPPayload.getClientHardwareAddress(); List<DHCPOption> options = DHCPPayload.getOptions(); for (DHCPOption option : options) { if (option.getCode() == DHCP_REQ_PARAM_OPTION_CODE_REQUESTED_IP) { desiredIPAddr = IPv4Address.of(option.getData()); log.debug("Got requested IP"); } else if (option.getCode() == DHCP_REQ_PARAM_OPTION_CODE_REQUESTED_PARAMTERS) { log.debug("Got requested param list"); requestOrder = getRequestedParameters(DHCPPayload, false); } } // Process DISCOVER message and prepare an OFFER with minimum-hold lease // A HOLD lease should be a small amount of time sufficient for the client to respond // with a REQUEST, at which point the ACK will set the least time to the DEFAULT synchronized (instance.getPool()) { if (!instance.getPool().hasAvailableAddresses()) { log.info("DHCP Pool is full! Consider increasing the pool size."); log.info("Device with MAC " + chaddr.toString() + " was not granted an IP lease"); return Command.CONTINUE; } DHCPBinding lease = instance.getPool().getSpecificAvailableLease(desiredIPAddr, chaddr); if (lease != null) { log.debug("Checking new lease with specific IP"); instance.getPool().setDHCPbinding(lease, chaddr, instance.getHoldTimeSec()); yiaddr = lease.getIPv4Address(); log.debug("Got new lease for " + yiaddr.toString()); } else { log.debug("Checking new lease for any IP"); lease = instance.getPool().getAnyAvailableLease(chaddr); instance.getPool().setDHCPbinding(lease, chaddr, instance.getHoldTimeSec()); yiaddr = lease.getIPv4Address(); log.debug("Got new lease for " + yiaddr.toString()); } } sendDHCPOffer(instance, sw, inPort, chaddr, IPv4SrcAddr, yiaddr, giaddr, xid, requestOrder); } // END IF DISCOVER /** (3) DHCP Request * -- UDP src port = 68 * -- UDP dst port = 67 * -- IP src addr = 0.0.0.0 * -- IP dst addr = 255.255.255.255 * -- Opcode = 0x01 * -- XID = transactionX * -- ciaddr = blank * -- yiaddr = blank * -- siaddr = DHCP DHCPServer IP * -- giaddr = GW IP * -- chaddr = Client's MAC * -- Options: * -- Option 53 = DHCP Request * -- Option 50 = IP requested (from offer) * -- Option 54 = DHCP DHCPServer IP **/ else if (Arrays.equals(DHCPPayload.getOption(DHCP.DHCPOptionCode.OptionCode_MessageType).getData(), DHCP_MSG_TYPE_REQUEST)) { log.debug(": DHCP REQUEST received"); IPv4SrcAddr = IPv4Payload.getSourceAddress(); xid = DHCPPayload.getTransactionId(); yiaddr = DHCPPayload.getYourIPAddress(); giaddr = DHCPPayload.getGatewayIPAddress(); chaddr = DHCPPayload.getClientHardwareAddress(); List<DHCPOption> options = DHCPPayload.getOptions(); for (DHCPOption option : options) { if (option.getCode() == DHCP_REQ_PARAM_OPTION_CODE_REQUESTED_IP) { desiredIPAddr = IPv4Address.of(option.getData()); if (!desiredIPAddr.equals(instance.getPool().getDHCPbindingFromMAC(chaddr).getIPv4Address())) { // This client wants a different IP than what we have on file, so cancel its HOLD lease now (if we have one) instance.getPool().cancelLeaseOfMAC(chaddr); return Command.CONTINUE; } } else if (option.getCode() == DHCP_REQ_PARAM_OPTION_CODE_DHCP_SERVER) { if (!IPv4Address.of(option.getData()).equals(instance.getServerIp())) { // We're not the DHCPServer the client wants to use, so cancel its HOLD lease now and ignore the client instance.getPool().cancelLeaseOfMAC(chaddr); return Command.CONTINUE; } } else if (option.getCode() == DHCP_REQ_PARAM_OPTION_CODE_REQUESTED_PARAMTERS) { requestOrder = getRequestedParameters(DHCPPayload, false); } } // Process REQUEST message and prepare an ACK with default lease time // This extends the hold lease time to that of a normal lease boolean sendACK = true; synchronized (instance.getPool()) { if (!instance.getPool().hasAvailableAddresses()) { log.info("DHCP Pool is full! Consider increasing the pool size."); log.info("Device with MAC " + chaddr.toString() + " was not granted an IP lease"); return Command.CONTINUE; } DHCPBinding lease; // Get any binding, in use now or not if (desiredIPAddr != null) { lease = instance.getPool().getDHCPbindingFromIPv4(desiredIPAddr); } else { lease = instance.getPool().getAnyAvailableLease(chaddr); } // This IP is not in our allocation range if (lease == null) { log.info("The IP " + desiredIPAddr.toString() + " is not in the range " + instance.getPool().getStartIp().toString() + "+"); log.info("Device with MAC " + chaddr.toString() + " was not granted an IP lease"); sendACK = false; // Determine if the IP in the binding we just retrieved is okay to allocate to the MAC requesting it } else if (!lease.getMACAddress().equals(chaddr) && lease.isActiveLease()) { log.debug("Tried to REQUEST an IP that is currently assigned to another MAC"); log.debug("Device with MAC " + chaddr.toString() + " was not granted an IP lease"); sendACK = false; // Check if we want to renew the MAC's current lease } else if (lease.getMACAddress().equals(chaddr) && lease.isActiveLease()) { log.debug("Renewing lease for MAC " + chaddr.toString()); instance.getPool().renewLease(lease.getIPv4Address(), instance.getLeaseTimeSec()); yiaddr = lease.getIPv4Address(); log.debug("Finalized renewed lease for " + yiaddr.toString()); // Check if we want to create a new lease for the MAC } else if (!lease.isActiveLease()){ log.debug("Assigning new lease for MAC " + chaddr.toString()); instance.getPool().setDHCPbinding(lease, chaddr, instance.getLeaseTimeSec()); yiaddr = lease.getIPv4Address(); log.debug("Finalized new lease for " + yiaddr.toString()); } else { log.debug("Don't know how we got here"); return Command.CONTINUE; } } if (sendACK) { sendDHCPAck(instance, sw, inPort, chaddr, IPv4SrcAddr, yiaddr, giaddr, xid, requestOrder); } else { sendDHCPNack(instance, sw, inPort, chaddr, giaddr, xid); } } // END IF REQUEST else if (Arrays.equals(DHCPPayload.getOption(DHCP.DHCPOptionCode.OptionCode_MessageType).getData(), DHCP_MSG_TYPE_RELEASE)) { if (!DHCPPayload.getServerIPAddress().equals(instance.getServerIp())) { log.info("DHCP RELEASE message not for our DHCP server"); // Send the packet out the port it would normally go out via the Forwarding module // Execution jumps to return Command.CONTINUE at end of receive() } else { log.debug("Got DHCP RELEASE. Cancelling remaining time on DHCP lease"); synchronized(instance.getPool()) { if (instance.getPool().cancelLeaseOfMAC(DHCPPayload.getClientHardwareAddress())) { log.info("Cancelled DHCP lease of " + DHCPPayload.getClientHardwareAddress().toString()); log.info("IP " + instance.getPool().getDHCPbindingFromMAC(DHCPPayload.getClientHardwareAddress()).getIPv4Address().toString() + " is now available in the DHCP address pool"); } else { log.debug("Lease of " + DHCPPayload.getClientHardwareAddress().toString() + " was already inactive"); } } } } // END IF RELEASE else if (Arrays.equals(DHCPPayload.getOption(DHCP.DHCPOptionCode.OptionCode_MessageType).getData(), DHCP_MSG_TYPE_DECLINE)) { log.debug("Got DHCP DECLINE. Cancelling HOLD time on DHCP lease"); synchronized(instance.getPool()) { if (instance.getPool().cancelLeaseOfMAC(DHCPPayload.getClientHardwareAddress())) { log.info("Cancelled DHCP lease of " + DHCPPayload.getClientHardwareAddress().toString()); log.info("IP " + instance.getPool().getDHCPbindingFromMAC(DHCPPayload.getClientHardwareAddress()).getIPv4Address().toString() + " is now available in the DHCP address pool"); } else { log.info("HOLD Lease of " + DHCPPayload.getClientHardwareAddress().toString() + " has already expired"); } } } // END IF DECLINE else if (Arrays.equals(DHCPPayload.getOption(DHCP.DHCPOptionCode.OptionCode_MessageType).getData(), DHCP_MSG_TYPE_INFORM)) { log.debug("Got DHCP INFORM. Retreiving requested parameters from message"); IPv4SrcAddr = IPv4Payload.getSourceAddress(); xid = DHCPPayload.getTransactionId(); yiaddr = DHCPPayload.getYourIPAddress(); giaddr = DHCPPayload.getGatewayIPAddress(); chaddr = DHCPPayload.getClientHardwareAddress(); // Get the requests from the INFORM message. True for inform -- we don't want to include lease information requestOrder = getRequestedParameters(DHCPPayload, true); // Process INFORM message and send an ACK with requested information sendDHCPAck(instance, sw, inPort, chaddr, IPv4SrcAddr, yiaddr, giaddr, xid, requestOrder); } // END IF INFORM } // END IF DHCP OPCODE REQUEST else if (DHCPPayload.getOpCode() == DHCP_OPCODE_REPLY) { // Do nothing right now. The DHCP DHCPServer isn't supposed to receive replies but ISSUE them instead log.debug("Got an OFFER/ACK (REPLY)...this shouldn't happen unless there's another DHCP Server somewhere"); } else { log.debug("Got DHCP packet, but not a known DHCP packet opcode"); } } // END IF DHCP packet } // END IF UDP packet } // END IF IPv4 packet return Command.CONTINUE; } // END of receive(pkt) /** * DHCPLeasePolice is a simple class that is instantiated and invoked * as a runnable thread. The objective is to clean up the expired DHCP * leases on a set time interval. Most DHCP leases are hours in length, * so the granularity of our check can be on the order of minutes (IMHO). * The period of the check for expired leases, in seconds, is specified * in the configuration file: * * floodlight/src/main/resources/floodlightdefault.properties * * as option: * * net.floodlightcontroller.dhcpserver.DHCPServer.lease-gc-period = <seconds> * * where gc stands for "garbage collection". * * @author Ryan Izard, rizard@g.clemson.edu * */ class DHCPLeasePolice implements Runnable { @Override public void run() { log.info("Cleaning any expired DHCP leases..."); ArrayList<DHCPBinding> newAvailableBindings; for (DHCPInstance instance : instances.values()) { synchronized(instance.getPool()) { // Loop through lease pool and check all leases to see if they are expired // If a lease is expired, then clean it up and make the binding available newAvailableBindings = instance.getPool().cleanExpiredLeases(); } for (DHCPBinding binding : newAvailableBindings) { log.info("MAC " + binding.getMACAddress().toString() + " has expired"); log.info("Lease now available for IP " + binding.getIPv4Address().toString()); } } } } // END DHCPLeasePolice Class @Override public synchronized void enable() { log.warn("DHCP server module enabled"); enabled = true; } @Override public synchronized void disable() { log.warn("DHCP server module disabled"); enabled = false; } @Override public boolean isEnabled() { return enabled; } @Override public synchronized boolean addInstance(DHCPInstance instance) { if (instances.containsKey(instance.getName())) { log.error("DHCP instance {} already present. Not adding", instance.getName()); return false; } else { instances.put(instance.getName(), instance); return true; } } @Override public DHCPInstance getInstance(String name) { return instances.get(name); } @Override public DHCPInstance getInstance(NodePortTuple member) { for (DHCPInstance instance : instances.values()) { if (instance.getMemberPorts().contains(member)) { return instance; } } return null; } @Override public DHCPInstance getInstance(VlanVid member) { for (DHCPInstance instance : instances.values()) { if (instance.getMemberVlans().contains(member)) { return instance; } } return null; } @Override public Collection<DHCPInstance> getInstances() { return Collections.unmodifiableCollection(instances.values()); } @Override public synchronized boolean deleteInstance(String name) { if (instances.containsKey(name)) { log.error("DHCP instance {} not present. Cannot delete", name); return false; } else { instances.remove(name); return true; } } } // END DHCPServer Class