mirror of
https://github.com/diamante0018/ServerList.git
synced 2025-04-20 11:15:43 +00:00
Add cmd line client pinger
This commit is contained in:
parent
acc58137c3
commit
bcde93dc07
7
pom.xml
7
pom.xml
@ -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>
|
||||||
|
160
src/main/java/com/diamante/serverlist/ClientEmulator.java
Normal file
160
src/main/java/com/diamante/serverlist/ClientEmulator.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
src/main/java/com/diamante/serverlist/InfoDumper.java
Normal file
51
src/main/java/com/diamante/serverlist/InfoDumper.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -42,6 +42,13 @@ 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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user