Perl Language
Упаковка и распаковка
Поиск…
Вручную преобразует синтаксис C Structs to Pack
Если вы когда-либо сталкиваетесь с C Binary API от Perl Code, используя функции syscall
, ioctl
или fcntl
, вам нужно знать, как построить память на совместимом с C пути.
Например, если вы когда-либо имели дело с какой-либо функцией, которая ожидала timespec
, вы бы заглянули в /usr/include/time.h
и находили:
struct timespec
{
__time_t tv_sec; /* Seconds. */
__syscall_slong_t tv_nsec; /* Nanoseconds. */
};
Вы танцуете с cpp
чтобы найти, что это на самом деле означает:
cpp -E /usr/include/time.h -o /dev/stdout | grep __time_t
# typedef long int __time_t;
cpp -E /usr/include/time.h -o /dev/stdout | grep __syscall_slong_t
# typedef long int __syscall_slong_t
Таким образом, его (подписанный) int
echo 'void main(){ printf("%#lx\n", sizeof(__syscall_slong_t)); }' |
gcc -x c -include stdio.h -include time.h - -o /tmp/a.out && /tmp/a.out
# 0x8
И это занимает 8 байтов. Итак, 64-битный подписанный int. И я на 64-битном процессоре. знак равно
Perldoc pack
говорит
q A signed quad (64-bit) value.
Поэтому, чтобы упаковать спецификацию времени:
sub packtime {
my ( $config ) = @_;
return pack 'qq', @{$config}{qw( tv_sec tv_nsec )};
}
И для распаковки времени:
sub unpacktime {
my ( $buf ) = @_;
my $out = {};
@{$out}{qw( tv_sec tv_nsec )} = unpack 'qq', $buf;
return $out;
}
Теперь вы можете просто использовать эти функции.
my $timespec = packtime({ tv_sec => 0, tv_nsec => 0 });
syscall( ..., $timespec ); # some syscall that reads timespec
later ...
syscall( ..., $timespec ); # some syscall that writes timespec
print Dumper( unpacktime( $timespec ));
Построение заголовка IPv4
Иногда вам приходится иметь дело со структурами, определенными в терминах типов данных C из Perl. Одним из таких приложений является создание сырых сетевых пакетов, если вы хотите сделать что-то более интересное, чем предлагать обычный API сокетов. Это как раз то, что для pack()
(и для unpack()
).
Обязательная часть IP-заголовка - 20 октетов (AKA «байты»). Как вы можете видеть позади этой ссылки, IP-адрес источника и получателя составляет два последних 32-битных значения в заголовке. Среди других полей есть некоторые из них с 16 битами, некоторые с 8 битами и несколько меньших фрагментов между 2 и 13 бит.
Предполагая, что в наш заголовок есть следующие переменные:
my ($dscp, $ecn, $length,
$id, $flags, $frag_off,
$ttl, $proto,
$src_ip,
$dst_ip);
Обратите внимание, что отсутствуют три поля из заголовка:
- Версия всегда 4 (в конце концов, это IPv4)
- В нашем примере IHL составляет 5, поскольку у нас нет поля опций ; длина указана в единицах 4 октета, поэтому 20 октетов дают длину 5.
- Контрольную сумму можно оставить равной 0. На самом деле нам придется ее вычислять, но код для этого здесь не касается.
Мы могли бы попытаться использовать битовые операции для построения, например, первых 32 бит:
my $hdr = 4 << 28 | 5 << 24 | $dscp << 18 | $ecn << 16 | $length;
Этот подход работает только до размера целого числа, хотя обычно это 64 бита, но может быть как минимум 32. Хуже того, это зависит от консистенции процессора, поэтому он будет работать на некоторых процессорах и терпеть неудачу на других. Попробуем pack()
:
my $hdr = pack('H2B8n', '45', sprintf("%06b%02b", $dscp, $ecn), $length);
Сначала в шаблоне задается H2
, двухсимвольная строка с двумя символами, с высокой степенью nybble . Соответствующим аргументом для пакета является «45» -версия 4, длина 5. Следующий шаблон - это B8
, 8-разрядная битовая строка, нисходящий бит-порядок внутри каждого байта . Нам нужно использовать битовые строки для управления компоновкой до кусков, меньших, чем nybble (4 бит), поэтому sprintf()
используется для построения такой битовой строки из 6 бит из $dscp
и 2 из $ecn
. Последним является n
, 16-разрядное значение без знака в байтах сети , т. Е. Всегда big-endian, независимо от того, какой у вас собственный целочисленный формат вашего процессора, и он заполняется из $length
.
Это первые 32 бита заголовка. Остальное можно построить аналогично:
шаблон | аргументация | замечания |
---|---|---|
n | $id | |
B16 | sprintf("%03b%013b", $flags, $frag_off) | То же, что и DSCP / ECN |
C2 | $ttl, $proto | Два последовательных беззнаковых октета |
n | 0 / $checksum | x может использоваться для вставки нулевого байта, но n позволяет нам указать аргумент, если мы выберем для вычисления контрольной суммы |
N2 | $src_ip, $dst_ip | используйте a4a4 чтобы упаковать результат двух вызовов gethostbyname() поскольку он уже находится в сетевом байтовом порядке! |
Таким образом, полный вызов пакета заголовка IPv4 будет следующим:
my $hdr = pack('H2B8n2B16C2nN2',
'45', sprintf("%06b%02b", $dscp, $ecn), $length,
$id, sprintf("%03b%013b", $flags, $frag_off),
$ttl, $proto, 0,
$src_ip, $dst_ip
);