Perl Language
포장하고 풉니 다.
수색…
수동으로 C 구조체를 팩 구문으로 변환
syscall
, ioctl
또는 fcntl
함수를 통해 Perl 코드에서 C 바이너리 API를 처리하는 경우 C 호환 방식으로 메모리를 생성하는 방법을 알아야합니다.
예를 들어, timespec
을 예상 한 일부 기능을 다루는 경우 /usr/include/time.h
를보고 다음을 찾으십시오.
struct timespec
{
__time_t tv_sec; /* Seconds. */
__syscall_slong_t tv_nsec; /* Nanoseconds. */
};
당신은 cpp
와 춤을 추면서 그것이 정말로 의미하는 것을 발견합니다 :
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
그래서 그것의 (서명 된) 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
그리고 8 바이트 걸립니다. 그래서 64bit int에 서명했습니다. 그리고 저는 64 비트 프로세서를 사용하고 있습니다. =)
Perldoc pack
은 말한다
q A signed quad (64-bit) value.
그래서 timespec을 포장하려면 :
sub packtime {
my ( $config ) = @_;
return pack 'qq', @{$config}{qw( tv_sec tv_nsec )};
}
timespec의 압축을 풀려면 다음을 수행하십시오.
sub unpacktime {
my ( $buf ) = @_;
my $out = {};
@{$out}{qw( tv_sec tv_nsec )} = unpack 'qq', $buf;
return $out;
}
이제 그 기능을 대신 사용할 수 있습니다.
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 ));
IPv4 헤더 생성
때로는 Perl의 C 데이터 유형에 따라 정의 된 구조를 다루어야합니다. 그러한 응용 프로그램 중 하나는 일반 소켓 API가 제공해야하는 것보다 더 멋진 기능을 수행하려는 경우를 대비하여 원시 네트워크 패킷을 만드는 것입니다. 이것은 pack()
(그리고 unpack()
물론)이있는 곳입니다.
IP 헤더의 필수 부분 은 20 옥텟 (AKA "bytes")입니다. 이 링크의 뒷부분에서 볼 수 있듯이 원본 및 대상 IP 주소가 머리글의 마지막 두 32 비트 값을 구성합니다. 다른 필드 중에는 16 비트, 8 비트 및 2 비트와 13 비트 사이의 작은 덩어리가 있습니다.
헤더에 포함 할 다음 변수가 있다고 가정합니다.
my ($dscp, $ecn, $length,
$id, $flags, $frag_off,
$ttl, $proto,
$src_ip,
$dst_ip);
헤더의 세 필드가 누락되었습니다.
- 버전은 항상 4입니다 (결국 IPv4입니다).
- IHL은 옵션 필드가 없으므로 예제에서는 5입니다. 길이는 4 옥텟 단위로 지정되므로 20 옥텟은 길이가 5입니다.
- 체크섬은 0으로 남겨 둘 수 있습니다. 실제로는 계산해야하지만 코드는 여기에 관련되지 않습니다.
우리는 처음 32 비트를 구성하기 위해 비트 연산을 시도하고 사용할 수 있습니다.
my $hdr = 4 << 28 | 5 << 24 | $dscp << 18 | $ecn << 16 | $length;
이 접근 방식은 정수의 크기까지만 작동합니다. 일반적으로 64 비트이지만 32로 낮을 수 있습니다. 더 나쁜 것은 CPU의 엔디안 에 따라 다르므로 일부 CPU에서 작동하고 다른 CPU에서는 작동하지 않습니다. pack()
해보자.
my $hdr = pack('H2B8n', '45', sprintf("%06b%02b", $dscp, $ecn), $length);
템플릿은 먼저 2 문자 16 진수 문자열 인 H2
먼저 지정합니다. pack에 해당하는 인수는 "45"- 버전 4, 길이 5입니다. 다음 템플리트는 B8
, 8 비트 비트 문자열이며 각 바이트 내에 내림차순으로 비트 순서가 있습니다 . 우리는 nybble (4 비트)보다 작은 청크로 레이아웃을 제어하기 위해 비트 문자열을 사용해야하므로 sprintf()
는 $dscp
6 비트와 $ecn
2로부터 비트 문자열을 구성하는 데 사용됩니다. 마지막 것은 n
네트워크 바이트 순서 의 부호없는 16 비트 값)입니다 . 즉, CPU의 기본 정수 형식이 무엇이든 관계없이 항상 big-endian이며 $length
부터 채워 $length
.
헤더의 처음 32 비트입니다. 나머지도 비슷하게 만들 수 있습니다.
주형 | 논의 | 비고 |
---|---|---|
n | $id | |
B16 | sprintf("%03b%013b", $flags, $frag_off) | DSCP / ECN과 동일 |
C2 | $ttl, $proto | 두 개의 연속 된 부호없는 옥텟 |
n | 0 / $checksum | x 는 null 바이트를 삽입하는 데 사용될 수 있지만 n 은 체크섬을 계산하도록 선택하면 인수를 지정할 수 있습니다. |
N2 | $src_ip, $dst_ip | a4a4 를 사용하여 두 개의 gethostbyname() 호출 결과를 네트워크 바이트 순서대로 a4a4 합니다! |
따라서 IPv4 헤더를 압축하는 완전한 호출은 다음과 같습니다.
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
);