0

(* In the following post, all IP's, Ports and Passwords have been changed. Sorry about the formatting of this post, the editor doesn't seem to like new lines.)

Question: How do I store integers as signed 32bit little endian?

Background: Im attempting to use RCon to connect to a minecraft server in bash. So far the server shows the connection is being received but I can't get the packet formatted correctly. I can connect to the server using mcrcon and see the packets in wireshark but when I attempt using my bash script, the packet length, requestid and type values look wrong.

The following is some of my sources, trouble shooting data and my code which may help in answering the question.

Source: https://wiki.vg/RCON

Implementation of: https://developer.valvesoftware.com/wiki/Source_RCON_Protocol

Server console:

[22:24:09 WARN]: Can't keep up! Is the server overloaded? Running 3190ms or 63 ticks behind

[22:24:23 INFO]: Rcon connection from: /164.256.8.10

[22:24:34 WARN]: Can't keep up! Is the server overloaded? Running 9961ms or 199 ticks behind

[22:24:55 WARN]: Can't keep up! Is the server overloaded? Running 2006ms or 40 ticks behind

[22:25:12 INFO]: Rcon connection from: /164.256.8.10

.

Wireshark: (mcrcon data) mcrcon packets

Code:

#!/bin/bash

# Length    int     Length of remainder of packet
# Request ID    int     Client-generated ID
# Type  int     3 for login, 2 to run a command, 0 for a multi-packet response
# Payload   byte[]  ASCII text
# 2-byte pad    byte, byte  Two null bytes 

# Connection details
RCON_HEADER=$(echo -e "\xff\xff\xff\xff")
HOST="192.168.0.173"
PORT=12345  
LENGTH=0 # Length of packet
REQUESTID=$RANDOM
PASSWORD="$1"
RES=0
COM=2
AUTH=3
NULL="\0"
COMMAND=${@:2}
echo "command: $COMMAND"



## Packet Format as per docs
#Packet Size in Bytes
#Request ID any int
#Type as above
#Body null terminated ascii string
#Empty string null terminated

build_packet()
{
    local TYPE="$1";
    $([ "$TYPE" == "$AUTH" ]) && local BODY="$PASSWORD" || local BODY=$COMMAND;
    local DATA="$REQUESTID$TYPE$BODY";    
    local LENGTH=${#DATA};
    local PACKET="$LENGTH$DATA";    
    echo $PACKET;
}



send()
{
    #local PACKET="$1"
    echo "sending: $PACKET"
    printf "$PACKET%s\0%s\0" >&5 &
}

read ()
{
    LENGTH="$1"
    RETURN=`dd bs=$1 count=1 <&5 2> /dev/null`
}


echo "trying to open socket"
# try to connect
if ! exec 5<> /dev/tcp/$HOST/$PORT; then
  echo "`basename $0`: unable to connect to $HOST:$PORT"
  exit 1
fi
echo "socket is open"


PACKET=$(build_packet $AUTH $PASSWORD);
echo "Command: $COMMAND"
echo "Packet: $PACKET"

send $PACKET

read 7
echo "RETURN: $RETURN"


PACKET=$(build_packet $COM $COMMAND);
echo "Command: $COMMAND"
echo "Packet: $PACKET"


send $PACKET

read 7
echo "RETURN: $RETURN"

.

Referenced Code: https://blog.chris007.de/using-bash-for-network-socket-operation/

Community
  • 1
  • 1
Stuperfied
  • 322
  • 2
  • 10
  • 3
    Opinionated: I wouldn't even think of using Bash to process binary data through sockets. Ever. Python or Perl come to mind instead. – m0skit0 Oct 05 '18 at 13:56
  • 2
    `bash` is simply unable to store arbitrary binary data, as any null byte is treated as a string terminator. – chepner Oct 05 '18 at 14:21
  • All other tasks on the server are performed by bash scripts, if I were to use say java or php to do this, I would then have the issue of interfacing the languages. If this can be done, it will probably also help a lot of other people. – Stuperfied Oct 05 '18 at 14:25
  • 3
    `dd` is written in C but you didn't hesitate to interface with it in your example. Bash is designed to run arbitrary programs written in arbitrary languages, but it's not designed as a general purpose languages. If you really want to choose the difficult route, you can hex encode all data before you put it into variables or arguments, and decode it right as you send it. – that other guy Oct 05 '18 at 17:27
  • That's actually not a bad idea, I was considering something along the lines of sed or piping but encoding might be the more flexible option. – Stuperfied Oct 05 '18 at 23:35
  • that other guy's solution worked. I simply hex encoded each string with null characters attached to the end. I will post an example of what I did at the end of my post. – Stuperfied Oct 07 '18 at 05:11
  • @Stuperfied - post your example and explanation as an ANSWER to your own question. There is no prohibition against it, and you have the opportunity to receive rep on the answer as well as your question -- but not the rep from selecting your own answer as answering your question (if I recall the math correctly) Also, don't forget you can use the `printf` *field-width* modifier to set the number of characters in output exactly as well. – David C. Rankin Oct 07 '18 at 05:26
  • Thank you all and if anyone has any simpler or cleaner solutions, please post for exactness. @DavidC.Rankin I have posted the answer as per your suggestion but will hold off ticking it in case anyone else posts a better one, most appreciated. – Stuperfied Oct 15 '18 at 02:54

2 Answers2

1

The solution was to hex encode and decode as follows:

build_packet()
{
    local TYPE="$1"
    local BODY=""
    $([ "$TYPE" == "$AUTH" ]) && BODY="$PASSWORD" || BODY="$COMMAND"

    # Add null chars "\0" and hex encode to preserve them "xxd -p".
    # David C Rankin also mentioned, you could possibly printf them in using flags.
    local DATA=`echo -ne "$ID\0\0\0\0$TYPE\0\0\0$BODY\0\0" | xxd -p | tr -d '\n'`

    local LEN="${#DATA}"
    LEN=$((LEN / 2))
    LEN="$(asc $LEN)"  
    LEN=`echo -ne "$LEN\0\0\0" | xxd -p | tr -d '\n'`
    local PACKET="$LEN$DATA"    
    echo $PACKET
}

Notes: # (asc) is a custom function that converts LEN to ascii char. I have no idea why the packet length has to be an ascii char but the type, which is also an integer does not. Perhaps someone else can answer that.

At transmission time the hex is decoded "echo -ne "$PACKET" | xxd -r -p >&5 &".

Stuperfied
  • 322
  • 2
  • 10
0

Pure

There is a way, for creating a binary packet by some build_pkt function.

build_pkt function

But as you could not store null character (\0) is bash strings, you have to use printf to develop your string:

declare -i REQUESTID=1

build_pkt() {
    local pkt='' len
    case $1 in
        AUTH ) local TYPE=3 BODY="$PASSWORD" ;;
        CMD ) local TYPE=2 BODY="${@:2}" ;;
        * ) return 1;;
    esac
    addtopkt() {
        local i v c
        printf -v v "%08x" $1
        for i in {3..0..-1};do
            printf -v c %03o 0x${v:2*i:2}
            pkt+=\\$c
        done
    }
    len=$(( ${#BODY} >0 ? ${#BODY}+10 : 10 ))
    addtopkt $len
    REQUESTID+=1
    addtopkt $REQUESTID
    addtopkt $TYPE
    pkt+="$BODY\0\0"
    printf "$pkt"
}

TCP connection

As my version of bash don't recognize /dev/tcp/, I use netcat (1 fork to background task) for this:

TMPFIFO=/tmp/.mcrcon-$$
mkfifo $TMPFIFO
exec 8> >(exec stdbuf -i 0 -o 0 nc $HOST $PORT >$TMPFIFO 2>&1)
exec 9<$TMPFIFO
rm $TMPFIFO

Then a quick'n dirty script to dump answer:

readrawanswer() {
     local foo LANG=C
     while IFS= read -u 9 -t .01 -r foo ;do
         printf "%q\n" "$foo"
     done
}

Let's go:

readrawanswer

build_pkt AUTH >&8
readrawanswer

I'm ready now to send help request to my spigot (minecraft) servr:

build_pkt CMD "help" >&8
readrawanswer

On my host, this will print:

$'\272\001\'\302\247e--------- \302\247fHelp: Index (1/10) \302\247e--------------------'
$'\302\2477Use /help [n] to get page n of help.'
$'\302\2476Aliases: \302\247fLists command aliases'
$'\302\2476Bukkit: \302\247fAll commands for Bukkit'
$'\302\2476Minecraft: \302\247fAll commands for Minecraft'
$'\302\2476/advancement: \302\247fA Mojang provided command.'
$'\302\2476/ban: \302\247fA Mojang provided command.'
$'\302\2476/ban-ip: \302\247fA Mojang provided command.'
$'\302\2476/banlist: \302\247fA Mojang provided command.'
$'\302\2476/bossbar: \302\247fA Mojang provided command.'

Then now, for 2nd page, I could:

build_pkt >&8 CMD "help 2" && readrawanswer

$'\307\001)\302\247e--------- \302\247fHelp: Index (2/10) \302\247e--------------------'
$'\302\2476/clear: \302\247fA Mojang provided command.'
$'\302\2476/clone: \302\247fA Mojang provided command.'
$'\302\2476/data: \302\247fA Mojang provided command.'
$'\302\2476/datapack: \302\247fA Mojang provided command.'
$'\302\2476/debug: \302\247fA Mojang provided command.'
$'\302\2476/defaultgamemode: \302\247fA Mojang provided command.'
$'\302\2476/deop: \302\247fA Mojang provided command.'
$'\302\2476/difficulty: \302\247fA Mojang provided command.'
$'\302\2476/effect: \302\247fA Mojang provided command.'

And finally, the whole help list, with pages 1 to 10:

for i in {1..10};do
    build_pkt >&8 CMD "help $i" && readrawanswer
done |
    sed 's/^.*[0-9]\/\([a-z:-]\+\): .*$/\1/p;d' |
    xargs |
    fold -s

On my host, this will render:

advancement ban ban-ip banlist bossbar clear clone data datapack debug 
defaultgamemode deop difficulty effect enchant execute experience fill 
forceload function gamemode gamerule give help kick kill list locate loot me 
minecraft:help minecraft:reload msg op pardon pardon-ip particle playsound 
plugins publish recipe reload replaceitem restart save-all save-off save-on say 
schedule scoreboard seed setblock setidletimeout setworldspawn spawnpoint 
spigot spreadplayers stop stopsound summon tag team teammsg teleport tell 
tellraw time timings title tm tp tps trigger version w weather whitelist 
worldborder xp

completion:

Once mcrcmd ready:

mcrcmd() { 
    build_pkt CMD "$@" 1>&8 && readrawanswer
}

Create a $mcrcmds array, holding all available commands:

mcrcmds=($(
    for i in {1..10};do
        build_pkt >&8 CMD "help $i" && readrawanswer
    done |
    sed 's/^.*[0-9]\/\([a-z:-]\+\): .*$/\1/p;d'
))

Then:

complete -W "${mcrcmds[*]}" mcrcmd 

Ready.

Then now mcrcmd is a function with bash completion ;-)

We are logged into a session, connected to minecraft server, ready to interact with them, by sending commands and retrieving answers.

Thanks to the link:

Source RCON Protocol

Further, go to Classic server protocol for translating $'\302\247'[0-f] to terminal colors...enter image description here

Let close nc:

exec 8>&- ; exec 9<&-

But, please consider doing this in , if not

F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137