Perl Language
Packa och packa upp
Sök…
Manuell konvertering av C-strukturer till Pack Syntax
Om du någonsin har att göra med C Binary API: er från Perl Code, via syscall
, ioctl
eller fcntl
funktionerna, måste du veta hur du konstruerar minnet på ett C-kompatibelt sätt.
Till exempel, om du någonsin hade att göra med någon funktion som förväntade sig en timespec
, skulle du undersöka /usr/include/time.h
och hitta:
struct timespec
{
__time_t tv_sec; /* Seconds. */
__syscall_slong_t tv_nsec; /* Nanoseconds. */
};
Du gör en dans med cpp
att hitta vad det verkligen betyder:
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
Så det är en (signerad) 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
Och det tar 8 byte. Så 64bit signerade int. Och jag är på en 64Bit-processor. =)
Perldoc- pack
säger
q A signed quad (64-bit) value.
Så för att packa ett tidssvar:
sub packtime {
my ( $config ) = @_;
return pack 'qq', @{$config}{qw( tv_sec tv_nsec )};
}
Och för att packa upp en tidspecifik:
sub unpacktime {
my ( $buf ) = @_;
my $out = {};
@{$out}{qw( tv_sec tv_nsec )} = unpack 'qq', $buf;
return $out;
}
Nu kan du bara använda dessa funktioner istället.
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 ));
Konstruera en IPv4-rubrik
Ibland måste man ta itu med strukturer definierade i termer av C-datatyper från Perl. En sådan applikation är skapandet av råa nätverkspaket, om du vill göra något snyggare än vad den vanliga socket API har att erbjuda. Det är just det pack()
(och unpack()
naturligtvis)) där för.
Den obligatoriska delen av en IP-rubrik är 20 oktetter (AKA "byte") lång. Som ni ser bakom denna länk utgör IP-adress för källa och destination de sista två 32-bitarsvärdena i rubriken. Bland de andra fälten finns några med 16 bitar, några med 8 bitar och några mindre bitar mellan 2 och 13 bitar.
Förutsatt att vi har följande variabler att fylla i vår rubrik:
my ($dscp, $ecn, $length,
$id, $flags, $frag_off,
$ttl, $proto,
$src_ip,
$dst_ip);
Observera att tre fält från rubriken saknas:
- Versionen är alltid 4 (det är trots allt IPv4)
- IHL är 5 i vårt exempel eftersom vi inte har ett alternativfält ; längd anges i enheter på 4 oktetter så 20 oktetter ger en längd på 5.
- Kontrollsumman kan lämnas vid 0. Vi måste faktiskt beräkna den men koden för att göra detta berör inte oss här.
Vi kan försöka använda bitoperationer för att konstruera t.ex. de första 32 bitarna:
my $hdr = 4 << 28 | 5 << 24 | $dscp << 18 | $ecn << 16 | $length;
Detta tillvägagångssätt fungerar dock bara upp till storleken på ett heltal, som vanligtvis är 64 bitar men kan vara så lågt som 32. Det är värre att det beror på CPU: s slutlighet så att det kommer att fungera på vissa processorer och misslyckas med andra. Låt oss försöka pack()
:
my $hdr = pack('H2B8n', '45', sprintf("%06b%02b", $dscp, $ecn), $length);
Mallen specificerar först H2
, en hexsträng med två tecken, hög nybble först . Motsvarande argument att packa är "45" - version 4, längd 5. Nästa mall är B8
, en 8-bitars bitsträng, fallande bitordning i varje byte . Vi måste använda bitsträngar för att kontrollera layouten ner till bitar mindre än en fina (4 bitar), så sprintf()
används för att konstruera en sådan bitsträng från 6 bitar från $dscp
och 2 från $ecn
. Den sista är n
, ett osignerat 16-bitarsvärde i Network Byte Order , dvs alltid big-endian oavsett vad din CPU: s ursprungliga heltalformat är, och det fylls från $length
.
Det är de första 32 bitarna i rubriken. Resten kan byggas på liknande sätt:
Mall | Argument | Anmärkningar |
---|---|---|
n | $id | |
B16 | sprintf("%03b%013b", $flags, $frag_off) | Samma som DSCP / ECN |
C2 | $ttl, $proto | Två på varandra följande osignerade oktetter |
n | 0 / $checksum | x kan användas för att infoga en nollbyte men n låter oss ange ett argument om vi skulle välja att beräkna en kontrollsumma |
N2 | $src_ip, $dst_ip | a4a4 att packa resultatet av två gethostbyname() samtal gethostbyname() som det redan finns i Network Byte Order! |
Så det kompletta samtalet att packa en IPv4-huvud skulle vara:
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
);