Recherche…


Conversion manuelle des structures C en syntaxe de pack

Si vous avez déjà affaire à des API C Binary à partir de Perl Code, via les fonctions syscall , ioctl ou fcntl , vous devez savoir comment construire de la mémoire à l’aide de C Compatible.

Par exemple, si vous étiez en /usr/include/time.h traiter avec une fonction qui attendait une timespec , vous devriez regarder dans /usr/include/time.h et trouver:

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

Vous faites une danse avec cpp pour trouver ce que cela signifie vraiment:

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

Donc c'est un int (signé)

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

Et cela prend 8 octets. Donc, 64 bits signé int. Et je suis sur un processeur 64 bits. =)

Le pack Perldoc dit

            q  A signed quad (64-bit) value.

Donc, pour emballer un timespec:

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

Et pour décompresser une fois:

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

Maintenant, vous pouvez simplement utiliser ces fonctions à la place.

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

Construire un en-tête IPv4

Parfois, vous devez gérer des structures définies en termes de types de données C à partir de Perl. L'une de ces applications est la création de paquets réseau bruts, au cas où vous souhaiteriez faire quelque chose de plus sophistiqué que ce que l'API de socket classique peut offrir. Ceci est juste ce que pack() (et unpack() bien sûr) est là pour.

La partie obligatoire d'un en-tête IP est longue de 20 octets (AKA "octets"). Comme vous pouvez le voir derrière ce lien, les adresses IP source et destination constituent les deux dernières valeurs 32 bits de l’en-tête. Parmi les autres champs, il y en a avec 16 bits, certains avec 8 bits et quelques petits morceaux entre 2 et 13 bits.

En supposant que nous ayons les variables suivantes à remplir dans notre en-tête:

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

Notez que trois champs de l'en-tête sont manquants:

  • La version est toujours 4 (c'est IPv4 après tout)
  • Le DIH est de 5 dans notre exemple car nous n'avons pas de champ d' options ; la longueur est spécifiée en unités de 4 octets, donc 20 octets donnent une longueur de 5.
  • La somme de contrôle peut être laissée à 0. En fait, il faudrait le calculer, mais le code pour cela ne nous concerne pas ici.

Nous pourrions essayer d'utiliser des opérations sur les bits pour construire par exemple les 32 premiers bits:

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

Cette approche ne fonctionne que jusqu'à la taille d'un entier mais, ce qui est généralement 64 bits mais peut être aussi bas que 32. Pire encore, cela dépend de la CPU de la boutisme il fonctionnera sur certains processeurs et ne parviennent pas à d' autres. Essayons pack() :

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

Le modèle spécifie d'abord H2 , une chaîne hexadécimale à deux caractères, nybble haut en premier . L'argument correspondant à pack est "45" - version 4, longueur 5. Le modèle suivant est B8 , une chaîne de bits de 8 bits, ordre des bits décroissant à l'intérieur de chaque octet . Nous devons utiliser des chaînes de bits pour contrôler la mise en page jusqu'à des morceaux plus petits qu'un nybble (4 bits), de sorte que le sprintf() soit utilisé pour construire une chaîne de 6 bits à partir de $dscp et 2 de $ecn . Le dernier est n , une valeur de 16 bits non signée dans Network Byte Order , c'est-à-dire toujours big-endian quel que soit le format entier natif de votre CPU, et il est rempli à partir de $length .

Ce sont les 32 premiers bits de l'en-tête. Le reste peut être construit de la même manière:

Modèle Argument Remarques
n $id
B16 sprintf("%03b%013b", $flags, $frag_off) Identique à DSCP / ECN
C2 $ttl, $proto Deux octets non signés consécutifs
n 0 / $checksum x pourrait être utilisé pour insérer un octet nul mais n nous permet de spécifier un argument si nous choisissons de calculer une somme de contrôle
N2 $src_ip, $dst_ip Utilisez a4a4 pour a4a4 le résultat de deux appels gethostbyname() tels qu'ils sont dans l'ordre des octets de réseau!

L'appel complet pour empaqueter un en-tête IPv4 serait:

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
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow