com.jagornet.dhcpv6.client.TestClient.java Source code

Java tutorial

Introduction

Here is the source code for com.jagornet.dhcpv6.client.TestClient.java

Source

/*
 * Copyright 2009 Jagornet Technologies, LLC.  All Rights Reserved.
 *
 * This software is the proprietary information of Jagornet Technologies, LLC. 
 * Use is subject to license terms.
 *
 */

/*
 *   This file TestClient.java is part of DHCPv6.
 *
 *   DHCPv6 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.
 *
 *   DHCPv6 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 DHCPv6.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package com.jagornet.dhcpv6.client;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineCoverage;
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.nio.NioDatagramChannelFactory;
import org.jboss.netty.channel.socket.oio.OioDatagramChannelFactory;
import org.jboss.netty.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jagornet.dhcpv6.message.DhcpMessage;
import com.jagornet.dhcpv6.option.DhcpClientIdOption;
import com.jagornet.dhcpv6.option.DhcpElapsedTimeOption;
import com.jagornet.dhcpv6.option.DhcpIaNaOption;
import com.jagornet.dhcpv6.option.DhcpRapidCommitOption;
import com.jagornet.dhcpv6.option.DhcpUserClassOption;
import com.jagornet.dhcpv6.server.netty.DhcpChannelDecoder;
import com.jagornet.dhcpv6.server.netty.DhcpChannelEncoder;
import com.jagornet.dhcpv6.util.DhcpConstants;

/**
 * A test client that sends request messages to a DHCPv6 server
 * via either unicast or multicast.
 * 
 * @author A. Gregory Rabil
 */
@ChannelPipelineCoverage("all")
public class TestClient extends SimpleChannelUpstreamHandler {
    /** The log. */
    private static Logger log = LoggerFactory.getLogger(TestClient.class);

    /** The options. */
    protected Options options = new Options();

    /** The parser. */
    protected CommandLineParser parser = new BasicParser();

    /** The formatter. */
    protected HelpFormatter formatter;

    /** The mcast net if. */
    protected NetworkInterface mcastNetIf = null;

    /** The server addr. */
    protected InetAddress serverAddr = DhcpConstants.LOCALHOST_V6;

    /** The server port. */
    protected int serverPort = DhcpConstants.SERVER_PORT;

    /** The client port. */
    protected int clientPort = DhcpConstants.CLIENT_PORT;

    protected boolean rapidCommit = false;

    protected int numRequests = 100;
    protected int requestsSent = 0;
    protected int successCnt = 0;
    protected long startTime = 0;
    protected long endTime = 0;

    protected DatagramChannel channel = null;

    protected ExecutorService executor = Executors.newCachedThreadPool();

    protected Map<Integer, DhcpMessage> requestMap = Collections
            .synchronizedMap(new HashMap<Integer, DhcpMessage>());

    /**
     * Instantiates a new test client.
     * 
     * @param args the args
     */
    public TestClient(String[] args) {
        setupOptions();

        if (!parseOptions(args)) {
            formatter = new HelpFormatter();
            String cliName = this.getClass().getName();
            formatter.printHelp(cliName, options);
            //            PrintWriter stderr = new PrintWriter(System.err, true);   // auto-flush=true
            //            formatter.printHelp(stderr, 80, cliName, null, options, 2, 2, null);
            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 addrOption = new Option("a", "address", true,
                "Address of DHCPv6 Server" + " [" + serverAddr.getHostAddress() + "]");
        options.addOption(addrOption);

        Option mcastOption = new Option("m", "multicast", true, "Multicast interface [none]");
        options.addOption(mcastOption);

        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 helpOption = new Option("?", "help", false, "Show this help page.");

        options.addOption(helpOption);
    }

    /**
     * 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")) {
                String n = cmd.getOptionValue("n");
                try {
                    numRequests = Integer.parseInt(n);
                } catch (NumberFormatException ex) {
                    numRequests = 100;
                    System.err.println("Invalid number of requests: '" + n + "' using default: " + numRequests
                            + " Exception=" + ex);
                }
            }
            if (cmd.hasOption("a")) {
                String a = cmd.getOptionValue("a");
                try {
                    serverAddr = InetAddress.getByName(a);
                } catch (UnknownHostException ex) {
                    serverAddr = DhcpConstants.LOCALHOST_V6;
                    System.err.println(
                            "Invalid address: '" + a + "' using default: " + serverAddr + " Exception=" + ex);
                }
            }
            if (cmd.hasOption("m")) {
                String m = cmd.getOptionValue("m");
                try {
                    mcastNetIf = NetworkInterface.getByName(m);
                } catch (SocketException ex) {
                    System.err.println("Invalid interface: " + m + " - " + ex);
                }
            }
            if (cmd.hasOption("cp")) {
                String p = cmd.getOptionValue("cp");
                try {
                    clientPort = Integer.parseInt(p);
                } catch (NumberFormatException ex) {
                    clientPort = DhcpConstants.CLIENT_PORT;
                    System.err.println("Invalid client port number: '" + p + "' using default: " + clientPort
                            + " Exception=" + ex);
                }
            }
            if (cmd.hasOption("sp")) {
                String p = cmd.getOptionValue("sp");
                try {
                    serverPort = Integer.parseInt(p);
                } catch (NumberFormatException ex) {
                    serverPort = DhcpConstants.SERVER_PORT;
                    System.err.println("Invalid server port number: '" + p + "' using default: " + serverPort
                            + " Exception=" + ex);
                }
            }
            if (cmd.hasOption("r")) {
                rapidCommit = true;
            }
        } catch (ParseException pe) {
            System.err.println("Command line option parsing failure: " + pe);
            return false;
        }
        return true;
    }

    /**
     * Start sending DHCPv6 INFORM_REQUESTs.
     */
    public void start() {
        DatagramChannelFactory factory = null;
        if (mcastNetIf != null) {
            factory = new OioDatagramChannelFactory(Executors.newCachedThreadPool());
            serverAddr = DhcpConstants.ALL_DHCP_RELAY_AGENTS_AND_SERVERS;
        } else {
            factory = new NioDatagramChannelFactory(Executors.newCachedThreadPool());
        }

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

        ChannelPipeline pipeline = Channels.pipeline();
        pipeline.addLast("logger", new LoggingHandler());
        pipeline.addLast("encoder", new DhcpChannelEncoder());
        pipeline.addLast("decoder", new DhcpChannelDecoder(client));
        pipeline.addLast("handler", this);

        if (mcastNetIf != null) {
            channel = factory.newChannel(pipeline);
            channel.getConfig().setNetworkInterface(mcastNetIf);
        } else {
            channel = factory.newChannel(pipeline);
        }
        channel.bind(client);

        List<DhcpMessage> requestMsgs = buildRequestMessages();

        for (DhcpMessage msg : requestMsgs) {
            executor.execute(new RequestSender(msg, server));
        }

        long ms = 0;
        if (rapidCommit)
            ms = requestMsgs.size() * 200;
        else
            ms = requestMsgs.size() * 20;

        synchronized (requestMap) {
            try {
                log.info("Waiting total of " + ms + " milliseconds for completion");
                requestMap.wait(ms);
            } catch (InterruptedException ex) {
                log.error("Interrupted", ex);
            }
        }

        log.info("Successfully processed " + successCnt + " of " + requestsSent + " messages in "
                + (endTime - startTime) + " milliseconds");

        log.info("Shutting down executor...");
        executor.shutdownNow();
        log.info("Closing channel...");
        channel.close();
        log.info("Done.");
        System.exit(0);
    }

    class RequestSender implements Runnable, ChannelFutureListener {
        DhcpMessage msg;
        InetSocketAddress server;

        public RequestSender(DhcpMessage msg, InetSocketAddress server) {
            this.msg = msg;
            this.server = server;
        }

        @Override
        public void run() {
            ChannelFuture future = channel.write(msg, server);
            future.addListener(this);
        }

        @Override
        public void operationComplete(ChannelFuture future) throws Exception {
            int id = msg.getTransactionId();
            if (future.isSuccess()) {
                if (startTime == 0) {
                    startTime = System.currentTimeMillis();
                }
                requestsSent++;
                log.info("Succesfully sent message id=" + id);
                requestMap.put(id, msg);
            } else {
                log.warn("Failed to send message id=" + msg.getTransactionId());
            }
        }
    }

    /**
     * Builds the request messages.
     * 
     * @return the list< dhcp message>
     */
    private List<DhcpMessage> buildRequestMessages() {
        List<DhcpMessage> requests = new ArrayList<DhcpMessage>();
        for (int id = 0; id < numRequests; id++) {
            DhcpMessage msg = new DhcpMessage(null, new InetSocketAddress(serverAddr, serverPort));

            msg.setTransactionId(id);
            String clientId = "clientid-" + id;
            DhcpClientIdOption dhcpClientId = new DhcpClientIdOption();
            dhcpClientId.getOpaqueDataOptionType().getOpaqueData().setAsciiValue(clientId);

            msg.putDhcpOption(dhcpClientId);

            DhcpElapsedTimeOption dhcpElapsedTime = new DhcpElapsedTimeOption();
            dhcpElapsedTime.getUnsignedShortOption().setUnsignedShort(1);
            msg.putDhcpOption(dhcpElapsedTime);

            DhcpUserClassOption dhcpUserClass = new DhcpUserClassOption();
            dhcpUserClass.addOpaqueData("FilterUserClass");
            msg.putDhcpOption(dhcpUserClass);

            if (rapidCommit) {
                msg.setMessageType(DhcpConstants.SOLICIT);
                DhcpIaNaOption dhcpIaNa = new DhcpIaNaOption();
                dhcpIaNa.getIaNaOption().setIaId(1);
                msg.putDhcpOption(dhcpIaNa);
                DhcpRapidCommitOption dhcpRapidCommit = new DhcpRapidCommitOption();
                msg.putDhcpOption(dhcpRapidCommit);
            } else {
                msg.setMessageType(DhcpConstants.INFO_REQUEST);
            }
            requests.add(msg);
        }
        return requests;
    }

    /*
     * (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 DhcpMessage) {

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

            DhcpMessage requestMsg = requestMap.remove(dhcpMessage.getTransactionId());
            if (requestMsg != null) {
                successCnt++;
                endTime = System.currentTimeMillis();
                synchronized (requestMap) {
                    if (requestMap.isEmpty()) {
                        requestMap.notify();
                    }
                }
            }
        } 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());
        }
    }

    @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) {
        new TestClient(args);
    }

}