From fc72d17a5575a0a6c042979fe3c018d19fa578a5 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 21 Feb 2021 22:11:43 -0500 Subject: [PATCH] ping: use icmp ping on linux if available This uses IPPROTO_ICMP sockets, which most modern linux systems support. This allows directly sending icmp echo requests without having to use the tcp ping check. As a fallback the old tcp ping method is used if socket setup fails. --- .../client/plugins/worldhopper/ping/Ping.java | 118 +++++++++++++++--- .../plugins/worldhopper/ping/RLLibC.java | 50 ++++++++ .../plugins/worldhopper/ping/Timeval.java | 41 ++++++ 3 files changed, 194 insertions(+), 15 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/RLLibC.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/Timeval.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/Ping.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/Ping.java index 42c88d4975..59f7eb85d4 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/Ping.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/Ping.java @@ -24,6 +24,8 @@ */ package net.runelite.client.plugins.worldhopper.ping; +import com.google.common.base.Charsets; +import com.google.common.primitives.Bytes; import com.sun.jna.Memory; import com.sun.jna.Pointer; import java.io.IOException; @@ -38,21 +40,42 @@ import net.runelite.http.api.worlds.World; @Slf4j public class Ping { - private static final String RUNELITE_PING = "RuneLitePing"; - - private static final int TIMEOUT = 2000; + private static final byte[] RUNELITE_PING = "RuneLitePing".getBytes(Charsets.UTF_8); + private static final int TIMEOUT = 2000; // ms private static final int PORT = 43594; + private static short seq; + public static int ping(World world) { + InetAddress inetAddress; + try + { + inetAddress = InetAddress.getByName(world.getAddress()); + } + catch (UnknownHostException ex) + { + log.warn("error resolving host for world ping", ex); + return -1; + } + try { switch (OSType.getOSType()) { case Windows: - return windowsPing(world); + return windowsPing(inetAddress); + case Linux: + try + { + return linuxPing(inetAddress); + } + catch (Exception ex) + { + return tcpPing(inetAddress); + } default: - return tcpPing(world); + return tcpPing(inetAddress); } } catch (IOException ex) @@ -62,22 +85,19 @@ public class Ping } } - private static int windowsPing(World world) throws UnknownHostException + private static int windowsPing(InetAddress inetAddress) { IPHlpAPI ipHlpAPI = IPHlpAPI.INSTANCE; Pointer ptr = ipHlpAPI.IcmpCreateFile(); try { - InetAddress inetAddress = InetAddress.getByName(world.getAddress()); byte[] address = inetAddress.getAddress(); - String dataStr = RUNELITE_PING; - int dataLength = dataStr.length() + 1; - Pointer data = new Memory(dataLength); - data.setString(0L, dataStr); - IcmpEchoReply icmpEchoReply = new IcmpEchoReply(new Memory(IcmpEchoReply.SIZE + dataLength)); + Memory data = new Memory(RUNELITE_PING.length); + data.write(0, RUNELITE_PING, 0, RUNELITE_PING.length); + IcmpEchoReply icmpEchoReply = new IcmpEchoReply(new Memory(IcmpEchoReply.SIZE + data.size())); assert icmpEchoReply.size() == IcmpEchoReply.SIZE; int packed = (address[0] & 0xff) | ((address[1] & 0xff) << 8) | ((address[2] & 0xff) << 16) | ((address[3] & 0xff) << 24); - int ret = ipHlpAPI.IcmpSendEcho(ptr, packed, data, (short) (dataLength), Pointer.NULL, icmpEchoReply, IcmpEchoReply.SIZE + dataLength, TIMEOUT); + int ret = ipHlpAPI.IcmpSendEcho(ptr, packed, data, (short) data.size(), Pointer.NULL, icmpEchoReply, IcmpEchoReply.SIZE + (int) data.size(), TIMEOUT); if (ret != 1) { return -1; @@ -91,12 +111,80 @@ public class Ping } } - private static int tcpPing(World world) throws IOException + private static int linuxPing(InetAddress inetAddress) throws IOException + { + RLLibC libc = RLLibC.INSTANCE; + byte[] address = inetAddress.getAddress(); + + int sock = libc.socket(libc.AF_INET, libc.SOCK_DGRAM, libc.IPPROTO_ICMP); + if (sock < 0) + { + throw new IOException("failed to open ICMP socket"); + } + + try + { + Timeval tv = new Timeval(); + tv.tv_sec = TIMEOUT / 1000; + if (libc.setsockopt(sock, libc.SOL_SOCKET, libc.SO_RCVTIMEO, tv.getPointer(), tv.size()) < 0) + { + throw new IOException("failed to set SO_RCVTIMEO"); + } + + short seqno = seq++; + + // struct icmphdr + byte[] request = { + 8, // type 8 - ipv4 echo request + 0, // code + 0, 0, // checksum (set by kernel) + 0, 0, // id (set by kernel) + (byte) (((seqno >> 8) & 0xff)), (byte) (seqno & 0xff) + }; + // append payload + request = Bytes.concat(request, RUNELITE_PING); + // struct sockaddr_in + byte[] addr = { + (byte) libc.AF_INET, 0, // sin_family + 0, 0, // sin_port + address[0], address[1], address[2], address[3], // sin_addr.s_addr + 0, 0, 0, 0, 0, 0, 0, 0 // padding + }; + + long start = System.nanoTime(); + if (libc.sendto(sock, request, request.length, 0, addr, addr.length) != request.length) + { + return -1; + } + + int size = 8 + RUNELITE_PING.length; // struct icmphdr + response + Memory response = new Memory(size); + if (libc.recvfrom(sock, response, size, 0, null, null) != size) + { + return -1; + } + long end = System.nanoTime(); + + short seq = (short) (((response.getByte(6) & 0xff) << 8) | response.getByte(7) & 0xff); + if (seqno != seq) + { + log.warn("sequence number mismatch ({} != {})", seqno, seq); + return -1; + } + + return (int) ((end - start) / 1_000_000); + } + finally + { + libc.close(sock); + } + } + + private static int tcpPing(InetAddress inetAddress) throws IOException { try (Socket socket = new Socket()) { socket.setSoTimeout(TIMEOUT); - InetAddress inetAddress = InetAddress.getByName(world.getAddress()); long start = System.nanoTime(); socket.connect(new InetSocketAddress(inetAddress, PORT)); long end = System.nanoTime(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/RLLibC.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/RLLibC.java new file mode 100644 index 0000000000..4692f04a71 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/RLLibC.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.worldhopper.ping; + +import com.sun.jna.Native; +import com.sun.jna.Pointer; +import com.sun.jna.platform.unix.LibC; + +interface RLLibC extends LibC +{ + RLLibC INSTANCE = Native.loadLibrary(NAME, RLLibC.class); + + int AF_INET = 2; + int SOCK_DGRAM = 2; + int SOL_SOCKET = 1; + int IPPROTO_ICMP = 1; + int SO_RCVTIMEO = 20; + + int socket(int domain, int type, int protocol); + + void close(int socket); + + int sendto(int sockfd, byte[] buf, int len, int flags, byte[] dest_addr, int addrlen); + + int recvfrom(int sockfd, Pointer buf, int len, int flags, Pointer src_addr, Pointer addrlen); + + int setsockopt(int sockfd, int level, int optname, Pointer optval, int optlen); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/Timeval.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/Timeval.java new file mode 100644 index 0000000000..e887b55190 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/Timeval.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.worldhopper.ping; + +import com.sun.jna.Structure; +import java.util.Arrays; +import java.util.List; + +public class Timeval extends Structure +{ + public long tv_sec; + public long tv_usec; + + @Override + protected List getFieldOrder() + { + return Arrays.asList("tv_sec", "tv_usec"); + } +}