/*
* 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 V6AddrBindingManager.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.v6.DhcpV6ClientFqdnOption;
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.DhcpV6DdnsComplete;
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.V6AddressBinding;
import com.jagornet.dhcp.xml.V6AddressBindingsType;
import com.jagornet.dhcp.xml.V6AddressPool;
import com.jagornet.dhcp.xml.V6AddressPoolsType;
/**
* The Class V6AddrBindingManager.
* Third-level abstract class that extends BaseAddrBindingManager to
* add behavior specific to DHCPv6 address bindings.
*
* @author A. Gregory Rabil
*/
public abstract class V6AddrBindingManager extends BaseAddrBindingManager
{
private static Logger log = LoggerFactory.getLogger(V6AddrBindingManager.class);
public V6AddrBindingManager()
{
super();
}
/**
* Fetch the V6AddressPoolsType XML object from the given LinkFilter XML object.
* The subclasses fetch the address pools for either NA or TA addresses.
*
* @param linkFilter the link filter
* @return the V6AddressPoolsType for this link filter, or null if none
*/
protected abstract V6AddressPoolsType getV6AddressPoolsType(LinkFilter linkFilter);
/**
* Fetch the AddressPoolsType XML object from the given Link XML object.
* The subclasses fetch the address pools for either NA or TA addresses.
*
* @param linkFilter the link
* @return the AddressPoolsType for this link, or null if none
*/
protected abstract V6AddressPoolsType getV6AddressPoolsType(Link link);
/**
* Build the list of V6AddressBindingPools from the list of configured V6AddressPools
* for the given configuration Link container object. The list of V6AddressBindingPools
* starts with the filtered AddressPools followed by non-filtered AddressPools.
*
* @param link the configuration Link object
*
* @return the list of V6AddressBindingPools (<? extends BindingPool>)
*
* @throws DhcpServerConfigException if there is a problem parsing a configured range
*/
protected List<? extends BindingPool> buildBindingPools(Link link)
throws DhcpServerConfigException
{
List<V6AddressBindingPool> bindingPools = new ArrayList<V6AddressBindingPool>();
// 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) {
V6AddressPoolsType poolsType = getV6AddressPoolsType(linkFilter);
if (poolsType != null) {
// add the filtered pools to the mapped list
List<V6AddressPool> pools = poolsType.getPoolList();
if ((pools != null) && !pools.isEmpty()) {
for (V6AddressPool pool : pools) {
V6AddressBindingPool 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());
}
}
}
}
V6AddressPoolsType poolsType = getV6AddressPoolsType(link);
if (poolsType != null) {
// add the unfiltered pools to the mapped list
List<V6AddressPool> pools = poolsType.getPoolList();
if ((pools != null) && !pools.isEmpty()) {
for (V6AddressPool pool : pools) {
V6AddressBindingPool 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());
}
//TODO this is very dangerous if the server is managing
// both NA and TA address pools because we'd delete
// all the addresses in pools of the other type
// reconcilePools(bindingPools);
return bindingPools;
}
/**
* Reconcile pools. Delete any IaAddress objects not contained
* within the given list of AddressBindingPools.
*
* @param bindingPools the list of AddressBindingPools
*/
protected void reconcilePools(List<V6AddressBindingPool> bindingPools)
{
if ((bindingPools != null) && !bindingPools.isEmpty()) {
List<Range> ranges = new ArrayList<Range>();
for (V6AddressBindingPool bp : bindingPools) {
ranges.add(bp.getRange());
}
iaMgr.reconcileIaAddresses(ranges);
}
}
/**
* Builds a binding pool from an AddressPool using the given link.
*
* @param pool the AddressPool to wrap as an AddressBindingPool
* @param link the link
*
* @return the binding pool
*
* @throws DhcpServerConfigException if there is a problem parsing the configured range
*/
protected V6AddressBindingPool buildV6BindingPool(V6AddressPool pool, Link link)
throws DhcpServerConfigException
{
return buildV6BindingPool(pool, link, null);
}
/**
* Builds a binding pool from an AddressPool using the given link and filter.
*
* @param pool the AddressPool to wrap as an AddressBindingPool
* @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 V6AddressBindingPool buildV6BindingPool(V6AddressPool pool, Link link,
LinkFilter linkFilter) throws DhcpServerConfigException
{
V6AddressBindingPool bp = new V6AddressBindingPool(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 address binding pool: " + bp.getStartAddress().getHostAddress() + "-" +
bp.getEndAddress().getHostAddress() + " size=" + bp.getSize());
return bp;
}
/**
* Fetch the AddressBindings from the given XML Link object.
*
* @param link
* @return
*/
protected abstract V6AddressBindingsType getV6AddressBindingsType(Link link);
/**
* Build the list of static bindings for the given link.
*
* @param link the link
* @return the list of static bindings
* @throws DhcpServerConfigException if the static binding is invalid
*/
protected List<? extends StaticBinding> buildStaticBindings(Link link)
throws DhcpServerConfigException
{
List<V6StaticAddressBinding> staticBindings = new ArrayList<V6StaticAddressBinding>();
V6AddressBindingsType bindingsType = getV6AddressBindingsType(link);
if (bindingsType != null) {
List<V6AddressBinding> bindings = bindingsType.getBindingList();
if ((bindings != null) && !bindings.isEmpty()) {
for (V6AddressBinding binding : bindings) {
V6StaticAddressBinding sab = buildV6StaticBinding(binding, link);
staticBindings.add(sab);
}
}
}
return staticBindings;
}
/**
* Build a static address binding from the given address binding.
*
* @param binding the address binding
* @param link the link
* @return the static address binding
* @throws DhcpServerConfigException if the static binding is invalid
*/
protected V6StaticAddressBinding buildV6StaticBinding(V6AddressBinding binding, Link link)
throws DhcpServerConfigException
{
try {
InetAddress inetAddr = InetAddress.getByName(binding.getIpAddress());
V6StaticAddressBinding sb = new V6StaticAddressBinding(binding, getIaType());
setIpAsUsed(link, inetAddr);
return sb;
}
catch (UnknownHostException ex) {
log.error("Invalid static binding address", ex);
throw new DhcpServerConfigException("Invalid static binding address", ex);
}
}
/**
* Perform the DDNS delete processing when a lease is released or expired.
*
* @param ia the IdentityAssoc of the client
* @param iaAddr the released or expired IaAddress
*/
protected void ddnsDelete(IdentityAssoc ia, IaAddress iaAddr)
{
DhcpV6ClientFqdnOption clientFqdnOption = null;
try {
if ((ia != null) && (iaAddr != null)) {
Collection<DhcpOption> opts = iaAddr.getDhcpOptions();
if (opts != null) {
for (DhcpOption opt : opts) {
if (opt.getCode() == DhcpConstants.V6OPTION_CLIENT_FQDN) {
clientFqdnOption = new DhcpV6ClientFqdnOption();
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) {
V6BindingAddress bindingAddr = null;
StaticBinding staticBinding =
findStaticBinding(link.getLink(), ia.getDuid(),
ia.getIatype(), ia.getIaid(), null);
if (staticBinding != null) {
bindingAddr =
buildV6StaticBindingFromIaAddr(iaAddr, staticBinding);
}
else {
bindingAddr =
buildV6BindingAddressFromIaAddr(iaAddr, link, null); // safe to send null requestMsg
}
if (bindingAddr != null) {
DdnsCallback ddnsComplete =
new DhcpV6DdnsComplete(bindingAddr, clientFqdnOption);
DhcpConfigObject configObj = bindingAddr.getConfigObj();
DdnsUpdater ddns =
new DdnsUpdater(link.getLink(), configObj,
bindingAddr.getIpAddress(), fqdn, ia.getDuid(),
configObj.getValidLifetime(),
clientFqdnOption.getUpdateAaaaBit(), 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);
}
}
/**
* 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<V6BindingAddress> bindingAddrs = new ArrayList<V6BindingAddress>();
for (IaAddress iaAddr : iaAddrs) {
// off-link check needed only for v4?
// if (!clientLink.getSubnet().contains(iaAddr.getIpAddress())) {
// log.info("Ignoring off-link binding address: " +
// iaAddr.getIpAddress().getHostAddress());
// continue;
// }
V6BindingAddress bindingAddr = null;
StaticBinding staticBinding =
findStaticBinding(clientLink.getLink(), ia.getDuid(),
ia.getIatype(), ia.getIaid(), requestMsg);
if (staticBinding != null) {
bindingAddr =
buildV6StaticBindingFromIaAddr(iaAddr, staticBinding);
}
else {
bindingAddr =
buildV6BindingAddressFromIaAddr(iaAddr, clientLink, 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 V6BindingAddress 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 V6BindingAddress buildV6BindingAddressFromIaAddr(IaAddress iaAddr,
DhcpLink clientLink, DhcpMessage requestMsg)
{
InetAddress inetAddr = iaAddr.getIpAddress();
BindingPool bp = findBindingPool(clientLink.getLink(), inetAddr, requestMsg);
if (bp != null) {
// TODO store the configured options in the persisted binding?
// ipAddr.setDhcpOptions(bp.getDhcpOptions());
return new V6BindingAddress(iaAddr, (V6AddressBindingPool)bp);
}
else {
log.error("Failed to create BindingAddress: No BindingPool found for IP=" +
inetAddr.getHostAddress());
}
// MUST have a BindingPool, otherwise something's broke
return null;
}
/**
* Build a V6BindingAddress given an IaAddress loaded from the database
* and a static binding for the client request.
*
* @param iaAddr
* @param staticBinding
* @return
*/
private V6BindingAddress buildV6StaticBindingFromIaAddr(IaAddress iaAddr,
StaticBinding staticBinding)
{
V6BindingAddress bindingAddr = new V6BindingAddress(iaAddr, staticBinding);
return bindingAddr;
}
/**
* Build a BindingAddress for the given InetAddress and DhcpLink.
*
* @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)
{
V6AddressBindingPool bp =
(V6AddressBindingPool) findBindingPool(clientLink.getLink(), inetAddr, requestMsg);
if (bp != null) {
bp.setUsed(inetAddr); // TODO check if this is necessary
IaAddress iaAddr = new IaAddress();
iaAddr.setIpAddress(inetAddr);
V6BindingAddress bindingAddr = new V6BindingAddress(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 BindingAddress: No BindingPool found for IP=" +
inetAddr.getHostAddress());
}
// MUST have a BindingPool, otherwise something's broke
return null;
}
}