Perl Language
Empacar y desempacar
Buscar..
Conversión manual de estructuras C para empaquetar sintaxis
Si alguna vez está tratando con API Binary B desde Perl Code, a través de las syscall
, ioctl
o fcntl
, necesita saber cómo construir memoria de una manera compatible con C.
Por ejemplo, si alguna vez estuvo tratando con alguna función que esperaba una timespec
, buscaría en /usr/include/time.h
y encontraría:
struct timespec
{
__time_t tv_sec; /* Seconds. */
__syscall_slong_t tv_nsec; /* Nanoseconds. */
};
cpp
un baile con cpp
para encontrar lo que realmente significa:
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
Así que es un int (firmado)
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
Y toma 8 bytes. Así que 64bit firmó int. Y estoy en un procesador de 64 bits. =)
Perldoc pack
dice
q A signed quad (64-bit) value.
Así que para empacar un timespec:
sub packtime {
my ( $config ) = @_;
return pack 'qq', @{$config}{qw( tv_sec tv_nsec )};
}
Y para descomprimir un timepec:
sub unpacktime {
my ( $buf ) = @_;
my $out = {};
@{$out}{qw( tv_sec tv_nsec )} = unpack 'qq', $buf;
return $out;
}
Ahora puedes usar esas funciones en su lugar.
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 ));
Construyendo un encabezado IPv4
A veces hay que tratar con estructuras definidas en términos de tipos de datos C de Perl. Una de esas aplicaciones es la creación de paquetes de red sin procesar, en caso de que quiera hacer algo más sofisticado que lo que ofrece la API de socket normal. Esto es justo para lo que está disponible pack()
(y unpack()
por supuesto).
La parte obligatoria de un encabezado IP es de 20 octetos (AKA "bytes") de largo. Como puede ver detrás de este enlace, la dirección IP de origen y destino constituyen los dos últimos valores de 32 bits en el encabezado. Entre los otros campos hay algunos con 16 bits, algunos con 8 bits y algunos fragmentos más pequeños entre 2 y 13 bits.
Suponiendo que tenemos las siguientes variables para incluir en nuestro encabezado:
my ($dscp, $ecn, $length,
$id, $flags, $frag_off,
$ttl, $proto,
$src_ip,
$dst_ip);
Tenga en cuenta que faltan tres campos del encabezado:
- La versión es siempre 4 (es IPv4 después de todo)
- IHL es 5 en nuestro ejemplo ya que no tenemos un campo de opciones ; La longitud se especifica en unidades de 4 octetos, por lo que 20 octetos dan una longitud de 5.
- La suma de control se puede dejar en 0. En realidad, tendríamos que calcularlo, pero el código para hacer esto no nos concierne aquí.
Podríamos probar y usar operaciones de bits para construir, por ejemplo, los primeros 32 bits:
my $hdr = 4 << 28 | 5 << 24 | $dscp << 18 | $ecn << 16 | $length;
Este enfoque sólo funciona hasta el tamaño de un entero sin embargo, que suele ser de 64 bits, pero puede ser tan bajo como 32. Peor aún, depende de la CPU endianness por lo que funciona en algunas CPUs y dejar a los demás. Probemos pack()
:
my $hdr = pack('H2B8n', '45', sprintf("%06b%02b", $dscp, $ecn), $length);
La plantilla primero especifica H2
, una cadena hexadecimal de 2 caracteres, primero nybble alto . El argumento correspondiente al paquete es "45": versión 4, longitud 5. La siguiente plantilla es B8
, una cadena de bits de 8 bits, orden de bits descendente dentro de cada byte . Necesitamos usar cadenas de bits para controlar el diseño a trozos más pequeños que un nybble (4 bits), por lo que el sprintf()
se utiliza para construir una cadena de bits de 6 bits desde $dscp
y 2 desde $ecn
. El último es n
, un valor sin signo de 16 bits en Orden de bytes de red , es decir, siempre big-endian sin importar cuál sea el formato de entero nativo de su CPU, y se llena desde $length
.
Esos son los primeros 32 bits del encabezado. El resto se puede construir de manera similar:
Modelo | Argumento | Observaciones |
---|---|---|
n | $id | |
B16 | sprintf("%03b%013b", $flags, $frag_off) | Igual que DSCP / ECN |
C2 | $ttl, $proto | Dos octetos sin firmar consecutivos |
n | 0 / $checksum | x podría utilizarse para insertar un byte nulo, pero n nos permite especificar un argumento en el caso de que decidamos calcular una suma de comprobación |
N2 | $src_ip, $dst_ip | ¡use a4a4 para empaquetar el resultado de dos llamadas gethostbyname() como ya está en Orden de bytes de red! |
Entonces, la llamada completa para empaquetar un encabezado IPv4 sería:
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
);