/* * Copyright 2009-2014 Jagornet Technologies, LLC. All Rights Reserved. * * This software is the proprietary information of Jagornet Technologies, LLC. * Use is subject to license terms. * */ /* * This file V4AddrBindingManagerImpl.java is part of Jagornet DHCP. * * Jagornet DHCP is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Jagornet DHCP is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Jagornet DHCP. If not, see <http://www.gnu.org/licenses/>. * */ package com.jagornet.dhcp.server.request.binding; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jagornet.dhcp.db.DhcpOption; import com.jagornet.dhcp.db.IaAddress; import com.jagornet.dhcp.db.IdentityAssoc; import com.jagornet.dhcp.message.DhcpMessage; import com.jagornet.dhcp.option.v4.DhcpV4ClientFqdnOption; import com.jagornet.dhcp.option.v4.DhcpV4RequestedIpAddressOption; import com.jagornet.dhcp.server.config.DhcpConfigObject; import com.jagornet.dhcp.server.config.DhcpLink; import com.jagornet.dhcp.server.config.DhcpServerConfigException; import com.jagornet.dhcp.server.config.DhcpServerPolicies; import com.jagornet.dhcp.server.config.DhcpServerPolicies.Property; import com.jagornet.dhcp.server.request.ddns.DdnsCallback; import com.jagornet.dhcp.server.request.ddns.DdnsUpdater; import com.jagornet.dhcp.server.request.ddns.DhcpV4DdnsComplete; import com.jagornet.dhcp.util.DhcpConstants; import com.jagornet.dhcp.xml.Link; import com.jagornet.dhcp.xml.LinkFilter; import com.jagornet.dhcp.xml.LinkFiltersType; import com.jagornet.dhcp.xml.V4AddressBinding; import com.jagornet.dhcp.xml.V4AddressBindingsType; import com.jagornet.dhcp.xml.V4AddressPool; import com.jagornet.dhcp.xml.V4AddressPoolsType; /** * The Class V4AddrBindingManagerImpl. * * @author A. Gregory Rabil */ public class V4AddrBindingManagerImpl extends BaseAddrBindingManager implements V4AddrBindingManager { private static Logger log = LoggerFactory.getLogger(V4AddrBindingManagerImpl.class); /** * Instantiates a new v4 addr binding manager impl. * * @throws DhcpServerConfigException the dhcp server config exception */ public V4AddrBindingManagerImpl() throws DhcpServerConfigException { super(); } /** * Build the list of V4AddressBindingPools from the list of configured V4AddressPools * for the given configuration Link container object. The list of V4AddressBindingPools * starts with the filtered V4AddressPools followed by non-filtered V4AddressPools. * * @param link the configuration Link object * * @return the list of V4AddressBindingPools (<? extends BindingPool>) * * @throws DhcpServerConfigException if there is a problem parsing a configured range */ protected List<? extends BindingPool> buildBindingPools(Link link) throws DhcpServerConfigException { List<V4AddressBindingPool> bindingPools = new ArrayList<V4AddressBindingPool>(); // Put the filtered pools first in the list of pools on this link LinkFiltersType linkFiltersType = link.getLinkFilters(); if (linkFiltersType != null) { List<LinkFilter> linkFilters = linkFiltersType.getLinkFilterList(); if ((linkFilters != null) && !linkFilters.isEmpty()) { for (LinkFilter linkFilter : linkFilters) { V4AddressPoolsType poolsType = linkFilter.getV4AddrPools(); if (poolsType != null) { // add the filtered pools to the mapped list List<V4AddressPool> pools = poolsType.getPoolList(); if ((pools != null) && !pools.isEmpty()) { for (V4AddressPool pool : pools) { V4AddressBindingPool abp = buildV4BindingPool(pool, link, linkFilter); bindingPools.add(abp); } } else { log.error("PoolList is null for PoolsType: " + poolsType); } } else { log.info("PoolsType is null for LinkFilter: " + linkFilter.getName()); } } } } V4AddressPoolsType poolsType = link.getV4AddrPools(); if (poolsType != null) { // add the unfiltered pools to the mapped list List<V4AddressPool> pools = poolsType.getPoolList(); if ((pools != null) && !pools.isEmpty()) { for (V4AddressPool pool : pools) { V4AddressBindingPool abp = buildV4BindingPool(pool, link); bindingPools.add(abp); } } else { log.error("PoolList is null for PoolsType: " + poolsType); } } else { log.info("PoolsType is null for Link: " + link.getName()); } reconcilePools(bindingPools); return bindingPools; } /** * Reconcile pools. Delete any IaAddress objects not contained * within the given list of V4AddressBindingPools. * * @param bindingPools the list of V4AddressBindingPools */ protected void reconcilePools(List<V4AddressBindingPool> bindingPools) { if ((bindingPools != null) && !bindingPools.isEmpty()) { List<Range> ranges = new ArrayList<Range>(); for (V4AddressBindingPool bp : bindingPools) { Range range = new Range(bp.getStartAddress(), bp.getEndAddress()); ranges.add(range); } iaMgr.reconcileIaAddresses(ranges); } } /** * Builds a binding pool from an V4AddressPool using the given link. * * @param pool the V4AddressPool to wrap as an V4AddressBindingPool * @param link the link * * @return the binding pool * * @throws DhcpServerConfigException if there is a problem parsing the configured range */ protected V4AddressBindingPool buildV4BindingPool(V4AddressPool pool, Link link) throws DhcpServerConfigException { return buildV4BindingPool(pool, link, null); } /** * Builds a binding pool from an V4AddressPool using the given link and filter. * * @param pool the V4AddressPool to wrap as an V4AddressBindingPool * @param link the link * @param linkFilter the link filter * * @return the binding pool * * @throws DhcpServerConfigException if there is a problem parsing the configured range */ protected V4AddressBindingPool buildV4BindingPool(V4AddressPool pool, Link link, LinkFilter linkFilter) throws DhcpServerConfigException { V4AddressBindingPool bp = new V4AddressBindingPool(pool); long leasetime = DhcpServerPolicies.effectivePolicyAsLong(bp, link, Property.V4_DEFAULT_LEASETIME); bp.setLeasetime(leasetime); bp.setLinkFilter(linkFilter); List<InetAddress> usedIps = iaMgr.findExistingIPs(bp.getStartAddress(), bp.getEndAddress()); if ((usedIps != null) && !usedIps.isEmpty()) { for (InetAddress ip : usedIps) { //TODO: for the quickest startup?... // set IP as used without checking if the binding has expired // let the reaper thread deal with all binding cleanup activity bp.setUsed(ip); } } log.info("Built v4 address binding pool: " + bp.getStartAddress().getHostAddress() + "-" + bp.getEndAddress().getHostAddress() + " size=" + bp.getSize()); return bp; } protected List<? extends StaticBinding> buildStaticBindings(Link link) throws DhcpServerConfigException { List<V4StaticAddressBinding> staticBindings = new ArrayList<V4StaticAddressBinding>(); V4AddressBindingsType bindingsType = link.getV4AddrBindings(); if (bindingsType != null) { List<V4AddressBinding> bindings = bindingsType.getBindingList(); if ((bindings != null) && !bindings.isEmpty()) { for (V4AddressBinding binding : bindings) { V4StaticAddressBinding sab = buildV4StaticBinding(binding, link); staticBindings.add(sab); } } } return staticBindings; } protected V4StaticAddressBinding buildV4StaticBinding(V4AddressBinding binding, Link link) throws DhcpServerConfigException { try { InetAddress inetAddr = InetAddress.getByName(binding.getIpAddress()); V4StaticAddressBinding sb = new V4StaticAddressBinding(binding); setIpAsUsed(link, inetAddr); return sb; } catch (UnknownHostException ex) { log.error("Invalid static binding address", ex); throw new DhcpServerConfigException("Invalid static binding address", ex); } } @Override public Binding findCurrentBinding(DhcpLink clientLink, byte[] macAddr, DhcpMessage requestMsg) { return super.findCurrentBinding(clientLink, macAddr, IdentityAssoc.V4_TYPE, 0, requestMsg); } @Override public Binding createDiscoverBinding(DhcpLink clientLink, byte[] macAddr, DhcpMessage requestMsg, byte state) { StaticBinding staticBinding = findStaticBinding(clientLink.getLink(), macAddr, IdentityAssoc.V4_TYPE, 0, requestMsg); if (staticBinding != null) { return super.createStaticBinding(clientLink, macAddr, IdentityAssoc.V4_TYPE, 0, staticBinding, requestMsg); } else { return super.createBinding(clientLink, macAddr, IdentityAssoc.V4_TYPE, 0, getInetAddrs(requestMsg), requestMsg, state); } } @Override public Binding updateBinding(Binding binding, DhcpLink clientLink, byte[] macAddr, DhcpMessage requestMsg, byte state) { StaticBinding staticBinding = findStaticBinding(clientLink.getLink(), macAddr, IdentityAssoc.V4_TYPE, 0, requestMsg); if (staticBinding != null) { return super.updateStaticBinding(binding, clientLink, macAddr, IdentityAssoc.V4_TYPE, 0, staticBinding, requestMsg); } else { return super.updateBinding(binding, clientLink, macAddr, IdentityAssoc.V4_TYPE, 0, getInetAddrs(requestMsg), requestMsg, state); } } /** * Get the Requested IP addresses from the client message, if any was provided. * * @param requestMsg the request msg * * @return a list of InetAddresses containing the requested IP, or null if none requested or * if the requested IP is bogus */ private List<InetAddress> getInetAddrs(DhcpMessage requestMsg) { List<InetAddress> inetAddrs = null; DhcpV4RequestedIpAddressOption reqIpOption = (DhcpV4RequestedIpAddressOption) requestMsg.getDhcpOption(DhcpConstants.V4OPTION_REQUESTED_IP); if (reqIpOption != null) { try { InetAddress inetAddr = InetAddress.getByName(reqIpOption.getIpAddress()); inetAddrs = new ArrayList<InetAddress>(); inetAddrs.add(inetAddr); } catch (UnknownHostException ex) { // client provided a bogus address, ignore it } } return inetAddrs; } /** * Create a Binding given an IdentityAssoc loaded from the database. * * @param ia the ia * @param clientLink the client link * @param requestMsg the request msg * * @return the binding */ protected Binding buildBindingFromIa(IdentityAssoc ia, DhcpLink clientLink, DhcpMessage requestMsg) { Binding binding = new Binding(ia, clientLink); Collection<? extends IaAddress> iaAddrs = ia.getIaAddresses(); if ((iaAddrs != null) && !iaAddrs.isEmpty()) { List<V4BindingAddress> bindingAddrs = new ArrayList<V4BindingAddress>(); for (IaAddress iaAddr : iaAddrs) { if (!clientLink.getSubnet().contains(iaAddr.getIpAddress())) { log.info("Ignoring off-link binding address: " + iaAddr.getIpAddress().getHostAddress()); continue; } V4BindingAddress bindingAddr = null; StaticBinding staticBinding = findStaticBinding(clientLink.getLink(), ia.getDuid(), ia.getIatype(), ia.getIaid(), requestMsg); if (staticBinding != null) { bindingAddr = buildV4StaticBindingFromIaAddr(iaAddr, staticBinding); } else { bindingAddr = buildV4BindingAddressFromIaAddr(iaAddr, clientLink.getLink(), requestMsg); } if (bindingAddr != null) bindingAddrs.add(bindingAddr); } // replace the collection of IaAddresses with BindingAddresses binding.setIaAddresses(bindingAddrs); } else { log.warn("IA has no addresses, binding is empty."); } return binding; } /** * Create a V4BindingAddress given an IaAddress loaded from the database. * * @param iaAddr the ia addr * @param clientLink the client link * @param requestMsg the request msg * * @return the binding address */ private V4BindingAddress buildV4BindingAddressFromIaAddr(IaAddress iaAddr, Link clientLink, DhcpMessage requestMsg) { InetAddress inetAddr = iaAddr.getIpAddress(); BindingPool bp = findBindingPool(clientLink, inetAddr, requestMsg); if (bp != null) { // TODO store the configured options in the persisted binding? // ipAddr.setDhcpOptions(bp.getDhcpOptions()); return new V4BindingAddress(iaAddr, (V4AddressBindingPool)bp); } else { log.error("Failed to create V4BindingAddress: No V4BindingPool found for IP=" + inetAddr.getHostAddress()); } // MUST have a BindingPool, otherwise something's broke return null; } private V4BindingAddress buildV4StaticBindingFromIaAddr(IaAddress iaAddr, StaticBinding staticBinding) { V4BindingAddress bindingAddr = new V4BindingAddress(iaAddr, staticBinding); return bindingAddr; } @Override protected BindingObject buildBindingObject(InetAddress inetAddr, DhcpLink clientLink, DhcpMessage requestMsg) { V4AddressBindingPool bp = (V4AddressBindingPool) findBindingPool(clientLink.getLink(), inetAddr, requestMsg); if (bp != null) { bp.setUsed(inetAddr); // TODO check if this is necessary IaAddress iaAddr = new IaAddress(); iaAddr.setIpAddress(inetAddr); V4BindingAddress bindingAddr = new V4BindingAddress(iaAddr, bp); setBindingObjectTimes(bindingAddr, bp.getPreferredLifetimeMs(), bp.getPreferredLifetimeMs()); // TODO store the configured options in the persisted binding? // bindingAddr.setDhcpOptions(bp.getDhcpOptions()); return bindingAddr; } else { log.error("Failed to create V4BindingAddress: No V4BindingPool found for IP=" + inetAddr.getHostAddress()); } // MUST have a BindingPool, otherwise something's broke return null; } @Override protected void ddnsDelete(IdentityAssoc ia, IaAddress iaAddr) { DhcpV4ClientFqdnOption clientFqdnOption = null; try { if ((ia != null) && (iaAddr != null)) { Collection<DhcpOption> opts = iaAddr.getDhcpOptions(); if (opts != null) { for (DhcpOption opt : opts) { if (opt.getCode() == DhcpConstants.V4OPTION_CLIENT_FQDN) { clientFqdnOption = new DhcpV4ClientFqdnOption(); clientFqdnOption.decode(ByteBuffer.wrap(opt.getValue())); break; } } } if (clientFqdnOption != null) { String fqdn = clientFqdnOption.getDomainName(); if ((fqdn != null) && !fqdn.isEmpty()) { DhcpLink link = serverConfig.findLinkForAddress(iaAddr.getIpAddress()); if (link != null) { V4BindingAddress bindingAddr = null; StaticBinding staticBinding = findStaticBinding(link.getLink(), ia.getDuid(), ia.getIatype(), ia.getIaid(), null); if (staticBinding != null) { bindingAddr = buildV4StaticBindingFromIaAddr(iaAddr, staticBinding); } else { bindingAddr = buildV4BindingAddressFromIaAddr(iaAddr, link.getLink(), null); // safe to send null requestMsg } if (bindingAddr != null) { DdnsCallback ddnsComplete = new DhcpV4DdnsComplete(bindingAddr, clientFqdnOption); DhcpConfigObject configObj = bindingAddr.getConfigObj(); DdnsUpdater ddns = new DdnsUpdater(link.getLink(), configObj, bindingAddr.getIpAddress(), fqdn, ia.getDuid(), configObj.getValidLifetime(), clientFqdnOption.getUpdateABit(), true, ddnsComplete); ddns.processUpdates(); } else { log.error("Failed to find binding for address: " + iaAddr.getIpAddress().getHostAddress()); } } else { log.error("Failed to find link for binding address: " + iaAddr.getIpAddress().getHostAddress()); } } else { log.error("FQDN is null or empty. No DDNS deletes performed."); } } else { log.warn("No Client FQDN option in current binding. No DDNS deletes performed."); } } } catch (Exception ex) { log.error("Failed to perform DDNS delete", ex); } } @Override protected byte getIaType() { return IdentityAssoc.V4_TYPE; } }