com.jagornet.dhcp.client.ClientSimulatorV6.java Source code

Java tutorial

Introduction

Here is the source code for com.jagornet.dhcp.client.ClientSimulatorV6.java

Source

/*
 * 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 ClientSimulatorV6.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.client;

import java.math.BigInteger;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.channel.socket.DatagramChannel;
import org.jboss.netty.channel.socket.DatagramChannelFactory;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jagornet.dhcp.message.DhcpV6Message;
import com.jagornet.dhcp.option.v6.DhcpV6ClientIdOption;
import com.jagornet.dhcp.option.v6.DhcpV6ElapsedTimeOption;
import com.jagornet.dhcp.option.v6.DhcpV6IaNaOption;
import com.jagornet.dhcp.server.netty.DhcpV6ChannelDecoder;
import com.jagornet.dhcp.server.netty.DhcpV6ChannelEncoder;
import com.jagornet.dhcp.util.DhcpConstants;
import com.jagornet.dhcp.util.Util;

/**
 * A test client that sends solict/request/release messages 
 * to a DHCPv6 server via multicast.
 * 
 * @author A. Gregory Rabil
 */
@ChannelHandler.Sharable
public class ClientSimulatorV6 extends SimpleChannelUpstreamHandler {
    private static Logger log = LoggerFactory.getLogger(ClientSimulatorV6.class);

    protected Random random = new Random();
    protected Options options = new Options();
    protected CommandLineParser parser = new BasicParser();
    protected HelpFormatter formatter;

    protected NetworkInterface DEFAULT_NETIF = null;
    protected NetworkInterface mcastNetIf = null;
    protected InetAddress DEFAULT_ADDR;
    protected InetAddress serverAddr;
    protected int serverPort = DhcpConstants.V6_SERVER_PORT;
    protected int clientPort = DhcpConstants.V6_CLIENT_PORT;
    protected boolean rapidCommit = false;
    protected int numRequests = 100;
    protected AtomicInteger solicitsSent = new AtomicInteger();
    protected AtomicInteger advertisementsReceived = new AtomicInteger();
    protected AtomicInteger requestsSent = new AtomicInteger();
    protected AtomicInteger requestRepliesReceived = new AtomicInteger();
    protected AtomicInteger releasesSent = new AtomicInteger();
    protected AtomicInteger releaseRepliesReceived = new AtomicInteger();
    protected int successCnt = 0;
    protected long startTime = 0;
    protected long endTime = 0;
    protected long timeout = 0;
    protected int poolSize = 0;
    protected Object syncDone = new Object();

    protected InetSocketAddress server = null;
    protected InetSocketAddress client = null;

    protected DatagramChannel channel = null;
    protected ExecutorService executor = Executors.newCachedThreadPool();

    protected Map<String, ClientMachine> clientMap = Collections
            .synchronizedMap(new HashMap<String, ClientMachine>());

    /**
     * Instantiates a new test client.
     *
     * @param args the args
     * @throws Exception the exception
     */
    public ClientSimulatorV6(String[] args) throws Exception {
        DEFAULT_NETIF = NetworkInterface.getNetworkInterfaces().nextElement();
        DEFAULT_ADDR = DhcpConstants.ALL_DHCP_RELAY_AGENTS_AND_SERVERS;

        setupOptions();

        if (!parseOptions(args)) {
            formatter = new HelpFormatter();
            String cliName = this.getClass().getName();
            formatter.printHelp(cliName, options);
            System.exit(0);
        }

        try {
            start();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Setup options.
     */
    private void setupOptions() {
        Option numOption = new Option("n", "number", true,
                "Number of client requests to send" + " [" + numRequests + "]");
        options.addOption(numOption);

        Option miOption = new Option("mi", "multicastinterface", true,
                "Multicast interface of the DHCPv6 Client" + " [" + DEFAULT_NETIF.getName() + "]");
        options.addOption(miOption);

        Option saOption = new Option("sa", "serveraddress", true,
                "Address of DHCPv6 Server" + " [" + DEFAULT_ADDR + "]");
        options.addOption(saOption);

        Option cpOption = new Option("cp", "clientport", true, "Client Port Number" + " [" + clientPort + "]");
        options.addOption(cpOption);

        Option spOption = new Option("sp", "serverport", true, "Server Port Number" + " [" + serverPort + "]");
        options.addOption(spOption);

        Option rOption = new Option("r", "rapidcommit", false, "Send rapid-commit Solicit requests");
        options.addOption(rOption);

        Option toOption = new Option("to", "timeout", true, "Timeout");
        options.addOption(toOption);

        Option psOption = new Option("ps", "poolsize", true,
                "Size of the pool; wait for release after this many requests");
        options.addOption(psOption);

        Option helpOption = new Option("?", "help", false, "Show this help page.");

        options.addOption(helpOption);
    }

    protected int parseIntegerOption(String opt, String str, int defval) {
        int val = defval;
        try {
            val = Integer.parseInt(str);
        } catch (NumberFormatException ex) {
            System.err.println("Invalid " + opt + " '" + str + "' using default: " + defval + " Exception=" + ex);
            val = defval;
        }
        return val;
    }

    protected InetAddress parseIpAddressOption(String opt, String str, InetAddress defaddr) {
        InetAddress addr = defaddr;
        try {
            addr = InetAddress.getByName(str);
        } catch (UnknownHostException ex) {
            System.err.println(
                    "Invalid " + opt + " address: '" + str + "' using default: " + defaddr + " Exception=" + ex);
            addr = defaddr;
        }
        return addr;
    }

    /**
     * Parses the options.
     * 
     * @param args the args
     * 
     * @return true, if successful
     */
    protected boolean parseOptions(String[] args) {
        try {
            CommandLine cmd = parser.parse(options, args);
            if (cmd.hasOption("?")) {
                return false;
            }
            if (cmd.hasOption("n")) {
                numRequests = parseIntegerOption("num requests", cmd.getOptionValue("n"), 100);
            }
            mcastNetIf = DEFAULT_NETIF;
            if (cmd.hasOption("mi")) {
                try {
                    mcastNetIf = NetworkInterface.getByName(cmd.getOptionValue("mi"));
                } catch (SocketException e) {
                    e.printStackTrace();
                    return false;
                }
            }
            serverAddr = DEFAULT_ADDR;
            if (cmd.hasOption("sa")) {
                serverAddr = parseIpAddressOption("server", cmd.getOptionValue("sa"), DEFAULT_ADDR);
            }
            if (cmd.hasOption("cp")) {
                clientPort = parseIntegerOption("client port", cmd.getOptionValue("cp"),
                        DhcpConstants.V6_CLIENT_PORT);
            }
            if (cmd.hasOption("sp")) {
                serverPort = parseIntegerOption("server port", cmd.getOptionValue("sp"),
                        DhcpConstants.V6_SERVER_PORT);
            }
            if (cmd.hasOption("r")) {
                rapidCommit = true;
            }
            if (cmd.hasOption("to")) {
                timeout = parseIntegerOption("timeout", cmd.getOptionValue("to"), 0);
            }
            if (cmd.hasOption("ps")) {
                poolSize = parseIntegerOption("pool size", cmd.getOptionValue("ps"), 0);
            }
        } catch (ParseException pe) {
            System.err.println("Command line option parsing failure: " + pe);
            return false;
        }
        return true;
    }

    /**
     * Start sending DHCPv6 SOLICITs.
     */
    public void start() {
        DatagramChannelFactory factory = new OioDatagramChannelFactory(Executors.newCachedThreadPool());

        server = new InetSocketAddress(serverAddr, serverPort);
        client = new InetSocketAddress(clientPort);

        ChannelPipeline pipeline = Channels.pipeline();
        pipeline.addLast("logger", new LoggingHandler());
        pipeline.addLast("encoder", new DhcpV6ChannelEncoder());
        pipeline.addLast("decoder", new DhcpV6ChannelDecoder(client, false));
        pipeline.addLast("executor",
                new ExecutionHandler(new OrderedMemoryAwareThreadPoolExecutor(16, 1048576, 1048576)));
        pipeline.addLast("handler", this);

        channel = factory.newChannel(pipeline);
        channel.getConfig().setNetworkInterface(mcastNetIf);
        channel.bind(client);

        for (int i = 1; i <= numRequests; i++) {
            executor.execute(new ClientMachine(i));
        }

        synchronized (syncDone) {
            long ms = timeout * 1000;
            try {
                log.info("Waiting total of " + timeout + " milliseconds for completion");
                syncDone.wait(ms);
            } catch (InterruptedException ex) {
                log.error("Interrupted", ex);
            }
        }

        log.info("Complete: solicitsSent=" + solicitsSent + " advertisementsReceived=" + advertisementsReceived
                + " requestsSent=" + requestsSent + " requestRepliesReceived=" + requestRepliesReceived
                + " releasesSent=" + releasesSent + " releaseRepliesReceived=" + releaseRepliesReceived
                + " elapsedTime=" + (endTime - startTime) + "ms");

        log.info("Shutting down executor...");
        executor.shutdownNow();
        log.info("Closing channel...");
        channel.close();
        log.info("Done.");
        if ((solicitsSent.get() == advertisementsReceived.get())
                && (requestsSent.get() == requestRepliesReceived.get())
                && (releasesSent.get() == releaseRepliesReceived.get())) {

            System.exit(0);
        } else {
            System.exit(1);
        }
    }

    /**
     * The Class ClientMachine.
     */
    class ClientMachine implements Runnable, ChannelFutureListener {
        DhcpV6Message msg;
        int id;
        String duid;
        boolean released;

        /**
         * Instantiates a new client machine.
         *
         * @param msg the msg
         * @param server the server
         */
        public ClientMachine(int id) {
            this.id = id;
            this.duid = buildDuid(id);
            this.released = false;
        }

        /* (non-Javadoc)
         * @see java.lang.Runnable#run()
         */
        @Override
        public void run() {
            if (poolSize > 0) {
                synchronized (clientMap) {
                    if (poolSize <= clientMap.size()) {
                        try {
                            log.info("Waiting for release...");
                            clientMap.wait();
                        } catch (InterruptedException ex) {
                            log.error("Interrupted", ex);
                        }
                    }
                    clientMap.put(duid, this);
                }
            } else {
                clientMap.put(duid, this);
            }
            solicit();
        }

        public void solicit() {
            msg = buildSolicitMessage(duid);
            ChannelFuture future = channel.write(msg, server);
            future.addListener(this);
        }

        public void request(DhcpV6Message advertiseMsg) {
            msg = buildRequestMessage(advertiseMsg);
            ChannelFuture future = channel.write(msg, server);
            future.addListener(this);
        }

        public void release(DhcpV6Message confirmMsg) {
            msg = buildReleaseMessage(confirmMsg);
            ChannelFuture future = channel.write(msg, server);
            future.addListener(this);
        }

        /* (non-Javadoc)
         * @see org.jboss.netty.channel.ChannelFutureListener#operationComplete(org.jboss.netty.channel.ChannelFuture)
         */
        @Override
        public void operationComplete(ChannelFuture future) throws Exception {
            if (future.isSuccess()) {
                if (startTime == 0) {
                    startTime = System.currentTimeMillis();
                    log.info("Starting at: " + startTime);
                }
                if (msg.getMessageType() == DhcpConstants.V6MESSAGE_TYPE_SOLICIT) {
                    solicitsSent.getAndIncrement();
                    log.info("Succesfully sent solicit message duid=" + duid + " cnt=" + solicitsSent);
                } else if (msg.getMessageType() == DhcpConstants.V6MESSAGE_TYPE_REQUEST) {
                    requestsSent.getAndIncrement();
                    log.info("Succesfully sent request message duid=" + duid + " cnt=" + requestsSent);
                } else if (msg.getMessageType() == DhcpConstants.V6MESSAGE_TYPE_RELEASE) {
                    released = true;
                    releasesSent.getAndIncrement();
                    log.info("Succesfully sent release message duid=" + duid + " cnt=" + releasesSent);
                }
            } else {
                log.warn("Failed to send message id=" + msg.getTransactionId());
            }
        }
    }

    private String buildDuid(long id) {
        byte[] bid = BigInteger.valueOf(id).toByteArray();
        byte[] chAddr = new byte[6];
        chAddr[0] = (byte) 0xde;
        chAddr[1] = (byte) 0xb1;
        if (bid.length == 4) {
            chAddr[2] = bid[0];
            chAddr[3] = bid[1];
            chAddr[4] = bid[2];
            chAddr[5] = bid[3];
        } else if (bid.length == 3) {
            chAddr[2] = 0;
            chAddr[3] = bid[0];
            chAddr[4] = bid[1];
            chAddr[5] = bid[2];
        } else if (bid.length == 2) {
            chAddr[2] = 0;
            chAddr[3] = 0;
            chAddr[4] = bid[0];
            chAddr[5] = bid[1];
        } else if (bid.length == 1) {
            chAddr[2] = 0;
            chAddr[3] = 0;
            chAddr[4] = 0;
            chAddr[5] = bid[0];
        }
        return "clientid-" + Util.toHexString(chAddr);
    }

    /**
     * Builds the solict message.
     * 
     * @return the  dhcp message
     */
    private DhcpV6Message buildSolicitMessage(String duid) {
        DhcpV6Message msg = new DhcpV6Message(null, new InetSocketAddress(serverAddr, serverPort));

        msg.setTransactionId(random.nextInt());
        DhcpV6ClientIdOption dhcpClientId = new DhcpV6ClientIdOption();
        dhcpClientId.getOpaqueData().setAscii(duid);

        msg.putDhcpOption(dhcpClientId);

        DhcpV6ElapsedTimeOption dhcpElapsedTime = new DhcpV6ElapsedTimeOption();
        dhcpElapsedTime.setUnsignedShort(1);
        msg.putDhcpOption(dhcpElapsedTime);

        msg.setMessageType(DhcpConstants.V6MESSAGE_TYPE_SOLICIT);
        DhcpV6IaNaOption dhcpIaNa = new DhcpV6IaNaOption();
        dhcpIaNa.setIaId(1);
        msg.putDhcpOption(dhcpIaNa);

        return msg;
    }

    private DhcpV6Message buildRequestMessage(DhcpV6Message advertisement) {

        DhcpV6Message msg = new DhcpV6Message(null, new InetSocketAddress(serverAddr, serverPort));

        msg.setTransactionId(advertisement.getTransactionId());
        msg.putDhcpOption(advertisement.getDhcpClientIdOption());
        msg.putDhcpOption(advertisement.getDhcpServerIdOption());
        msg.setMessageType(DhcpConstants.V6MESSAGE_TYPE_REQUEST);
        msg.putDhcpOption(advertisement.getIaNaOptions().get(0));

        return msg;
    }

    private DhcpV6Message buildReleaseMessage(DhcpV6Message reply) {

        DhcpV6Message msg = new DhcpV6Message(null, new InetSocketAddress(serverAddr, serverPort));

        msg.setTransactionId(reply.getTransactionId());
        msg.putDhcpOption(reply.getDhcpClientIdOption());
        msg.putDhcpOption(reply.getDhcpServerIdOption());
        msg.setMessageType(DhcpConstants.V6MESSAGE_TYPE_RELEASE);
        msg.putDhcpOption(reply.getIaNaOptions().get(0));

        return msg;
    }

    /*
     * (non-Javadoc)
     * @see org.jboss.netty.channel.SimpleChannelHandler#messageReceived(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.MessageEvent)
     */
    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
        Object message = e.getMessage();
        if (message instanceof DhcpV6Message) {

            DhcpV6Message dhcpMessage = (DhcpV6Message) message;
            if (log.isDebugEnabled())
                log.debug("Received: " + dhcpMessage.toStringWithOptions());
            else
                log.info("Received: " + dhcpMessage.toString());

            if (dhcpMessage.getMessageType() == DhcpConstants.V6MESSAGE_TYPE_ADVERTISE) {
                ClientMachine client = clientMap
                        .get(dhcpMessage.getDhcpClientIdOption().getOpaqueData().getAscii());
                if (client != null) {
                    advertisementsReceived.getAndIncrement();
                    client.request(dhcpMessage);
                } else {
                    log.error("Received advertise for client not found in map: duid="
                            + dhcpMessage.getDhcpClientIdOption().getOpaqueData().getAscii());
                }
            } else if (dhcpMessage.getMessageType() == DhcpConstants.V6MESSAGE_TYPE_REPLY) {
                String key = dhcpMessage.getDhcpClientIdOption().getOpaqueData().getAscii();
                ClientMachine client = clientMap.get(key);
                if (client != null) {
                    if (!client.released) {
                        requestRepliesReceived.getAndIncrement();
                        client.release(dhcpMessage);
                    } else {
                        releaseRepliesReceived.getAndIncrement();
                        clientMap.remove(key);
                        if (releaseRepliesReceived.get() == numRequests) {
                            endTime = System.currentTimeMillis();
                            log.info("Ending at: " + endTime);
                            synchronized (syncDone) {
                                syncDone.notifyAll();
                            }
                        } else if (poolSize > 0) {
                            synchronized (clientMap) {
                                clientMap.notify();
                            }
                        }
                    }
                } else {
                    log.error("Received reply for client not found in map: duid="
                            + dhcpMessage.getDhcpClientIdOption().getOpaqueData().getAscii());
                }
            } else {
                log.warn("Received unhandled message type: " + dhcpMessage.getMessageType());
            }
        } else {
            // Note: in theory, we can't get here, because the
            // codec would have thrown an exception beforehand
            log.error("Received unknown message object: " + message.getClass());
        }
    }

    /* (non-Javadoc)
     * @see org.jboss.netty.channel.SimpleChannelUpstreamHandler#exceptionCaught(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.ExceptionEvent)
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
        log.error("Exception caught: ", e.getCause());
        e.getChannel().close();
    }

    /**
     * The main method.
     * 
     * @param args the arguments
     */
    public static void main(String[] args) {
        try {
            new ClientSimulatorV6(args);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}