/* * 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 V6PrefixBindingManagerImpl.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.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Timer; import java.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jagornet.dhcp.db.IaAddress; import com.jagornet.dhcp.db.IaPrefix; import com.jagornet.dhcp.db.IdentityAssoc; import com.jagornet.dhcp.message.DhcpMessage; import com.jagornet.dhcp.option.v6.DhcpV6ClientIdOption; import com.jagornet.dhcp.option.v6.DhcpV6IaPdOption; import com.jagornet.dhcp.option.v6.DhcpV6IaPrefixOption; 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.xml.Link; import com.jagornet.dhcp.xml.LinkFilter; import com.jagornet.dhcp.xml.LinkFiltersType; import com.jagornet.dhcp.xml.V6PrefixBinding; import com.jagornet.dhcp.xml.V6PrefixBindingsType; import com.jagornet.dhcp.xml.V6PrefixPool; import com.jagornet.dhcp.xml.V6PrefixPoolsType; /** * The Class PrefixBindingManagerImpl. * * @author A. Gregory Rabil */ public class V6PrefixBindingManagerImpl extends BaseBindingManager implements V6PrefixBindingManager { private static Logger log = LoggerFactory.getLogger(V6PrefixBindingManagerImpl.class); /** * Instantiates a new prefix binding manager impl. */ public V6PrefixBindingManagerImpl() { super(); } protected void startReaper() { //TODO: separate properties for address/prefix binding managers? long reaperStartupDelay = DhcpServerPolicies.globalPolicyAsLong(Property.BINDING_MANAGER_REAPER_STARTUP_DELAY); long reaperRunPeriod = DhcpServerPolicies.globalPolicyAsLong(Property.BINDING_MANAGER_REAPER_RUN_PERIOD); reaper = new Timer("BindingReaper"); reaper.schedule(new ReaperTimerTask(), reaperStartupDelay, reaperRunPeriod); } /** * Build the list of PrefixBindingPools from the list of configured PrefixPools * for the given configuration Link container object. The list of PrefixBindingPools * starts with the filtered PrefixPools followed by non-filtered PrefixPools. * * @param link the configuration Link object * * @return the list of PrefixBindingPools (<? extends BindingPool>) * * @throws DhcpServerConfigException if there is a problem parsing a configured range */ protected List<? extends BindingPool> buildBindingPools(Link link) throws DhcpServerConfigException { List<V6PrefixBindingPool> bindingPools = new ArrayList<V6PrefixBindingPool>(); // 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) { V6PrefixPoolsType poolsType = linkFilter.getV6PrefixPools(); if (poolsType != null) { // add the filtered pools to the mapped list List<V6PrefixPool> pools = poolsType.getPoolList(); if ((pools != null) && !pools.isEmpty()) { for (V6PrefixPool pool : pools) { V6PrefixBindingPool abp = buildV6BindingPool(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()); } } } } V6PrefixPoolsType poolsType = link.getV6PrefixPools(); if (poolsType != null) { // add the unfiltered pools to the mapped list List<V6PrefixPool> pools = poolsType.getPoolList(); if ((pools != null) && !pools.isEmpty()) { for (V6PrefixPool pool : pools) { V6PrefixBindingPool abp = buildV6BindingPool(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 PrefixBindingPools. * * @param bindingPools the list of PrefixBindingPools */ protected void reconcilePools(List<V6PrefixBindingPool> bindingPools) { if ((bindingPools != null) && !bindingPools.isEmpty()) { List<Range> ranges = new ArrayList<Range>(); for (V6PrefixBindingPool bp : bindingPools) { Range range = new Range(bp.getStartAddress(), bp.getEndAddress()); ranges.add(range); } iaMgr.reconcileIaAddresses(ranges); } } /** * Builds a binding pool from an PrefixPool using the given link. * * @param pool the PrefixPool to wrap as an PrefixBindingPool * @param link the link * * @return the binding pool * * @throws DhcpServerConfigException if there is a problem parsing the configured range */ protected V6PrefixBindingPool buildV6BindingPool(V6PrefixPool pool, Link link) throws DhcpServerConfigException { return buildV6BindingPool(pool, link, null); } /** * Builds a binding pool from an PrefixPool using the given link and filter. * * @param pool the AddressPool to wrap as an PrefixBindingPool * @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 V6PrefixBindingPool buildV6BindingPool(V6PrefixPool pool, Link link, LinkFilter linkFilter) throws DhcpServerConfigException { V6PrefixBindingPool bp = new V6PrefixBindingPool(pool); long pLifetime = DhcpServerPolicies.effectivePolicyAsLong(bp, link, Property.PREFERRED_LIFETIME); bp.setPreferredLifetime(pLifetime); long vLifetime = DhcpServerPolicies.effectivePolicyAsLong(bp, link, Property.VALID_LIFETIME); bp.setValidLifetime(vLifetime); 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 prefix binding pool: " + bp.getStartAddress().getHostAddress() + "-" + bp.getEndAddress().getHostAddress() + ", size=" + bp.getSize()); return bp; } protected List<? extends StaticBinding> buildStaticBindings(Link link) throws DhcpServerConfigException { List<V6StaticPrefixBinding> staticBindings = new ArrayList<V6StaticPrefixBinding>(); V6PrefixBindingsType bindingsType = link.getV6PrefixBindings(); if (bindingsType != null) { List<V6PrefixBinding> bindings = bindingsType.getBindingList(); if ((bindings != null) && !bindings.isEmpty()) { for (V6PrefixBinding binding : bindings) { V6StaticPrefixBinding spb = buildStaticBinding(binding, link); staticBindings.add(spb); } } } return staticBindings; } protected V6StaticPrefixBinding buildStaticBinding(V6PrefixBinding binding, Link link) throws DhcpServerConfigException { try { InetAddress inetAddr = InetAddress.getByName(binding.getPrefix()); V6StaticPrefixBinding sb = new V6StaticPrefixBinding(binding); setIpAsUsed(link, inetAddr); return sb; } catch (UnknownHostException ex) { log.error("Invalid static binding address", ex); throw new DhcpServerConfigException("Invalid static binding address", ex); } } public Binding findCurrentBinding(DhcpLink clientLink, DhcpV6ClientIdOption clientIdOption, DhcpV6IaPdOption iaPdOption, DhcpMessage requestMsg) { byte[] duid = clientIdOption.getDuid(); long iaid = iaPdOption.getIaId(); return super.findCurrentBinding(clientLink, duid, IdentityAssoc.PD_TYPE, iaid, requestMsg); } public Binding createSolicitBinding(DhcpLink clientLink, DhcpV6ClientIdOption clientIdOption, DhcpV6IaPdOption iaPdOption, DhcpMessage requestMsg, byte state) { byte[] duid = clientIdOption.getDuid(); long iaid = iaPdOption.getIaId(); StaticBinding staticBinding = findStaticBinding(clientLink.getLink(), duid, IdentityAssoc.PD_TYPE, iaid, requestMsg); if (staticBinding != null) { return super.createStaticBinding(clientLink, duid, IdentityAssoc.PD_TYPE, iaid, staticBinding, requestMsg); } else { return super.createBinding(clientLink, duid, IdentityAssoc.PD_TYPE, iaid, getInetAddrs(iaPdOption), requestMsg, state); } } public Binding updateBinding(Binding binding, DhcpLink clientLink, DhcpV6ClientIdOption clientIdOption, DhcpV6IaPdOption iaPdOption, DhcpMessage requestMsg, byte state) { byte[] duid = clientIdOption.getDuid(); long iaid = iaPdOption.getIaId(); StaticBinding staticBinding = findStaticBinding(clientLink.getLink(), duid, IdentityAssoc.PD_TYPE, iaid, requestMsg); if (staticBinding != null) { return super.updateStaticBinding(binding, clientLink, duid, IdentityAssoc.PD_TYPE, iaid, staticBinding, requestMsg); } else { return super.updateBinding(binding, clientLink, duid, IdentityAssoc.PD_TYPE, iaid, getInetAddrs(iaPdOption), requestMsg, state); } } public void releaseIaPrefix(IaPrefix iaPrefix) { try { if (DhcpServerPolicies.globalPolicyAsBoolean( Property.BINDING_MANAGER_DELETE_OLD_BINDINGS)) { iaMgr.deleteIaPrefix(iaPrefix); // free the prefix only if it is deleted from the db, // otherwise, we will get a unique constraint violation // if another client obtains this released prefix freeAddress(iaPrefix.getIpAddress()); } else { iaPrefix.setStartTime(null); iaPrefix.setPreferredEndTime(null); iaPrefix.setValidEndTime(null); iaPrefix.setState(IaPrefix.RELEASED); iaMgr.updateIaPrefix(iaPrefix); } } catch (Exception ex) { log.error("Failed to release address", ex); } } public void declineIaPrefix(IaPrefix iaPrefix) { try { iaPrefix.setStartTime(null); iaPrefix.setPreferredEndTime(null); iaPrefix.setValidEndTime(null); iaPrefix.setState(IaPrefix.DECLINED); iaMgr.updateIaPrefix(iaPrefix); } catch (Exception ex) { log.error("Failed to decline address", ex); } } /** * Callback from the ExpireTimerTask started when the lease was granted. * NOT CURRENTLY USED * * @param iaPrefix the ia prefix */ public void expireIaPrefix(IaPrefix iaPrefix) { try { if (DhcpServerPolicies.globalPolicyAsBoolean( Property.BINDING_MANAGER_DELETE_OLD_BINDINGS)) { log.debug("Deleting expired prefix: " + iaPrefix.getIpAddress()); iaMgr.deleteIaPrefix(iaPrefix); // free the prefix only if it is deleted from the db, // otherwise, we will get a unique constraint violation // if another client obtains this released prefix freeAddress(iaPrefix.getIpAddress()); } else { iaPrefix.setStartTime(null); iaPrefix.setPreferredEndTime(null); iaPrefix.setValidEndTime(null); iaPrefix.setState(IaPrefix.EXPIRED); log.debug("Updating expired prefix: " + iaPrefix.getIpAddress()); iaMgr.updateIaPrefix(iaPrefix); } } catch (Exception ex) { log.error("Failed to expire address", ex); } } /** * Callback from the RepearTimerTask started when the BindingManager initialized. * Find any expired prefixes as of now, and expire them already. */ public void expirePrefixes() { List<IaPrefix> expiredPrefs = iaMgr.findExpiredIaPrefixes(); if ((expiredPrefs != null) && !expiredPrefs.isEmpty()) { for (IaPrefix iaPrefix : expiredPrefs) { expireIaPrefix(iaPrefix); } } } /** * Extract the list of IP addresses from within the given IA_PD option. * * @param iaNaOption the IA_PD option * * @return the list of InetAddresses for the IPs in the IA_PD option */ private List<InetAddress> getInetAddrs(DhcpV6IaPdOption iaPdOption) { List<InetAddress> inetAddrs = null; List<DhcpV6IaPrefixOption> iaPrefs = iaPdOption.getIaPrefixOptions(); if ((iaPrefs != null) && !iaPrefs.isEmpty()) { inetAddrs = new ArrayList<InetAddress>(); for (DhcpV6IaPrefixOption iaPrefix : iaPrefs) { InetAddress inetAddr = iaPrefix.getInetAddress(); inetAddrs.add(inetAddr); } } 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> iaPrefs = ia.getIaAddresses(); if ((iaPrefs != null) && !iaPrefs.isEmpty()) { List<V6BindingPrefix> bindingPrefixes = new ArrayList<V6BindingPrefix>(); for (IaAddress iaAddr : iaPrefs) { // off-link check needed only for v4? // if (!clientLink.getSubnet().contains(iaAddr.getIpAddress())) { // log.info("Ignoring off-link binding address: " + // iaAddr.getIpAddress().getHostAddress()); // continue; // } V6BindingPrefix bindingPrefix = null; StaticBinding staticBinding = findStaticBinding(clientLink.getLink(), ia.getDuid(), ia.getIatype(), ia.getIaid(), requestMsg); if (staticBinding != null) { bindingPrefix = buildV6BindingPrefixFromIaPrefix((IaPrefix)iaAddr, staticBinding); } else { bindingPrefix = buildBindingAddrFromIaPrefix((IaPrefix)iaAddr, clientLink.getLink(), requestMsg); } if (bindingPrefix != null) bindingPrefixes.add(bindingPrefix); } // replace the collection of IaPrefixes with BindingPrefixes binding.setIaAddresses(bindingPrefixes); } else { log.warn("IA has no prefixes, binding is empty."); } return binding; } /** * Create a BindingPrefix given an IaPrefix loaded from the database. * * @param iaPrefix the ia prefix * @param clientLink the client link * @param requestMsg the request msg * * @return the binding address */ private V6BindingPrefix buildBindingAddrFromIaPrefix(IaPrefix iaPrefix, Link clientLink, DhcpMessage requestMsg) { InetAddress inetAddr = iaPrefix.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 V6BindingPrefix(iaPrefix, (V6PrefixBindingPool)bp); } else { log.error("Failed to create BindingPrefix: No BindingPool found for IP=" + inetAddr.getHostAddress()); } // MUST have a BindingPool, otherwise something's broke return null; } /** * Build a BindingPrefix given an IaAddress loaded from the database * and a static binding for the client request. * * @param iaPrefix * @param staticBinding * @return */ private V6BindingPrefix buildV6BindingPrefixFromIaPrefix(IaPrefix iaPrefix, StaticBinding staticBinding) { V6BindingPrefix bindingPrefix = new V6BindingPrefix(iaPrefix, staticBinding); return bindingPrefix; } /** * Build a BindingPrefix for the given InetAddress and Link. * * @param inetAddr the inet addr * @param clientLink the client link * @param requestMsg the request msg * * @return the binding address */ protected BindingObject buildBindingObject(InetAddress inetAddr, DhcpLink clientLink, DhcpMessage requestMsg) { V6PrefixBindingPool bp = (V6PrefixBindingPool) findBindingPool(clientLink.getLink(), inetAddr, requestMsg); if (bp != null) { bp.setUsed(inetAddr); // TODO check if this is necessary IaPrefix iaPrefix = new IaPrefix(); iaPrefix.setIpAddress(inetAddr); iaPrefix.setPrefixLength((short)bp.getAllocPrefixLen()); V6BindingPrefix bindingPrefix = new V6BindingPrefix(iaPrefix, bp); setBindingObjectTimes(bindingPrefix, bp.getPreferredLifetimeMs(), bp.getPreferredLifetimeMs()); // TODO store the configured options in the persisted binding? // bindingPrefix.setDhcpOptions(bp.getDhcpOptions()); return bindingPrefix; } else { log.error("Failed to create BindingPrefix: No BindingPool found for IP=" + inetAddr.getHostAddress()); } // MUST have a BindingPool, otherwise something's broke return null; } /** * The Class ReaperTimerTask. */ class ReaperTimerTask extends TimerTask { /* (non-Javadoc) * @see java.util.TimerTask#run() */ @Override public void run() { // log.debug("Expiring addresses..."); expirePrefixes(); } } }