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.service;
021
022
023import java.net.InetAddress;
024import java.net.InetSocketAddress;
025
026import org.apache.directory.server.dhcp.DhcpException;
027import org.apache.directory.server.dhcp.messages.DhcpMessage;
028import org.apache.directory.server.dhcp.messages.MessageType;
029import org.apache.directory.server.dhcp.options.AddressOption;
030import org.apache.directory.server.dhcp.options.OptionsField;
031import org.apache.directory.server.dhcp.options.dhcp.ClientIdentifier;
032import org.apache.directory.server.dhcp.options.dhcp.IpAddressLeaseTime;
033import org.apache.directory.server.dhcp.options.dhcp.MaximumDhcpMessageSize;
034import org.apache.directory.server.dhcp.options.dhcp.ParameterRequestList;
035import org.apache.directory.server.dhcp.options.dhcp.RequestedIpAddress;
036import org.apache.directory.server.dhcp.options.dhcp.ServerIdentifier;
037import org.apache.directory.server.dhcp.store.DhcpStore;
038
039
040/**
041 * A default implementation of the DHCP service. Does the tedious low-level
042 * chores of handling DHCP messages, but delegates the lease-handling to a
043 * supplied DhcpStore.
044 * 
045 * @see org.apache.directory.server.dhcp.store.DhcpStore
046 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
047 */
048public class StoreBasedDhcpService extends AbstractDhcpService
049{
050    private final DhcpStore dhcpStore;
051
052
053    public StoreBasedDhcpService( DhcpStore dhcpStore )
054    {
055        this.dhcpStore = dhcpStore;
056    }
057
058
059    /**
060     * Try to get an existing lease. The lease may have been created during
061     * earlier DHCP negotiations or a recent DHCPDISCOVER.
062     * 
063     * @param clientAddress
064     * @param request
065     * @return
066     * @throws DhcpException
067     */
068    private Lease getExistingLease( InetSocketAddress clientAddress, DhcpMessage request ) throws DhcpException
069    {
070        // determine requested lease time
071        IpAddressLeaseTime requestedLeaseTimeOption = ( IpAddressLeaseTime ) request.getOptions().get(
072            IpAddressLeaseTime.class );
073        long requestedLeaseTime = null != requestedLeaseTimeOption ? requestedLeaseTimeOption.getIntValue() * 1000
074            : -1L;
075
076        // try to get the lease (address) requested by the client
077        InetAddress requestedAddress = null;
078        AddressOption requestedAddressOption = ( AddressOption ) request.getOptions().get( RequestedIpAddress.class );
079
080        if ( null != requestedAddressOption )
081        {
082            requestedAddress = requestedAddressOption.getAddress();
083        }
084
085        if ( null == requestedAddress )
086        {
087            requestedAddress = request.getCurrentClientAddress();
088        }
089
090        InetAddress selectionBase = determineSelectionBase( clientAddress, request );
091
092        Lease lease = dhcpStore.getExistingLease( request.getHardwareAddress(), requestedAddress, selectionBase,
093            requestedLeaseTime, request.getOptions() );
094
095        if ( null == lease )
096        {
097            return null;
098        }
099
100        return lease;
101    }
102
103
104    /**
105     * Determine a lease to offer in response to a DHCPDISCOVER message.
106     * <p>
107     * When a server receives a DHCPDISCOVER message from a client, the server
108     * chooses a network address for the requesting client. If no address is
109     * available, the server may choose to report the problem to the system
110     * administrator. If an address is available, the new address SHOULD be
111     * chosen as follows:
112     * <ul>
113     * <li> The client's current address as recorded in the client's current
114     * binding, ELSE
115     * <li> The client's previous address as recorded in the client's (now
116     * expired or released) binding, if that address is in the server's pool of
117     * available addresses and not already allocated, ELSE
118     * <li> The address requested in the 'Requested IP Address' option, if that
119     * address is valid and not already allocated, ELSE
120     * <li> A new address allocated from the server's pool of available
121     * addresses; the address is selected based on the subnet from which the
122     * message was received (if 'giaddr' is 0) or on the address of the relay
123     * agent that forwarded the message ('giaddr' when not 0).
124     * </ul>
125     * 
126     * @param clientAddress
127     * @param request
128     * @return
129     */
130    private Lease getLeaseOffer( InetSocketAddress clientAddress, DhcpMessage request ) throws DhcpException
131    {
132        // determine requested lease time
133        IpAddressLeaseTime requestedLeaseTimeOption = ( IpAddressLeaseTime ) request.getOptions().get(
134            IpAddressLeaseTime.class );
135        long requestedLeaseTime = null != requestedLeaseTimeOption ? requestedLeaseTimeOption.getIntValue() * 1000
136            : -1L;
137
138        // try to get the lease (address) requested by the client
139        InetAddress requestedAddress = null;
140        AddressOption requestedAddressOption = ( AddressOption ) request.getOptions().get( RequestedIpAddress.class );
141
142        if ( null != requestedAddressOption )
143        {
144            requestedAddress = requestedAddressOption.getAddress();
145        }
146
147        InetAddress selectionBase = determineSelectionBase( clientAddress, request );
148
149        return dhcpStore.getLeaseOffer( request.getHardwareAddress(), requestedAddress, selectionBase,
150            requestedLeaseTime, request.getOptions() );
151    }
152
153
154    /*
155     * @see org.apache.directory.server.dhcp.service.AbstractDhcpService#handleRELEASE(java.net.InetSocketAddress,
156     *      java.net.InetSocketAddress,
157     *      org.apache.directory.server.dhcp.messages.DhcpMessage)
158     */
159    @Override
160    protected DhcpMessage handleRELEASE( InetSocketAddress localAddress, InetSocketAddress clientAddress,
161        DhcpMessage request ) throws DhcpException
162    {
163        // check server ident
164        AddressOption serverIdentOption = ( AddressOption ) request.getOptions().get( ServerIdentifier.class );
165
166        if ( null != serverIdentOption && serverIdentOption.getAddress().isAnyLocalAddress() )
167        {
168            return null; // not me?! FIXME: handle authoritative server case
169        }
170
171        Lease lease = getExistingLease( clientAddress, request );
172
173        DhcpMessage reply = initGeneralReply( localAddress, request );
174
175        if ( null == lease )
176        {
177            // null lease? send NAK
178            // FIXME...
179            reply.setMessageType( MessageType.DHCPNAK );
180            reply.setCurrentClientAddress( null );
181            reply.setAssignedClientAddress( null );
182            reply.setNextServerAddress( null );
183        }
184        else
185        {
186            dhcpStore.releaseLease( lease );
187
188            // lease Ok, send ACK
189            // FIXME...
190            reply.getOptions().merge( lease.getOptions() );
191
192            reply.setAssignedClientAddress( lease.getClientAddress() );
193            reply.setNextServerAddress( lease.getNextServerAddress() );
194
195            // fix options
196            OptionsField options = reply.getOptions();
197
198            // these options must not be present
199            options.remove( RequestedIpAddress.class );
200            options.remove( ParameterRequestList.class );
201            options.remove( ClientIdentifier.class );
202            options.remove( MaximumDhcpMessageSize.class );
203
204            // these options must be present
205            options.add( new IpAddressLeaseTime( ( lease.getExpires() - System.currentTimeMillis() ) / 1000L ) );
206
207            stripUnwantedOptions( request, options );
208        }
209
210        return reply;
211
212    }
213
214
215    /*
216     * @see org.apache.directory.server.dhcp.service.AbstractDhcpService#handleDISCOVER(java.net.InetSocketAddress,
217     *      org.apache.directory.server.dhcp.messages.DhcpMessage)
218     */
219    @Override
220    protected DhcpMessage handleDISCOVER( InetSocketAddress localAddress, InetSocketAddress clientAddress,
221        DhcpMessage request ) throws DhcpException
222    {
223        Lease lease = getLeaseOffer( clientAddress, request );
224
225        // null lease? don't offer one.
226        if ( null == lease )
227        {
228            return null;
229        }
230
231        DhcpMessage reply = initGeneralReply( localAddress, request );
232
233        reply.getOptions().merge( lease.getOptions() );
234
235        reply.setMessageType( MessageType.DHCPOFFER );
236
237        reply.setAssignedClientAddress( lease.getClientAddress() );
238        reply.setNextServerAddress( lease.getNextServerAddress() );
239
240        // fix options
241        OptionsField options = reply.getOptions();
242
243        // these options must not be present
244        options.remove( RequestedIpAddress.class );
245        options.remove( ParameterRequestList.class );
246        options.remove( ClientIdentifier.class );
247        options.remove( MaximumDhcpMessageSize.class );
248
249        // these options must be present
250        options.add( new IpAddressLeaseTime( ( lease.getExpires() - System.currentTimeMillis() ) / 1000L ) );
251
252        stripUnwantedOptions( request, options );
253
254        return reply;
255    }
256
257
258    /*
259     * @see org.apache.directory.server.dhcp.service.AbstractDhcpService#handleREQUEST(java.net.InetSocketAddress,
260     *      org.apache.directory.server.dhcp.messages.DhcpMessage)
261     */
262    @Override
263    protected DhcpMessage handleREQUEST( InetSocketAddress localAddress, InetSocketAddress clientAddress,
264        DhcpMessage request ) throws DhcpException
265    {
266        // check server ident
267        AddressOption serverIdentOption = ( AddressOption ) request.getOptions().get( ServerIdentifier.class );
268
269        if ( null != serverIdentOption && serverIdentOption.getAddress().isAnyLocalAddress() )
270        {
271            return null; // not me?! FIXME: handle authoritative server case
272        }
273
274        Lease lease = getExistingLease( clientAddress, request );
275
276        DhcpMessage reply = initGeneralReply( localAddress, request );
277
278        if ( null == lease )
279        {
280            // null lease? send NAK
281            reply.setMessageType( MessageType.DHCPNAK );
282            reply.setCurrentClientAddress( null );
283            reply.setAssignedClientAddress( null );
284            reply.setNextServerAddress( null );
285        }
286        else
287        {
288            // lease Ok, send ACK
289            reply.getOptions().merge( lease.getOptions() );
290
291            reply.setAssignedClientAddress( lease.getClientAddress() );
292            reply.setNextServerAddress( lease.getNextServerAddress() );
293
294            // fix options
295            OptionsField options = reply.getOptions();
296
297            // these options must not be present
298            options.remove( RequestedIpAddress.class );
299            options.remove( ParameterRequestList.class );
300            options.remove( ClientIdentifier.class );
301            options.remove( MaximumDhcpMessageSize.class );
302
303            // these options must be present
304            options.add( new IpAddressLeaseTime( ( lease.getExpires() - System.currentTimeMillis() ) / 1000L ) );
305
306            stripUnwantedOptions( request, options );
307        }
308        return reply;
309    }
310}