/*
* Copyright 2009-2014 Jagornet Technologies, LLC. All Rights Reserved.
*
* This software is the proprietary information of Jagornet Technologies, LLC.
* Use is subject to license terms.
*
*/
/*
* This file NettyDhcpServer.java is part of Jagornet DHCP.
*
* Jagornet DHCP 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.
*
* Jagornet DHCP 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 Jagornet DHCP. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.jagornet.dhcp.server.netty;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.DatagramChannel;
import org.jboss.netty.channel.socket.DatagramChannelFactory;
import org.jboss.netty.channel.socket.nio.NioDatagramChannelFactory;
import org.jboss.netty.channel.socket.oio.OioDatagramChannelFactory;
import org.jboss.netty.handler.execution.ExecutionHandler;
import org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor;
import org.jboss.netty.handler.logging.LoggingHandler;
import org.jboss.netty.logging.InternalLoggerFactory;
import org.jboss.netty.logging.Log4JLoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jagornet.dhcp.server.config.DhcpServerConfigException;
import com.jagornet.dhcp.server.config.DhcpServerPolicies;
import com.jagornet.dhcp.server.config.DhcpServerPolicies.Property;
import com.jagornet.dhcp.util.DhcpConstants;
import com.jagornet.dhcp.util.Util;
/**
* This is a JBoss Netty based DHCPv6 server that uses
* Java NIO DatagramChannels for receiving unicast messages.
* It uses OIO (java.net) DatagramSockets for receiving
* multicast messages. Java 7 is required to support
* MulticastChannels.
*
* @author A. Gregory Rabil
*/
public class NettyDhcpServer
{
private static Logger log = LoggerFactory.getLogger(NettyDhcpServer.class);
/** The V6 unicast socket addresses */
private List<InetAddress> v6Addrs;
/** The V6 mulitcast network interfaces */
private List<NetworkInterface> v6NetIfs;
/** The DHCPv6 server port. */
private int v6Port;
/** The V4 unicast socket addresses */
private List<InetAddress> v4Addrs;
/** The V4 broadcast network interface */
private NetworkInterface v4NetIf;
/** The DHCPv4 server port. */
private int v4Port;
/** The collection of channels this server listens on. */
protected Collection<DatagramChannel> channels = new ArrayList<DatagramChannel>();
/** The executor service thread pool for processing requests. */
protected ExecutorService executorService = Executors.newCachedThreadPool();
/**
* Create a NettyDhcpServer.
*
* @param v6Addrs the addresses to listen on for unicast traffic
* @param v6NetIfs the network interfaces to listen on for multicast traffic
* @param v6Port the port to listen on
* @param v4Addrs the addresses to listen on for unicast traffic
* @param v4NetIf the network interface to listen on for broadcast traffic
* @param v4Port the port to listen on
*/
public NettyDhcpServer(List<InetAddress> v6Addrs, List<NetworkInterface> v6NetIfs, int v6Port,
List<InetAddress> v4Addrs, NetworkInterface v4NetIf, int v4Port)
{
this.v6Addrs = v6Addrs;
this.v6NetIfs = v6NetIfs;
this.v6Port = v6Port;
this.v4Addrs = v4Addrs;
this.v4NetIf = v4NetIf;
this.v4Port = v4Port;
}
/**
* Start the server.
*
* @throws Exception the exception
*/
public void start() throws Exception
{
try {
InternalLoggerFactory.setDefaultFactory(new Log4JLoggerFactory());
boolean ignoreSelfPackets =
DhcpServerPolicies.globalPolicyAsBoolean(Property.DHCP_IGNORE_SELF_PACKETS);
int corePoolSize =
DhcpServerPolicies.globalPolicyAsInt(Property.CHANNEL_THREADPOOL_SIZE);
int maxChannelMemorySize =
DhcpServerPolicies.globalPolicyAsInt(Property.CHANNEL_MAX_CHANNEL_MEMORY);
int maxTotalMemorySize =
DhcpServerPolicies.globalPolicyAsInt(Property.CHANNEL_MAX_TOTAL_MEMORY);
int receiveBufSize =
DhcpServerPolicies.globalPolicyAsInt(Property.CHANNEL_READ_BUFFER_SIZE);
int sendBufSize =
DhcpServerPolicies.globalPolicyAsInt(Property.CHANNEL_WRITE_BUFFER_SIZE);
log.info("Initializing channels:" +
" corePoolSize=" + corePoolSize +
" maxChannelMemorySize=" + maxChannelMemorySize +
" maxTotalMemorySize=" + maxTotalMemorySize +
" receiveBufferSize=" + receiveBufSize +
" sendBufferSize=" + sendBufSize);
boolean v6SocketChecked = false;
if (v6Addrs != null) {
// test if this socket is already in use, which means
// there is probably already a DHCPv6 server running
checkSocket(v6Port);
v6SocketChecked = true;
for (InetAddress addr : v6Addrs) {
// local address for packets received on this channel
InetSocketAddress sockAddr = new InetSocketAddress(addr, v6Port);
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("logger", new LoggingHandler());
pipeline.addLast("decoder", new DhcpV6UnicastChannelDecoder(sockAddr, ignoreSelfPackets));
pipeline.addLast("encoder", new DhcpV6ChannelEncoder());
pipeline.addLast("executor", new ExecutionHandler(
new OrderedMemoryAwareThreadPoolExecutor(corePoolSize,
maxChannelMemorySize,
maxTotalMemorySize)));
pipeline.addLast("handler", new DhcpV6ChannelHandler());
String io = null;
DatagramChannelFactory factory = null;
if (Util.IS_WINDOWS) {
// Use OioDatagramChannels for IPv6 unicast addresses on Windows
factory = new OioDatagramChannelFactory(executorService);
io = "Old I/O";
}
else {
// Use NioDatagramChannels for IPv6 unicast addresses on real OSes
factory = new NioDatagramChannelFactory(executorService);
io = "New I/O";
}
// create an unbound channel
DatagramChannel channel = factory.newChannel(pipeline);
channel.getConfig().setReuseAddress(true);
channel.getConfig().setReceiveBufferSize(receiveBufSize);
channel.getConfig().setSendBufferSize(sendBufSize);
log.info("Binding " + io + " datagram channel on IPv6 socket address: " + sockAddr);
ChannelFuture future = channel.bind(sockAddr);
future.await();
if (!future.isSuccess()) {
log.error("Failed to bind to IPv6 unicast channel: " + future.getCause());
throw new IOException(future.getCause());
}
channels.add(channel);
}
}
if (v6NetIfs != null) {
if (!v6SocketChecked) {
// if in-use socket check has not been done yet, then do it
checkSocket(v6Port);
}
for (NetworkInterface netIf : v6NetIfs) {
// find the link local IPv6 address for this interface
InetAddress addr = Util.netIfIPv6LinkLocalAddress(netIf);
if (addr == null) {
String msg = "No IPv6 link local addresses found on interface: " + netIf;
log.error(msg);
throw new DhcpServerConfigException(msg);
}
// local address for packets received on this channel
InetSocketAddress sockAddr = new InetSocketAddress(addr, v6Port);
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("logger", new LoggingHandler());
pipeline.addLast("decoder", new DhcpV6ChannelDecoder(sockAddr, ignoreSelfPackets));
pipeline.addLast("encoder", new DhcpV6ChannelEncoder());
pipeline.addLast("executor", new ExecutionHandler(
new OrderedMemoryAwareThreadPoolExecutor(corePoolSize,
maxChannelMemorySize,
maxTotalMemorySize)));
pipeline.addLast("handler", new DhcpV6ChannelHandler());
// Use OioDatagramChannels for IPv6 multicast interfaces
DatagramChannelFactory factory =
new OioDatagramChannelFactory(executorService);
// create an unbound channel
DatagramChannel channel = factory.newChannel(pipeline);
channel.getConfig().setReuseAddress(true);
channel.getConfig().setReceiveBufferSize(receiveBufSize);
channel.getConfig().setSendBufferSize(sendBufSize);
// must be bound in order to join multicast group
InetSocketAddress wildAddr = new InetSocketAddress(DhcpConstants.ZEROADDR_V6, v6Port);
log.info("Binding New I/O multicast channel on IPv6 wildcard address: " + wildAddr);
ChannelFuture future = channel.bind(wildAddr);
future.await();
if (!future.isSuccess()) {
log.error("Failed to bind to IPv6 multicast channel: " + future.getCause());
throw new IOException(future.getCause());
}
InetSocketAddress relayGroup =
new InetSocketAddress(DhcpConstants.ALL_DHCP_RELAY_AGENTS_AND_SERVERS, v6Port);
log.info("Joining multicast group: " + relayGroup +
" on interface: " + netIf.getName());
channel.joinGroup(relayGroup, netIf);
InetSocketAddress serverGroup =
new InetSocketAddress(DhcpConstants.ALL_DHCP_SERVERS, v6Port);
log.info("Joining multicast group: " + serverGroup +
" on interface: " + netIf.getName());
channel.joinGroup(serverGroup, netIf);
channels.add(channel);
}
}
boolean v4SocketChecked = false;
Map<InetAddress, Channel> v4UcastChannels = new HashMap<InetAddress, Channel>();
if (v4Addrs != null) {
checkSocket(v4Port);
v4SocketChecked = true;
for (InetAddress addr : v4Addrs) {
// local address for packets received on this channel
InetSocketAddress sockAddr = new InetSocketAddress(addr, v4Port);
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("logger", new LoggingHandler());
pipeline.addLast("decoder", new DhcpV4UnicastChannelDecoder(sockAddr, ignoreSelfPackets));
pipeline.addLast("encoder", new DhcpV4ChannelEncoder());
pipeline.addLast("executor", new ExecutionHandler(
new OrderedMemoryAwareThreadPoolExecutor(corePoolSize,
maxChannelMemorySize,
maxTotalMemorySize)));
pipeline.addLast("handler", new DhcpV4ChannelHandler(null));
String io = null;
DatagramChannelFactory factory = null;
if (Util.IS_WINDOWS) {
// Use OioDatagramChannels for IPv4 unicast addresses on Windows
factory = new OioDatagramChannelFactory(executorService);
io = "Old I/O";
}
else {
// Use NioDatagramChannels for IPv4 unicast addresses on real OSes
factory = new NioDatagramChannelFactory(executorService);
io = "New I/O";
}
// create an unbound channel
DatagramChannel channel = factory.newChannel(pipeline);
channel.getConfig().setReuseAddress(true);
channel.getConfig().setBroadcast(true);
channel.getConfig().setReceiveBufferSize(receiveBufSize);
channel.getConfig().setSendBufferSize(sendBufSize);
v4UcastChannels.put(addr, channel);
log.info("Binding " + io + " datagram channel on IPv4 socket address: " + sockAddr);
ChannelFuture future = channel.bind(sockAddr);
future.await();
if (!future.isSuccess()) {
log.error("Failed to bind to IPv4 unicast channel: " + future.getCause());
throw new IOException(future.getCause());
}
channels.add(channel);
}
}
if (v4NetIf != null) {
if (!v4SocketChecked) {
// if in-use socket check has not been done yet, then do it
checkSocket(v4Port);
}
boolean foundV4Addr = false;
// get the first v4 address on the interface and bind to it
Enumeration<InetAddress> addrs = v4NetIf.getInetAddresses();
while (addrs.hasMoreElements()) {
InetAddress addr = addrs.nextElement();
if (addr instanceof Inet4Address) {
Channel bcastChannel = v4UcastChannels.get(addr);
if (bcastChannel == null) {
String msg = "IPv4 address: " + addr.getHostAddress() +
" for broadcast interface: " + v4NetIf +
" must be included in unicast IPv4 address list";
log.error(msg);
throw new DhcpServerConfigException(msg);
}
foundV4Addr = true;
InetSocketAddress sockAddr = new InetSocketAddress(addr, v4Port);
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("logger", new LoggingHandler());
pipeline.addLast("decoder", new DhcpV4ChannelDecoder(sockAddr, ignoreSelfPackets));
pipeline.addLast("encoder", new DhcpV4ChannelEncoder());
pipeline.addLast("executor", new ExecutionHandler(
new OrderedMemoryAwareThreadPoolExecutor(corePoolSize,
maxChannelMemorySize,
maxTotalMemorySize)));
pipeline.addLast("handler", new DhcpV4ChannelHandler(bcastChannel));
DatagramChannelFactory factory = new NioDatagramChannelFactory(executorService);
// create an unbound channel
DatagramChannel channel = factory.newChannel(pipeline);
channel.getConfig().setReuseAddress(true);
channel.getConfig().setBroadcast(true);
channel.getConfig().setReceiveBufferSize(receiveBufSize);
channel.getConfig().setSendBufferSize(sendBufSize);
InetSocketAddress wildAddr = new InetSocketAddress(DhcpConstants.ZEROADDR_V4, v4Port);
log.info("Binding New I/O datagram channel on IPv4 wildcard address: " + wildAddr);
ChannelFuture future = channel.bind(wildAddr);
future.await();
if (!future.isSuccess()) {
log.error("Failed to bind to IPv4 broadcast channel: " + future.getCause());
throw new IOException(future.getCause());
}
channels.add(channel);
break; // no need to continue looking at IPs on the interface
}
}
if (!foundV4Addr) {
String msg = "No IPv4 addresses found on interface: " + v4NetIf;
log.error(msg);
throw new DhcpServerConfigException(msg);
}
}
}
catch (Exception ex) {
log.error("Failed to initialize server: " + ex, ex);
throw ex;
}
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
shutdown();
}
});
}
private void checkSocket(int port) throws SocketException {
DatagramSocket ds = null;
try {
log.info("Checking for existing socket on port=" + port);
ds = new DatagramSocket(port);
}
finally {
if (ds != null) {
ds.close();
}
}
}
/**
* Shutdown.
*/
public void shutdown()
{
log.info("Closing channels");
for (DatagramChannel channel : channels) {
channel.close();
}
log.info("Executor shutdown");
executorService.shutdown();
}
}