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