| 1 | /* |
| 2 | * $Id: Ping.java 8033 2007-09-03 04:02:36Z dfs $ |
| 3 | * |
| 4 | * Copyright 2004-2007 Daniel F. Savarese |
| 5 | * Contact Information: http://www.savarese.org/contact.html |
| 6 | * |
| 7 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 8 | * you may not use this file except in compliance with the License. |
| 9 | * You may obtain a copy of the License at |
| 10 | * |
| 11 | * http://www.savarese.org/software/ApacheLicense-2.0 |
| 12 | * |
| 13 | * Unless required by applicable law or agreed to in writing, software |
| 14 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 16 | * See the License for the specific language governing permissions and |
| 17 | * limitations under the License. |
| 18 | */ |
| 19 | |
| 20 | package cz.vutbr.feec.utko.iptv.ping; |
| 21 | |
| 22 | import java.io.IOException; |
| 23 | import java.net.InetAddress; |
| 24 | import java.net.Inet6Address; |
| 25 | import java.util.concurrent.*; |
| 26 | |
| 27 | import org.savarese.vserv.tcpip.*; |
| 28 | import org.savarese.rocksaw.net.RawSocket; |
| 29 | import static org.savarese.rocksaw.net.RawSocket.PF_INET; |
| 30 | import static org.savarese.rocksaw.net.RawSocket.PF_INET6; |
| 31 | import static org.savarese.rocksaw.net.RawSocket.getProtocolByName; |
| 32 | import static org.savarese.vserv.tcpip.ICMPPacket.OFFSET_ICMP_CHECKSUM; |
| 33 | |
| 34 | /** |
| 35 | * <p> |
| 36 | * The Ping class is a simple demo showing how you can send ICMP echo requests |
| 37 | * and receive echo replies using raw sockets. It has been updated to work with |
| 38 | * both IPv4 and IPv6. |
| 39 | * </p> |
| 40 | * |
| 41 | * <p> |
| 42 | * Note, this is not a model of good programming. The point of the example is to |
| 43 | * show how the RawSocket API calls work. There is much kluginess surrounding |
| 44 | * the actual packet and protocol handling, all of which is outside of the scope |
| 45 | * of what RockSaw does. |
| 46 | * </p> |
| 47 | * |
| 48 | * @author <a href="http://www.savarese.org/">Daniel F. Savarese</a> |
| 49 | */ |
| 50 | |
| 51 | public class Ping { |
| 52 | |
| 53 | public static interface EchoReplyListener { |
| 54 | public void notifyEchoReply(ICMPEchoPacket packet, byte[] data, |
| 55 | int dataOffset, byte[] srcAddress) throws IOException; |
| 56 | } |
| 57 | |
| 58 | public static class Pinger { |
| 59 | private static final int TIMEOUT = 10000; |
| 60 | |
| 61 | protected RawSocket socket; |
| 62 | protected ICMPEchoPacket sendPacket, recvPacket; |
| 63 | protected int offset, length, dataOffset; |
| 64 | protected int requestType, replyType; |
| 65 | protected byte[] sendData, recvData, srcAddress; |
| 66 | protected int sequence, identifier; |
| 67 | protected EchoReplyListener listener; |
| 68 | |
| 69 | protected Pinger(int id, int protocolFamily, int protocol) |
| 70 | throws IOException { |
| 71 | sequence = 0; |
| 72 | identifier = id; |
| 73 | setEchoReplyListener(null); |
| 74 | |
| 75 | sendPacket = new ICMPEchoPacket(1); |
| 76 | recvPacket = new ICMPEchoPacket(1); |
| 77 | sendData = new byte[84]; |
| 78 | recvData = new byte[84]; |
| 79 | |
| 80 | sendPacket.setData(sendData); |
| 81 | recvPacket.setData(recvData); |
| 82 | sendPacket.setIPHeaderLength(5); |
| 83 | recvPacket.setIPHeaderLength(5); |
| 84 | sendPacket.setICMPDataByteLength(56); |
| 85 | recvPacket.setICMPDataByteLength(56); |
| 86 | |
| 87 | offset = sendPacket.getIPHeaderByteLength(); |
| 88 | dataOffset = offset + sendPacket.getICMPHeaderByteLength(); |
| 89 | length = sendPacket.getICMPPacketByteLength(); |
| 90 | |
| 91 | socket = new RawSocket(); |
| 92 | socket.open(protocolFamily, protocol); |
| 93 | |
| 94 | try { |
| 95 | socket.setSendTimeout(TIMEOUT); |
| 96 | socket.setReceiveTimeout(TIMEOUT); |
| 97 | } catch (java.net.SocketException se) { |
| 98 | socket.setUseSelectTimeout(true); |
| 99 | socket.setSendTimeout(TIMEOUT); |
| 100 | socket.setReceiveTimeout(TIMEOUT); |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | public Pinger(int id) throws IOException { |
| 105 | this(id, PF_INET, getProtocolByName("icmp")); |
| 106 | |
| 107 | srcAddress = new byte[4]; |
| 108 | requestType = ICMPPacket.TYPE_ECHO_REQUEST; |
| 109 | replyType = ICMPPacket.TYPE_ECHO_REPLY; |
| 110 | } |
| 111 | |
| 112 | protected void computeSendChecksum(InetAddress host) throws IOException { |
| 113 | sendPacket.computeICMPChecksum(); |
| 114 | } |
| 115 | |
| 116 | public void setEchoReplyListener(EchoReplyListener l) { |
| 117 | listener = l; |
| 118 | } |
| 119 | |
| 120 | /** |
| 121 | * Closes the raw socket opened by the constructor. After calling this |
| 122 | * method, the object cannot be used. |
| 123 | */ |
| 124 | public void close() throws IOException { |
| 125 | socket.close(); |
| 126 | } |
| 127 | |
| 128 | public void sendEchoRequest(InetAddress host) throws IOException { |
| 129 | sendPacket.setType(requestType); |
| 130 | sendPacket.setCode(0); |
| 131 | sendPacket.setIdentifier(identifier); |
| 132 | sendPacket.setSequenceNumber(sequence++); |
| 133 | |
| 134 | OctetConverter |
| 135 | .longToOctets(System.nanoTime(), sendData, dataOffset); |
| 136 | |
| 137 | computeSendChecksum(host); |
| 138 | |
| 139 | socket.write(host, sendData, offset, length); |
| 140 | } |
| 141 | |
| 142 | public void receive() throws IOException { |
| 143 | socket.read(recvData, srcAddress); |
| 144 | } |
| 145 | |
| 146 | public void receiveEchoReply() throws IOException { |
| 147 | do { |
| 148 | receive(); |
| 149 | } while (recvPacket.getType() != replyType |
| 150 | || recvPacket.getIdentifier() != identifier); |
| 151 | |
| 152 | if (listener != null) |
| 153 | listener.notifyEchoReply(recvPacket, recvData, dataOffset, |
| 154 | srcAddress); |
| 155 | } |
| 156 | |
| 157 | /** |
| 158 | * Issues a synchronous ping. |
| 159 | * |
| 160 | * @param host |
| 161 | * The host to ping. |
| 162 | * @return The round trip time in nanoseconds. |
| 163 | */ |
| 164 | public long ping(InetAddress host) throws IOException { |
| 165 | sendEchoRequest(host); |
| 166 | receiveEchoReply(); |
| 167 | |
| 168 | long end = System.nanoTime(); |
| 169 | long start = OctetConverter.octetsToLong(recvData, dataOffset); |
| 170 | |
| 171 | return (end - start); |
| 172 | } |
| 173 | |
| 174 | /** |
| 175 | * @return The number of bytes in the data portion of the ICMP ping |
| 176 | * request packet. |
| 177 | */ |
| 178 | public int getRequestDataLength() { |
| 179 | return sendPacket.getICMPDataByteLength(); |
| 180 | } |
| 181 | |
| 182 | /** @return The number of bytes in the entire IP ping request packet. */ |
| 183 | public int getRequestPacketLength() { |
| 184 | return sendPacket.getIPPacketLength(); |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | public static class PingerIPv6 extends Pinger { |
| 189 | private static final int IPPROTO_ICMPV6 = 58; |
| 190 | private static final int ICMPv6_TYPE_ECHO_REQUEST = 128; |
| 191 | private static final int ICMPv6_TYPE_ECHO_REPLY = 129; |
| 192 | |
| 193 | /** |
| 194 | * Operating system kernels are supposed to calculate the ICMPv6 |
| 195 | * checksum for the sender, but Microsoft's IPv6 stack does not do this. |
| 196 | * Nor does it support the IPV6_CHECKSUM socket option. Therefore, in |
| 197 | * order to work on the Windows family of operating systems, we have to |
| 198 | * calculate the ICMPv6 checksum. |
| 199 | */ |
| 200 | private static class ICMPv6ChecksumCalculator extends IPPacket { |
| 201 | ICMPv6ChecksumCalculator() { |
| 202 | super(1); |
| 203 | } |
| 204 | |
| 205 | private int computeVirtualHeaderTotal(byte[] destination, |
| 206 | byte[] source, int icmpLength) { |
| 207 | int total = 0; |
| 208 | |
| 209 | for (int i = 0; i < source.length;) |
| 210 | total += (((source[i++] & 0xff) << 8) | (source[i++] & 0xff)); |
| 211 | for (int i = 0; i < destination.length;) |
| 212 | total += (((destination[i++] & 0xff) << 8) | (destination[i++] & 0xff)); |
| 213 | |
| 214 | total += (icmpLength >>> 16); |
| 215 | total += (icmpLength & 0xffff); |
| 216 | total += IPPROTO_ICMPV6; |
| 217 | |
| 218 | return total; |
| 219 | } |
| 220 | |
| 221 | int computeChecksum(byte[] data, ICMPPacket packet, |
| 222 | byte[] destination, byte[] source) { |
| 223 | int startOffset = packet.getIPHeaderByteLength(); |
| 224 | int checksumOffset = startOffset + OFFSET_ICMP_CHECKSUM; |
| 225 | int ipLength = packet.getIPPacketLength(); |
| 226 | int icmpLength = packet.getICMPPacketByteLength(); |
| 227 | |
| 228 | setData(data); |
| 229 | |
| 230 | return _computeChecksum_(startOffset, checksumOffset, ipLength, |
| 231 | computeVirtualHeaderTotal(destination, source, |
| 232 | icmpLength), true); |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | private byte[] localAddress; |
| 237 | private ICMPv6ChecksumCalculator icmpv6Checksummer; |
| 238 | |
| 239 | public PingerIPv6(int id) throws IOException { |
| 240 | // socket.open(protocolFamily, |
| 241 | super(id, PF_INET6, IPPROTO_ICMPV6 /* getProtocolByName("ipv6-icmp") */); |
| 242 | |
| 243 | icmpv6Checksummer = new ICMPv6ChecksumCalculator(); |
| 244 | srcAddress = new byte[16]; |
| 245 | localAddress = new byte[16]; |
| 246 | requestType = ICMPv6_TYPE_ECHO_REQUEST; |
| 247 | replyType = ICMPv6_TYPE_ECHO_REPLY; |
| 248 | } |
| 249 | |
| 250 | protected void computeSendChecksum(InetAddress host) throws IOException { |
| 251 | // This is necessary only for Windows, which doesn't implement |
| 252 | // RFC 2463 correctly. |
| 253 | socket.getSourceAddressForDestination(host, localAddress); |
| 254 | icmpv6Checksummer.computeChecksum(sendData, sendPacket, host |
| 255 | .getAddress(), localAddress); |
| 256 | } |
| 257 | |
| 258 | public void receive() throws IOException { |
| 259 | socket.read(recvData, offset, length, srcAddress); |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | public static final void main(String[] args) throws Exception { |
| 264 | if (args.length < 1 || args.length > 2) { |
| 265 | System.err.println("usage: Ping host [count]"); |
| 266 | System.exit(1); |
| 267 | } |
| 268 | |
| 269 | final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor( |
| 270 | 2); |
| 271 | |
| 272 | try { |
| 273 | final InetAddress address = InetAddress.getByName(args[0]); |
| 274 | final String hostname = address.getCanonicalHostName(); |
| 275 | final String hostaddr = address.getHostAddress(); |
| 276 | final int count; |
| 277 | // Ping programs usually use the process ID for the identifier, |
| 278 | // but we can't get it and this is only a demo. |
| 279 | final int id = 65535; |
| 280 | final Pinger ping; |
| 281 | |
| 282 | if (args.length == 2) |
| 283 | count = Integer.parseInt(args[1]); |
| 284 | else |
| 285 | count = 5; |
| 286 | |
| 287 | if (address instanceof Inet6Address) |
| 288 | ping = new Ping.PingerIPv6(id); |
| 289 | else |
| 290 | ping = new Ping.Pinger(id); |
| 291 | |
| 292 | ping.setEchoReplyListener(new EchoReplyListener() { |
| 293 | StringBuffer buffer = new StringBuffer(128); |
| 294 | |
| 295 | public void notifyEchoReply(ICMPEchoPacket packet, byte[] data, |
| 296 | int dataOffset, byte[] srcAddress) throws IOException { |
| 297 | long end = System.nanoTime(); |
| 298 | long start = OctetConverter.octetsToLong(data, dataOffset); |
| 299 | // Note: Java and JNI overhead will be noticeable (100-200 |
| 300 | // microseconds) for sub-millisecond transmission times. |
| 301 | // The first ping may even show several seconds of delay |
| 302 | // because of initial JIT compilation overhead. |
| 303 | double rtt = (double) (end - start) / 1e6; |
| 304 | |
| 305 | buffer.setLength(0); |
| 306 | buffer.append(packet.getICMPPacketByteLength()).append( |
| 307 | " bytes from ").append(hostname).append(" ("); |
| 308 | buffer.append(InetAddress.getByAddress(srcAddress) |
| 309 | .toString()); |
| 310 | buffer.append("): icmp_seq=").append( |
| 311 | packet.getSequenceNumber()).append(" ttl=").append( |
| 312 | packet.getTTL()).append(" time=").append(rtt) |
| 313 | .append(" ms"); |
| 314 | System.out.println(buffer.toString()); |
| 315 | } |
| 316 | }); |
| 317 | |
| 318 | System.out.println("PING " + hostname + " (" + hostaddr + ") " |
| 319 | + ping.getRequestDataLength() + "(" |
| 320 | + ping.getRequestPacketLength() + ") bytes of data)."); |
| 321 | |
| 322 | final CountDownLatch latch = new CountDownLatch(1); |
| 323 | |
| 324 | executor.scheduleAtFixedRate(new Runnable() { |
| 325 | int counter = count; |
| 326 | |
| 327 | public void run() { |
| 328 | try { |
| 329 | if (counter > 0) { |
| 330 | ping.sendEchoRequest(address); |
| 331 | if (counter == count) |
| 332 | latch.countDown(); |
| 333 | --counter; |
| 334 | } else |
| 335 | executor.shutdown(); |
| 336 | } catch (IOException ioe) { |
| 337 | ioe.printStackTrace(); |
| 338 | } |
| 339 | } |
| 340 | }, 0, 1, TimeUnit.SECONDS); |
| 341 | |
| 342 | // We wait for first ping to be sent because Windows times out |
| 343 | // with WSAETIMEDOUT if echo request hasn't been sent first. |
| 344 | // POSIX does the right thing and just blocks on the first receive. |
| 345 | // An alternative is to bind the socket first, which should allow a |
| 346 | // receive to be performed frst on Windows. |
| 347 | latch.await(); |
| 348 | |
| 349 | for (int i = 0; i < count; ++i) |
| 350 | ping.receiveEchoReply(); |
| 351 | |
| 352 | ping.close(); |
| 353 | } catch (Exception e) { |
| 354 | executor.shutdown(); |
| 355 | e.printStackTrace(); |
| 356 | } |
| 357 | } |
| 358 | |
| 359 | } |