Perl Language
Inpakken en uitpakken
Zoeken…
C-structuren handmatig omzetten naar syntaxispakket
Als je ooit te maken hebt met C Binaire API's van Perl Code, via de functies syscall
, ioctl
of fcntl
, moet je weten hoe je geheugen op een C-compatibele manier kunt opbouwen.
Als u bijvoorbeeld te maken had met een functie die een timespec
verwachtte, zou u kijken naar /usr/include/time.h
en het volgende vinden:
struct timespec
{
__time_t tv_sec; /* Seconds. */
__syscall_slong_t tv_nsec; /* Nanoseconds. */
};
Je doet een dans met cpp
om te ontdekken wat dat echt betekent:
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
Het is dus een (ondertekende) 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
En het kost 8 bytes. Dus 64bit ondertekende int. En ik gebruik een 64-bits processor. =)
Perldoc- pack
zegt
q A signed quad (64-bit) value.
Dus om een timespec in te pakken:
sub packtime {
my ( $config ) = @_;
return pack 'qq', @{$config}{qw( tv_sec tv_nsec )};
}
En om een timespec uit te pakken:
sub unpacktime {
my ( $buf ) = @_;
my $out = {};
@{$out}{qw( tv_sec tv_nsec )} = unpack 'qq', $buf;
return $out;
}
Nu kunt u die functies gewoon gebruiken.
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 ));
Een IPv4-header construeren
Soms hebt u te maken met structuren die zijn gedefinieerd in termen van C-gegevenstypen van Perl. Een dergelijke toepassing is het maken van onbewerkte netwerkpakketten, voor het geval je iets leukers wilt doen dan wat de reguliere socket-API te bieden heeft. Dit is precies waar pack()
(en unpack()
natuurlijk) voor is.
Het verplichte deel van een IP-header is 20 octetten (AKA "bytes") lang. Zoals u achter deze link kunt zien, vormen bron en doel-IP-adres de laatste twee 32-bits waarden in de koptekst. Onder de andere velden bevinden zich sommige met 16 bits, sommige met 8 bits en een paar kleinere brokken tussen 2 en 13 bits.
Ervan uitgaande dat we de volgende variabelen in onze header hebben:
my ($dscp, $ecn, $length,
$id, $flags, $frag_off,
$ttl, $proto,
$src_ip,
$dst_ip);
Merk op dat drie velden uit de kop ontbreken:
- De versie is altijd 4 (het is tenslotte IPv4)
- IHR is van 5 in ons voorbeeld omdat we niet over een veld opties hebben; lengte wordt gespecificeerd in eenheden van 4 octetten, dus 20 octetten geeft een lengte van 5.
- De controlesom kan op 0 worden gelaten. Eigenlijk zouden we het moeten berekenen, maar de code om dit te doen gaat ons hier niet aan.
We kunnen proberen bitoperaties te gebruiken om bijvoorbeeld de eerste 32 bits te construeren:
my $hdr = 4 << 28 | 5 << 24 | $dscp << 18 | $ecn << 16 | $length;
Deze benadering werkt echter alleen tot de grootte van een geheel getal, dat meestal 64 bits is maar 32 kan zijn. Erger nog, het hangt af van de endianness van de CPU, dus het werkt op sommige CPU's en faalt op andere. Laten we pack()
proberen:
my $hdr = pack('H2B8n', '45', sprintf("%06b%02b", $dscp, $ecn), $length);
De sjabloon geeft eerst H2
, een hex-string van 2 tekens, eerst hoog nybble . Het overeenkomstige argument om in te pakken is "45" - versie 4, lengte 5. Het volgende sjabloon is B8
, een 8-bit bitstring, afnemende bitvolgorde binnen elke byte . We moeten bitstrings gebruiken om de lay-out te regelen tot brokken die kleiner zijn dan een nybble (4 bits), dus de sprintf()
wordt gebruikt om zo'n bitstring te construeren uit 6 bits van $dscp
en 2 van $ecn
. De laatste is n
, een niet-ondertekende 16-bits waarde in Network Byte Order , dat wil zeggen altijd big-endian ongeacht wat het native integer-formaat van uw CPU is, en het wordt gevuld vanaf $length
.
Dat zijn de eerste 32 bits van de koptekst. De rest kan op dezelfde manier worden gebouwd:
Sjabloon | Argument | Opmerkingen |
---|---|---|
n | $id | |
B16 | sprintf("%03b%013b", $flags, $frag_off) | Hetzelfde als DSCP / ECN |
C2 | $ttl, $proto | Twee opeenvolgende niet-ondertekende octetten |
n | 0 / $checksum | x kan worden gebruikt om een null-byte in te voegen, maar n laat ons een argument specificeren als we ervoor kiezen om een controlesom te berekenen |
N2 | $src_ip, $dst_ip | gebruik a4a4 om het resultaat van twee aanroepen van gethostbyname() te pakken zoals deze zich al in Network Byte Order bevinden! |
Dus de complete oproep om een IPv4-header in te pakken zou zijn:
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
);