/* * 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 SqliteLeaseManager.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.db; import java.io.File; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.almworks.sqlite4java.SQLiteConnection; import com.almworks.sqlite4java.SQLiteException; import com.almworks.sqlite4java.SQLiteStatement; import com.jagornet.dhcp.server.config.DhcpServerPolicies; import com.jagornet.dhcp.server.config.DhcpServerPolicies.Property; import com.jagornet.dhcp.server.request.binding.Range; /** * The SqliteLeaseManager implementation class for the IaManager interface. * This is the main database access class for handling client bindings. * * @author A. Gregory Rabil */ public class SqliteLeaseManager extends LeaseManager { private static Logger log = LoggerFactory.getLogger(SqliteLeaseManager.class); protected static File DATABASE_FILE = new File("db/sqlite/jagornet-dhcpv6"); private static final ThreadLocal<SQLiteConnection> threadConnection = new ThreadLocal<SQLiteConnection>() { @Override protected SQLiteConnection initialValue() { SQLiteConnection connection = new SQLiteConnection(DATABASE_FILE); try { connection.open(); log.debug("Created new SQLite connection: " + connection.toString()); } catch (SQLiteException ex) { log.error("Failed to open connection: " + ex); } return connection; } @Override protected void finalize() throws Throwable { SQLiteConnection connection = this.get(); if (connection != null) { log.debug("Disposing SQLite connection: " + connection.toString()); connection.dispose(); } }; }; protected static SQLiteConnection getSQLiteConnection() { return threadConnection.get(); } // Spring bean init-method public void init() throws Exception { if (!DATABASE_FILE.exists()) { File path = DATABASE_FILE.getParentFile(); if (path != null) { path.mkdirs(); } String schemaFilename = DbSchemaManager.SCHEMA_V2_FILENAME; log.info("Creating new SQLite schema from file: " + schemaFilename); SQLiteConnection sqliteConnection = getSQLiteConnection(); List<String> schemaDDL = DbSchemaManager.getSchemaDDL(schemaFilename); for (String ddl : schemaDDL) { sqliteConnection.exec(ddl); } } else { //TODO: validate the existing schema } } /** * Insert dhcp lease. * * @param lease the lease */ protected void insertDhcpLease(final DhcpLease lease) { SQLiteConnection connection = null; SQLiteStatement statement = null; try { connection = getSQLiteConnection(); statement = connection.prepare("insert into dhcplease" + " (ipaddress, duid, iatype, iaid, prefixlen, state," + " starttime, preferredendtime, validendtime," + " ia_options, ipaddr_options)" + " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); statement.bind(1, lease.getIpAddress().getAddress()); statement.bind(2, lease.getDuid()); statement.bind(3, lease.getIatype()); statement.bind(4, lease.getIaid()); statement.bind(5, lease.getPrefixLength()); statement.bind(6, lease.getState()); statement.bind(7, lease.getStartTime().getTime()); statement.bind(8, lease.getPreferredEndTime().getTime()); statement.bind(9, lease.getValidEndTime().getTime()); statement.bind(10, encodeOptions(lease.getIaDhcpOptions())); statement.bind(11, encodeOptions(lease.getIaAddrDhcpOptions())); while (statement.step()) { log.debug("insertDhcpLease: step=true"); } } catch (SQLiteException ex) { log.error("insertDhcpLease failed", ex); throw new RuntimeException(ex); } finally { closeStatement(statement); closeConnection(connection); } } /** * Update dhcp lease. * * @param lease the lease */ protected void updateDhcpLease(final DhcpLease lease) { SQLiteConnection connection = null; SQLiteStatement statement = null; try { connection = getSQLiteConnection(); statement = connection.prepare("update dhcplease" + " set state=?," + " starttime=?," + " preferredendtime=?," + " validendtime=?," + " ia_options=?," + " ipaddr_options=?" + " where ipaddress=?"); statement.bind(1, lease.getState()); statement.bind(2, lease.getStartTime().getTime()); statement.bind(3, lease.getPreferredEndTime().getTime()); statement.bind(4, lease.getValidEndTime().getTime()); statement.bind(5, encodeOptions(lease.getIaDhcpOptions())); statement.bind(6, encodeOptions(lease.getIaAddrDhcpOptions())); statement.bind(7, lease.getIpAddress().getAddress()); while (statement.step()) { log.debug("updateDhcpLease: step=true"); } } catch (SQLiteException ex) { log.error("updateDhcpLease failed", ex); throw new RuntimeException(ex); } finally { closeStatement(statement); closeConnection(connection); } } /** * Delete dhcp lease. * * @param lease the lease */ protected void deleteDhcpLease(final DhcpLease lease) { SQLiteConnection connection = null; SQLiteStatement statement = null; try { connection = getSQLiteConnection(); statement = connection.prepare("delete from dhcplease" + " where ipaddress=?"); statement.bind(1, lease.getIpAddress().getAddress()); while (statement.step()) { log.debug("deleteDhcpLease: step=true"); } } catch (SQLiteException ex) { log.error("deleteDhcpLease failed", ex); throw new RuntimeException(ex); } finally { closeStatement(statement); closeConnection(connection); } } /** * Update ia options. */ protected void updateIaOptions(final InetAddress inetAddr, final Collection<DhcpOption> iaOptions) { SQLiteConnection connection = null; SQLiteStatement statement = null; try { connection = getSQLiteConnection(); statement = connection.prepare("update dhcplease" + " set ia_options=?" + " where ipaddress=?"); statement.bind(1, encodeOptions(iaOptions)); statement.bind(2, inetAddr.getAddress()); while (statement.step()) { log.debug("updateIaOptions: step=true"); } } catch (SQLiteException ex) { log.error("updateIaOptions failed", ex); throw new RuntimeException(ex); } finally { closeStatement(statement); closeConnection(connection); } } /** * Update ipaddr options. */ protected void updateIpAddrOptions(final InetAddress inetAddr, final Collection<DhcpOption> ipAddrOptions) { SQLiteConnection connection = null; SQLiteStatement statement = null; try { connection = getSQLiteConnection(); statement = connection.prepare("update dhcplease" + " set ipaddr_options=?" + " where ipaddress=?"); statement.bind(1, encodeOptions(ipAddrOptions)); statement.bind(2, inetAddr.getAddress()); while (statement.step()) { log.debug("updateIpAddrOptions: step=true"); } } catch (SQLiteException ex) { log.error("updateIpAddrOptions failed", ex); throw new RuntimeException(ex); } finally { closeStatement(statement); closeConnection(connection); } } /** * Find dhcp leases for ia. * * @param duid the duid * @param iatype the iatype * @param iaid the iaid * @return the list */ protected List<DhcpLease> findDhcpLeasesForIA(final byte[] duid, final byte iatype, final long iaid) { SQLiteConnection connection = null; SQLiteStatement statement = null; try { connection = getSQLiteConnection(); statement = connection.prepare("select * from dhcplease" + " where duid = ?" + " and iatype = ?" + " and iaid = ?"); statement.bind(1, duid); statement.bind(2, iatype); statement.bind(3, iaid); return mapLeases(statement); } catch (SQLiteException ex) { log.error("findDhcpLeasesForIA failed", ex); throw new RuntimeException(ex); } finally { closeStatement(statement); closeConnection(connection); } } /** * Find dhcp lease for InetAddr. * * @param inetAddr the InetAddr * @return the DhcpLease */ protected DhcpLease findDhcpLeaseForInetAddr(final InetAddress inetAddr) { List<DhcpLease> leases = null; SQLiteConnection connection = null; SQLiteStatement statement = null; try { connection = getSQLiteConnection(); statement = connection.prepare( "select * from dhcplease" + " where ipaddress = ?"); statement.bind(1, inetAddr.getAddress()); leases = mapLeases(statement); if ((leases != null) && (leases.size() > 0)) { if (leases.size() == 1) { return leases.get(0); } else { //TODO: this really should be impossible because of the unique // constraint on the IP address log.error("Found more than one lease for IP=" + inetAddr.getHostAddress()); } } } catch (SQLiteException ex) { log.error("findDhcpLeaseForInetAddr failed", ex); throw new RuntimeException(ex); } finally { closeStatement(statement); closeConnection(connection); } return null; } /* (non-Javadoc) * @see com.jagornet.dhcpv6.db.IaManager#updateIaAddr(com.jagornet.dhcpv6.db.IaAddress) */ public void updateIaAddr(final IaAddress iaAddr) { SQLiteConnection connection = null; SQLiteStatement statement = null; try { connection = getSQLiteConnection(); statement = connection.prepare("update dhcplease" + " set state = ?," + ((iaAddr instanceof IaPrefix) ? " prefixlen = ?," : "") + " starttime = ?," + " preferredendtime = ?," + " validendtime = ?" + " where ipaddress = ?"); int i=1; statement.bind(i++, iaAddr.getState()); if (iaAddr instanceof IaPrefix) { statement.bind(i++, ((IaPrefix)iaAddr).getPrefixLength()); } Date start = iaAddr.getStartTime(); if (start != null) { statement.bind(i++, start.getTime()); } else { statement.bindNull(i++); } Date preferred = iaAddr.getPreferredEndTime(); if (preferred != null) { statement.bind(i++, preferred.getTime()); } else { statement.bindNull(i++); } Date valid = iaAddr.getValidEndTime(); if (valid != null) { statement.bind(i++, valid.getTime()); } else { statement.bindNull(i++); } statement.bind(i++, iaAddr.getIpAddress().getAddress()); while(statement.step()) { log.debug("updateIaAddr: step=true"); } } catch (SQLiteException ex) { log.error("updateIaAddr failed", ex); throw new RuntimeException(ex); } finally { closeStatement(statement); closeConnection(connection); } } /* (non-Javadoc) * @see com.jagornet.dhcpv6.db.IaManager#deleteIaAddr(com.jagornet.dhcpv6.db.IaAddress) */ public void deleteIaAddr(final IaAddress iaAddr) { SQLiteConnection connection = null; SQLiteStatement statement = null; try { connection = getSQLiteConnection(); statement = connection.prepare("delete from dhcplease" + " where ipaddress = ?"); statement.bind(1, iaAddr.getIpAddress().getAddress()); while(statement.step()) { log.debug("deleteIaAddr: step=true"); } } catch (SQLiteException ex) { log.error("deleteIaAddr failed", ex); throw new RuntimeException(ex); } finally { closeStatement(statement); closeConnection(connection); } } /* (non-Javadoc) * @see com.jagornet.dhcpv6.db.IaManager#findExistingIPs(java.net.InetAddress, java.net.InetAddress) */ @Override public List<InetAddress> findExistingIPs(final InetAddress startAddr, final InetAddress endAddr) { List<InetAddress> inetAddrs = new ArrayList<InetAddress>(); SQLiteConnection connection = null; SQLiteStatement statement = null; try { connection = getSQLiteConnection(); statement = connection.prepare( "select ipaddress from dhcplease" + " where ipaddress >= ? and ipaddress <= ?" + " order by ipaddress"); statement.bind(1, startAddr.getAddress()); statement.bind(2, endAddr.getAddress()); while (statement.step()) { InetAddress inetAddr = null; try { inetAddr = InetAddress.getByAddress(statement.columnBlob(0)); } catch (UnknownHostException e) { throw new RuntimeException("Unable to map ipaddress", e); } inetAddrs.add(inetAddr); } return inetAddrs; } catch (SQLiteException ex) { log.error("findExistingIPs failed", ex); throw new RuntimeException(ex); } finally { closeStatement(statement); closeConnection(connection); } } /* (non-Javadoc) * @see com.jagornet.dhcpv6.db.IaManager#findUnusedIaAddresses(java.net.InetAddress, java.net.InetAddress) */ @Override public List<IaAddress> findUnusedIaAddresses(final InetAddress startAddr, final InetAddress endAddr) { long offerExpireMillis = DhcpServerPolicies.globalPolicyAsLong(Property.BINDING_MANAGER_OFFER_EXPIRATION); final long offerExpiration = new Date().getTime() - offerExpireMillis; SQLiteConnection connection = null; SQLiteStatement statement = null; try { connection = getSQLiteConnection(); statement = connection.prepare( "select * from dhcplease" + " where ((state=" + IaAddress.ADVERTISED + " and starttime <= ?)" + " or (state=" + IaAddress.EXPIRED + " or state=" + IaAddress.RELEASED + "))" + " and ipaddress >= ? and ipaddress <= ?" + " order by state, validendtime, ipaddress"); statement.bind(1, offerExpiration); statement.bind(2, startAddr.getAddress()); statement.bind(3, endAddr.getAddress()); List<DhcpLease> leases = mapLeases(statement); return toIaAddresses(leases); } catch (SQLiteException ex) { log.error("findUnusedIaAddresses failed", ex); throw new RuntimeException(ex); } finally { closeStatement(statement); closeConnection(connection); } } protected List<DhcpLease> findExpiredLeases(final byte iatype) { SQLiteConnection connection = null; SQLiteStatement statement = null; try { connection = getSQLiteConnection(); statement = connection.prepare( "select * from dhcplease" + " where iatype = ?" + " and state != " + IaAddress.STATIC + " and validendtime < ? order by validendtime"); statement.bind(1, iatype); statement.bind(2, new Date().getTime()); return mapLeases(statement); } catch (SQLiteException ex) { log.error("findExpiredLeases failed", ex); throw new RuntimeException(ex); } finally { closeStatement(statement); closeConnection(connection); } } /* (non-Javadoc) * @see com.jagornet.dhcpv6.db.IaManager#findUnusedIaPrefixes(java.net.InetAddress, java.net.InetAddress) */ @Override public List<IaPrefix> findUnusedIaPrefixes(final InetAddress startAddr, final InetAddress endAddr) { long offerExpireMillis = DhcpServerPolicies.globalPolicyAsLong(Property.BINDING_MANAGER_OFFER_EXPIRATION); final long offerExpiration = new Date().getTime() - offerExpireMillis; SQLiteConnection connection = null; SQLiteStatement statement = null; try { connection = getSQLiteConnection(); statement = connection.prepare( "select * from dhcplease" + " where ((state=" + IaPrefix.ADVERTISED + " and starttime <= ?)" + " or (state=" + IaPrefix.EXPIRED + " or state=" + IaPrefix.RELEASED + "))" + " and ipaddress >= ? and ipaddress <= ?" + " order by state, validendtime, ipaddress"); statement.bind(1, offerExpiration); statement.bind(2, startAddr.getAddress()); statement.bind(3, endAddr.getAddress()); List<DhcpLease> leases = mapLeases(statement); return toIaPrefixes(leases); } catch (SQLiteException ex) { log.error("findUnusedIaPrefixes failed", ex); throw new RuntimeException(ex); } finally { closeStatement(statement); closeConnection(connection); } } /* (non-Javadoc) * @see com.jagornet.dhcpv6.db.IaManager#findExpiredIaPrefixes() */ @Override public List<IaPrefix> findExpiredIaPrefixes() { SQLiteConnection connection = null; SQLiteStatement statement = null; try { connection = getSQLiteConnection(); statement = connection.prepare( "select * from dhcplease" + " where iatype = " + IdentityAssoc.PD_TYPE + " and validendtime < ? order by validendtime"); statement.bind(1, new Date().getTime()); List<DhcpLease> leases = mapLeases(statement); return toIaPrefixes(leases); } catch (SQLiteException ex) { log.error("findExpiredIaPrefixes failed", ex); throw new RuntimeException(ex); } finally { closeStatement(statement); closeConnection(connection); } } /* (non-Javadoc) * @see com.jagornet.dhcpv6.db.IaManager#reconcileIaAddresses(java.util.List) */ @Override public void reconcileIaAddresses(List<Range> ranges) { List<byte[]> args = new ArrayList<byte[]>(); StringBuilder query = new StringBuilder(); query.append("delete from dhcplease where ipaddress"); Iterator<Range> rangeIter = ranges.iterator(); while (rangeIter.hasNext()) { Range range = rangeIter.next(); query.append(" not between ? and ?"); args.add(range.getStartAddress().getAddress()); args.add(range.getEndAddress().getAddress()); if (rangeIter.hasNext()) query.append(" and ipaddress"); } SQLiteConnection connection = null; SQLiteStatement statement = null; try { connection = getSQLiteConnection(); statement = connection.prepare(query.toString()); int i = 1; for (byte[] bs : args) { statement.bind(i++, bs); } while (statement.step()) { log.debug("reconcileIaAddresses: step=true"); } } catch (SQLiteException ex) { log.error("reconcileIaAddresses failed", ex); throw new RuntimeException(ex); } finally { closeStatement(statement); closeConnection(connection); } } /** * The Class DhcpLeaseRowMapper. */ protected List<DhcpLease> mapLeases(SQLiteStatement statement) throws SQLiteException { List<DhcpLease> leases = new ArrayList<DhcpLease>(); while (statement.step()) { DhcpLease lease = new DhcpLease(); try { lease.setIpAddress(InetAddress.getByAddress(statement.columnBlob(0))); } catch (UnknownHostException e) { throw new RuntimeException("Unable to map dhcplease", e); } lease.setDuid(statement.columnBlob(1)); lease.setIatype((byte)statement.columnInt(2)); lease.setIaid(statement.columnLong(3)); lease.setPrefixLength((short)statement.columnInt(4)); lease.setState((byte)statement.columnInt(5)); long starttime; starttime = statement.columnLong(6); lease.setStartTime(new Date(starttime)); long preferredendtime; preferredendtime = statement.columnLong(7); lease.setPreferredEndTime(new Date(preferredendtime)); long validendtime; validendtime = statement.columnLong(8); lease.setValidEndTime(new Date(validendtime)); lease.setIaDhcpOptions(decodeOptions(statement.columnBlob(9))); lease.setIaAddrDhcpOptions(decodeOptions(statement.columnBlob(10))); leases.add(lease); } return leases; } private void closeStatement(SQLiteStatement statement) { if (statement != null) statement.dispose(); } private void closeConnection(SQLiteConnection connection) { // if (connection != null) // connection.dispose(); } /** * For unit tests only */ public void deleteAllIAs() { SQLiteConnection connection = null; SQLiteStatement statement = null; try { connection = getSQLiteConnection(); connection.exec("delete from dhcplease"); } catch (SQLiteException ex) { log.error("deleteAllIAs failed", ex); throw new RuntimeException(ex); } finally { closeStatement(statement); closeConnection(connection); } } }