/*******************************************************************************
* 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.lang.reflect.UndeclaredThrowableException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import org.apache.log4j.Category;
import org.apache.log4j.Logger;
import org.opennms.core.utils.InetAddressUtils;
import org.opennms.netmgt.config.dhcpd.DhcpdConfigFactory;
import org.opennms.netmgt.daemon.AbstractServiceDaemon;
import org.opennms.netmgt.utils.IpValidator;
/**
* <P>
* The DHCP client daemon serves as a multiplexor for DHCP requests and
* responses. The Bootp/DHCP protocol specifies that a DHCP server listens for
* requests on local UDP/67 and will either send/broadcast responses to UDP/68
* or UDP/67 or will unicast responses back to the client's UDP port on from
* which the request originated.
* </p>
*
* <p>
* The DHCP daemon accepts client connections on TCP/5818. Once a client is
* connected it can begin forwarding requests. A list of all currently connected
* clients is maintained. Requests have the following format:
* </p>
* <ul>
* <li>byte 1 - byte 4 : 32-bit remote host IP address</li>
* <li>byte 5 - byte 8 : 32-bit buffer length</li>
* <li>byte 9 - byte n : buffer containing the formatted DHCP discover request.
* </li>
* </ul>
*
* <p>
* The client indicates that it is finished by sending a request with the remote
* host IP address set to zero (0).
* </p>
*
* <p>
* Incoming requests are sent to UDP/67 on specified remote host. If the remote
* host is runnning a DHCP server it will send/broadcast an appropriate response
* to UDP/68 or UDP/67 (or will unicast the response).
* </p>
*
* <p>
* The DHCP daemon includes a listener thread which binds to UDP/68 or UDP/67
* and simply listens for any incoming DHCP responses. In extended mode,
* threads are started on both ports. When a datagram is received by the
* listener thread(s) it loops through the list of currently connected clients
* and forwards the DHCP response packet to each client. It is the responsibility
* of the client to validate that the datagram is in response to a DHCP request
* packet that it generated.
* </p>
*
* @author <A HREF="mailto:mike@opennms.org">Mike </A>
* @author <A HREF="http://www.opennms.org/">OpenNMS </A>
*/
public final class Dhcpd extends AbstractServiceDaemon implements Runnable, Observer {
/**
* The singular instance of the DHCP server.
*/
private final static Dhcpd m_singleton = new Dhcpd();
/**
* List of clients currently connected to the DHCP daemon
*/
private static List<Client> m_clients;
/**
* Socket over which the daemon actively listens for new client connection
* requests.
*/
private ServerSocket m_server;
/**
* DHCP response port 68 listener.
*/
private Receiver m_listener;
/**
* DHCP response port 67 listener.
*/
private Receiver2 m_listener2;
/**
* The working thread
*/
private Thread m_worker;
/**
* Constructs a new DHCP server instance. All of the internal fields are
* initialized to <code>null</code>.
*
*/
private Dhcpd() {
super("OpenNMS.Dhcpd");
m_clients = null;
m_server = null;
m_listener = null;
m_listener2 = null;
m_worker = null;
}
/**
* <p>onStart</p>
*/
protected void onStart() {
boolean relayMode = false;
log().debug("start: DHCP client daemon starting...");
// Only allow start to be called once.
if (m_worker != null && m_worker.isAlive()) {
throw new IllegalStateException("The server is already running");
}
// Unless the worker has died, then stop and continue
if (m_worker != null) {
stop();
}
// the client list
m_clients = Collections.synchronizedList(new LinkedList<Client>());
// load the dhcpd configuration
DhcpdConfigFactory dFactory = null;
try {
DhcpdConfigFactory.reload();
dFactory = DhcpdConfigFactory.getInstance();
} catch (Exception ex) {
log().error("Failed to load dhcpd configuration", ex);
throw new UndeclaredThrowableException(ex);
}
// open the server
//
try {
if (log().isDebugEnabled()) {
log().debug("start: listening on TCP port " + dFactory.getPort() + " for incoming client requests.");
}
m_server = new ServerSocket(dFactory.getPort(), 0, InetAddressUtils.addr("127.0.0.1"));
} catch (IOException ex) {
if (ex instanceof java.net.BindException) {
managerLog().error("Failed to listen on DHCP port, perhaps something else is already listening?", ex);
log().error("Failed to listen on DHCP port, perhaps something else is already listening?", ex);
} else {
log().error("Failed to initialize DHCP socket", ex);
}
throw new UndeclaredThrowableException(ex);
}
// see if we have a valid relay address
String myIpStr = DhcpdConfigFactory.getInstance().getMyIpAddress();
if (log().isDebugEnabled()) {
log().debug("Checking string \"" + myIpStr + "\" to see if we have an IP address");
}
if (myIpStr != null && !myIpStr.equals("") && !myIpStr.equalsIgnoreCase("broadcast")) {
if(IpValidator.isIpValid(myIpStr)) {
relayMode = true;
}
}
if (log().isDebugEnabled()) {
log().debug("Setting relay mode " + relayMode);
}
// open the receiver socket(s)
if(!relayMode || (dFactory.getExtendedMode() != null && dFactory.getExtendedMode().equalsIgnoreCase("true"))) {
try {
log().debug("start: starting receiver thread for port 68");
m_listener = new Receiver(m_clients);
m_listener.start();
} catch (IOException ex) {
try {
m_server.close();
} catch (IOException ex1) {
}
throw new UndeclaredThrowableException(ex);
}
}
if(relayMode) {
try {
log().debug("start: starting receiver thread for port 67");
m_listener2 = new Receiver2(m_clients);
m_listener2.start();
} catch (IOException ex) {
try {
m_server.close();
} catch (IOException ex1) {
}
throw new UndeclaredThrowableException(ex);
}
}
m_worker = new Thread(this, getName());
m_worker.start();
}
/**
* <p>onStop</p>
*/
protected void onStop() {
if (m_worker == null) {
return;
}
// stop the receiver
if (m_listener != null) {
m_listener.stop();
}
// stop the other receiver
if (m_listener2 != null) {
m_listener2.stop();
}
// close the server socket
try {
m_server.close();
} catch (IOException ex) {
}
// close all the clients
Object[] list = null;
synchronized (m_clients) {
list = m_clients.toArray();
}
for (int x = 0; list != null && x < list.length; x++) {
((Client) list[x]).stop();
}
m_server = null;
m_clients = null;
m_worker = null;
m_listener = null;
m_listener2 = null;
}
/**
* The main routine of the DHCP server. This method accepts incoming client
* requests and starts new client handlers to process each request.
*/
public void run() {
try {
waitForStatus(RUNNING);
} catch (InterruptedException e1) {
// ignore
}
log().debug("run: DHCPD client daemon running...");
/*
* Begin accepting connections from clients
* For each new client create new DHCP Client Handler
* thread to handle the client's requests.
*/
try {
m_server.setSoTimeout(1000); // Wake up every second to check the
// status
for (;;) {
synchronized (this) {
if (isPaused()) {
try {
waitForStatus(RUNNING);
} catch (InterruptedException e) {
// ignore
}
} else if (!isRunning()) {
break;
}
}
Socket sock;
try {
sock = m_server.accept();
} catch (InterruptedIOException iE) {
continue;
}
// Add the client's new socket connection to the client list
log().debug("run: got connection request...creating client handler...");
try {
Client clnt = new Client(sock);
m_clients.add(clnt);
clnt.addObserver(this);
clnt.start();
} catch (IOException ioE) {
log().error("I/O exception occured creating client handler.", ioE);
}
}
} catch (IOException ioE) {
log().error("I/O exception occured processing incoming request", ioE);
} catch (Throwable t) {
log().error("An undeclared throwable was caught", t);
} finally {
log().debug("run: DHCPD client daemon run completed setting status to stopped");
}
}
/**
* {@inheritDoc}
*
* This method is called by the observable instances that the server has
* registered to receive.
*/
public void update(Observable inst, Object ignored) {
synchronized (this) {
if (m_clients != null) {
m_clients.remove(inst);
}
}
}
/**
* Returns the singular instance of the DHCP server.
*
* @return a {@link org.opennms.netmgt.dhcpd.Dhcpd} object.
*/
public static Dhcpd getInstance() {
return m_singleton;
}
/**
* Contacts the public server and checks to see if the the passed address is
* a DHCP server.
*
* @param address
* The address to query.
* @param timeout
* The time to wait between retries.
* @param retries
* The maximum number of attempts.
* @return response time in milliseconds if remote box is a DHCP server or
* -1 if it is NOT.
* @throws java.io.IOException
* Thrown if an error occurs.
*/
public static long isServer(InetAddress address, long timeout, int retries) throws IOException {
return Poller.isServer(address, timeout, retries);
}
/**
* Contacts the public server and checks to see if the the passed address is
* a DHCP server.
*
* @param address
* The address to query.
* @return response time in milliseconds if remote box is a DHCP server or
* -1 if it is NOT.
* @throws java.io.IOException
* Thrown if an error occurs.
*/
public static long isServer(InetAddress address) throws IOException {
return Poller.isServer(address, Poller.DEFAULT_TIMEOUT, Poller.DEFAULT_RETRIES);
}
/**
* <p>onInit</p>
*/
protected void onInit() {
}
private Category managerLog() {
return Logger.getLogger("OpenNMS.Manager");
}
}