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    
021    package org.apache.directory.server.dhcp.protocol;
022    
023    import java.net.InetAddress;
024    import java.net.InetSocketAddress;
025    
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.service.DhcpService;
029    import org.apache.mina.core.service.IoHandler;
030    import org.apache.mina.core.session.IdleStatus;
031    import org.apache.mina.core.session.IoSession;
032    import org.apache.mina.filter.codec.ProtocolCodecFilter;
033    import org.slf4j.Logger;
034    import org.slf4j.LoggerFactory;
035    
036    /**
037     * Implementation of a DHCP protocol handler which delegates the work of
038     * generating replys to a DhcpService implementation.
039     * 
040     * @see org.apache.directory.server.dhcp.service.DhcpService
041     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
042     * @version $Rev: 725712 $, $Date: 2008-12-11 16:32:04 +0100 (Thu, 11 Dec 2008) $
043     */
044    public class DhcpProtocolHandler implements IoHandler {
045        private static final Logger logger = LoggerFactory
046                .getLogger(DhcpProtocolHandler.class);
047    
048        /**
049         * Default DHCP client port
050         */
051        public static final int CLIENT_PORT = 68;
052    
053        /**
054         * Default DHCP server port
055         */
056        public static final int SERVER_PORT = 67;
057    
058        /**
059         * The DHCP service implementation. The implementation is supposed to be
060         * thread-safe.
061         */
062        private final DhcpService dhcpService;
063    
064        /**
065         * 
066         */
067        public DhcpProtocolHandler(DhcpService service) {
068            this.dhcpService = service;
069        }
070    
071        public void sessionCreated(IoSession session) throws Exception {
072            logger.debug("{} CREATED", session.getLocalAddress());
073            session.getFilterChain().addFirst("codec",
074                    new ProtocolCodecFilter(new DhcpProtocolCodecFactory()));
075        }
076    
077        public void sessionOpened(IoSession session) {
078            logger.debug("{} -> {} OPENED", session.getRemoteAddress(), session
079                    .getLocalAddress());
080        }
081    
082        public void sessionClosed(IoSession session) {
083            logger.debug("{} -> {} CLOSED", session.getRemoteAddress(), session
084                    .getLocalAddress());
085        }
086    
087        public void sessionIdle(IoSession session, IdleStatus status) {
088            // ignore
089        }
090    
091        public void exceptionCaught(IoSession session, Throwable cause) {
092            logger.error("EXCEPTION CAUGHT ", cause);
093            cause.printStackTrace(System.out);
094    
095            session.close( true );
096        }
097    
098        public void messageReceived(IoSession session, Object message)
099                throws Exception {
100            if (logger.isDebugEnabled())
101                logger.debug("{} -> {} RCVD: {} " + message, session.getRemoteAddress(),
102                        session.getLocalAddress());
103    
104            final DhcpMessage request = (DhcpMessage) message;
105    
106            final DhcpMessage reply = dhcpService.getReplyFor(
107                    (InetSocketAddress) session.getServiceAddress(),
108                    (InetSocketAddress) session.getRemoteAddress(), request);
109    
110            if (null != reply) {
111                final InetSocketAddress isa = determineMessageDestination(request, reply);
112                session.write(reply, isa);
113            }
114        }
115    
116        /**
117         * Determine where to send the message: <br>
118         * If the 'giaddr' field in a DHCP message from a client is non-zero, the
119         * server sends any return messages to the 'DHCP server' port on the BOOTP
120         * relay agent whose address appears in 'giaddr'. If the 'giaddr' field is
121         * zero and the 'ciaddr' field is nonzero, then the server unicasts DHCPOFFER
122         * and DHCPACK messages to the address in 'ciaddr'. If 'giaddr' is zero and
123         * 'ciaddr' is zero, and the broadcast bit is set, then the server broadcasts
124         * DHCPOFFER and DHCPACK messages to 0xffffffff. If the broadcast bit is not
125         * set and 'giaddr' is zero and 'ciaddr' is zero, then the server unicasts
126         * DHCPOFFER and DHCPACK messages to the client's hardware address and
127         * 'yiaddr' address. In all cases, when 'giaddr' is zero, the server
128         * broadcasts any DHCPNAK messages to 0xffffffff.
129         * 
130         * @param request
131         * @param reply
132         * @return
133         */
134        private InetSocketAddress determineMessageDestination(DhcpMessage request,
135                DhcpMessage reply) {
136    
137            final MessageType mt = reply.getMessageType();
138            if (!isNullAddress(request.getRelayAgentAddress()))
139                // send to agent, if received via agent.
140                return new InetSocketAddress(request.getRelayAgentAddress(), SERVER_PORT);
141            else if (null != mt && mt == MessageType.DHCPNAK)
142                // force broadcast for DHCPNAKs
143                return new InetSocketAddress("255.255.255.255", 68);
144            else // not a NAK...
145            if (!isNullAddress(request.getCurrentClientAddress()))
146                // have a current address? unicast to it.
147                return new InetSocketAddress(request.getCurrentClientAddress(),
148                        CLIENT_PORT);
149            else
150                return new InetSocketAddress("255.255.255.255", 68);
151        }
152    
153        /**
154         * Determine, whether the given address ist actually the null address
155         * "0.0.0.0".
156         * 
157         * @param relayAgentAddress
158         * @return
159         */
160        private boolean isNullAddress(InetAddress addr) {
161            final byte a[] = addr.getAddress();
162            for (int i = 0; i < a.length; i++)
163                if (a[i] != 0)
164                    return false;
165            return true;
166        }
167    
168        public void messageSent(IoSession session, Object message) {
169            if (logger.isDebugEnabled())
170                logger.debug("{} -> {} SENT: " + message, session.getRemoteAddress(),
171                        session.getLocalAddress());
172        }
173    }