Perl Language
Imballa e disimballa
Ricerca…
Conversione manuale di C Structs in Pack Sintassi
Se hai a che fare con le API C binarie dal codice Perl, tramite le funzioni syscall
, ioctl
o fcntl
, devi sapere come costruire la memoria in un modo compatibile con C.
Ad esempio, se hai mai avuto a che fare con una funzione che si aspettava un timespec
, dovresti cercare in /usr/include/time.h
e trovare:
struct timespec
{
__time_t tv_sec; /* Seconds. */
__syscall_slong_t tv_nsec; /* Nanoseconds. */
};
Fai una danza con cpp
per trovare cosa significhi veramente:
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
Quindi è un (firmato) 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
E ci vogliono 8 byte. Così firmato a 64 bit int. E io sono su un processore a 64 bit. =)
Il pack
Perldoc dice
q A signed quad (64-bit) value.
Quindi per imballare un timespec:
sub packtime {
my ( $config ) = @_;
return pack 'qq', @{$config}{qw( tv_sec tv_nsec )};
}
E per decomprimere un timespec:
sub unpacktime {
my ( $buf ) = @_;
my $out = {};
@{$out}{qw( tv_sec tv_nsec )} = unpack 'qq', $buf;
return $out;
}
Ora puoi semplicemente usare quelle funzioni.
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 ));
Costruire un'intestazione IPv4
A volte devi gestire strutture definite in termini di tipi di dati C da Perl. Una di queste applicazioni è la creazione di pacchetti di rete grezzi, nel caso in cui si desideri fare qualcosa di più interessante di quello che l'API di socket normale ha da offrire. Questo è esattamente ciò che pack()
(e unpack()
ovviamente) è lì per.
La parte obbligatoria di un'intestazione IP è lunga 20 ottetti (AKA "byte"). Come puoi vedere dietro questo link, l'indirizzo IP di origine e di destinazione costituisce gli ultimi due valori a 32 bit nell'intestazione. Tra gli altri campi vi sono alcuni con 16 bit, alcuni con 8 bit e alcuni pezzi più piccoli tra 2 e 13 bit.
Supponendo che abbiamo le seguenti variabili da inserire nella nostra intestazione:
my ($dscp, $ecn, $length,
$id, $flags, $frag_off,
$ttl, $proto,
$src_ip,
$dst_ip);
Si noti che mancano tre campi dall'intestazione:
- La versione è sempre 4 (dopo tutto è IPv4)
- IHL è 5 nel nostro esempio in quanto non abbiamo un campo opzioni ; la lunghezza è specificata in unità di 4 ottetti, quindi 20 ottetti danno una lunghezza di 5.
- Il checksum può essere lasciato a 0. In realtà dovremmo calcolarlo, ma il codice per farlo non ci riguarda qui.
Potremmo provare e usare le operazioni di bit per costruire ad esempio i primi 32 bit:
my $hdr = 4 << 28 | 5 << 24 | $dscp << 18 | $ecn << 16 | $length;
Questo approccio funziona solo fino alla dimensione di un numero intero, che di solito è di 64 bit ma può arrivare fino a 32. Peggio ancora, dipende dall'endianità della CPU, quindi funzionerà su alcune CPU e fallirà su altre. Proviamo pack()
:
my $hdr = pack('H2B8n', '45', sprintf("%06b%02b", $dscp, $ecn), $length);
Il modello specifica innanzitutto H2
, una stringa esadecimale di 2 caratteri, prima il nybble alto . L'argomento corrispondente al pacchetto è "45" - versione 4, lunghezza 5. Il modello successivo è B8
, una stringa di bit a 8 bit, ordine di bit discendente all'interno di ciascun byte . Abbiamo bisogno di utilizzare le stringhe di bit per controllare il layout fino a chunks più piccoli di un nybble (4 bit), quindi lo sprintf()
viene utilizzato per costruire una stringa di bit da 6 bit da $dscp
e 2 da $ecn
. L'ultimo è n
, un valore a 16 bit senza segno in Network Byte Order , ovvero sempre big-endian, indipendentemente dal formato intero nativo della CPU, ed è pieno di $length
.
Questo è il primo 32 bit dell'intestazione. Il resto può essere costruito in modo simile:
Modello | Discussione | Osservazioni |
---|---|---|
n | $id | |
B16 | sprintf("%03b%013b", $flags, $frag_off) | Come DSCP / ECN |
C2 | $ttl, $proto | Due ottetti consecutivi senza segno |
n | 0 / $checksum | x potrebbe essere usato per inserire un byte null ma n ci permette di specificare un argomento se dovessimo scegliere di calcolare un checksum |
N2 | $src_ip, $dst_ip | usa a4a4 per impacchettare il risultato di due chiamate gethostbyname() come già avviene in Network Byte Order! |
Quindi la chiamata completa per comprimere un header IPv4 sarebbe:
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
);