Как сохранить 32-разрядное целое число со знаком с прямым порядком байтов в bash?
(* В следующем посте все IP-адреса, порты и пароли были изменены. Извините за форматирование этого поста, редактору не нравятся новые строки.)
Вопрос: Как мне хранить целые числа как 32-битные числа с прямым порядком байтов со знаком?
Предыстория: я пытаюсь использовать RCon для подключения к серверу Minecraft в Bash. Пока сервер показывает, что соединение получено, но я не могу правильно отформатировать пакет. Я могу подключиться к серверу с помощью mcrcon и увидеть пакеты в wireshark, но когда я пытаюсь использовать свой bash-скрипт, значения длины пакета, requesttid и type выглядят неправильно.
Ниже приведены некоторые из моих источников, данные об устранении неполадок и мой код, которые могут помочь в ответе на вопрос.
Источник: https://wiki.vg/RCON
Реализация: https://developer.valvesoftware.com/wiki/Source_RCON_Protocol
Консоль сервера:
[22:24:09 ВНИМАНИЕ]: Не успеваю! Сервер перегружен? Бег 3190 мс или 63 тика позади
[22:24:23 INFO]: Rcon соединение с: /164.256.8.10
[22:24:34 ВНИМАНИЕ]: Не успеваю! Сервер перегружен? Бег 9961мс или 199 тиков позади
[22:24:55 ВНИМАНИЕ]: Не успеваю! Сервер перегружен? Запуск 2006 мс или 40 тиков позади
[22:25:12 INFO]: Rcon соединение с: /164.256.8.10
,
Код:
#!/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"
,
Код ссылки: https://blog.chris007.de/using-bash-for-network-socket-operation/
1 ответ
Решение состояло в том, чтобы закодировать и декодировать в шестнадцатеричном формате следующим образом
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
}
Примечания: # (asc) - это пользовательская функция, которая преобразует LEN в ascii char. Я понятия не имею, почему длина пакета должна быть символом ascii, а тип, который также является целым числом, - нет. Возможно, кто-то еще может ответить на это.
Во время передачи гекс декодируется "echo -ne "$PACKET" | xxd -r -p >&5 &".
Чистый баш
Есть способ bash, дляbuild_pkt
. Но поскольку вы не могли хранить нулевой символ (\0
) - это строки bash, вы должны использовать printf для разработки своей строки:
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"
}
Как моя версия bash
не узнаю /dev/tcp/
, Я использую netcat
(1 форк для фоновой задачи) для этого:
TMPFIFO=/tmp/.mcrcon-$$
mkfifo $TMPFIFO
exec 8> >(exec stdbuf -i 0 -o 0 nc $HOST $PORT >$TMPFIFO 2>&1)
exec 9<$TMPFIFO
rm $TMPFIFO
Затем быстрый скрипт для вывода ответа:
readrawanswer() {
local foo LANG=C
while IFS= read -u 9 -t .01 -r foo ;do
printf "%q\n" "$foo"
done
}
Пошли:
readrawanswer
build_pkt AUTH >&8
readrawanswer
build_pkt CMD "help" >&8
readrawanswer
На моем хосте, подключенном к spigot
сервер, это напечатает:
$'\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.'
Теперь я мог:
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.'
И наконец:
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
На моем хосте это отобразит:
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
Ради забавы:
mcrcmd() {
build_pkt CMD "$@" 1>&8 && readrawanswer
}
mcrcmds=($(
for i in {1..10};do
build_pkt >&8 CMD "help $i" && readrawanswer
done |
sed 's/^.*[0-9]\/\([a-z:-]\+\): .*$/\1/p;d'
))
complete -W "${mcrcmds[*]}" mcrcmd
То теперь mcrcmd
это функция с завершением bash;-)
Спасибо за ссылку:
Далее перейдите к Классическому протоколу сервера для перевода$'\302\247'[0-f]
к цветам терминала...
Пусть закроют nc
:
exec 8>&- ; exec 9<&-
Но, пожалуйста, подумайте о том, чтобы сделать это на perl, python, если не c