[Bukkit plug-in development tutorial] [high quality plug-in series] [protocol] teaches you how to Ping the server from the outside

Keywords: Java Back-end Network Protocol Minecraft

Introduction

        As we all know, the server and client of Minecraft are separated. The client and the server communicate with each other through TCP / IP (especially Java version and UDP version) (so we need to configure the port property of server.properties on the server and the input required for client connection   IP:PORT). If we know the specific communication protocol between the client and the server, we can disguise the client to initiate an access request to the server for a series of operations (such as pressure test)

Knowledge points

The knowledge points involved in this section are as follows:

  • Parsing and encapsulation of specific protocols
  • Socket API
  • BIO

explain

         As we all know, as of the posting date, the latest version of Minecraft Server has reached 1.18 +. For such a mature project that has carried out countless version iterations for many years, its protocol must also undergo a series of development and changes. Therefore, considering the difficulty of getting started, this paper will introduce the MC C / S communication protocol version from low to high, based on the downward compatibility of MC Server , the higher version server also supports the resolution of lower version clients. Therefore, we use Sugarcane 1.17.1 to complete the test of this chapter with the higher version server.

start

BETA 1.8 - 1.3

         Before Minecraft 1.4, if you need to request the server to return the current basic information, you only need to send 0xFE to the server, and the server will return its current status information according to the following protocol:

Field nameField typematters needing attention
Package IDByteThe returned package ID should be:   0xFF
Field lengthShortThe length of the remainder of the packet
MOTDA paragraph to   UTF-16BE   Encoded stringFrom here on, all fields should be separated by § in the same string. The maximum length of this string is 64 bytes.
Number of online playersThe number of players currently playing on the server
Maximum playersMaximum number of players the server can support

Based on the above, we can write the following program to parse the data packet. Here we give the general tools and methods first

    /**
     * Gets the verified legal string content
     * @apiNote The packet ID must be 0xFF and the length must be legal
     * */
    protected static String getSecureString(InputStream inputStream, InputStreamReader inputStreamReader) throws IOException {
        int packetId = inputStream.read();
        if (packetId == -1)
            throw new IOException("Premature end of stream.");
        if (packetId != 0xFF)
            throw new IOException("Invalid packet ID (" + packetId + ").");
        int length = inputStreamReader.read();
        if (length == -1)
            throw new IOException("Premature end of stream.");
        if (length == 0)
            throw new IOException("Invalid string length.");

        char[] chars = new char[length];
        if (inputStreamReader.read(chars, 0, length) != length)
            throw new IOException("Premature end of stream.");
        return new String(chars);
    }

  Parsing code

    /**
     * @version BETA - 1.3
     * */
    private void connect() throws IOException {
        try (
                Socket socket = new Socket()
        ) {
            socket.setSoTimeout(TIMEOUT);
            socket.connect(new InetSocketAddress(host, port), TIMEOUT);
            try (
                    OutputStream dataOutputStream = socket.getOutputStream();
                    InputStream inputStream = socket.getInputStream();
                    InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_16BE);
            ) {
                dataOutputStream.write(0xFE);

                String string = getSecureString(inputStream, inputStreamReader);
                String[] args = string.split("§");
                motd = args[0];
                onlinePlayers = Integer.parseInt(args[1]);
                maxPlayers = Integer.parseInt(args[2]);
            }
        }
    }

The returned data content (sensitive part) has been processed

Raw data (visual data other than package ID and field length)

After analysis

         Careful partners can see that data such as motd and the number of online players have been obtained, but some data such as serverVersion have not been captured. Don't worry, these are the elements added to the high version protocol.

1.6

        A small partner will ask: why do you talk about 1.6 first? Where are 1.4 and 1.5? The reason is very simple, because the protocols of 1.4 and 1.5 are simplified versions of 1.6 and 1.6   In order to be compatible with the previous version, the Notchian server only accepts the old version of the protocol.

Client to server

        For 1.4 + MC client and server   TCP connection. It does not perform authentication and login (e.g agreement and Protocol encryption Instead, it sends packets in the following format:

  1. FE - packet identifier of server list ping
  2. 01 - payload of server list ping (always 1)
  3. FA - packet identifier of plug-in message
  4. 00 0B - the length of the following string, in characters, as a short string (always 11)
  5. 00 4D 00 43 00 7C 00 50 00 69 00 6E 00 67 00 48 00 6F 00 73 00 74 - coded as UTF-16BE String MC|PingHost of string
  6. XX XX - length of other data, as short. Calculated as, where is the number of bytes in UTF-16BE encoded hostname. 7 + len(hostname)len(hostname)
  7. XX—Protocol version For example, the last version (74) 4a
  8. XX - the length of the following string, in characters, as a short string
  9. ... - the hostname to which the client connects, encoded as UTF-16BE character string
  10. XX - the port to which the client is connecting, as an integer.

Note: all data types are big endian   For downward compatibility, all Notchian servers only care about the first three bytes (and you can only send these three bytes), while the Bukkit server only cares about the first two bytes. After reading, the response will be sent to the client, and all legacy servers (< = 1.6) will respond accordingly. FE 01 FA

Packet example:

0000000: fe01 fa00 0b00 4d00 4300 7c00 5000 6900  ......M.C.|.P.i.
0000010: 6e00 6700 4800 6f00 7300 7400 1949 0009  n.g.H.o.s.t..I..
0000020: 006c 006f 0063 0061 006c 0068 006f 0073  .l.o.c.a.l.h.o.s
0000030: 0074 0000 63dd                           .t..c.

Server to client

         After the first three bytes, the form is encoded as   The data packet of UTF-16BE string starts with ASCII 0167 characters and is used between each element   \ 0 as the separator, the specific resolution is as follows:  

Field nameField typematters needing attention
Package IDByteThe returned package ID should be:   0xFF
Field lengthShortThe length of the remainder of the packet

Protocol version

A paragraph to   UTF-16BE   Encoded stringfor example   seventy-four
Server versionas   1.8.7
MOTDFrom here on, all fields should be separated by § in the same string. The maximum length of this string is 64 bytes.
Number of online playersThe number of players currently playing on the server
Maximum playersMaximum number of players the server can support

Parsing code

    private void connect() throws IOException {
        try (
                Socket socket = new Socket()
        ) {
            socket.setSoTimeout(TIMEOUT);
            socket.connect(new InetSocketAddress(host, port), TIMEOUT);
            try (
                    DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
                    InputStream inputStream = socket.getInputStream();
                    InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_16BE);
            ) {
                dataOutputStream.write(new byte[]{(byte) 0xFE, (byte) 0x01});

                String string = ServerInfoV1_3.getSecureString(inputStream, inputStreamReader);

                if (string.startsWith("§")) {
                    String[] data = string.split("\0");
                    pingVersion = Integer.parseInt(data[0].substring(1));
                    protocolVersion = Integer.parseInt(data[1]);
                    serverVersion = data[2];
                    motd = data[3];
                    onlinePlayers = Integer.parseInt(data[4]);
                    maxPlayers = Integer.parseInt(data[5]);
                } else {
                    String[] data = string.split("§");
                    motd = data[0];
                    onlinePlayers = Integer.parseInt(data[1]);
                    maxPlayers = Integer.parseInt(data[2]);
                }
            }
        }
    }

Returned data content

Raw data (visual data other than package ID and field length)

After analysis

1.4 - 1.5

         Before Minecraft 1.6, the operation from the client to the server was much simpler. Only two bytes of start ID were sent: FE 01

current

         After 1.6 +, the connection mode between the client and the server changes.

1. Shake hands

First, the client send status is set to 1   Handshake Data packet.

Packet identificationField nameField typenote
0x00Protocol versionVarIntsee also Protocol version number . the version the client plans to use to connect to the server (not important for ping). If the client is pinging to determine the version to use, it should be set by convention. - 1
server addresscharacter stringThe host name or IP used for the connection, such as localhost or 127.0.0.1. The notqian server does not use this information. Note that the SRV record is completely redirected. For example, if _minecraft._tcp.example.com points to mc.example.org, the user connected to example.com will provide mc.example.org as the server address in addition to connecting to it.
Server portUnsigned shortThe default value is 25565. This information is not used by the Notchian server.
Next statusVarIntstate Expected 1, but Sign in It can also be 2.

request

         Client follow up request Packet. This packet has no fields.

Packet identificationField nameField typenote
0x00No field

response

         The server should use Respond to the response packet Please note that the Notchian server will wait to receive for unknown reasons   Ping  The packet takes 30 seconds, then times out and sends a response.

Packet identificationField nameField typenote
0x00JSON responsecharacter stringSee below; As with all strings, this is prefixed with the length of VarInt

The JSON response field is a  JSON   Object in the following format:

{
    "version": {
        "name": "1.8.7",
        "protocol": 47
    },
    "players": {
        "max": 100,
        "online": 5,
        "sample": [
            {
                "name": "thinkofdeath",
                "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20"
            }
        ]
    },
    "description": {
        "text": "Hello world"
    },
    "favicon": "data:image/png;base64,<data>"
}

         For this version of protocol transmission, we need to use the varInt function specified by Minecraft to convert int to varInt type, so as to construct the correct handshake packet. The conversion code is as follows:

    /**
     * varInt Read function
     */
    protected static int readVarInt(DataInputStream in) throws IOException {
        int i = 0;
        int j = 0;
        while (true) {
            int k = in.readByte();
            i |= (k & 0x7F) << (j++ * 7);
            if (j > 5)
                throw new RuntimeException("VarInt too big");
            if ((k & 0x80) != 0x80)
                break;
        }
        return i;
    }
    /**
     * varInt Write function
     * */
    protected static void writeVarInt(DataOutputStream out, int paramInt) throws IOException {
        while (true) {
            if ((paramInt & ~0x7F) == 0) {
                out.writeByte(paramInt);
                return;
            }
            out.writeByte(paramInt & 0x7F | 0x80);
            paramInt >>>= 7;
        }
    }

Based on the above, the following parsing code can be given, and the parsing json part will not be repeated:

    /**
     * The format of sending data packet is: packet length + content
     * */
    private void connect() throws IOException {
        try (Socket socket = new Socket()) {
            socket.setSoTimeout(9000);
            socket.connect(new InetSocketAddress(host, port), 9000);
            try (
                    DataOutputStream out = new DataOutputStream(socket.getOutputStream());
                    DataInputStream in = new DataInputStream(socket.getInputStream());
                    //> Handshake
                    ByteArrayOutputStream handshake_bytes = new ByteArrayOutputStream();
                    DataOutputStream handshake = new DataOutputStream(handshake_bytes);
            ) {
                handshake.writeByte(PACKET_HANDSHAKE);
                writeVarInt(handshake, packageProtocolVersion);
                writeVarInt(handshake, host.length());
                handshake.writeBytes(host);
                handshake.writeShort(port);
                writeVarInt(handshake, PACKET_STATUS_HANDSHAKE);

                //< Status Handshake
                writeVarInt(out, handshake_bytes.size()); // Size of packet
                out.write(handshake_bytes.toByteArray());

                //< Status Request
                out.writeByte(0x01); // Size of packet
                out.writeByte(PACKET_STATUS_REQUEST);

                //< Status Response
                // https://wiki.vg/Protocol#Response
                readVarInt(in); // Size
                pingVersion = readVarInt(in);
                int length = readVarInt(in);
                byte[] data = new byte[length];
                in.readFully(data);
                String json = new String(data, StandardCharsets.UTF_8);

                JsonObject jsonObject = new Gson().fromJson(json, JsonObject.class);

                parseJson(jsonObject);
            }
        }
    }

Parsed data (the original data is in json form, there are a large number of key value pairs, which are too chaotic and will not be given)

last

         Minecraft series will be continuously updated, and biological AI, self-made class library and other contents will be updated in the later stage. If you are also an MC fan, you might as well pay attention to a wave~

Posted by Xproterg^vi on Wed, 01 Dec 2021 15:28:20 -0800