Add cmd line client pinger

This commit is contained in:
6arelyFuture 2022-07-20 13:52:46 +02:00
parent acc58137c3
commit bcde93dc07
Signed by: Future
GPG Key ID: FA77F074E98D98A5
8 changed files with 343 additions and 11 deletions

View File

@ -5,6 +5,13 @@
<artifactId>ServerList</artifactId> <artifactId>ServerList</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.5.0</version>
</dependency>
</dependencies>
<reporting> <reporting>
<plugins> <plugins>
<plugin> <plugin>

View File

@ -0,0 +1,160 @@
/*
* Copyright (C) 2022 Diamante
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.diamante.serverlist;
import java.lang.management.ManagementFactory;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.io.IOException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.channels.IllegalBlockingModeException;
import java.util.Set;
/**
* The purpose of this module is to ping the servers on the server list.
*
* @author Diamante
*/
public class ClientEmulator implements Runnable {
public static final int SERVER_QUERY = 1347374924;
public static final int SERVER_INFO_SIZE = 2129;
private final Set<Server> servers;
private static final int SOCKET_TIMEOUT = 4000;
private DatagramSocket socket;
public ClientEmulator(Set<Server> servers) {
try {
socket = new DatagramSocket();
socket.setSoTimeout(SOCKET_TIMEOUT);
}
catch (SocketException ex) {
System.err.println("ClientEmulator: SocketException while creating new DatagramSocket");
System.exit(-1);
}
this.servers = servers;
}
public ClientEmulator() {
try {
socket = new DatagramSocket();
socket.setSoTimeout(SOCKET_TIMEOUT);
}
catch (SocketException ex) {
System.err.println("ClientEmulator: SocketException while creating new DatagramSocket");
System.exit(-1);
}
this.servers = null;
}
public void pingSingleServer(String ip) {
var to = Utils.stringToServer(ip);
if (to != null) {
handleServer(to);
}
}
private void sendDatagramPacket(DatagramPacket packet) {
try {
socket.send(packet);
}
catch (IOException | IllegalArgumentException | IllegalBlockingModeException ex) {
// Socket will timeout
System.err.println("sendDatagramPacket: exception while sending a packet");
}
}
private DatagramPacket receiveDatagramPacket() {
var buffer = new byte[SERVER_INFO_SIZE];
var packet = new DatagramPacket(buffer, buffer.length);
try {
socket.receive(packet);
System.out.println(String.format("receiveDatagramPacket: Server %s:%d returned a packet", packet.getAddress().toString(), packet.getPort()));
return packet;
}
catch (SocketException ex) {
System.err.println("receiveDatagramPacket: SocketException");
return null;
}
catch (IllegalBlockingModeException ex) {
System.err.println("receiveDatagramPacket: IllegalBlockingModeException");
return null;
}
catch (SocketTimeoutException ex) {
System.err.println("receiveDatagramPacket: SocketTimeoutException");
return null;
}
catch (IOException ex) {
System.err.println("receiveDatagramPacket: IOException");
return null;
}
}
private byte[] generateClientPing() {
// Use this as tick
var jvmUpTime = (Long) ManagementFactory.getRuntimeMXBean().getUptime();
var tickLE = Utils.longSwap(jvmUpTime.intValue());
var magicLE = Utils.longSwap(SERVER_QUERY);
var data = new byte[8];
System.arraycopy(magicLE, 0, data, 0, 4);
System.arraycopy(tickLE, 0, data, 4, 4);
return data;
}
private void sendPingToServer(Server server) {
var data = generateClientPing();
// We use the net_port like a client would.
var packet = new DatagramPacket(data, data.length, server.getAddress(), server.getNetPort());
sendDatagramPacket(packet);
}
private void handleServer(Server server) {
sendPingToServer(server);
var response = receiveDatagramPacket();
if (response == null) {
return;
}
var rawData = response.getData();
InfoDumper.dumpServerResponse(rawData);
}
@Override
public void run() {
synchronized (servers) {
var it = servers.iterator();
while (it.hasNext()) {
var server = it.next();
handleServer(server);
}
}
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 2022 Diamante
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.diamante.serverlist;
import java.nio.charset.StandardCharsets;
/**
*
* @author Diamante
*/
public class InfoDumper {
public static void dumpServerResponse(byte[] data) {
assert data.length == ClientEmulator.SERVER_INFO_SIZE;
var magicLE = new byte[4];
var playersLE = new byte[4];
var maxPlayersLE = new byte[4];
var rawDataLE = new byte[2048];
System.arraycopy(data, 0, magicLE, 0, 4);
System.arraycopy(data, 8, playersLE, 0, 4);
System.arraycopy(data, 12, maxPlayersLE, 0, 4);
System.arraycopy(data, 81, rawDataLE, 0, 2048);
var playersBE = Utils.longSwap(playersLE);
var maxPlayersBE = Utils.longSwap(maxPlayersLE);
System.out.println(String.format("dumpServerResponse: Players %d:%d", playersBE, maxPlayersBE));
String infoString = new String(rawDataLE, StandardCharsets.UTF_8);
System.out.println(infoString);
}
}

View File

@ -18,24 +18,67 @@ package com.diamante.serverlist;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
/** /**
* *
* @author Diamante * @author Diamante
*/ */
public class Main { public class Main {
private enum Mode {
Emulator, Master, Bad;
}
private Mode mode;
public static final AtomicBoolean running = new AtomicBoolean(true); public static final AtomicBoolean running = new AtomicBoolean(true);
private final MasterServer server; private MasterServer server;
public Main() { public Main() {
server = new MasterServer(); mode = Mode.Bad;
}
private Mode getMode() {
return mode;
}
private void setMode(Mode mode) {
this.mode = mode;
} }
public MasterServer getServer() { public MasterServer getServer() {
return server; return server;
} }
private void createMasterServer() {
server = new MasterServer();
}
private Options createOptions() {
var options = new Options();
var master = new Option("master", "master server mode");
var emulator = new Option("emulator", "client emulator mode");
var ping = Option.builder("ping")
.argName("IP:Port")
.hasArg()
.desc("Server to ping")
.build();
options.addOption(master);
options.addOption(emulator);
options.addOption(ping);
return options;
}
public static void main(String[] args) { public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread() { Runtime.getRuntime().addShutdownHook(new Thread() {
@Override @Override
@ -46,11 +89,37 @@ public class Main {
}); });
var main = new Main(); var main = new Main();
var options = main.createOptions();
var ip = new String();
var parser = new DefaultParser();
try {
var line = parser.parse(options, args);
if (line.hasOption("master")) {
main.setMode(Mode.Master);
} else if (line.hasOption("emulator")) {
main.setMode(Mode.Emulator);
}
if (line.hasOption("ping")) {
ip = line.getOptionValue("ping");
}
}
catch (ParseException exp) {
System.err.println("Parsing failed. Reason: " + exp.getMessage());
}
if (main.getMode() == Mode.Master) {
main.createMasterServer();
while (running.get() && main.getServer().isValid()) { while (running.get() && main.getServer().isValid()) {
main.getServer().await(); main.getServer().await();
} }
main.getServer().stop(); main.getServer().stop();
} else if (main.getMode() == Mode.Emulator) {
var emulator = new ClientEmulator();
emulator.pingSingleServer(ip);
}
System.out.println("Normal shutdown");
} }
} }

View File

@ -101,6 +101,8 @@ public class MasterServer {
} else { } else {
System.out.println("handlePacket: magic is not recognized"); System.out.println("handlePacket: magic is not recognized");
} }
serverList.dumpOnlineServers();
} }
public void await() { public void await() {

View File

@ -42,11 +42,18 @@ public class Server {
this.time = System.currentTimeMillis() / 1000L; this.time = System.currentTimeMillis() / 1000L;
} }
public Server(InetAddress address, Short netPort) {
this.address = address;
this.netPort = netPort;
this.time = System.currentTimeMillis() / 1000L;
}
public InetAddress getAddress() { public InetAddress getAddress() {
return address; return address;
} }
public short getNetPort(){ public short getNetPort() {
return netPort; return netPort;
} }

View File

@ -79,7 +79,7 @@ public class ServerList {
/** /**
* The first 4 bytes will contain the numbers of servers we are going to * The first 4 bytes will contain the numbers of servers we are going to
* send in LE Then we have 4 bytes for the IP address in LE Finally 2 bytes * send in LE Then we have 4 bytes for the IP address in LE Finally 2 bytes
* for the net_port in LE Repeat for each server * for the net_port in LE. Repeat for each server
* *
* @param version the version of the client * @param version the version of the client
* @return the raw bytes to send to the client * @return the raw bytes to send to the client
@ -117,7 +117,7 @@ public class ServerList {
builder.write(portLE); builder.write(portLE);
} }
catch (IOException ex) { catch (IOException ex) {
System.err.println("createResponse: IOException while writing server data)"); System.err.println("createResponse: IOException while writing server data");
} }
} }
} }
@ -128,6 +128,23 @@ public class ServerList {
byteBuffer.clear(); byteBuffer.clear();
byteBuffer.put(builder.toByteArray()); byteBuffer.put(builder.toByteArray());
byteBuffer.flip(); byteBuffer.flip();
try {
builder.close();
}
catch (IOException ex) {
System.err.println("createResponse: IOException in builder.close()");
}
return byteBuffer.array(); return byteBuffer.array();
} }
public void dumpOnlineServers() {
if (!Main.running.get()) {
return;
}
var thread = new Thread(new ClientEmulator(serverList));
thread.start();
}
} }

View File

@ -16,6 +16,10 @@
*/ */
package com.diamante.serverlist; package com.diamante.serverlist;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.BufferOverflowException; import java.nio.BufferOverflowException;
import java.nio.ReadOnlyBufferException; import java.nio.ReadOnlyBufferException;
@ -48,7 +52,7 @@ public class Utils {
} }
/** /**
* Flips the array of in around * Flips the array around
* *
* @param in array of in. Length must be multiple of 4 * @param in array of in. Length must be multiple of 4
*/ */
@ -113,7 +117,7 @@ public class Utils {
return buffer.getShort(0); return buffer.getShort(0);
} }
catch (BufferOverflowException | ReadOnlyBufferException ex) { catch (BufferOverflowException | ReadOnlyBufferException ex) {
System.err.println("longSwap: BufferOverflowException or ReadOnlyBufferException"); System.err.println("shortSwap: BufferOverflowException or ReadOnlyBufferException");
return -1; return -1;
} }
} }
@ -129,4 +133,19 @@ public class Utils {
buffer.putShort(in); buffer.putShort(in);
return buffer.array(); return buffer.array();
} }
public static Server stringToServer(String in) {
try {
var parts = in.split(":");
if (parts.length < 2) {
return null;
}
return new Server(InetAddress.getByName(parts[0]), Short.parseShort(parts[1]));
}
catch (UnknownHostException ex) {
return null;
}
}
} }