skb_in.cとskb_out.cの詳細解析
NetFilter_Skb_modプロジェクトは、Netfilterが提供するフック関数メカニズムを利用して、カーネルレベルでのパケットキャプチャと再送信を実現します。主にC言語でNF_IP_LOCAL_INおよびNF_IP_LOCAL_OUTの2つの段階におけるSKBネットワークパケット処理を実装しており、パケット内容の印刷、長さ測定、チェックサムの再計算を含みます。Netfilterとskb構造体を学習する研究者にとって有益な参考資料となります。プロジェクトアドレス: https://gitcode.com/qq_35493457/NetFilter_Skb_mod
この記事では、TCP、UDP、ICMPパケットがカーネルネットワークプロトコルスタック内でどのように処理されるかを詳細に説明し、パケット識別、コンテンツ抽出、チェックサム計算、デバッグ出力などの重要なステップを解説しています。skb_in.cとskb_out.cのコード分析を通じて、パケットがフックに入り込んで最終的に処理されるまでの完全な流れを明らかにします。
TCPパケット処理のロジック
skb_in.cとskb_out.cにおいて、TCPパケット処理のロジックは主要機能の一つです。以下にその実装詳細を深く解析します:
1. TCPパケットの識別と抽出
パケットがNetfilterのフックメカニズムを通じてカーネルに入る際、まずプロトコルタイプがIPPROTO_TCPであるか確認されます。TCPパケットの場合、さらにヘッダ情報とデータコンテンツを抽出します:
if (likely(network_header->protocol == IPPROTO_TCP)) {
tcp_header = tcp_hdr(buffer);
payload_data = buffer->data + network_header->ihl * 4 + tcp_header->doff * 4;
total_header_size = network_header->ihl * 4 + tcp_header->doff * 4;
payload_length = buffer->len - network_header->ihl * 4 - tcp_header->doff * 4;
}
network_header->protocol: TCPプロトコルかどうかをチェックtcp_hdr(buffer): TCPヘッダポインタを取得payload_data: TCPデータ部分の開始アドレスを指すtotal_header_size: IPとTCPヘッダの総長を計算payload_length: データ部分の長さを計算
2. パケットコンテンツの出力
デバッグと検証のために、コードはTCPパケットのコンテンツをカーネルログに出力します:
printk("**************packet_processing_start*****************\n");
printk("header size: %d", total_header_size);
printk("\r\n");
printk("payload size: %d", buffer->len - total_header_size);
printk("\r\n");
printk("data length: %d", payload_length);
printk("\r\n");
for (index = 0; index < payload_length; index++) {
printk("%c", payload_data[index]);
}
printk: ヘッダ長、データ長、データコンテンツを出力- ループ出力: 各文字を順次出力
3. チェックサムの再計算
TCPパケットのチェックサムはデータ完全性を確保するために再計算が必要です:
network_header->check = 0;
network_header->check = ip_fast_csum((unsigned char *)network_header, network_header->ihl);
if (buffer->ip_summed == CHECKSUM_HW) {
tcp_header->check = csum_tcpudp_magic(network_header->saddr, network_header->daddr,
(ntohs(network_header->tot_len) - network_header->ihl * 4),
IPPROTO_TCP,
csum_partial(tcp_header, (ntohs(network_header->tot_len) - network_header->ihl * 4, 0));
buffer->csum = offsetof(struct tcphdr, check);
}
ip_fast_csum: IPヘッダのチェックサムを高速計算csum_tcpudp_magic: TCPチェックサムを計算し、転送中にデータが改変されていないことを保証
4. フローチャート
以下はTCPパケット処理のフローチャートです:
5. 主要データ構造
TCPパケット処理に関連する主要データ構造は以下の通りです:
| データ構造 | 説明 |
|---|---|
struct iphdr |
IPヘッダで、プロトコルタイプ、送信元/宛先IPアドレスなどの情報を含む |
struct tcphdr |
TCPヘッダで、送信元/宛先ポート、シーケンス番号、チェックサムなどの情報を含む |
struct sk\_buff |
カーネル内でネットワークパケットを表す構造で、データポインタ、長さなどの情報を含む |
6. コード例
以下は簡略化されたTCPパケット処理コードの断片です:
if (likely(network_header->protocol == IPPROTO_TCP)) {
tcp_header = tcp_hdr(buffer);
payload_data = buffer->data + network_header->ihl * 4 + tcp_header->doff * 4;
payload_length = buffer->len - network_header->ihl * 4 - tcp_header->doff * 4;
for (index = 0; index < payload_length; index++) {
printk("%c", payload_data[index]);
}
tcp_header->check = 0;
tcp_header->check = csum_tcpudp_magic(network_header->saddr, network_header->daddr, payload_length, IPPROTO_TCP, 0);
}
上記ロジックにより、skb_in.cとskb_out.cはTCPパケットの効率的な処理を実現し、パケットの完全性と正確性を確保します。
UDPパケット処理のロジック
skb_in.cとskb_out.cにおいて、UDPパケット処理のロジックも主要機能の一つです。以下ではその実装フロー、重要なコード断片、およびチェックサム計算メカニズムについて詳細に説明します。
UDPパケット処理フロー
- パケット識別と抽出 パケットのプロトコルタイプがIPPROTO_UDPの場合、カーネルはudp_hdr(buffer)を呼び出してUDPヘッダ情報を抽出します。その後、以下の手順でパケットを処理します:
udp_header = udp_hdr(buffer);
payload_data = buffer->data + network_header->ihl * 4 + sizeof(struct udphdr);
total_header_size = network_header->ihl * 4 + sizeof(struct udphdr);
payload_length = ntohs(network_header->tot_len) - network_header->ihl * 4 - sizeof(struct udphdr);
payload_data: UDPデータペイロードの開始位置を指すtotal_header_size: IPヘッダとUDPヘッダの総長payload_length: UDPデータペイロードの実際の長さ
- パケットコンテンツの出力 カーネルはprintkを使用してパケットのヘッダ情報とペイロードコンテンツを出力し、デバッグを容易にします:
printk("header size: %d", total_header_size);
printk("\r\n");
printk("len - header: %d", buffer->len - total_header_size);
printk("\r\n");
printk("data length: %d", payload_length);
printk("\r\n");
ペイロードデータは16進数と文字形式で出力されます:
for (index = 0; index < payload_length; index++) {
printk(" %02x", payload_data[index]);
if ((index + 1) % 16 == 0)
printk("\r\n");
}
- パケット線形化 パケットがフラグメント化されている場合(buffer->data_len != 0)、skb_linearizeを呼び出して線形化します:
if (skb_linearize(buffer)) {
printk("linearization error\r\n");
return NF_DROP;
}
チェックサム計算
UDPチェックサムの計算はパケット処理の重要なステップであり、データ完全性を確保します。チェックサム計算のロジックは以下の通りです:
- IPヘッダチェックサム チェックサムをリセットしてからIPヘッダのチェックサムを再計算します:
network_header->check = 0;
network_header->check = ip_fast_csum((unsigned char*)network_header, network_header->ihl);
- UDPチェックサム パケットがハードウェアチェックサムを有効にしている場合(CHECKSUM_HW)、csum_tcpudp_magicを呼び出してUDPチェックサムを計算します:
udp_header->check = csum_tcpudp_magic(
network_header->saddr, network_header->daddr,
(ntohs(network_header->tot_len) - network_header->ihl * 4),
IPPROTO_UDP,
csum_partial(udp_header, (ntohs(network_header->tot_len) - network_header->ihl * 4), 0)
);
フローチャート
以下はUDPパケット処理のフローチャートです:
要点まとめ
- パケット線形化: フラグメント化されたパケットが正しく処理されることを保証
- チェックサム計算: ネットワーク転送中のパケット完全性を保証
- デバッグ出力: printkを通じて詳細なデバッグ情報を提供し、開発者が問題を分析するのを容易にする
ICMPパケット処理のロジック
skb_in.cとskb_out.cにおいて、ICMPパケット処理のロジックは主にmy_func関数によって実装されています。以下に詳細な処理フローを分析します:
1. ICMPパケット識別
パケットのプロトコルタイプがIPPROTO_ICMPの場合、システムはICMP処理ロジックに入ります。以下のコード断片はICMPパケットの識別方法を示しています:
else if(likely(network_header->protocol==IPPROTO_ICMP)) {
icmp_header = icmp_hdr(buffer);
payload_data = buffer->data + network_header->ihl * 4 + sizeof(struct icmphdr);
total_header_size = network_header->ihl * 4 + sizeof(struct icmphdr);
payload_length = ntohs(network_header->tot_len) - network_header->ihl * 4 - sizeof(struct icmphdr);
}
2. パケットコンテンツ出力
システムはICMPパケットのヘッダ情報とコンテンツ長を出力し、デバッグと分析を容易にします:
printk("header size: %d", total_header_size);
printk("\r\n");
printk("len - header: %d", buffer->len - total_header_size);
printk("\r\n");
printk("data length: %d", payload_length);
printk("\r\n");
3. パケット線形化処理
パケットが非線形の場合(buffer->data_len != 0)、システムは線形化を試みます:
if(buffer->data_len != 0) {
if(skb_linearize(buffer)) {
printk("linearization error\r\n");
printk("buffer->data_len %d\r\n", buffer->data_len);
return NF_DROP;
}
}
4. パケットコンテンツ出力
システムは各バイト単位でICMPパケットのコンテンツを出力します:
for(index = 0; index < payload_length; index++) {
printk("%c", payload_data[index]);
}
printk("\r\n");
5. チェックサム再計算
ハードウェアチェックサムモード(CHECKSUM_HW)の場合、システムはICMPパケットのチェックサムを再計算します:
network_header->check = 0;
network_header->check = ip_fast_csum((unsigned char*)network_header, network_header->ihl);
if(buffer->ip_summed == CHECKSUM_HW) {
icmp_header->checksum = ip_compute_csum(icmp_header, (ntohs(network_header->tot_len) - network_header->ihl * 4);
}
6. 処理完了マーカー
処理完了後、システムは完了マーカーを出力します:
printk("*********************ICMP_complete********************\n");
フローチャート
以下はICMPパケット処理ロジックを示すフローチャートです:
主要データ構造
| 変数名 | 型 | 説明 |
|---|---|---|
network\_header |
struct iphdr\* |
IPヘッダポインタ |
icmp\_header |
struct icmphdr\* |
ICMPヘッダポインタ |
payload\_data |
unsigned char\* |
パケットコンテンツポインタ |
total\_header\_size |
int |
ヘッダ長 |
payload\_length |
int |
データコンテンツ長 |
コード例
以下は簡略化されたICMPパケット処理コードの例です:
if (network_header->protocol == IPPROTO_ICMP) {
icmp_header = icmp_hdr(buffer);
payload_data = buffer->data + network_header->ihl * 4 + sizeof(struct icmphdr);
total_header_size = network_header->ihl * 4 + sizeof(struct icmphdr);
payload_length = ntohs(network_header->tot_len) - network_header->ihl * 4 - sizeof(struct icmphdr);
// ヘッダ情報出力
printk("ICMP Header Size: %d\n", total_header_size);
// 線形化処理
if (buffer->data_len != 0 && skb_linearize(buffer)) {
printk("Failed to linearize buffer\n");
return NF_DROP;
}
// チェックサム再計算
network_header->check = 0;
network_header->check = ip_fast_csum((unsigned char*)network_header, network_header->ihl);
if (buffer->ip_summed == CHECKSUM_HW) {
icmp_header->checksum = ip_compute_csum(icmp_header, payload_length);
}
}
チェックサム再計算メカニズム
ネットワークパケット処理において、チェックサムはデータ完全性を確保するための重要なメカニズムです。skb_in.cとskb_out.cはNetfilterのフックメカニズムを通じてパケットのキャプチャとチェックサム再計算を実現します。このセクションでは、このメカニズムの実装原理とコードロジックを詳細に解析します。
チェックサムの役割
チェックサムは、パケット転送中に改変または破損していないかを検証するために使用されます。skb_in.cとskb_out.cでは、チェックサムの計算と検証は主に以下のプロトコルに対して行われます:
- TCP: csum_tcpudp_magic関数を使用してチェックサムを再計算
- UDP: 同様にcsum_tcpudp_magic関数を使用
- ICMP: ip_compute_csum関数を使用してチェックサムを計算
チェックサム再計算のフロー
以下はチェックサム再計算の核心フローです:
コード実装
以下はskb_in.cにおけるチェックサム再計算の重要なコード断片です:
// IPヘッダチェックサム再計算
network_header->check = 0;
network_header->check = ip_fast_csum((unsigned char*)network_header, network_header->ihl);
// TCPチェックサム再計算
if (buffer->ip_summed == CHECKSUM_HW) {
tcp_header->check = csum_tcpudp_magic(network_header->saddr, network_header->daddr,
(ntohs(network_header->tot_len) - network_header->ihl * 4),
IPPROTO_TCP,
csum_partial(tcp_header, (ntohs(network_header->tot_len) - network_header->ihl * 4), 0));
buffer->csum = offsetof(struct tcphdr, check);
}
主要関数解析
ip_fast_csumIPヘッダのチェックサムを高速計算するための関数で、IPヘッダポインタとIPヘッダ長(4バイト単位)を入力として受け取ります。csum_tcpudp_magicTCPまたはUDPチェックサムを計算するための関数で、送信元アドレス、宛先アドレス、データ長、プロトコルタイプ、部分チェックサム値をパラメータとして持ちます。ip_compute_csumICMPチェックサムを計算するための関数で、ICMPヘッダポインタとデータ長を入力として受け取ります。
注意事項
- チェックサム再計算前に、チェックサムフィールドをゼロクリアする必要があります(例: network_header->check = 0)
- フラグメント化されたパケット(buffer->data_len != 0)の場合、まずskb_linearizeを呼び出してデータの線形化を確実にする必要があります
- チェックサム計算はハードウェアアクセラレーション(CHECKSUM_HW)に依存するため、ネットワークカードがサポートしていることを確認する必要があります
上記メカニズムにより、skb_in.cとskb_out.cはパケットを効率的に処理し、その完全性を確保することができます。