/******************************************************************************* * Copyright 2013-2016 alladin-IT GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package at.alladin.rmbt.client.v2.task; import java.io.DataOutputStream; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.nio.channels.DatagramChannel; import java.util.TreeSet; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.regex.Matcher; import java.util.regex.Pattern; import at.alladin.rmbt.client.QualityOfServiceTest; import at.alladin.rmbt.client.RMBTClient; import at.alladin.rmbt.client.v2.task.result.QoSTestResult; import at.alladin.rmbt.client.v2.task.result.QoSTestResultEnum; import at.alladin.rmbt.util.net.udp.NioUdpStreamSender; import at.alladin.rmbt.util.net.udp.StreamSender.UdpStreamCallback; import at.alladin.rmbt.util.net.udp.StreamSender.UdpStreamSenderSettings; import at.alladin.rmbt.util.net.udp.UdpStreamReceiver; import at.alladin.rmbt.util.net.udp.UdpStreamReceiver.UdpStreamReceiverSettings; /** * * @author lb * */ public class UdpTask extends AbstractQoSTask { private final static boolean ABORT_ON_DUPLICATE_UDP_PACKETS = false; private final static Pattern QOS_RECEIVE_RESPONSE_PATTERN = Pattern.compile("RCV ([\\d]*) ([\\d]*)"); private final Integer packetCountIncoming; private final Integer packetCountOutgoing; private Integer outgoingPort; private final Integer incomingPort; private final long timeout; private final long delay; private final static long DEFAULT_TIMEOUT = 3000000000L; private final static long DEFAULT_DELAY = 300000000L; private final static int UDP_TEST_ONE_DIRECTION_IDENTIFIER = 1; private final static int UDP_TEST_AWAIT_RESPONSE_IDENTIFIER = 3; private final static int UDP_TEST_RESPONSE = 2; public final static String PARAM_NUM_PACKETS_INCOMING = "in_num_packets"; public final static String PARAM_NUM_PACKETS_OUTGOING = "out_num_packets"; public final static String PARAM_PORT = "in_port"; public final static String PARAM_PORT_OUT = "out_port"; public final static String PARAM_TIMEOUT = "timeout"; public final static String PARAM_DELAY = "delay"; public final static String RESULT_OUTGOING_PACKETS = "udp_result_out_num_packets"; public final static String RESULT_INCOMING_PACKETS = "udp_result_in_num_packets"; public final static String RESULT_INCOMING_PLR = "udp_result_in_packet_loss_rate"; public final static String RESULT_NUM_PACKETS_INCOMING_RESPONSE = "udp_result_in_response_num_packets"; public final static String RESULT_OUTGOING_PLR = "udp_result_out_packet_loss_rate"; public final static String RESULT_NUM_PACKETS_OUTGOING_RESPONSE = "udp_result_out_response_num_packets"; public final static String RESULT_PORT_OUTGOING = "udp_objective_out_port"; public final static String RESULT_PORT_INCOMING = "udp_objective_in_port"; public final static String RESULT_NUM_PACKETS_INCOMING = "udp_objective_in_num_packets"; public final static String RESULT_NUM_PACKETS_OUTGOING = "udp_objective_out_num_packets"; public final static String RESULT_DELAY = "udp_objective_delay"; public final static String RESULT_TIMEOUT = "udp_objective_timeout"; /** * * @author lb * */ public static class UdpPacketData { int remotePort; int numPackets; int dupNumPackets; int rcvServerResponse; public UdpPacketData(int remotePort, int numPackets, int dupNumPackets) { this.remotePort = remotePort; this.numPackets = numPackets; this.dupNumPackets = dupNumPackets; this.rcvServerResponse = 0; } @Override public String toString() { return "UdpPacketData [remotePort=" + remotePort + ", numPackets=" + numPackets + ", dupNumPackets=" + dupNumPackets + ", rcvServerResponse=" + rcvServerResponse + "]"; } } /** * * @param taskDesc */ public UdpTask(QualityOfServiceTest nnTest, TaskDesc taskDesc, int threadId) { super(nnTest, taskDesc, threadId, threadId); String value = (String) taskDesc.getParams().get(PARAM_NUM_PACKETS_INCOMING); this.packetCountIncoming = value != null ? Integer.valueOf(value) : null; value = (String) taskDesc.getParams().get(PARAM_NUM_PACKETS_OUTGOING); this.packetCountOutgoing = value != null ? Integer.valueOf(value) : null; value = (String) taskDesc.getParams().get(PARAM_PORT); this.incomingPort = value != null ? Integer.valueOf(value) : null; value = (String) taskDesc.getParams().get(PARAM_PORT_OUT); this.outgoingPort = value != null ? Integer.valueOf(value) : null; value = (String) taskDesc.getParams().get(PARAM_TIMEOUT); this.timeout = value != null ? Long.valueOf(value) : DEFAULT_TIMEOUT; value = (String) taskDesc.getParams().get(PARAM_DELAY); this.delay = value != null ? Long.valueOf(value) : DEFAULT_DELAY; } /** * */ public QoSTestResult call() throws Exception { final QoSTestResult result = initQoSTestResult(QoSTestResultEnum.UDP); try { onStart(result); DatagramSocket socket = null; final UdpPacketData outgoingPacketData = new UdpPacketData(0, 0, 0); final UdpPacketData incomingPacketData = new UdpPacketData(0, 0, 0); try { final CountDownLatch outgoingLatch = new CountDownLatch(1); //run UDP OUT test: if (this.packetCountOutgoing != null) { ControlConnectionResponseCallback outgoingRequestCallback = new ControlConnectionResponseCallback() { public void onResponse(final String response, final String request) { try { if (request.startsWith("GET UDPPORT")) { if (response != null && !response.startsWith("ERR")) { outgoingPort = Integer.valueOf(response); sendCommand("UDPTEST OUT " + outgoingPort + " " + packetCountOutgoing, this); } } else if (request.startsWith("UDPTEST OUT")) { if (response != null && response.startsWith("OK")) { Future<UdpPacketData> udpOutTimeoutTask = RMBTClient.getCommonThreadPool().submit(new Callable<UdpPacketData>() { public UdpPacketData call() throws Exception { sendUdpPackets(outgoingPacketData); return outgoingPacketData; } }); try { udpOutTimeoutTask.get(timeout, TimeUnit.NANOSECONDS); } catch (Exception e) { System.err.println("UDP Outgoing Timeout reached!"); e.printStackTrace(); udpOutTimeoutTask.cancel(true); } outgoingLatch.countDown(); } } } catch (Exception e) { e.printStackTrace(); } } }; if (outgoingPort == null) { sendCommand("GET UDPPORT", outgoingRequestCallback); } else { sendCommand("UDPTEST OUT " + outgoingPort + " " + packetCountOutgoing, outgoingRequestCallback); } if (!outgoingLatch.await(timeout, TimeUnit.NANOSECONDS)) { System.out.println("OUT " + outgoingPort + " TIMEOUT REACHED: " + outgoingPacketData); } //request results; final CountDownLatch outgoingResultLatch = new CountDownLatch(1); final ControlConnectionResponseCallback outgoingResultRequestCallback = new ControlConnectionResponseCallback() { public void onResponse(final String response, final String request) { if (response != null && response.startsWith("RCV")) { System.out.println("UDPTASK OUT :" + outgoingPort + " -> " + response); Matcher m = QOS_RECEIVE_RESPONSE_PATTERN.matcher(response); if (m.find()) { outgoingPacketData.rcvServerResponse = Integer.valueOf(m.group(1)); } outgoingResultLatch.countDown(); } } }; sendCommand("GET UDPRESULT OUT " + outgoingPort, outgoingResultRequestCallback); outgoingResultLatch.await(CONTROL_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS); } //run UDP IN test: if (this.packetCountIncoming != null && this.incomingPort != null) { socket = new DatagramSocket(incomingPort); final DatagramSocket dgSocket = socket; sendCommand("UDPTEST IN " + incomingPort + " " + packetCountIncoming, null); socket.setSoTimeout((int)(timeout/1000000)); Future<UdpPacketData> udpInTimeoutTask = RMBTClient.getCommonThreadPool().submit(new Callable<UdpPacketData>() { public UdpPacketData call() throws Exception { receiveUdpPackets(dgSocket, packetCountIncoming, incomingPacketData); return incomingPacketData; } }); try { udpInTimeoutTask.get(timeout, TimeUnit.NANOSECONDS); } catch (TimeoutException e) { System.err.println("UDP Incoming Timeout reached!"); udpInTimeoutTask.cancel(true); } final CountDownLatch incomingLatch = new CountDownLatch(1); final ControlConnectionResponseCallback incomingResultRequestCallback = new ControlConnectionResponseCallback() { public void onResponse(final String response, final String request) { if (response != null && response.startsWith("RCV")) { System.out.println("UDPTASK IN :" + incomingPort + " -> " + response); Matcher m = QOS_RECEIVE_RESPONSE_PATTERN.matcher(response); if (m.find()) { incomingPacketData.rcvServerResponse = Integer.valueOf(m.group(1)); } incomingLatch.countDown(); } } }; //wait a short amount of time until requesting results Thread.sleep(150); //request server results: sendCommand("GET UDPRESULT IN " + incomingPort, incomingResultRequestCallback); incomingLatch.await(CONTROL_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS); } } catch (Exception e) { e.printStackTrace(); } finally { if (socket != null && socket.isConnected()) { socket.close(); } } if (this.packetCountOutgoing != null) { System.out.println("OUT " + outgoingPort + ": " + outgoingPacketData); result.getResultMap().put(RESULT_NUM_PACKETS_OUTGOING, packetCountOutgoing); result.getResultMap().put(RESULT_PORT_OUTGOING, outgoingPort); result.getResultMap().put(RESULT_OUTGOING_PACKETS, outgoingPacketData != null ? outgoingPacketData.rcvServerResponse : 0); result.getResultMap().put(RESULT_NUM_PACKETS_OUTGOING_RESPONSE, outgoingPacketData != null ? outgoingPacketData.numPackets : 0); final int outgoingPackets = (outgoingPacketData != null ? outgoingPacketData.numPackets : 0); final int lostPackets = packetCountOutgoing - outgoingPackets; System.out.println("UDP Test: outgoing all: " + outgoingPackets + ", lost: " + lostPackets); if (lostPackets > 0) { int packetLossRate = (int) (((float)lostPackets / (float)packetCountOutgoing) * 100f); result.getResultMap().put(RESULT_OUTGOING_PLR, String.valueOf(packetLossRate)); } else { result.getResultMap().put(RESULT_OUTGOING_PLR, "0"); } } if (this.packetCountIncoming != null && this.incomingPort != null) { System.out.println("IN " + incomingPort + ": " + incomingPacketData); final int incomingPackets = incomingPacketData != null ? incomingPacketData.rcvServerResponse : 0; result.getResultMap().put(RESULT_NUM_PACKETS_INCOMING, packetCountIncoming); result.getResultMap().put(RESULT_PORT_INCOMING, incomingPort); result.getResultMap().put(RESULT_INCOMING_PACKETS, incomingPacketData != null ? incomingPacketData.numPackets : 0); result.getResultMap().put(RESULT_NUM_PACKETS_INCOMING_RESPONSE, incomingPackets); final int lostPackets = packetCountIncoming - incomingPackets; if (lostPackets > 0) { int packetLossRate = (int) (((float)lostPackets / (float)packetCountIncoming) * 100f); result.getResultMap().put(RESULT_INCOMING_PLR, String.valueOf(packetLossRate)); } else { result.getResultMap().put(RESULT_INCOMING_PLR, "0"); } } result.getResultMap().put(RESULT_DELAY, delay); result.getResultMap().put(RESULT_TIMEOUT, timeout); return result; } catch (Exception e) { e.printStackTrace(); throw e; } finally { onEnd(result); } } /* * (non-Javadoc) * @see at.alladin.rmbt.client.v2.task.AbstractRmbtTask#initTask() */ @Override public void initTask() { } /** * * @return * @throws Exception */ public DatagramChannel sendUdpPackets(final UdpPacketData packetData) throws Exception { final UdpStreamSenderSettings<DatagramChannel> udpSettings = new UdpStreamSenderSettings<DatagramChannel>(null, true, InetAddress.getByName(getTestServerAddr()), outgoingPort, packetCountOutgoing, delay, timeout, TimeUnit.NANOSECONDS, false, 10000); final NioUdpStreamSender udpStreamSender = new NioUdpStreamSender(udpSettings, new UdpStreamCallback() { final TreeSet<Integer> packetsReceived = new TreeSet<Integer>(); final TreeSet<Integer> duplicatePackets = new TreeSet<Integer>(); public boolean onSend(DataOutputStream dataOut, int packetNumber) throws IOException { System.out.println("UDP OUT Test: sending packet #" + packetNumber); dataOut.writeByte(UDP_TEST_AWAIT_RESPONSE_IDENTIFIER); dataOut.writeByte(packetNumber); dataOut.write(params.getUUID().getBytes()); dataOut.write(String.valueOf(System.currentTimeMillis()).getBytes()); return true; } public synchronized void onReceive(final DatagramPacket dp) throws IOException { final byte[] buffer = dp.getData(); int packetNumber = buffer[1]; System.out.println("UDP OUT Test: received packet: #" + packetNumber + " -> " + buffer); //check udp packet: if (buffer[0] != UDP_TEST_RESPONSE) { udpSettings.getSocket().close(); throw new IOException("bad UDP IN TEST packet identifier"); } //check for duplicate packets: if (packetsReceived.contains(packetNumber)) { duplicatePackets.add(packetNumber); if (ABORT_ON_DUPLICATE_UDP_PACKETS) { udpSettings.getSocket().close(); throw new IOException("duplicate UDP IN TEST packet id"); } else { System.out.println("duplicate UDP IN TEST packet id"); } } else { packetsReceived.add(packetNumber); } packetData.numPackets = packetsReceived.size(); packetData.dupNumPackets = duplicatePackets.size(); } public void onBind(Integer port) throws IOException { System.out.println("UDP; Binding on port " + port); } }); return udpStreamSender.send(); } /** * * @param socket * @return * @throws InterruptedException */ public void receiveUdpPackets(final DatagramSocket socket, int packets, final UdpPacketData packetData) throws InterruptedException { final TreeSet<Integer> packetsReceived = new TreeSet<Integer>(); final TreeSet<Integer> duplicatePackets = new TreeSet<Integer>(); try { final int timeOutMs = (int) TimeUnit.MILLISECONDS.convert(timeout, TimeUnit.NANOSECONDS); socket.setSoTimeout(timeOutMs); final UdpStreamReceiverSettings settings = new UdpStreamReceiverSettings(socket, packets, true); final UdpStreamReceiver udpStreamReceiver = new UdpStreamReceiver(settings, new UdpStreamCallback() { public boolean onSend(DataOutputStream dataOut, int packetNumber) throws IOException { dataOut.writeByte(UDP_TEST_RESPONSE); dataOut.writeByte(packetNumber); dataOut.write(params.getUUID().getBytes()); dataOut.write(String.valueOf(System.currentTimeMillis()).getBytes()); return true; } public void onReceive(DatagramPacket dp) throws IOException { final byte[] data = dp.getData(); final int packetNumber = data[1]; System.out.println("UDP IN Test: received packet #" + packetNumber + " on port: " + socket.getLocalPort() + " -> " + data); //check udp packet: if (data[0] != UDP_TEST_ONE_DIRECTION_IDENTIFIER && data[0] != UDP_TEST_AWAIT_RESPONSE_IDENTIFIER) { throw new IOException("bad UDP IN TEST packet identifier"); } //check for duplicate packets: if (packetsReceived.contains(packetNumber)) { duplicatePackets.add(packetNumber); if (ABORT_ON_DUPLICATE_UDP_PACKETS) { throw new IOException("duplicate UDP IN TEST packet id"); } } else { packetsReceived.add(packetNumber); } packetData.dupNumPackets = duplicatePackets.size(); packetData.numPackets = packetsReceived.size(); } public void onBind(Integer port) throws IOException { // TODO Auto-generated method stub } }); udpStreamReceiver.receive(); } catch (IOException e) { e.printStackTrace(); } finally { if (socket != null && !socket.isClosed()) { socket.close(); } } } /* * (non-Javadoc) * @see at.alladin.rmbt.client.v2.task.QoSTask#getTestType() */ public QoSTestResultEnum getTestType() { return QoSTestResultEnum.UDP; } /* * (non-Javadoc) * @see at.alladin.rmbt.client.v2.task.QoSTask#needsQoSControlConnection() */ public boolean needsQoSControlConnection() { return true; } }