/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2006-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.netmgt.dhcpd; import java.io.IOException; import java.io.InterruptedIOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.Socket; import java.util.Observable; import org.opennms.core.fiber.Fiber; import org.opennms.core.utils.InetAddressUtils; import org.opennms.core.utils.ThreadCategory; import edu.bucknell.net.JDHCP.DHCPMessage; final class Client extends Observable implements Runnable, Fiber { private final static short DHCP_TARGET_PORT = 67; private static InetAddress NULL_ADDR; private DatagramSocket m_sender; private Socket m_client; private ObjectOutputStream m_objsOut; private String m_name; private int m_status; private Thread m_worker; private UnicastListener m_unicastListener; private boolean m_keepListening; static { NULL_ADDR = InetAddressUtils.addr("0.0.0.0"); } /** * The remote DHCP server we sent the request to has the option of either * unicasting the response directly back to us or broadcasting the response * to port 68 on the local subnet. The Receiver class handles the broadcast * scenario and this class will take care of the unicast scenario. */ public final class UnicastListener extends Thread { /** * Udp connection over which unicast DHCP response will be received. */ DatagramSocket m_incomingUdp; /** * Client where any received responses will be forwarded. */ Client m_client; /** * Constructor * * @param incoming * UDP socket over which the DHCP request was sent * @param client * The client to which any unicasted responses are to be * forwarded */ public UnicastListener(DatagramSocket incoming, Client client) { super("UnicastListener-UDP-" + incoming.getLocalPort()); ThreadCategory.getInstance(this.getClass()).debug("constructing UnicastListener-UDP-" + incoming.getLocalPort()); m_incomingUdp = incoming; m_client = client; } /** * Does the work of the thread. Listens for unicasted responses from the * DHCP server. If a response is received it will be forwarded to the * client which requested that the DHCP request be generated. */ public void run() { ThreadCategory log = ThreadCategory.getInstance(this.getClass()); if (log.isDebugEnabled()) log.debug("thread " + this.getName() + " running..."); // set socket timeout to 1 second so the value of m_keepListening // can be checked periodically try { m_incomingUdp.setSoTimeout(1000); } catch (IOException ioE) { log.error("UnicastListener.run: unable to set socket timeout, reason: " + ioE.getMessage()); m_keepListening = true; } // According to RFC 2131 a DHCP client must be prepared to receive a // DHCP message up to 576 bytes. Although larger messages can // be negotiated between the DHCP client and server if desired. // // Allocating a 2k buffer which should be more than sufficient. // Any incoming packet larger than this size will cause an // arrayOutOfBoundsException to be generated, in that case an // error message will be logged and the packet will be discarded. // byte[] dgbuf = new byte[2048]; // Wait for any incoming unicast responses. // while (m_keepListening) { try { DatagramPacket pkt = new DatagramPacket(dgbuf, dgbuf.length); m_incomingUdp.receive(pkt); Message msg = new Message(pkt.getAddress(), new DHCPMessage(pkt.getData())); try { m_client.sendMessage(msg); } catch (IOException ex) { log.warn("Error sending unicast response to client " + m_client.getName()); } } catch (InterruptedIOException ex) { // Check exit flag continue; } catch (ArrayIndexOutOfBoundsException oobE) { // Packet was too large for buffer...log and discard log.debug("UnicastListener.run: array out of bounds exception.", oobE); log.warn("UnicastListener.run: malformed DHCP packet, packet too large for buffer (buffer sz=" + dgbuf.length + "), discarding packet."); } catch (IOException ioE) { log.error("UnicastListener.run: io exception receiving response", ioE); m_keepListening = false; } catch (Throwable E) { log.error("UnicastListener.run: exception receiving response", E); m_keepListening = false; } } if (log.isDebugEnabled()) log.debug("thread " + super.getName() + " exiting..."); } } Client(Socket clnt) throws IOException { m_name = "DHCPClient-TCP-" + clnt.getPort(); m_client = clnt; m_worker = null; m_status = START_PENDING; m_sender = new DatagramSocket(); // Construct UnicastListener thread which will receive the // DHCP response if the remote DHCP server unicasts the response // directly back over the outgoing Datagram Socket // ThreadCategory.getInstance(this.getClass()).debug("Client.ctor: outgoing udp socket port: " + m_sender.getLocalPort()); m_unicastListener = new UnicastListener(m_sender, this); m_keepListening = true; m_objsOut = new ObjectOutputStream(m_client.getOutputStream()); m_objsOut.reset(); m_objsOut.flush(); } void sendMessage(Message msg) throws IOException { m_objsOut.writeObject(msg); m_objsOut.flush(); } /** * <p>start</p> */ public synchronized void start() { if (m_worker != null) throw new IllegalStateException("The fiber has already been started"); // Start UnicastListener thread. // m_unicastListener.start(); m_worker = new Thread(this, getName()); m_worker.setDaemon(true); m_worker.start(); m_status = STARTING; } /** * <p>stop</p> */ public synchronized void stop() { m_status = STOP_PENDING; try { m_objsOut.close(); m_client.close(); } catch (IOException ex) { } m_sender.close(); m_worker.interrupt(); } /** * <p>getStatus</p> * * @return a int. */ public synchronized int getStatus() { return m_status; } /** * <p>getName</p> * * @return a {@link java.lang.String} object. */ public String getName() { return m_name; } /** * <p>run</p> */ public void run() { ThreadCategory log = ThreadCategory.getInstance(this.getClass()); boolean isOk = true; // get the input stream as a object stream // ObjectInputStream input = null; try { input = new ObjectInputStream(m_client.getInputStream()); } catch (IOException ex) { log.warn("Failed to read client's input stream", ex); isOk = false; } // set the state // if (isOk) { synchronized (this) { m_status = RUNNING; } } // Roundy, Roundy, Round we go... // while (isOk && m_status == RUNNING) { try { Message msg = (Message) input.readObject(); if (msg.getAddress().equals(NULL_ADDR)) { if (log.isDebugEnabled()) log.debug("Got disconnect request from Poller corresponding to sending port " + m_sender.getLocalPort()); isOk = false; } else { if (log.isDebugEnabled()) log.debug("Got request... adress = " + msg.getAddress()); byte[] dhcp = msg.getMessage().externalize(); DatagramPacket pkt = new DatagramPacket(dhcp, dhcp.length, msg.getAddress(), DHCP_TARGET_PORT); try { if (log.isDebugEnabled()) log.debug("sending request on port: " + m_sender.getLocalPort()); m_sender.send(pkt); } catch (IOException ex) { } // discard } } catch (ClassNotFoundException ex) { log.warn("Failed to read message, no class found", ex); isOk = false; } catch (IOException ex) { log.warn("Failed to read message, I/O error", ex); isOk = false; } catch (ClassCastException ex) { log.warn("Failed to read an appropriate message", ex); isOk = false; } catch (Throwable t) { log.warn("Undeclared throwable caught", t); isOk = false; } } synchronized (this) { m_status = STOP_PENDING; } // stop the unicast listener thread and wait for it to exit // m_keepListening = false; if (log.isDebugEnabled()) log.debug("run: waiting for UnicastListener thread " + this.getName() + " to die..."); try { m_unicastListener.join(); } catch (InterruptedException e) { log.debug("run: interrupted while waiting for UnicastListener thread " + this.getName() + " to die", e); } if (log.isDebugEnabled()) log.debug("run: UnicastListener thread " + this.getName() + " is dead..."); // close the datagram socket // m_sender.close(); // close the client's socket // try { input.close(); m_client.close(); } catch (IOException e) { } // Notify // notifyObservers(); synchronized (this) { m_status = STOPPED; } } // end run() method }