Java tutorial
/* * 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(); } } }