QueryThreadGs4.java

net.minecraft.server.rcon.thread.QueryThreadGs4

信息

  • 全限定名:net.minecraft.server.rcon.thread.QueryThreadGs4
  • 类型:public class
  • 包:net.minecraft.server.rcon.thread
  • 源码路径:src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java
  • 起始行号:L25
  • 继承:GenericThread
  • 职责:

    TODO

字段/常量

  • LOGGER

    • 类型: Logger
    • 修饰符: private static final
    • 源码定位: L26
    • 说明:

      TODO

  • GAME_TYPE

    • 类型: String
    • 修饰符: private static final
    • 源码定位: L27
    • 说明:

      TODO

  • GAME_ID

    • 类型: String
    • 修饰符: private static final
    • 源码定位: L28
    • 说明:

      TODO

  • CHALLENGE_CHECK_INTERVAL

    • 类型: long
    • 修饰符: private static final
    • 源码定位: L29
    • 说明:

      TODO

  • RESPONSE_CACHE_TIME

    • 类型: long
    • 修饰符: private static final
    • 源码定位: L30
    • 说明:

      TODO

  • lastChallengeCheck

    • 类型: long
    • 修饰符: private
    • 源码定位: L31
    • 说明:

      TODO

  • port

    • 类型: int
    • 修饰符: private final
    • 源码定位: L32
    • 说明:

      TODO

  • serverPort

    • 类型: int
    • 修饰符: private final
    • 源码定位: L33
    • 说明:

      TODO

  • maxPlayers

    • 类型: int
    • 修饰符: private final
    • 源码定位: L34
    • 说明:

      TODO

  • serverName

    • 类型: String
    • 修饰符: private final
    • 源码定位: L35
    • 说明:

      TODO

  • worldName

    • 类型: String
    • 修饰符: private final
    • 源码定位: L36
    • 说明:

      TODO

  • socket

    • 类型: DatagramSocket
    • 修饰符: private
    • 源码定位: L37
    • 说明:

      TODO

  • buffer

    • 类型: byte[]
    • 修饰符: private final
    • 源码定位: L38
    • 说明:

      TODO

  • hostIp

    • 类型: String
    • 修饰符: private
    • 源码定位: L39
    • 说明:

      TODO

  • serverIp

    • 类型: String
    • 修饰符: private
    • 源码定位: L40
    • 说明:

      TODO

  • validChallenges

    • 类型: Map<SocketAddress,QueryThreadGs4.RequestChallenge>
    • 修饰符: private final
    • 源码定位: L41
    • 说明:

      TODO

  • rulesResponse

    • 类型: NetworkDataOutputStream
    • 修饰符: private final
    • 源码定位: L42
    • 说明:

      TODO

  • lastRulesResponse

    • 类型: long
    • 修饰符: private
    • 源码定位: L43
    • 说明:

      TODO

  • serverInterface

    • 类型: ServerInterface
    • 修饰符: private final
    • 源码定位: L44
    • 说明:

      TODO

内部类/嵌套类型

  • net.minecraft.server.rcon.thread.QueryThreadGs4.RequestChallenge
    • 类型: class
    • 修饰符: private static
    • 源码定位: L269
    • 说明:

      TODO

构造器

private QueryThreadGs4(ServerInterface serverInterface, int port) @ L46

  • 构造器名:QueryThreadGs4
  • 源码定位:L46
  • 修饰符:private

参数:

  • serverInterface: ServerInterface
  • port: int

说明:

TODO

方法

下面的方法块按源码顺序生成。

public static QueryThreadGs4 create(ServerInterface serverInterface) @ L74

  • 方法名:create
  • 源码定位:L74
  • 返回类型:QueryThreadGs4
  • 修饰符:public static

参数:

  • serverInterface: ServerInterface

说明:

TODO

private void sendTo(byte[] data, DatagramPacket src) @ L85

  • 方法名:sendTo
  • 源码定位:L85
  • 返回类型:void
  • 修饰符:private

参数:

  • data: byte[]
  • src: DatagramPacket

说明:

TODO

private boolean processPacket(DatagramPacket packet) @ L89

  • 方法名:processPacket
  • 源码定位:L89
  • 返回类型:boolean
  • 修饰符:private

参数:

  • packet: DatagramPacket

说明:

TODO

private byte[] buildRuleResponse(DatagramPacket packet) @ L131

  • 方法名:buildRuleResponse
  • 源码定位:L131
  • 返回类型:byte[]
  • 修饰符:private

参数:

  • packet: DatagramPacket

说明:

TODO

private byte[] getIdentBytes(SocketAddress src) @ L184

  • 方法名:getIdentBytes
  • 源码定位:L184
  • 返回类型:byte[]
  • 修饰符:private

参数:

  • src: SocketAddress

说明:

TODO

private Boolean validChallenge(DatagramPacket src) @ L188

  • 方法名:validChallenge
  • 源码定位:L188
  • 返回类型:Boolean
  • 修饰符:private

参数:

  • src: DatagramPacket

说明:

TODO

private void sendChallenge(DatagramPacket src) @ L198

  • 方法名:sendChallenge
  • 源码定位:L198
  • 返回类型:void
  • 修饰符:private

参数:

  • src: DatagramPacket

说明:

TODO

private void pruneChallenges() @ L204

  • 方法名:pruneChallenges
  • 源码定位:L204
  • 返回类型:void
  • 修饰符:private

参数:

说明:

TODO

public void run() @ L214

  • 方法名:run
  • 源码定位:L214
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

public boolean start() @ L239

  • 方法名:start
  • 源码定位:L239
  • 返回类型:boolean
  • 修饰符:public

参数:

说明:

TODO

private void recoverSocketError(Exception e) @ L248

  • 方法名:recoverSocketError
  • 源码定位:L248
  • 返回类型:void
  • 修饰符:private

参数:

  • e: Exception

说明:

TODO

private boolean initSocket() @ L258

  • 方法名:initSocket
  • 源码定位:L258
  • 返回类型:boolean
  • 修饰符:private

参数:

说明:

TODO

代码

public class QueryThreadGs4 extends GenericThread {
    private static final Logger LOGGER = LogUtils.getLogger();
    private static final String GAME_TYPE = "SMP";
    private static final String GAME_ID = "MINECRAFT";
    private static final long CHALLENGE_CHECK_INTERVAL = 30000L;
    private static final long RESPONSE_CACHE_TIME = 5000L;
    private long lastChallengeCheck;
    private final int port;
    private final int serverPort;
    private final int maxPlayers;
    private final String serverName;
    private final String worldName;
    private DatagramSocket socket;
    private final byte[] buffer = new byte[1460];
    private String hostIp;
    private String serverIp;
    private final Map<SocketAddress, QueryThreadGs4.RequestChallenge> validChallenges;
    private final NetworkDataOutputStream rulesResponse;
    private long lastRulesResponse;
    private final ServerInterface serverInterface;
 
    private QueryThreadGs4(ServerInterface serverInterface, int port) {
        super("Query Listener");
        this.serverInterface = serverInterface;
        this.port = port;
        this.serverIp = serverInterface.getServerIp();
        this.serverPort = serverInterface.getServerPort();
        this.serverName = serverInterface.getServerName();
        this.maxPlayers = serverInterface.getMaxPlayers();
        this.worldName = serverInterface.getLevelIdName();
        this.lastRulesResponse = 0L;
        this.hostIp = "0.0.0.0";
        if (!this.serverIp.isEmpty() && !this.hostIp.equals(this.serverIp)) {
            this.hostIp = this.serverIp;
        } else {
            this.serverIp = "0.0.0.0";
 
            try {
                InetAddress addr = InetAddress.getLocalHost();
                this.hostIp = addr.getHostAddress();
            } catch (UnknownHostException var4) {
                LOGGER.warn("Unable to determine local host IP, please set server-ip in server.properties", (Throwable)var4);
            }
        }
 
        this.rulesResponse = new NetworkDataOutputStream(1460);
        this.validChallenges = Maps.newHashMap();
    }
 
    public static @Nullable QueryThreadGs4 create(ServerInterface serverInterface) {
        int port = serverInterface.getProperties().queryPort;
        if (0 < port && 65535 >= port) {
            QueryThreadGs4 result = new QueryThreadGs4(serverInterface, port);
            return !result.start() ? null : result;
        } else {
            LOGGER.warn("Invalid query port {} found in server.properties (queries disabled)", port);
            return null;
        }
    }
 
    private void sendTo(byte[] data, DatagramPacket src) throws IOException {
        this.socket.send(new DatagramPacket(data, data.length, src.getSocketAddress()));
    }
 
    private boolean processPacket(DatagramPacket packet) throws IOException {
        byte[] buf = packet.getData();
        int len = packet.getLength();
        SocketAddress socketAddress = packet.getSocketAddress();
        LOGGER.debug("Packet len {} [{}]", len, socketAddress);
        if (3 <= len && -2 == buf[0] && -3 == buf[1]) {
            LOGGER.debug("Packet '{}' [{}]", PktUtils.toHexString(buf[2]), socketAddress);
            switch (buf[2]) {
                case 0:
                    if (!this.validChallenge(packet)) {
                        LOGGER.debug("Invalid challenge [{}]", socketAddress);
                        return false;
                    } else if (15 == len) {
                        this.sendTo(this.buildRuleResponse(packet), packet);
                        LOGGER.debug("Rules [{}]", socketAddress);
                    } else {
                        NetworkDataOutputStream dos = new NetworkDataOutputStream(1460);
                        dos.write(0);
                        dos.writeBytes(this.getIdentBytes(packet.getSocketAddress()));
                        dos.writeString(this.serverName);
                        dos.writeString("SMP");
                        dos.writeString(this.worldName);
                        dos.writeString(Integer.toString(this.serverInterface.getPlayerCount()));
                        dos.writeString(Integer.toString(this.maxPlayers));
                        dos.writeShort((short)this.serverPort);
                        dos.writeString(this.hostIp);
                        this.sendTo(dos.toByteArray(), packet);
                        LOGGER.debug("Status [{}]", socketAddress);
                    }
                default:
                    return true;
                case 9:
                    this.sendChallenge(packet);
                    LOGGER.debug("Challenge [{}]", socketAddress);
                    return true;
            }
        } else {
            LOGGER.debug("Invalid packet [{}]", socketAddress);
            return false;
        }
    }
 
    private byte[] buildRuleResponse(DatagramPacket packet) throws IOException {
        long now = Util.getMillis();
        if (now < this.lastRulesResponse + 5000L) {
            byte[] data = this.rulesResponse.toByteArray();
            byte[] ident = this.getIdentBytes(packet.getSocketAddress());
            data[1] = ident[0];
            data[2] = ident[1];
            data[3] = ident[2];
            data[4] = ident[3];
            return data;
        } else {
            this.lastRulesResponse = now;
            this.rulesResponse.reset();
            this.rulesResponse.write(0);
            this.rulesResponse.writeBytes(this.getIdentBytes(packet.getSocketAddress()));
            this.rulesResponse.writeString("splitnum");
            this.rulesResponse.write(128);
            this.rulesResponse.write(0);
            this.rulesResponse.writeString("hostname");
            this.rulesResponse.writeString(this.serverName);
            this.rulesResponse.writeString("gametype");
            this.rulesResponse.writeString("SMP");
            this.rulesResponse.writeString("game_id");
            this.rulesResponse.writeString("MINECRAFT");
            this.rulesResponse.writeString("version");
            this.rulesResponse.writeString(this.serverInterface.getServerVersion());
            this.rulesResponse.writeString("plugins");
            this.rulesResponse.writeString(this.serverInterface.getPluginNames());
            this.rulesResponse.writeString("map");
            this.rulesResponse.writeString(this.worldName);
            this.rulesResponse.writeString("numplayers");
            this.rulesResponse.writeString(this.serverInterface.getPlayerCount() + "");
            this.rulesResponse.writeString("maxplayers");
            this.rulesResponse.writeString(this.maxPlayers + "");
            this.rulesResponse.writeString("hostport");
            this.rulesResponse.writeString(this.serverPort + "");
            this.rulesResponse.writeString("hostip");
            this.rulesResponse.writeString(this.hostIp);
            this.rulesResponse.write(0);
            this.rulesResponse.write(1);
            this.rulesResponse.writeString("player_");
            this.rulesResponse.write(0);
            String[] players = this.serverInterface.getPlayerNames();
 
            for (String player : players) {
                this.rulesResponse.writeString(player);
            }
 
            this.rulesResponse.write(0);
            return this.rulesResponse.toByteArray();
        }
    }
 
    private byte[] getIdentBytes(SocketAddress src) {
        return this.validChallenges.get(src).getIdentBytes();
    }
 
    private Boolean validChallenge(DatagramPacket src) {
        SocketAddress sockAddr = src.getSocketAddress();
        if (!this.validChallenges.containsKey(sockAddr)) {
            return false;
        } else {
            byte[] data = src.getData();
            return this.validChallenges.get(sockAddr).getChallenge() == PktUtils.intFromNetworkByteArray(data, 7, src.getLength());
        }
    }
 
    private void sendChallenge(DatagramPacket src) throws IOException {
        QueryThreadGs4.RequestChallenge challenge = new QueryThreadGs4.RequestChallenge(src);
        this.validChallenges.put(src.getSocketAddress(), challenge);
        this.sendTo(challenge.getChallengeBytes(), src);
    }
 
    private void pruneChallenges() {
        if (this.running) {
            long now = Util.getMillis();
            if (now >= this.lastChallengeCheck + 30000L) {
                this.lastChallengeCheck = now;
                this.validChallenges.values().removeIf(challenge -> challenge.before(now));
            }
        }
    }
 
    @Override
    public void run() {
        LOGGER.info("Query running on {}:{}", this.serverIp, this.port);
        this.lastChallengeCheck = Util.getMillis();
        DatagramPacket request = new DatagramPacket(this.buffer, this.buffer.length);
 
        try {
            while (this.running) {
                try {
                    this.socket.receive(request);
                    this.pruneChallenges();
                    this.processPacket(request);
                } catch (SocketTimeoutException var8) {
                    this.pruneChallenges();
                } catch (PortUnreachableException var9) {
                } catch (IOException var10) {
                    this.recoverSocketError(var10);
                }
            }
        } finally {
            LOGGER.debug("closeSocket: {}:{}", this.serverIp, this.port);
            this.socket.close();
        }
    }
 
    @Override
    public boolean start() {
        if (this.running) {
            return true;
        } else {
            return !this.initSocket() ? false : super.start();
        }
    }
 
    private void recoverSocketError(Exception e) {
        if (this.running) {
            LOGGER.warn("Unexpected exception", (Throwable)e);
            if (!this.initSocket()) {
                LOGGER.error("Failed to recover from exception, shutting down!");
                this.running = false;
            }
        }
    }
 
    private boolean initSocket() {
        try {
            this.socket = new DatagramSocket(this.port, InetAddress.getByName(this.serverIp));
            this.socket.setSoTimeout(500);
            return true;
        } catch (Exception var2) {
            LOGGER.warn("Unable to initialise query system on {}:{}", this.serverIp, this.port, var2);
            return false;
        }
    }
 
    private static class RequestChallenge {
        private final long time = new Date().getTime();
        private final int challenge;
        private final byte[] identBytes;
        private final byte[] challengeBytes;
        private final String ident;
 
        public RequestChallenge(DatagramPacket src) {
            byte[] buf = src.getData();
            this.identBytes = new byte[4];
            this.identBytes[0] = buf[3];
            this.identBytes[1] = buf[4];
            this.identBytes[2] = buf[5];
            this.identBytes[3] = buf[6];
            this.ident = new String(this.identBytes, StandardCharsets.UTF_8);
            this.challenge = RandomSource.createThreadLocalInstance().nextInt(16777216);
            this.challengeBytes = String.format(Locale.ROOT, "\t%s%d\u0000", this.ident, this.challenge).getBytes(StandardCharsets.UTF_8);
        }
 
        public Boolean before(long time) {
            return this.time < time;
        }
 
        public int getChallenge() {
            return this.challenge;
        }
 
        public byte[] getChallengeBytes() {
            return this.challengeBytes;
        }
 
        public byte[] getIdentBytes() {
            return this.identBytes;
        }
 
        public String getIdent() {
            return this.ident;
        }
    }
}

引用的其他类

  • ServerInterface

    • 引用位置: 参数/字段
  • NetworkDataOutputStream

    • 引用位置: 字段/构造调用
    • 关联成员: NetworkDataOutputStream()
  • PktUtils

    • 引用位置: 方法调用
    • 关联成员: PktUtils.intFromNetworkByteArray(), PktUtils.toHexString()
  • GenericThread

    • 引用位置: 继承
  • RandomSource

    • 引用位置: 方法调用
    • 关联成员: RandomSource.createThreadLocalInstance()
  • Util

    • 引用位置: 方法调用
    • 关联成员: Util.getMillis()