001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *  
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *  
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License. 
018 *  
019 */
020package org.apache.directory.server.dhcp.store;
021
022
023import java.net.InetAddress;
024import java.util.Map;
025
026import org.apache.directory.server.dhcp.DhcpException;
027import org.apache.directory.server.dhcp.messages.HardwareAddress;
028import org.apache.directory.server.dhcp.options.OptionsField;
029import org.apache.directory.server.dhcp.options.vendor.HostName;
030import org.apache.directory.server.dhcp.options.vendor.SubnetMask;
031import org.apache.directory.server.dhcp.service.Lease;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035
036/**
037 * Abstract base implementation of a {@link DhcpStore}.
038 * 
039 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
040 */
041public abstract class AbstractDhcpStore implements DhcpStore
042{
043    private static final Logger LOG = LoggerFactory.getLogger( AbstractDhcpStore.class );
044
045
046    /*
047     * @see org.apache.directory.server.dhcp.service.DhcpStore#getLeaseOffer(org.apache.directory.server.dhcp.messages.HardwareAddress,
048     *      java.net.InetAddress, java.net.InetAddress, long,
049     *      org.apache.directory.server.dhcp.options.OptionsField)
050     */
051    public Lease getLeaseOffer( HardwareAddress hardwareAddress, InetAddress requestedAddress,
052        InetAddress selectionBase, long requestedLeaseTime, OptionsField options ) throws DhcpException
053    {
054        Subnet subnet = findSubnet( selectionBase );
055
056        if ( null == subnet )
057        {
058            LOG.warn( "Don't know anything about the sbnet containing {}", selectionBase );
059            return null;
060        }
061
062        // try to find existing lease
063        Lease lease = null;
064        lease = findExistingLease( hardwareAddress, lease );
065
066        if ( null != lease )
067        {
068            return lease;
069        }
070
071        Host host = null;
072        host = findDesignatedHost( hardwareAddress );
073
074        if ( null != host )
075        {
076            // make sure that the host is actually within the subnet. Depending
077            // on the way the DhcpStore configuration is implemented, it is not
078            // possible to violate this condition, but we can't be sure.
079            if ( !subnet.contains( host.getAddress() ) )
080            {
081                LOG.warn( "Host {} is not within the subnet for which an address is requested", host );
082            }
083            else
084            {
085                // build properties map
086                Map properties = getProperties( subnet );
087                properties.putAll( getProperties( host ) );
088
089                // build lease
090                lease = new Lease();
091                lease.setAcquired( System.currentTimeMillis() );
092
093                long leaseTime = determineLeaseTime( requestedLeaseTime, properties );
094
095                lease.setExpires( System.currentTimeMillis() + leaseTime );
096
097                lease.setHardwareAddress( hardwareAddress );
098                lease.setState( Lease.STATE_NEW );
099                lease.setClientAddress( host.getAddress() );
100
101                // set lease options
102                OptionsField o = lease.getOptions();
103
104                // set (client) host name
105                o.add( new HostName( host.getName() ) );
106
107                // add subnet settings
108                o.add( new SubnetMask( subnet.getNetmask() ) );
109                o.merge( subnet.getOptions() );
110
111                // add the host's options. they override existing
112                // subnet options as they take the precedence.
113                o.merge( host.getOptions() );
114            }
115        }
116
117        // update the lease state
118        if ( null != lease && lease.getState() != Lease.STATE_ACTIVE )
119        {
120            lease.setState( Lease.STATE_OFFERED );
121            updateLease( lease );
122        }
123
124        return lease;
125    }
126
127
128    /*
129     * @see org.apache.directory.server.dhcp.store.DhcpStore#getExistingLease(org.apache.directory.server.dhcp.messages.HardwareAddress,
130     *      java.net.InetAddress, java.net.InetAddress, long,
131     *      org.apache.directory.server.dhcp.options.OptionsField)
132     */
133    public Lease getExistingLease( HardwareAddress hardwareAddress, InetAddress requestedAddress,
134        InetAddress selectionBase, long requestedLeaseTime, OptionsField options ) throws DhcpException
135    {
136        // try to find existing lease. if we don't find a lease based on the
137        // client's
138        // hardware address, we send a NAK.
139        Lease lease = null;
140        lease = findExistingLease( hardwareAddress, lease );
141
142        if ( null == lease )
143        {
144            return null;
145        }
146
147        // check whether the notions of the client address match
148        if ( !lease.getClientAddress().equals( requestedAddress ) )
149        {
150            LOG.warn( "Requested address " + requestedAddress + " for " + hardwareAddress
151                + " doesn't match existing lease " + lease );
152            return null;
153        }
154
155        // check whether addresses and subnet match
156        Subnet subnet = findSubnet( selectionBase );
157
158        if ( null == subnet )
159        {
160            LOG.warn( "No subnet found for existing lease {}", lease );
161            return null;
162        }
163
164        if ( !subnet.contains( lease.getClientAddress() ) )
165        {
166            LOG.warn( "Client with existing lease {} is on wrong subnet {}", lease, subnet );
167            return null;
168        }
169
170        if ( !subnet.isInRange( lease.getClientAddress() ) )
171        {
172            LOG.warn( "Client with existing lease {} is out of valid range for subnet {}", lease, subnet );
173            return null;
174        }
175
176        // build properties map
177        Map properties = getProperties( subnet );
178
179        // update lease options
180        OptionsField o = lease.getOptions();
181        o.clear();
182
183        // add subnet settings
184        o.add( new SubnetMask( subnet.getNetmask() ) );
185        o.merge( subnet.getOptions() );
186
187        // check whether there is a designated host.
188        Host host = findDesignatedHost( hardwareAddress );
189        if ( null != host )
190        {
191            // check whether the host matches the address (using a fixed
192            // host address is mandatory).
193            if ( host.getAddress() != null && !host.getAddress().equals( lease.getClientAddress() ) )
194            {
195                LOG.warn( "Existing fixed address for " + hardwareAddress + " conflicts with existing lease "
196                    + lease );
197                return null;
198            }
199
200            properties.putAll( getProperties( host ) );
201
202            // set (client) host name
203            o.add( new HostName( host.getName() ) );
204
205            // add the host's options
206            o.merge( host.getOptions() );
207        }
208
209        // update other lease fields
210        long leaseTime = determineLeaseTime( requestedLeaseTime, properties );
211        lease.setExpires( System.currentTimeMillis() + leaseTime );
212        lease.setHardwareAddress( hardwareAddress );
213
214        // update the lease state
215        if ( lease.getState() != Lease.STATE_ACTIVE )
216        {
217            lease.setState( Lease.STATE_ACTIVE );
218            updateLease( lease );
219        }
220
221        // store information about the lease
222        updateLease( lease );
223
224        return lease;
225    }
226
227
228    /**
229     * Determine the lease time based on the time requested by the client, the
230     * properties and a global default.
231     * 
232     * @param requestedLeaseTime
233     * @param properties
234     * @return long
235     */
236    private long determineLeaseTime( long requestedLeaseTime, Map properties )
237    {
238        // built-in default
239        long leaseTime = 1000L * 3600;
240        Integer propMaxLeaseTime = ( Integer ) properties.get( DhcpConfigElement.PROPERTY_MAX_LEASE_TIME );
241
242        if ( null != propMaxLeaseTime )
243        {
244            if ( requestedLeaseTime > 0 )
245            {
246                leaseTime = Math.min( propMaxLeaseTime.intValue() * 1000L, requestedLeaseTime );
247            }
248            else
249            {
250                leaseTime = propMaxLeaseTime.intValue() * 1000L;
251            }
252        }
253
254        return leaseTime;
255    }
256
257
258    /*
259     * @see org.apache.directory.server.dhcp.store.DhcpStore#releaseLease(org.apache.directory.server.dhcp.service.Lease)
260     */
261    public void releaseLease( Lease lease )
262    {
263        lease.setState( Lease.STATE_RELEASED );
264        updateLease( lease );
265    }
266
267
268    /**
269     * Update the (possibly changed) lease in the store.
270     * 
271     * @param lease
272     */
273    protected abstract void updateLease( Lease lease );
274
275
276    /**
277     * Return a list of all options applicable to the given config element. List
278     * list must contain the options specified for the element and all parent
279     * elements in an aggregated fashion. For instance, the options for a host
280     * must include the global default options, the options of classes the host
281     * is a member of, the host's group options and the host's options.
282     * 
283     * @param element
284     * @return OptionsField
285     */
286    protected abstract OptionsField getOptions( DhcpConfigElement element );
287
288
289    /**
290     * Return a list of all options applicable to the given config element. List
291     * list must contain the options specified for the element and all parent
292     * elements in an aggregated fashion. For instance, the options for a host
293     * must include the global default options, the options of classes the host
294     * is a member of, the host's group options and the host's options.
295     * 
296     * @param element
297     * @return Map
298     */
299    protected abstract Map getProperties( DhcpConfigElement element );
300
301
302    /**
303     * Find an existing lease in the store.
304     * 
305     * @param hardwareAddress
306     * @param existingLease
307     * @return Map
308     */
309    protected abstract Lease findExistingLease( HardwareAddress hardwareAddress, Lease existingLease );
310
311
312    /**
313     * Find a host to with the explicitely designated hardware address.
314     * 
315     * @param hardwareAddress
316     * @return Host
317     * @throws DhcpException
318     */
319    protected abstract Host findDesignatedHost( HardwareAddress hardwareAddress ) throws DhcpException;
320
321
322    /**
323     * Find the subnet definition matching the given address.
324     * 
325     * @param clientAddress
326     * @return Subnet
327     */
328    protected abstract Subnet findSubnet( InetAddress clientAddress );
329}