0

I'm trying to connect to the RCON of a Steam game-server which uses the Steam RCON Protocol. I googled a bit and found the following code that someone did a while back for this exact purpose. After examining the code for a while, it really seems like it should work, but for some reason it doesn't.

Here is the code:

class RconSteam {

    /*
     * The constants are the packet types.
     */
    static final int EXECUTE_COMMAND_PACKET = 2;
    static final int AUTHORIZATION_PACKET = 3;
    static final int AUTHORIZATION_RESPONSE = 2;
    static final SocketAddress serverAddress = new InetSocketAddress("IP ADDRESS", PORT);
    static final String rconPassword = "password";
    static InputStream inputStream;
    static OutputStream outputStream;

    /**
     * Starts the application.
     * 
     * @param args
     *            command-line arguments
     * @throws IOException
     */
    public static void main(String[] args) {
        try {
            /*
             * Prepare the socket and retrieve the streams.
             */
            Socket socket = new Socket();
            socket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 0));
            socket.connect(serverAddress);
            inputStream = socket.getInputStream();
            outputStream = socket.getOutputStream();

            /*
             * Authorize the user and then send commands. Multiple commands can
             * be sent, but the user may need to be reauthorized after some
             * time.
             */
            boolean auth = sendAuthorizationPacket();
            if (auth) {
                if(sendCommand("serverchat HELLO"))
                {
                    System.out.println("WINNER");
                } else {
                    System.out.println("FAILED");
                }
            } else {
                System.out.println("OH NOES");
            }
            /*
             * Close the socket once its done being used.
             */
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Sends a command to the server. The user must be authorized for the server
     * to execute the commands.
     * 
     * @param command
     *            the command
     * @throws IOException
     */
    static boolean sendCommand(String command) throws IOException {
        byte[] packet = createPacket(2000, EXECUTE_COMMAND_PACKET, command);
        outputStream.write(packet);

        /*
         * The server responds to command execution packets as well, but those
         * responses are not very important. However, it may be worthwhile to
         * check if the server actually responded with text. If a response
         * contains no textual message, that usually means that the user needs
         * to be reauthorized with the server. Another way to check if the user
         * needs to be reauthorized is simply by joining the server and seeing
         * if the commands sent have any effect.
         */
        ByteBuffer response = parsePacket();

        int type = response.getInt(8);
        int id = response.getInt(4);

        System.out.println(type);
        System.out.println(id);

        return ((type == EXECUTE_COMMAND_PACKET) && (id) == 2000);
    }

    /**
     * Sends an authorization packet to the server.
     * 
     * @return whether or not the user was authorized by the server
     * @throws IOException
     */
    static boolean sendAuthorizationPacket() throws IOException {
        /*
         * Create the authorization packet and send it.
         */
        byte[] packet = createPacket(1000, AUTHORIZATION_PACKET, rconPassword);
        outputStream.write(packet);

        /*
         * Read the response (the first packet is a junk one) and check if the
         * server authorized the user. The user is authorized if the server
         * responds with the same packet ID as the one sent, which is 1000 in
         * this case.
         */
        ByteBuffer response = parsePacket();
        return (response.getInt(8) == AUTHORIZATION_RESPONSE) && (response.getInt(4) == 1000);
    }

    /**
     * Creates an RCON packet.
     * 
     * @param id
     *            the packet ID (not an opcode)
     * @param type
     *            the type
     * @param command
     *            the command
     * @return the bytes representing the packet
     */
    static byte[] createPacket(int id, int type, String command) {
        ByteBuffer packet = ByteBuffer.allocate(command.length() + 16);
        packet.order(LITTLE_ENDIAN);
        packet.putInt(command.length() + 12).putInt(id).putInt(type).put(command.getBytes()).putInt(0);
        return packet.array();
    }

    /**
     * Parses the next packet from the socket's input stream.
     * 
     * @return the next packet
     * @throws IOException
     */
    static ByteBuffer parsePacket() throws IOException {
        /*
         * Read the length of the packet.
         */
        byte[] length = new byte[4];
        inputStream.read(length);

        /*
         * Create a buffer to contain the packet's payload.
         */
        ByteBuffer packet = ByteBuffer.allocate(4120);
        packet.order(LITTLE_ENDIAN);
        packet.put(length);

        /*
         * Read the payload.
         */
        for (int i = 0; i < packet.getInt(0); i++) {
            packet.put((byte) inputStream.read());
        }

        return packet;
    }
}

The program runs through the program without errors, however it seems to only get a response from the server the first time it sends a packet. I can successfully authenticate to the server and get the reponse for that from the server, but the second command doesn't seem to get properly responded to and closes the connection(?).

I tried testing to skip to the serverchat HELLO command right away, and I do get a reponse from the server that I'm not authenticated.

I thought I'd look whats going on with Wireshark, but I'm not sure what to make of this. My limited wireshark experience tells me that there is something wrong after sending the 2nd packet. https://i.sstatic.net/DomzM.png

At this point, I am not entirely sure if the problem lies within this code, or if it's server or network related (if so, sorry for posting on SO). Other tools seem to work fine though, so I'm guessing it's code-related.

Any help appreciated!

EDIT: Link to the screenshot if it's too small: https://i.sstatic.net/DomzM.png

EDIT 2: Code after update:

class RconSteam {

    /*
     * The constants are the packet types.
     */
    static final int EXECUTE_COMMAND_PACKET = 2;
    static final int AUTHORIZATION_PACKET = 3;
    static final int AUTHORIZATION_RESPONSE = 2;
    static final SocketAddress serverAddress = new InetSocketAddress("IP HERE", PORT);
    static final String rconPassword = "password\0";
    static InputStream inputStream;
    static OutputStream outputStream;

    /**
     * Starts the application.
     * 
     * @param args
     *            command-line arguments
     * @throws IOException
     */
    public static void main(String[] args) {
        try {
            /*
             * Prepare the socket and retrieve the streams.
             */
            Socket socket = new Socket();
            socket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 0));
            socket.connect(serverAddress);
            inputStream = socket.getInputStream();
            outputStream = socket.getOutputStream();

            /*
             * Authorize the user and then send commands. Multiple commands can
             * be sent, but the user may need to be reauthorized after some
             * time.
             */
            boolean auth = sendAuthorizationPacket();
            if (auth) {
                if(sendCommand("serverchat HELLO"))
                {
                    System.out.println("WINNER");
                } else {
                    System.out.println("FAILED");
                }
            } else {
                System.out.println("OH NOES");
            }
            /*
             * Close the socket once its done being used.
             */
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Sends a command to the server. The user must be authorized for the server
     * to execute the commands.
     * 
     * @param command
     *            the command
     * @throws IOException
     */
    static boolean sendCommand(String command) throws IOException {
        byte[] packet = createPacket(2000, EXECUTE_COMMAND_PACKET, command+'\0');
        outputStream.write(packet);

        /*
         * The server responds to command execution packets as well, but those
         * responses are not very important. However, it may be worthwhile to
         * check if the server actually responded with text. If a response
         * contains no textual message, that usually means that the user needs
         * to be reauthorized with the server. Another way to check if the user
         * needs to be reauthorized is simply by joining the server and seeing
         * if the commands sent have any effect.
         */
        ByteBuffer response = parsePacket();

        int type = response.getInt(8);
        int id = response.getInt(4);

        System.out.println(type);
        System.out.println(id);

        return ((type == EXECUTE_COMMAND_PACKET) && (id) == 2000);
    }

    /**
     * Sends an authorization packet to the server.
     * 
     * @return whether or not the user was authorized by the server
     * @throws IOException
     */
    static boolean sendAuthorizationPacket() throws IOException {
        /*
         * Create the authorization packet and send it.
         */
        byte[] packet = createPacket(1000, AUTHORIZATION_PACKET, rconPassword);
        outputStream.write(packet);

        /*
         * Read the response (the first packet is a junk one) and check if the
         * server authorized the user. The user is authorized if the server
         * responds with the same packet ID as the one sent, which is 1000 in
         * this case.
         */
        ByteBuffer response = parsePacket();
        return (response.getInt(8) == AUTHORIZATION_RESPONSE) && (response.getInt(4) == 1000);
    }

    /**
     * Creates an RCON packet.
     * 
     * @param id
     *            the packet ID (not an opcode)
     * @param type
     *            the type
     * @param command
     *            the command
     * @return the bytes representing the packet
     */
    static byte[] createPacket(int id, int type, String command) {
        ByteBuffer packet = ByteBuffer.allocate(command.length() + 16);
        packet.order(LITTLE_ENDIAN);
        //packet.putInt(command.length() + 12).putInt(id).putInt(type).put(command.getBytes()).putInt(0);
        packet.putInt(command.length() + 14).putInt(id).putInt(type).put(command.getBytes()).put((byte)0).put((byte)0);
        return packet.array();
    }

    /**
     * Parses the next packet from the socket's input stream.
     * 
     * @return the next packet
     * @throws IOException
     */
    static ByteBuffer parsePacket() throws IOException {
        /*
         * Read the length of the packet.
         */
        byte[] length = new byte[4];
        inputStream.read(length);

        /*
         * Create a buffer to contain the packet's payload.
         */
        ByteBuffer packet = ByteBuffer.allocate(4120);
        packet.order(LITTLE_ENDIAN);
        packet.put(length);

        /*
         * Read the payload.
         */
        for (int i = 0; i < packet.getInt(0); i++) {
            packet.put((byte) inputStream.read());
        }

        return packet;
    }
}

1 Answer 1

0

The problem seems to be in the way you create packet.

static byte[] createPacket(int id, int type, String command) {
  ByteBuffer packet = ByteBuffer.allocate(command.length() + 16);
  packet.order(ByteOrder.LITTLE_ENDIAN);
  packet.putInt(command.length() + 12).putInt(id).putInt(type).put(command.getBytes()).putInt(0);
  return packet.array();}

One symptom is the fact that you allocate extra 16 bytes, but increase the length only by 12.

From the Steam RCON Protocol page the packet structure is as follows:

Size            32-bit little-endian Signed Integer
ID              32-bit little-endian Signed Integer
Type            32-bit little-endian Signed Integer
Body            Null-terminated ASCII String
Empty String    Null-terminated ASCII String (0x00)

Please note few things, which are missing from your implementation:

  1. Your commands are not NULL terminated Strings (Java does not terminate String with null. Steam protocol however requires it. You have to add a single zero byte after putting the command into the ByteBuffer

  2. You are terminating the packet with integer of value zero, rather than a single zero byte.

  3. Your size should equal:
    3 integers + command length + 1 byte + 1 byte == command length + 14

Therefore your packet creation should be:

packet.putInt(command.length() + 14).putInt(id).putInt(type).put(command.getBytes()).put(0).put(0);

Hope this helps.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for reply. I tried your suggestions, but the problem persists. Maybe I missed something? I added the edited code at the bottom of the OP.
I should note that the authentication to the server (aka first packet) does work both before and after your suggestions. It's just the 2nd packet that never works.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.