001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements. See the NOTICE file distributed with this
004     * work for additional information regarding copyright ownership. The ASF
005     * licenses this file to you under the Apache License, Version 2.0 (the
006     * "License"); you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     * 
009     * http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
013     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
014     * License for the specific language governing permissions and limitations under
015     * the License.
016     * 
017     */
018    
019    package org.apache.directory.server.dhcp.service;
020    
021    import java.net.InetAddress;
022    import java.net.InetSocketAddress;
023    import java.util.Iterator;
024    
025    import org.apache.directory.server.dhcp.DhcpException;
026    import org.apache.directory.server.dhcp.messages.DhcpMessage;
027    import org.apache.directory.server.dhcp.messages.MessageType;
028    import org.apache.directory.server.dhcp.options.DhcpOption;
029    import org.apache.directory.server.dhcp.options.OptionsField;
030    import org.apache.directory.server.dhcp.options.dhcp.ParameterRequestList;
031    import org.apache.directory.server.dhcp.options.dhcp.ServerIdentifier;
032    import org.slf4j.Logger;
033    import org.slf4j.LoggerFactory;
034    
035    /**
036     * Abstract implementation of the server-side DHCP protocol. This class just
037     * provides some utility methods and dispatches server-bound messages to handler
038     * methods which can be overridden to provide the functionality.
039     * <p>
040     * Client-bound messages and BOOTP messages are ignored.
041     * 
042     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
043     * @version $Rev: 545042 $, $Date: 2007-06-06 22:32:01 -0500 (Mi, 06 Jun 2007) $
044     * 
045     */
046    public abstract class AbstractDhcpService implements DhcpService {
047        private static final Logger logger = LoggerFactory
048                .getLogger(AbstractDhcpService.class);
049    
050        /*
051         * @see org.apache.directory.server.dhcp.DhcpService#getReplyFor(org.apache.directory.server.dhcp.messages.DhcpMessage)
052         */
053        public final DhcpMessage getReplyFor(InetSocketAddress localAddress,
054                InetSocketAddress clientAddress, DhcpMessage request)
055                throws DhcpException {
056            // ignore messages with an op != REQUEST/REPLY
057            if (request.getOp() != DhcpMessage.OP_BOOTREQUEST
058                    && request.getOp() != DhcpMessage.OP_BOOTREPLY)
059                return null;
060    
061            // message type option MUST be set - we don't support plain BOOTP.
062            if (null == request.getMessageType()) {
063                logger.warn("Missing message type option - plain BOOTP not supported.");
064                return null;
065            }
066    
067            // dispatch based on the message type
068            switch (request.getMessageType().getCode()){
069                // client-to-server messages
070                case MessageType.CODE_DHCPDISCOVER :
071                    return handleDISCOVER(localAddress, clientAddress, request);
072                case MessageType.CODE_DHCPREQUEST :
073                    return handleREQUEST(localAddress, clientAddress, request);
074                case MessageType.CODE_DHCPRELEASE :
075                    return handleRELEASE(localAddress, clientAddress, request);
076                case MessageType.CODE_DHCPINFORM :
077                    return handleINFORM(localAddress, clientAddress, request);
078    
079                case MessageType.CODE_DHCPOFFER :
080                    return handleOFFER(localAddress, clientAddress, request);
081    
082                    // server-to-client messages
083                case MessageType.CODE_DHCPDECLINE :
084                case MessageType.CODE_DHCPACK :
085                case MessageType.CODE_DHCPNAK :
086                    return null; // just ignore them
087    
088                default :
089                    return handleUnknownMessage(clientAddress, request);
090            }
091        }
092    
093        /**
094         * Handle unknown DHCP message. The default implementation just logs and
095         * ignores it.
096         * 
097         * @param clientAddress
098         * @param request the request message
099         * @return DhcpMessage response message or <code>null</code> to ignore (don't reply to)
100         *         it.
101         */
102        protected DhcpMessage handleUnknownMessage(InetSocketAddress clientAddress,
103                DhcpMessage request) {
104            if (logger.isWarnEnabled())
105                logger.warn("Got unknkown DHCP message: " + request + " from:  "
106                        + clientAddress);
107            return null;
108        }
109    
110        /**
111         * Handle DHCPINFORM message. The default implementation just ignores it.
112         * 
113         * @param localAddress
114         * @param clientAddress
115         * @param request the request message
116         * @return DhcpMessage response message or <code>null</code> to ignore (don't reply to)
117         *         it.
118         */
119        protected DhcpMessage handleINFORM(InetSocketAddress localAddress,
120                InetSocketAddress clientAddress, DhcpMessage request)
121                throws DhcpException {
122            if (logger.isDebugEnabled())
123                logger.debug("Got INFORM message: " + request + " from:  "
124                        + clientAddress);
125            return null;
126        }
127    
128        /**
129         * Handle DHCPRELEASE message. The default implementation just ignores it.
130         * 
131         * @param localAddress
132         * @param clientAddress
133         * @param request the request message
134         * @return DhcpMessage response message or <code>null</code> to ignore (don't reply to)
135         *         it.
136         */
137        protected DhcpMessage handleRELEASE(InetSocketAddress localAddress,
138                InetSocketAddress clientAddress, DhcpMessage request)
139                throws DhcpException {
140            if (logger.isDebugEnabled())
141                logger.debug("Got RELEASE message: " + request + " from:  "
142                        + clientAddress);
143            return null;
144        }
145    
146        /**
147         * Handle DHCPREQUEST message. The default implementation just ignores it.
148         * 
149         * @param localAddress
150         * @param clientAddress
151         * @param request the request message
152         * @return DhcpMessage response message or <code>null</code> to ignore (don't reply to)
153         *         it.
154         */
155        protected DhcpMessage handleREQUEST(InetSocketAddress localAddress,
156                InetSocketAddress clientAddress, DhcpMessage request)
157                throws DhcpException {
158            if (logger.isDebugEnabled())
159                logger.debug("Got REQUEST message: " + request + " from:  "
160                        + clientAddress);
161            return null;
162        }
163    
164        /**
165         * Handle DHCPDISCOVER message. The default implementation just ignores it.
166         * 
167         * @param localAddress
168         * @param clientAddress
169         * @param request the request message
170         * @return DhcpMessage response message or <code>null</code> to ignore (don't reply to)
171         *         it.
172         * @throws DhcpException
173         */
174        protected DhcpMessage handleDISCOVER(InetSocketAddress localAddress,
175                InetSocketAddress clientAddress, DhcpMessage request)
176                throws DhcpException {
177            if (logger.isDebugEnabled())
178                logger.debug("Got DISCOVER message: " + request + " from:  "
179                        + clientAddress);
180            return null;
181        }
182    
183        /**
184         * Handle DHCPOFFER message. The default implementation just ignores it.
185         * 
186         * @param localAddress
187         * @param clientAddress
188         * @param request the request message
189         * @return DhcpMessage response message or <code>null</code> to ignore (don't reply to)
190         *         it.
191         * @throws DhcpException
192         */
193        protected DhcpMessage handleOFFER(InetSocketAddress localAddress,
194                InetSocketAddress clientAddress, DhcpMessage request)
195                throws DhcpException {
196            if (logger.isDebugEnabled())
197                logger
198                        .debug("Got OFFER message: " + request + " from:  " + clientAddress);
199            return null;
200        }
201    
202        /**
203         * Initialize a general DHCP reply message. Sets:
204         * <ul>
205         * <li>op=BOOTREPLY
206         * <li>htype, hlen, xid, flags, giaddr, chaddr like in request message
207         * <li>hops, secs to 0.
208         * <li>server hostname to the hostname appropriate for the interface the
209         * request was received on
210         * <li>the server identifier set to the address of the interface the request
211         * was received on
212         * </ul>
213         * 
214         * @param localAddress
215         * @param request
216         * @return DhcpMessage
217         */
218        protected final DhcpMessage initGeneralReply(InetSocketAddress localAddress,
219                DhcpMessage request) {
220            final DhcpMessage reply = new DhcpMessage();
221    
222            reply.setOp(DhcpMessage.OP_BOOTREPLY);
223    
224            reply.setHardwareAddress(request.getHardwareAddress());
225            reply.setTransactionId(request.getTransactionId());
226            reply.setFlags(request.getFlags());
227            reply.setRelayAgentAddress(request.getRelayAgentAddress());
228    
229            // set server hostname
230            reply.setServerHostname(localAddress.getHostName());
231    
232            // set server identifier based on the IF on which we received the packet
233            reply.getOptions().add(new ServerIdentifier(localAddress.getAddress()));
234    
235            return reply;
236        }
237    
238        /**
239         * Check if an address is the zero-address
240         * 
241         * @param addr
242         * @return boolean
243         */
244        private boolean isZeroAddress(byte[] addr) {
245            for (int i = 0; i < addr.length; i++)
246                if (addr[i] != 0)
247                    return false;
248            return true;
249        }
250    
251        /**
252         * Determine address on which to base selection. If the relay agent address is
253         * set, we use the relay agent's address, otherwise we use the address we
254         * received the request from.
255         * 
256         * @param clientAddress
257         * @param request
258         * @return InetAddress
259         */
260        protected final InetAddress determineSelectionBase(
261                InetSocketAddress clientAddress, DhcpMessage request) {
262            // FIXME: do we know
263            // a) the interface address over which we received a message (!)
264            // b) the client address (if specified)
265            // c) the relay agent address?
266    
267            // if the relay agent address is set, we use it as the selection base
268            if (!isZeroAddress(request.getRelayAgentAddress().getAddress()))
269                return request.getRelayAgentAddress();
270    
271            return clientAddress.getAddress();
272        }
273    
274        /**
275         * Strip options that the client doesn't want, if the ParameterRequestList
276         * option is present.
277         * 
278         * @param request
279         * @param options
280         */
281        protected final void stripUnwantedOptions(DhcpMessage request,
282                OptionsField options) {
283            final ParameterRequestList prl = (ParameterRequestList) request
284                    .getOptions().get(ParameterRequestList.class);
285            if (null != prl) {
286                final byte[] list = prl.getData();
287                for (final Iterator i = options.iterator(); i.hasNext();) {
288                    final DhcpOption o = (DhcpOption) i.next();
289                    for (int j = 0; j < list.length; j++)
290                        if (list[j] == o.getTag())
291                            continue;
292                    i.remove();
293                }
294            }
295        }
296    }