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
);


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow