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
021package org.apache.directory.server.dhcp.protocol;
022
023
024import java.net.InetAddress;
025import java.net.InetSocketAddress;
026
027import org.apache.directory.server.dhcp.messages.DhcpMessage;
028import org.apache.directory.server.dhcp.messages.MessageType;
029import org.apache.directory.server.dhcp.service.DhcpService;
030import org.apache.mina.core.service.IoHandlerAdapter;
031import org.apache.mina.core.session.IdleStatus;
032import org.apache.mina.core.session.IoSession;
033import org.apache.mina.filter.codec.ProtocolCodecFilter;
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036
037
038/**
039 * Implementation of a DHCP protocol handler which delegates the work of
040 * generating replys to a DhcpService implementation.
041 * 
042 * @see org.apache.directory.server.dhcp.service.DhcpService
043 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
044 */
045public class DhcpProtocolHandler extends IoHandlerAdapter
046{
047    private static final Logger LOG = LoggerFactory.getLogger( DhcpProtocolHandler.class );
048
049    /**
050     * Default DHCP client port
051     */
052    public static final int CLIENT_PORT = 68;
053
054    /**
055     * Default DHCP server port
056     */
057    public static final int SERVER_PORT = 67;
058
059    /**
060     * The DHCP service implementation. The implementation is supposed to be
061     * thread-safe.
062     */
063    private final DhcpService dhcpService;
064
065
066    /**
067     * 
068     */
069    public DhcpProtocolHandler( DhcpService service )
070    {
071        this.dhcpService = service;
072    }
073
074
075    @Override
076    public void sessionCreated( IoSession session ) throws Exception
077    {
078        LOG.debug( "{} CREATED", session.getLocalAddress() );
079        session.getFilterChain().addFirst( "codec",
080            new ProtocolCodecFilter( new DhcpProtocolCodecFactory() ) );
081    }
082
083
084    @Override
085    public void sessionOpened( IoSession session )
086    {
087        LOG.debug( "{} -> {} OPENED", session.getRemoteAddress(), session
088            .getLocalAddress() );
089    }
090
091
092    @Override
093    public void sessionClosed( IoSession session )
094    {
095        LOG.debug( "{} -> {} CLOSED", session.getRemoteAddress(), session
096            .getLocalAddress() );
097    }
098
099
100    @Override
101    public void sessionIdle( IoSession session, IdleStatus status )
102    {
103        // ignore
104    }
105
106
107    @Override
108    public void exceptionCaught( IoSession session, Throwable cause )
109    {
110        LOG.error( "EXCEPTION CAUGHT ", cause );
111        cause.printStackTrace( System.out );
112
113        session.closeNow();
114    }
115
116
117    @Override
118    public void messageReceived( IoSession session, Object message )
119        throws Exception
120    {
121        if ( LOG.isDebugEnabled() )
122        {
123            LOG.debug( "{} -> {} RCVD: {} ", message, session.getRemoteAddress(),
124                session.getLocalAddress() );
125        }
126
127        final DhcpMessage request = ( DhcpMessage ) message;
128
129        final DhcpMessage reply = dhcpService.getReplyFor(
130            ( InetSocketAddress ) session.getServiceAddress(),
131            ( InetSocketAddress ) session.getRemoteAddress(), request );
132
133        if ( null != reply )
134        {
135            final InetSocketAddress isa = determineMessageDestination( request, reply );
136            session.write( reply, isa );
137        }
138    }
139
140
141    /**
142     * Determine where to send the message: <br>
143     * If the 'giaddr' field in a DHCP message from a client is non-zero, the
144     * server sends any return messages to the 'DHCP server' port on the BOOTP
145     * relay agent whose address appears in 'giaddr'. If the 'giaddr' field is
146     * zero and the 'ciaddr' field is nonzero, then the server unicasts DHCPOFFER
147     * and DHCPACK messages to the address in 'ciaddr'. If 'giaddr' is zero and
148     * 'ciaddr' is zero, and the broadcast bit is set, then the server broadcasts
149     * DHCPOFFER and DHCPACK messages to 0xffffffff. If the broadcast bit is not
150     * set and 'giaddr' is zero and 'ciaddr' is zero, then the server unicasts
151     * DHCPOFFER and DHCPACK messages to the client's hardware address and
152     * 'yiaddr' address. In all cases, when 'giaddr' is zero, the server
153     * broadcasts any DHCPNAK messages to 0xffffffff.
154     * 
155     * @param request
156     * @param reply
157     * @return
158     */
159    //This will suppress PMD.AvoidUsingHardCodedIP warnings in this class
160    @SuppressWarnings("PMD.AvoidUsingHardCodedIP")
161    private InetSocketAddress determineMessageDestination( DhcpMessage request,
162        DhcpMessage reply )
163    {
164
165        final MessageType mt = reply.getMessageType();
166
167        if ( !isNullAddress( request.getRelayAgentAddress() ) )
168        {
169            // send to agent, if received via agent.
170            return new InetSocketAddress( request.getRelayAgentAddress(), SERVER_PORT );
171        }
172        else if ( null != mt && mt == MessageType.DHCPNAK )
173        {
174            // force broadcast for DHCPNAKs
175            return new InetSocketAddress( "255.255.255.255", 68 );
176        }
177        else
178        {
179            // not a NAK...
180            if ( !isNullAddress( request.getCurrentClientAddress() ) )
181            {
182                // have a current address? unicast to it.
183                return new InetSocketAddress( request.getCurrentClientAddress(),
184                    CLIENT_PORT );
185            }
186            else
187            {
188                return new InetSocketAddress( "255.255.255.255", 68 );
189            }
190        }
191    }
192
193
194    /**
195     * Determine, whether the given address ist actually the null address
196     * "0.0.0.0".
197     * 
198     * @param relayAgentAddress
199     * @return
200     */
201    private boolean isNullAddress( InetAddress addr )
202    {
203        final byte[] a = addr.getAddress();
204
205        for ( int i = 0; i < a.length; i++ )
206        {
207            if ( a[i] != 0 )
208            {
209                return false;
210            }
211        }
212
213        return true;
214    }
215
216
217    @Override
218    public void messageSent( IoSession session, Object message )
219    {
220        if ( LOG.isDebugEnabled() )
221        {
222            LOG.debug( "{} -> {} SENT: ", message, session.getRemoteAddress(),
223                session.getLocalAddress() );
224        }
225    }
226
227    
228    @Override
229    public void inputClosed( IoSession session )
230    {
231    }
232}