Skip to main content

atmos/kernel/net/
tcp_state.rs

1// L4 (Transport) — TCP 受信側状態機械の「純粋ロジック」。
2//
3// ハードウェア(NIC)・グローバル状態(`TCP_SOCKETS`/`NET_LOCK`)・ログ出力に
4// 一切依存しない自己完結モジュールとして `tcp.rs` から切り出した
5// (`addr_util.rs` と同じ狙い)。これによりホスト側ユニットテスト
6// (`tests/`, `make test`)から `#[path]` で直接取り込み、
7// パケットロス・順序逆転・重複といった**異常系を含めて**検証できる。
8//
9// 呼び出し側 (`tcp::parse_tcp_ipv4_packet`) の責務:
10//   1. IPv4/TCP ヘッダから `Segment` を組み立てる
11//   2. `on_segment()` を呼ぶ
12//   3. 戻り値 `SegmentOutcome` に従って rx_buffer への追記と ACK 送信を行う
13// 状態(`RecvState`)の更新は `on_segment()` が直接行う。
14
15/// TCP フラグビット。
16pub const FLAG_FIN: u8 = 0x01;
17pub const FLAG_SYN: u8 = 0x02;
18pub const FLAG_RST: u8 = 0x04;
19pub const FLAG_PSH: u8 = 0x08;
20pub const FLAG_ACK: u8 = 0x10;
21
22#[derive(Copy, Clone, PartialEq, Eq, Debug)]
23pub enum TcpState {
24    Closed,
25    SynSent,
26    Established,
27    FinWait1,
28    FinWait2,
29    TimeWait,
30    CloseWait,
31    LastAck,
32}
33
34pub fn tcp_state_name(state: TcpState) -> &'static str {
35    match state {
36        TcpState::Closed => "CLOSED",
37        TcpState::SynSent => "SYN-SENT",
38        TcpState::Established => "ESTABLISHED",
39        TcpState::FinWait1 => "FIN-WAIT-1",
40        TcpState::FinWait2 => "FIN-WAIT-2",
41        TcpState::TimeWait => "TIME-WAIT",
42        TcpState::CloseWait => "CLOSE-WAIT",
43        TcpState::LastAck => "LAST-ACK",
44    }
45}
46
47/// 受信した 1 セグメントのうち、状態遷移の判断に必要な情報だけを抜き出したもの。
48#[derive(Copy, Clone, Debug)]
49pub struct Segment {
50    pub seq: u32,
51    pub ack: u32,
52    pub flags: u8,
53    pub payload_len: u32,
54}
55
56/// ソケットのうち、受信側状態機械が読み書きする部分だけを抜き出したもの。
57#[derive(Copy, Clone, PartialEq, Eq, Debug)]
58pub struct RecvState {
59    pub state: TcpState,
60    /// 次に自分が送信するときに使うシーケンス番号。
61    pub snd_nxt: u32,
62    /// 次に受信を期待するシーケンス番号(これ未満は受信済み)。
63    pub rcv_nxt: u32,
64    /// 相手からの FIN を「順序どおりに」受け取ったか。
65    /// これが true になると上位層は EOF と解釈するため、**データの取りこぼしが
66    /// ある状態で true にしてはならない**(レスポンス途中切れの直接原因になる)。
67    pub fin_received: bool,
68}
69
70/// `on_segment` の結果として呼び出し側が行うべき副作用。
71#[derive(Copy, Clone, PartialEq, Eq, Debug)]
72pub struct SegmentOutcome {
73    /// true なら、このセグメントのペイロードを rx_buffer へ追記してよい
74    /// (順序どおりに到着したデータ)。false ならペイロードは破棄する。
75    pub accept_payload: bool,
76    /// true なら ACK を送り返す。
77    pub send_ack: bool,
78}
79
80/// ソケットテーブルから空きスロットを 1 つ選ぶ(純粋ロジック)。
81///
82/// 実体の `TcpSocket` は `Vec` を含み `Copy` でないため配列ごと渡せない。
83/// 代わりに「長さ」と「その添字が使用中か」を返すクロージャだけを受け取ることで、
84/// グローバル状態から完全に切り離してテストできるようにしている。
85///
86/// 満杯時のエラー文字列は呼び出し側・テストの双方が依存するため定数化してある。
87pub const ERR_TABLE_FULL: &str = "tcp socket table full";
88
89pub fn find_free_slot(len: usize, is_occupied: impl Fn(usize) -> bool) -> Result<usize, &'static str> {
90    for i in 0..len {
91        if !is_occupied(i) {
92            return Ok(i);
93        }
94    }
95    Err(ERR_TABLE_FULL)
96}
97
98/// エフェメラルポートカウンタの次の値を、既存の生きたソケットと衝突しない
99/// ところまで進めて返す(純粋ロジック)。
100///
101/// 【2026-08-31】カウンタは 40000 起点で単調増加し、65535 到達で
102/// 40000 へラップアラウンドする。ラップアラウンド後、テーブルに残っている
103/// 生きたソケットと同じローカルポートを再び払い出すと、`tcp_find_socket`
104/// は `local_port + remote_ip + remote_port` の一致だけで判定するため、
105/// 別の生きた接続と誤って混線するリスクがある
106/// (`TCP_SOCKET_TABLE_SIZE` が小さくポート範囲が広いため実害の可能性は
107/// 低いが、理論上は起こりうる。`spec/TODO.md` 参照)。
108///
109/// `is_in_use` が真を返す間は候補を進め続ける。全ポートが使用中
110/// (テーブルが空いているのにこれが起きることは無いはずだが、
111/// 呼び出し側の不整合に備えて)なら `None` を返し、
112/// 呼び出し側は従来どおり衝突未確認のまま払い出すか、エラーにする。
113///
114/// 計算量: 最悪 O(ポート範囲) だが、通常は 0〜数回で衝突なしと判定できる。
115pub fn next_ephemeral_port(start: u16, is_in_use: impl Fn(u16) -> bool) -> Option<u16> {
116    const MIN: u16 = 40000;
117    let mut p = if start < MIN { MIN } else { start };
118    let first = p;
119    loop {
120        if !is_in_use(p) {
121            return Some(p);
122        }
123        p = if p == u16::MAX { MIN } else { p + 1 };
124        if p == first {
125            return None;
126        }
127    }
128}
129
130/// RFC 1982 準拠のシリアル番号比較: `a` が `b` より新しければ true。
131/// 単純な `a > b` では 2^32 の折り返し(wraparound)で誤判定するため必須。
132fn seq_gt(a: u32, b: u32) -> bool {
133    (a.wrapping_sub(b) as i32) > 0
134}
135
136/// 受信セグメント 1 個を状態機械へ適用する。
137pub fn on_segment(st: &mut RecvState, seg: &Segment) -> SegmentOutcome {
138    let mut out = SegmentOutcome {
139        accept_payload: false,
140        send_ack: false,
141    };
142
143    // --- RST: 状態を問わず即座に接続断 ---
144    // これを見落とすと、正当な接続拒否を受けても SYN 再送タイムアウトが
145    // 尽きるまで無駄に待ち続けることになる。
146    if seg.flags & FLAG_RST != 0 {
147        st.state = TcpState::Closed;
148        return out;
149    }
150
151    match st.state {
152        TcpState::SynSent => {
153            // SYN+ACK のみを受理する。SYN 単独(同時オープン)は本スタックでは非対応。
154            if seg.flags & (FLAG_SYN | FLAG_ACK) == (FLAG_SYN | FLAG_ACK) {
155                st.state = TcpState::Established;
156                st.rcv_nxt = seg.seq.wrapping_add(1);
157                st.snd_nxt = seg.ack;
158                out.send_ack = true;
159            }
160        }
161
162        TcpState::Established
163        | TcpState::FinWait1
164        | TcpState::FinWait2
165        | TcpState::CloseWait
166        | TcpState::LastAck => {
167            // --- ACK 処理 ---
168            // 【重要】以前は `snd_nxt = ack` と無条件に代入していたため、
169            // 遅延・重複した古い ACK が届くと snd_nxt が**巻き戻り**、
170            // 次の送信が既に送った番号を再利用して TCP ストリームを破壊して
171            // いた。snd_nxt は決して後退させない(前進のみ)。
172            if seg.flags & FLAG_ACK != 0 && seq_gt(seg.ack, st.snd_nxt) {
173                st.snd_nxt = seg.ack;
174            }
175
176            // --- ペイロード処理 ---
177            // 順序どおり(seq == rcv_nxt)のときのみ受理する。本スタックは
178            // 順序逆転セグメントの再組み立てバッファを持たないため、
179            // ギャップがある場合は破棄するしかない。
180            let in_order_data = seg.payload_len > 0 && seg.seq == st.rcv_nxt;
181            if in_order_data {
182                st.rcv_nxt = st.rcv_nxt.wrapping_add(seg.payload_len);
183                out.accept_payload = true;
184                out.send_ack = true;
185            } else if seg.payload_len > 0 {
186                // 【改善】順序逆転・重複データを黙って捨てるのではなく、
187                // 現在の rcv_nxt を載せた ACK(重複 ACK)を返す。相手はこれを
188                // 見て欠落セグメントを再送できる。以前は無反応だったため、
189                // 相手側の RTO が切れるまで回復の手がかりが無かった。
190                out.send_ack = true;
191            }
192
193            // --- FIN 処理 ---
194            // 【重要な修正】FIN はシーケンス空間上、ペイロードの直後の 1 番地を
195            // 占める。したがって FIN が「順序どおり」なのは
196            //     seg.seq + payload_len == rcv_nxt(ペイロード処理後)
197            // のときだけである。
198            //
199            // 以前の実装は `seq == rcv_nxt || payload_len > 0` という条件で
200            // FIN を受理していたため、
201            //   * ギャップがあってペイロードを破棄したセグメント(payload_len>0)
202            //   * 順序逆転した純粋 FIN
203            // でも `fin_received = true` にしてしまっていた。上位層はこれを
204            // EOF と解釈するので、**まだ届いていないデータがあるのに受信完了と
205            // 誤認**し、レスポンスが途中で切れる(TLS 復号エラー・CSS の途中
206            // 切断などの直接原因)。順序どおりの FIN のみを受理する。
207            if seg.flags & FLAG_FIN != 0 {
208                let fin_seq = seg.seq.wrapping_add(seg.payload_len);
209                if fin_seq == st.rcv_nxt {
210                    st.rcv_nxt = st.rcv_nxt.wrapping_add(1);
211                    st.fin_received = true;
212                    out.send_ack = true;
213
214                    match st.state {
215                        TcpState::Established => st.state = TcpState::CloseWait,
216                        TcpState::FinWait1 => {
217                            // 自分の FIN も同時に ACK されていれば TimeWait へ。
218                            if seg.flags & FLAG_ACK != 0 && seg.ack == st.snd_nxt {
219                                st.state = TcpState::TimeWait;
220                            }
221                        }
222                        TcpState::FinWait2 => st.state = TcpState::TimeWait,
223                        _ => {}
224                    }
225                } else {
226                    // 順序外の FIN。受理せず、重複 ACK だけ返して相手に
227                    // 欠落分の再送を促す。fin_received は立てない。
228                    out.send_ack = true;
229                }
230            }
231
232            // --- 自分が送った FIN に対する ACK ---
233            if st.state == TcpState::FinWait1
234                && seg.flags & FLAG_ACK != 0
235                && seg.ack == st.snd_nxt
236                && seg.flags & FLAG_FIN == 0
237            {
238                st.state = TcpState::FinWait2;
239            }
240        }
241
242        TcpState::Closed | TcpState::TimeWait => {
243            // 何もしない(クローズ済みソケットへの遅延パケット等)。
244        }
245    }
246
247    out
248}