Suche…


Manuelles Konvertieren von C-Strukturen in Pack-Syntax

Wenn Sie jemals mit C Binary - API von Perl - Code, über die Umgang syscall , ioctl oder fcntl Funktionen, müssen Sie wissen , wie das Gedächtnis in einer C - kompatibelen Weise zu konstruieren.

Wenn Sie beispielsweise mit einer Funktion zu timespec , die eine timespec erwartete, würden Sie nach /usr/include/time.h suchen und /usr/include/time.h finden:

struct timespec
{
    __time_t tv_sec;            /* Seconds.  */
    __syscall_slong_t tv_nsec;  /* Nanoseconds.  */
};

Sie machen einen Tanz mit cpp um herauszufinden, was das wirklich bedeutet:

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

Also es ist ein (signiert) 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

Und es dauert 8 Bytes. Also 64bit mit int. Und ich bin auf einem 64-Bit-Prozessor. =)

Perldoc pack sagt

            q  A signed quad (64-bit) value.

Um eine Zeitangabe zu packen:

sub packtime {
    my ( $config ) = @_; 
    return pack 'qq', @{$config}{qw( tv_sec tv_nsec )};
}

Und um eine Zeitangabe auszupacken:

sub unpacktime {
   my ( $buf ) = @_;
   my $out = {};
   @{$out}{qw( tv_sec tv_nsec )} = unpack 'qq', $buf;
   return $out;
}

Jetzt können Sie stattdessen diese Funktionen verwenden.

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

Erstellen eines IPv4-Headers

Manchmal müssen Sie sich mit Strukturen beschäftigen, die in C-Datentypen von Perl definiert sind. Eine solche Anwendung ist die Erstellung von rohen Netzwerkpaketen, falls Sie etwas Schickeres tun möchten als das, was die normale Socket-API zu bieten hat. Dies ist genau das, wofür pack() (und unpack() natürlich) da ist.

Der obligatorische Teil eines IP-Headers ist 20 Bytes (AKA "Bytes") lang. Wie Sie hinter diesem Link sehen können, bilden Quell- und Ziel-IP-Adresse die letzten beiden 32-Bit-Werte im Header. Unter den anderen Feldern befinden sich einige mit 16 Bit, einige mit 8 Bit und einige kleinere Chunks zwischen 2 und 13 Bit.

Angenommen, wir haben die folgenden Variablen in unserem Header:

my ($dscp, $ecn, $length,
    $id, $flags, $frag_off,
    $ttl, $proto,
    $src_ip,
    $dst_ip);

Beachten Sie, dass drei Felder aus der Kopfzeile fehlen:

  • Die Version ist immer 4 (es ist immerhin IPv4)
  • IHL ist in unserem Beispiel 5, da wir kein Optionsfeld haben . Die Länge wird in Einheiten von 4 Oktetten angegeben, so dass 20 Oktette eine Länge von 5 ergeben.
  • Die Prüfsumme kann bei 0 belassen werden. Eigentlich müssten wir sie berechnen, aber der Code, der dies tut, betrifft uns hier nicht.

Wir könnten versuchen, Bitoperationen zu verwenden, um zB die ersten 32 Bits zu erstellen:

my $hdr = 4 << 28 | 5 << 24 | $dscp << 18 | $ecn << 16 | $length;

Dieser Ansatz funktioniert jedoch nur bis zur Größe einer ganzen Zahl, die normalerweise 64 Bit beträgt, aber bis zu 32 Bit betragen kann. Schlimmer noch, sie hängt von der Endianness der CPU ab. Daher funktioniert sie auf einigen CPUs und auf anderen nicht. Versuchen wir pack() :

my $hdr = pack('H2B8n', '45', sprintf("%06b%02b", $dscp, $ecn), $length);

Die Vorlage gibt zuerst H2 , eine 2-stellige Hex-Zeichenfolge, zuerst High Nybble . Das entsprechende zu packende Argument ist "45" - Version 4, Länge 5. Die nächste Vorlage ist B8 , eine 8-Bit-Bitfolge mit absteigender Bitfolge in jedem Byte . Wir müssen Bit-Strings verwenden, um das Layout auf Chunks zu beschränken, die kleiner als ein Nybble (4 Bit) sind. sprintf() der sprintf() verwendet, um einen solchen Bit-String aus 6 Bits von $dscp und 2 von $ecn . Der letzte Wert ist n , ein vorzeichenloser 16-Bit-Wert in der Netzwerk-Byte-Reihenfolge , dh, unabhängig von dem nativen Integer-Format Ihrer CPU, immer Big-Endian, und es wird aus $length gefüllt.

Das sind die ersten 32 Bits des Headers. Der Rest kann ähnlich aufgebaut sein:

Vorlage Streit Bemerkungen
n $id
B16 sprintf("%03b%013b", $flags, $frag_off) Gleich wie DSCP / ECN
C2 $ttl, $proto Zwei aufeinander folgende vorzeichenlose Oktette
n 0 / $checksum x könnte zum Einfügen eines Null-Bytes verwendet werden, aber mit n können wir ein Argument angeben, falls eine Prüfsumme berechnet werden soll
N2 $src_ip, $dst_ip Verwenden Sie a4a4 um das Ergebnis von zwei gethostbyname() a4a4 zu packen, da es bereits in der Netzwerk-Byte-Reihenfolge ist!

Der vollständige Aufruf zum Packen eines IPv4-Headers wäre also:

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
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow