構造 IPv4 標頭
有時你必須處理根據 Perl 的 C 資料型別定義的結構。一個這樣的應用程式是建立原始網路資料包,以防你想要做一些比常規套接字 API 提供的更好的東西。這正是 pack()
(當然還有 unpack()
)的用武之地。
IP 報頭的必需部分是 20 個八位位元組(AKA位元組)長。正如你在此連結後面看到的那樣,源和目標 IP 地址構成標頭中的最後兩個 32 位值。其他欄位中有一些是 16 位,一些是 8 位,還有一些小塊,位於 2 到 13 位之間。
假設我們有以下變數填充到標題中:
my ($dscp, $ecn, $length,
$id, $flags, $frag_off,
$ttl, $proto,
$src_ip,
$dst_ip);
請注意,標題中的三個欄位缺失:
- 版本總是 4(畢竟是 IPv4)
- 國際人道法在我們的例子中是 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);
模板首先指定 H2
,一個 2 字元的十六進位制字串,首先是高 nybble 。pack 的對應引數是 45
- 版本 4,長度為 5.下一個模板是 B8
,一個 8 位的位字串,每個位元組內的降序位。我們需要使用位字串來控制佈局到小於 nybble(4 位)的塊,所以 sprintf()
用於構造來自 $dscp
的 6 位和來自 $ecn
的 2 位的字串。最後一個是 n
,網路位元組順序中的無符號 16 位值,即無論你的 CPU 的原生整數格式是什麼,總是大端,並且它是從 $length
填充的。
這是標題的前 32 位。其餘的可以類似地構建:
模板 | 爭論 | 備註 |
---|---|---|
n |
$id |
|
B16 |
sprintf("%03b%013b", $flags, $frag_off) |
與 DSCP / ECN 相同 |
C2 |
$ttl, $proto |
兩個連續的無符號八位位元組 |
n |
0 / $checksum |
x 可用於插入空位元組,但 n 允許我們指定一個引數,如果我們選擇計算校驗和 |
N2 |
$src_ip, $dst_ip |
使用 a4a4 打包兩個 gethostbyname() 呼叫的結果,因為它已經在網路位元組順序中了! |
因此,打包 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
);