IPv4 プロトコル

このページの目次

4000 バイトの IPv4 データグラムが 1500 バイトの出口 MTU に遭遇した場合、3 つのフラグメントに分割されるか、破棄されてエラー通知がトリガーされるかのどちらかになります。鍵となるのは DF(Don't Fragment)ビット、パス、およびエンドポイントの処理です。IPv4 はベストエフォートなデータグラム伝送を提供し、アドレス、長さ、TTL、フラグメントフィールドが、現在のパケットがどのように転送されるかを決定します。信頼性、順序付け、輻輳回復は、それぞれ対応する上位層のメカニズムに依存します。

参照すべき問題この記事の入り口最も計算ミスしやすい部分
ヘッダ長とパケット長をどう区別するかフィールドごとの解説とチェックサムIHL × 4、Total Length はヘッダを含む
複数のルートが一致する場合、どちらを選ぶかCIDR と次のホップ最長プレフィックス、ポリシー、およびネイバー
フラグメントオフセットがなぜ 185 なのかフラグメンテーションの完全な計算例IP データオフセットは 8 バイト単位
最後のフラグメントを受信したら配信できるか再構成の条件総長さ、ギャップ、オーバーラップ、およびタイムアウト
小さなパケットは通るが、大きなパケットは通らないPMTUDサイズ依存の現象と因果関係の証拠
DSCP/ECN はどのように共存するかサービスと輻輳のマーキング6 ビットの DSCP と 2 ビットの ECN は独立している

アドレスとルーティング:32 ビットアドレスは物理的な位置そのものではない

CIDR プレフィックスは、アドレスをルーティング集約に使用される上位ビットと、残りの部分に分割します。192.0.2.130/26 が属するネットワークは 192.0.2.128/26 であり、マスクは 255.255.255.192 です。通常のブロードキャストサブネットのセマンティクスでは、アドレス範囲は 192.0.2.191 まで続き、その中でネットワークアドレスとブロードキャストアドレスは通常のホストとは異なる用途を持ちます。「サブネットごとに必ず 2 を減算する」というルールを /31 のポイントツーポイントや /32 のホストレートに一般化することはできません。

通常の宛先ルーティングテーブルでは、198.51.100.0/24 に一致するパケットは、同じく一致する 0.0.0.0/0 よりも優先して使用されます。ポリシールーティング、VRF、マルチパスは選択のコンテキストを増やすことができますが、ARP がルーティング判断に代わるわけではありません。ルーティングが出口/次のホップを選択した後に、初めてそのリンクのネイバーマッピングが処理されます。

アドレスは送信者の認証を提供しません。NAT はアドレスおよび関連フィールドを書き換える可能性がありますが、IPv4 を信頼性のある伝送プロトコルにはしません。32 ビットの番号空間はすべてが公開割り当て可能であることを意味するものではなく、歴史的な割り当てプールの枯渇は、すべての地域、機関、および用途が同じ日にアドレスを失うことを意味するものではありません。

基本ヘッダのフィールドごとの理解

IPv4 基本ヘッダ:各行 32 ビット、合計 20 バイト;フィールド幅はビット比率に従う V=4 IHL DSCP ECN Total Length Identification Flags Fragment Offset TTL Protocol Header Checksum Source Address Destination Address IHL は 4 バイト単位;IHL > 5 の場合、その後にオプションと必要なパディングが続く 図はネットワークバイトオーダーで左から右に配置されています;コードで Flags/Offset を解析する際は、0x4000、0x2000、0x1fff などのマスクを使用してください。
フィールドセマンティクス解釈の境界
Version / IHLIPv4;ヘッダ長は IHL×4 バイト最小 20 バイト、最大 60 バイト;5 未満であってはならない
Total Length現在の IP パケットのヘッダとデータの合計最大 65535 バイト、イーサネットパディング/FCS を含まない
Identification再構成が必要なデータグラムに識別子を提供すべての IPv4 パケットがグローバルにユニークな番号ではない
Flags / Fragment Offsetフラグメンテーション禁止、さらにフラグメントあり、データの位置オフセットはイーサネットヘッダまたは IP ヘッダから始まらない
TTL転送の存続期間を制限ルーティング転送時に少なくとも減算される;初期値だけでシステムを正確に特定することはできない
Protocol上位層を示す(例:TCP 6、UDP 17)ポート番号ではない
Header Checksum現在の IPv4 ヘッダのチェックサムIP ペイロードをカバーせず、送信元も認証しない

解析時は、まず実際に利用可能なバイトが十分であることを確認し、バージョン、IHL、および Total Length の間の関係をチェックします。イーサネットから受信した追加のパディングを IP データに含めてはいけません。IP Options には独自の形式とコピー属性があり、すべてのフラグメントに同じヘッダが単純にコピーされるとは限りません。RFC 791:IPv4 フォーマットとフラグメンテーション

TTL とヘッダチェックサムがホップごとに更新される理由

ルータは転送時に TTL を更新し、ヘッダが変更されるため、チェックサムも更新する必要があります。ソース/宛先は通常の非 NAT 転送中変更されませんが、それは IP ヘッダ全体が変わらないことを意味するものではありません。TTL が尽きるとパケットは破棄され、プロトコルとリソース/ポリシーの条件が許す場合に ICMP Time Exceeded が生成されます。エラー通知には例外とレート制限があり、すべての破棄に対して可視な ICMP が返されるとは限りません。

IPv4 ヘッダチェックサムは 16 ビットワードに対して 1 の補数和を計算し、その逆数を取ります。桁あふれは下位 16 ビットに折り返されます。完全なヘッダを検証する場合、1 の補数和はすべて 1 になるはずであり、等価な「逆数後の結果」は 0 になります。これは IPv4 ヘッダのみを保護します。TCP/UDP のチェックサムには擬似ヘッダとペイロードのカバレッジルールが別途あり、IPv4 UDP にはゼロチェックサムの特定のセマンティクスもあります。すべての上位層が同じ保証を提供するとは言えません。

末尾のコードは完全なヘッダを構築し、チェックサムを検証し、TTL を 64 から 63 に変更して、古いチェックサムがもはや有効でないことを確認し、再計算を行います。これは単に「ルータはホップごとに 1 を減算する」と覚えるよりも、フィールド間の依存関係をよりよく示しています。

TTL が 1 しか変わらないのに、なぜ古いチェックサムが無効になるのか?

TTL と Protocol で構成される 16 ビットワードを金色で、チェックサムフィールドを青色で示します。両方の変化は同じヘッダ内で発生します。下部には元の値を保持し、再計算で何が修正されたかを確認できるようにしています。ペイロードのスイッチは意図的にヘッダを変更せず、チェックサムのカバレッジ範囲の境界を示しています。

図を準備しています
TTL が 1 しか変わらないのに、なぜ古いチェックサムが無効になるのか?

IPv4 ヘッダチェックサムはヘッダフィールドをカバーします。TTL の変更は古いチェックサムを破壊しますが、ペイロードの変更はしません;ヘッダチェックサムからはデータや送信者の信頼性を証明できません。

フラグメンテーション:データ長に合わせ、フラグメント全体の長さではない

元のデータグラムの Total Length=4000、IHL=5、オプションなしと仮定すると、IP データは 3980 バイトです。出口 MTU=1500 で、各フラグメントは少なくとも 20 バイトのヘッダを保持する必要があります。末尾以外のフラグメントのデータ長は 8 バイトの整数倍である必要があり、最大値は floor((1500−20)/8)×8=1480 バイトです。

4000 バイトの IP データグラムが MTU 1500 を通過:分割されるのは 3980 バイトのデータ 元のパケット:20 バイト IP ヘッダ + 3980 バイト IP データ データのインデックス 0 … 3979、IP オプションなし、DF=0 と仮定 フラグメント 1:1480 バイトのデータ offset=0、MF=1 フラグメント 2:1480 バイトのデータ offset=185、MF=1 フラグメント 3:1020 バイトのデータ offset=370、MF=0 Total Length = 1500 Total Length = 1500 Total Length = 1040 オフセットは元の IP データ部分から計算され、単位は 8 バイトです。各フラグメントには独自の IP ヘッダが付加され、最初のフラグメントのみがトランスポート層の開始ヘッダを含みます。
フラグメント元のデータ範囲データ長Offset フィールドMF本フラグメントの Total Length
10~14791480011500
21480~2959148018511500
32960~3979102037001040

1480/8=185、2960/8=370 です。末尾以外のフラグメントはデータ長が 8 に合わせられ、1500 バイトのフラグメント全体の長さは 8 の整数倍ではありません。各フラグメントには独自の Total Length、オフセット、MF、およびチェックサムがあり、Identification は同じデータグラムに属するフラグメントを結びつけるために使用されます。TCP/UDP の開始ヘッダは元のデータの先頭だけに位置し、各フラグメントにコピーされるわけではありません。

DF は 16 ビット Flags/Offset ワードの 0x4000、MF は 0x2000、オフセットマスクは 0x1fff です。これは、図で左から数えたフラグビットの番号を最下位ビットの番号と誤認しないようにするためです。DF を設定してもデータが自動的に小さくなるわけではなく、出口に収まらない場合は通常、破棄されて適切なフィードバックが提供されます。

Identification の制約は、ソース、宛先、プロトコル、および再構成のタイムウィンドウと組み合わせて考慮する必要があります。RFC 6864 はフラグメンテーションのない atomic datagram に対して ID の使用要件を緩和しているため、「すべての IP パケットにユニークな ID が必要」とは限りません。高いパケットレートでは、限られた ID 空間と再構成がさらに注目されるようになります。RFC 6864:IPv4 ID のセマンティクス

再構成は区間を埋める必要があり、最後のフラグメントを待つだけではない

MF=0 の最後のフラグメントを受信した後、8×Offset + 本フラグメントのデータ長 によって元の IP データの総長さを得ることができます。この例では 370×8+1020=3980 バイトとなり、元の最初のフラグメントのヘッダ長を加えて 4000 バイトになります。Offset の元の値を最後のフラグメントの Total Length に直接足してはいけません。さらに、最後のフラグメントの IP ヘッダをデータ範囲に再度含めてはいけません。

総長さが分かっても不十分で、前の区間にギャップがないことを確認し、対応するオーバーラップ、境界、およびリソースのチェックを行う必要があります。フラグメントは順序不同で到着する可能性があります。通常の IP 転送パスではホップごとの再構成は必要ありませんが、ファイアウォールや NAT などの中間処理では、その機能のために再構成が行われる可能性があります。タイムアウトまたはリソース制限により再構成が失敗した場合、IP 層自体は特定のフラグメントの再送を要求しません。上位層が再送するかどうか、およびどのように再送するかはプロトコルに依存します。

再構成タイムアウトは実装および設定によって決定され、ネットワーク全体の統一された定数ではありません。Linux 7.2 の IP_FRAG_TIME は 30×HZ であり、設定可能な ipfrag_time の初期化に使用されます。これは依然としてその実装の基準値であり、実際のネットワーク上のすべてのデバイスを表すものではありません。フラグメンテーションは中間デバイスの処理をより複雑にするため、現代のアプリケーションは通常、適切なパケット分割によってこれを回避したいと考えています。

最後のフラグメントが届いたのに、なぜまだ配信できないのか?

最後に最後のフラグメントを配信し、中間のフラグメントを補完することで、「長さが分かること」と「すでに揃っていること」の違いを観察します。各ボタン上の Offset はパケットフィールドであり、区間図上の位置はすでに 8 倍されています。同じフラグメントを重複して配信しても、既存のカバレッジを再利用するだけで、受信済みバイト数を虚増させることはできません。

図を準備しています
最後のフラグメントが届いたのに、なぜまだ配信できないのか?

MF=0 は終点を明らかにするだけです。再構成には、終点からゼロまでの間にギャップがないことが要求されます。Offset の単位は 8 バイトであり、重複配信は他の場所の穴を埋めることはできません。

PMTUD:エラー通知がエンドポイントのパケットサイズ調整を支援する

古典的な IPv4 PMTUD では、エンドポイントは DF パケットを送信し、中間の出口 MTU が不十分な場合、ルータは破棄し、条件に従って ICMP Destination Unreachable Type 3 Code 4 を返し、MTU などの情報を含めます。エンドポイントはこれに基づいてパス推定を更新し、トランスポート/アプリケーション層が後続のパケット分割を調整します。これは、元のパケットの DF を自動的にクリアして送信を続けるわけではありません。RFC 1191

必要な ICMP がフィルタリングされる場合、エンドポイントは過大なパケットを継続して再送し、ブラックホールを形成する可能性があります。しかし、「小さな ping は通るが、大きな ping は通らない」のはサイズに関連する問題を示すだけであり、フィルタリング、レート制限、または異なるパスである可能性もあります。検証には、探知サイズ、プロトコル、および方向を制御し、出入口または ICMP の証拠を観察する必要があり、1 回の探知だけで結論を下すことはできません。

TCP MSS 通告は、対向側が送信する TCP データの規模を制約しますが、それは全体の MTU とは等しくなく、UDP はカバーしません。MSS clamping は特定の TCP パス問題を緩和できますが、その根拠となる PMTU が不完全である可能性があり、パスが変化する可能性もあるため、すべての ICMP がシャットダウンされているシナリオにおける必然的な修復策ではありません。PLPMTUD/DPLPMTUD は、パケット分割層の探知と確認メカニズムを利用して利用可能なサイズを発見するのを助けますが、そのステートマシンは正しく実装されている必要があります。RFC 8899:データグラム伝送の DPLPMTUD

DSCP:マーキングはネットワークポリシーによって実現される必要がある

IPv4 の 2 番目のバイトの上位 6 ビットは DSCP、下位 2 ビットは ECN であり、ECN は「DSCP の下位 2 ビット」ではありません。DSCP はホップごとの転送動作を選択し、デバイスがそれを信頼するか、再マーキングするか、整形するか、どのキューにマッピングするかは、ネットワークポリシーによって決定されます。RFC 2474:DS フィールド

CS クラス、AF クラス、および EF は異なるサービス設計を表します。AF の異なるドロップ優先度は、対応するクラス内での処理次元であり、すべてのコードポイントを数字の大きさで統一された高低順に並べることはできません。EF の一般的なコードポイントは 10 進数の 46 ですが、それは任意のデバイス上の「無限の最高優先度」を意味するものではありません。低遅延動作には、サービスレート、アクセス制御、およびスケジューリングの制約の設定が必要です。RFC 3246:EF PHB

例えば、DSCP=46、ECN=ECT(0)=2 の場合、完全なバイトは (46<<2)|2=0xBA になります。DSCP を変更する際は、必要な ECN ビットを保持し、輻輳信号を破壊しないようにするために、バイト全体を安易に上書きしてはいけません。

ECN:まずネゴシエーションがあり、その後輻輳通知を解釈する

ECN 2 ビット意味
00Not-ECT
10ECT(0)
01ECT(1)
11CE、輻輳を経験

RFC 3168 の古典的な TCP ECN の例では、SYN は TCP ECE/CWR フラグを使用してネゴシエーションを表現し、SYN-ACK は対応する応答を使用します。これは、初期 SYN の IP ヘッダに ECT を書き込むことでネゴシエーションを行うわけではありません。その後、適切なデータパケットに ECT をマークでき、ECN をサポートするネットワークノードは、対応する輻輳条件で CE をマークできます。受信側は ACK の ECE で報告し、送信側は輻輳応答を実行し、CWR で応答済みであることを示します。RFC 3168:古典的な ECN

CE は、それ以降パケットが決して失われないことを保証するものではありません。キュー、深刻な輻輳、およびサポートされていないパスにより、パケットは依然としてドロップされる可能性があります。送信側のウィンドウ応答は使用されている輻輳アルゴリズムに依存し、一律に「毎回半分にする」と呼べるものではありません。DCTCP などには異なるフィードバック処理があります。後続の仕様では一部の実験的制限が緩和されているため、上記のプロセスを明確に古典的なベースラインとして扱い、すべての現代の ECN の派生方式をカバーするためにこれを使用しないでください。RFC 8311:ECN 実験的境界の更新

ローカル構築、フラグメンテーション、およびチェックサム

import struct
from ipaddress import IPv4Address, IPv4Network

def checksum(data):
    assert len(data)%2 == 0
    total = sum(struct.unpack('!%dH' % (len(data)//2), data))
    while total >> 16:
        total = (total&0xffff)+(total>>16)
    return (~total)&0xffff

header = bytearray(struct.pack('!BBHHHBBH4s4s',
    0x45, (46<<2)|2, 4000, 0x1234, 0, 64, 17, 0,
    IPv4Address('192.0.2.10').packed, IPv4Address('198.51.100.20').packed))
header[10:12] = struct.pack('!H', checksum(header))
assert checksum(header) == 0
assert header[1] == 0xBA
header[8] = 63
assert checksum(header) != 0
header[10:12] = bytes(2)
header[10:12] = struct.pack('!H', checksum(header))
assert checksum(header) == 0

payload = bytes(i%251 for i in range(3980))
chunk = ((1500-20)//8)*8
fragments = []
for start in range(0,len(payload),chunk):
    data = payload[start:start+chunk]
    fragments.append((start//8, int(start+len(data)<len(payload)), len(data)+20, data))
assert [f[:3] for f in fragments] == [(0,1,1500),(185,1,1500),(370,0,1040)]
assert b''.join(f[3] for f in fragments) == payload
assert fragments[-1][0]*8 + len(fragments[-1][3]) + 20 == 4000
assert str(IPv4Network('192.0.2.130/26', strict=False)) == '192.0.2.128/26'
print('IPv4 ヘッダチェックサム、TTL 更新、DS バイト、フラグメント区間、および CIDR チェックが通過')

これはオプションなしの教育用パケットのローカル検証であり、探知トラフィックの送信、MTU/MSS の変更、または輻輳制御設定の調整は行われていません。

継続的な参照