Minecraft Status

CLI tool to check Minecraft server status

Minecraft server status checker

Example usage

With the default port 25565:

python mc_status.py --host example.example.com

Or, with a custom port:

python mc_status.py --host example.example.com --port 1234

References

https://wiki.vg/Server_List_Ping#Status_Request

https://gist.github.com/barneygale/1209061

https://gist.github.com/clarence112/9a3e971283d7f4052a0c33f11de9b7c5

Python script

import socket
import struct
import json
import argparse

def unpack_varint(s):
    d = 0
    for i in range(5):
        b = ord(s.recv(1))
        d |= (b & 0x7F) << 7*i
        if not b & 0x80:
            break
    return d

def pack_varint(d):
    o = b""
    while True:
        b = d & 0x7F
        d >>= 7
        o += struct.pack("B", b | (0x80 if d > 0 else 0))
        if d == 0:
            break
    return o

def pack_data(d):
    h = pack_varint(len(d))
    if type(d) == str:
        d = bytes(d, "utf-8")
    return h + d

def pack_port(i):
    return struct.pack('>H', i)

def get_info(host='localhost', port=25565):

    s = None
    # First try to resolve and connect the host as a domain that points to an A record
    try:
        host = socket.getaddrinfo(host, None, socket.AF_INET)[0][4][0]
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((host, port))
    except socket.gaierror:
        pass
    # Then try to resolve and connect the host as an AAAA record
    if not s:
        try:
            host = socket.getaddrinfo(host, None, socket.AF_INET6)[0][4][0]
            s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
            s.connect((host, port, 0, 0))
        except socket.gaierror:
            pass
    # If the host is still unresolved, raise an error
    if not s:
        raise socket.gaierror(f"Host could not be resolved: {host}")

    # Send handshake + status request
    s.send(pack_data(b"\x00\x00" + pack_data(host.encode('utf8')) + pack_port(port) + b"\x01"))
    s.send(pack_data("\x00"))

    # Read response
    unpack_varint(s)     # Packet length
    unpack_varint(s)     # Packet ID
    l = unpack_varint(s) # String length

    d = b""
    while len(d) < l:
        d += s.recv(1024)

    # Close our socket
    s.close()

    # Load json and return
    return json.loads(d.decode('utf8'))


def main():
    parser = argparse.ArgumentParser(description='Get Minecraft server information.')
    parser.add_argument('--host', type=str, help='The host of the server.')
    parser.add_argument('--port', type=int, default=25565, help='The port of the server.')
    args = parser.parse_args()
    print(get_info(args.host, args.port))


if __name__ == "__main__":
    main()