皆さんこんにちは、シャオリンです。
Byte とのインタビューで、ある読者に次のような質問がありました。
要約すると、次の 2 つの質問があります。
- TCP 3 ウェイ ハンドシェイクで、クライアントが 2 回目のハンドシェイクで受信した ack 確認番号が予期したものと異なる場合、どうなりますか? RST メッセージを直接破棄するか、返す必要がありますか?
- 誤った ack (2 回目のハンドシェイクでの ack) はどのような状況で受信されますか?
よくある質問
馬鹿にしないで、この質問を直接言って、RST メッセージを返します。プロセスは次のとおりです。
過去の接続を回避するためのスリーウェイ ハンドシェイク
クライアントが連続して接続を確立するために複数の SYN メッセージを送信した後、ネットワークが輻輳すると、クライアントは正しくない ack を受信する場合があります。具体的なプロセスは次のとおりです。
- クライアントは最初に SYN (seq = 90) メッセージを送信しましたが、ネットワークによってブロックされ、サーバーが受信しませんでした. その後、クライアントは SYN (seq = 100) メッセージを再送信しました. SYN、しかし再送信 送信された SYN のシリアル番号は同じです。
- 「古い SYN メッセージ」は「最新の SYN」メッセージよりも前にサーバーに到着したため、この時点でサーバーはクライアントに SYN + ACK メッセージを返し、このメッセージの確認番号は 91 (90+1 ) です。 .
- クライアントがそれを受信した後、受信することを期待する確認番号は 90+1 ではなく 100+1 である必要があるため、RST メッセージが返されます。
- サーバーは RST メッセージを受信すると、接続を終了します。
- 最新の SYN がサーバーに到着すると、クライアントとサーバーは正常にスリーウェイ ハンドシェイクを完了できます。
上記の「古い SYN メッセージ」は履歴接続と呼ばれます.TCP が接続を確立するためにスリーウェイ ハンドシェイクを使用する主な理由は、「履歴接続」が接続を初期化するのを防ぐためです.
また、RFC 793 から、TCP 接続がスリーウェイ ハンドシェイクを使用する主な理由を知ることができます。
スリーウェイ ハンドシェイクの主な理由は、古い重複した接続の開始によって混乱が生じるのを防ぐためです。
簡単に言えば、スリーウェイ ハンドシェイクの最大の理由は、古い繰り返しの接続初期化による混乱を防ぐことです。RFC によって提供された履歴接続を防止するスリーウェイ ハンドシェイクのケース ダイアグラムは次のとおりです。
RFC793
双方向ハンドシェイク接続の場合、過去の接続をブロックできないのに、TCP 双方向ハンドシェイクで過去の接続を阻止できないのはなぜですか?
主に、2 つのハンドシェイクの場合、「パッシブ イニシエーター」には「アクティブ イニシエーター」が履歴接続を防止するための中間状態がないため、最初に結論を直接述べさせてください。接続し、リソースの浪費につながります。
考えてみてください。2回のハンドシェイクの場合、「パッシブイニシエーター」はSYNメッセージを受信した後にESTABLISHED状態に入ります。つまり、この時点で相手にデータを送信できますが、「アクティブイニシエーター」はまだです。この時点で ESTABLISHED 状態. ESTABLISHED 状態にならない場合、これが履歴接続であると想定して、アクティブ イニシエータはこの接続が履歴接続であると判断し、切断するために RST メッセージを返し、「パッシブ」イニシエーター」は ESTABLISHED 状態に入るため、データを送信できますが、これが過去の接続であることを認識せず、RST メッセージを受信した後にのみ切断します。
双方向ハンドシェイクが履歴接続のブロックに失敗する
上記のシナリオでは、「パッシブ イニシエーター」はデータを「アクティブ イニシエーター」に送信する前に履歴接続をブロックしなかったため、「パッシブ イニシエーター」は履歴接続を確立して無駄にデータを送信したことがわかります。 「パッシブイニシエーター」のリソース。
したがって、この現象を解決するには、「パッシブイニシエーター」がデータを送信する前、つまり接続が確立される前に履歴接続をブロックして、リソースの浪費を引き起こさないようにし、この機能を実現するには、三回握手。
ソースコード分析
return RST と言ったら RST を返しますか? もちろん、そうではありません。ソース コードを使用して、私が言った結論を証明する必要があります。
ソースコード解析が必要と聞くと落胆する学生もいるかもしれません。
実際、今日の問題を分析するために、そうでない場合は理解できる限り、コードのロジックを表現するために中国語も使用するので、私のテキストを単純に読むこともできます。
今回は、SYN_SENT 状態で誤った確認番号を含む syn+ack メッセージを処理する方法を分析することに焦点を当てます。
SYN_SENT 状態のクライアントは、サーバーから syn+ack メッセージを受信した後、最終的に tcp_rcv_state_process を呼び出し、そこで TCP 状態に従って対応する処理を行います. ここでは、SYN_SENT 状態のみに焦点を当てます.
// net/ipv4/tcp_ipv4.c
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
...
int queued = 0;
...
switch (sk->sk_state) {
case TCP_CLOSE:
...
case TCP_LISTEN:
...
case TCP_SYN_SENT:
....
queued = tcp_rcv_synsent_state_process(sk, skb, th);
if (queued >= 0)
return queued;
...
}
次に、tcp_rcv_synsent_state_process 関数が引き続き呼び出されることがわかります。
static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th)
{
....
if (th->ack) {
/* rfc793:
* "If the state is SYN-SENT then
* first check the ACK bit
* If the ACK bit is set
* If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send
* a reset (unless the RST bit is set, if so drop
* the segment and return)"
*/
// ack 的确认号不是预期的
if (!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_una) ||
after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt))
//回 RST 报文
goto reset_and_undo;
...
}
上記の関数から、SYN_SENT 状態では、クライアントが誤った確認番号を含む syn+ack メッセージを受信すると、RST メッセージを返すことがわかります。
まとめ
TCP 3 ウェイ ハンドシェイクで、クライアントが 2 回目のハンドシェイクで受信した ack 確認番号が予期したものと異なる場合、どうなりますか? RST メッセージを直接破棄するか、返す必要がありますか?
RST メッセージを返します。
誤った ack (2 回目のハンドシェイクでの ack) はどのような状況で受信されますか?
クライアントが複数の SYN パケットを送信し、ネットワークが輻輳した場合、「古い SYN パケット」が「新しい SYN パケット」よりも先にサーバーに到着し、サーバーは受信した「古い SYN パケット」に追従するようになります。 syn+ack メッセージを返信しますが、このメッセージの確認応答番号はクライアントが受信することを期待するものではないため、クライアントは RST メッセージを返します。
以上!
RFC文書+ソースコードを使って私の結論を説明する、そんな厳格な小林、誰もいない