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.
This commit is contained in:
Adam
2021-02-21 22:11:43 -05:00
parent e58202c153
commit fc72d17a55
3 changed files with 194 additions and 15 deletions

View File

@@ -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();

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2021, Adam <Adam@sigterm.info>
* 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);
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2021, Adam <Adam@sigterm.info>
* 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<String> getFieldOrder()
{
return Arrays.asList("tv_sec", "tv_usec");
}
}