Skip to main content

atmos/kernel/
net_stack.rs

1//! ネットワークスタックの階層抽象化トレイト(アプリ⇄カーネル境界の疎結合層)。
2//!
3//! [`kernel::net`] の具体実装(TCP/DNS/HTTP 等)を [`LinkLayerApi`]/[`InternetLayerApi`]/
4//! [`TransportLayerApi`]/[`ApplicationLayerApi`] としてラップし、`TcpIpStack` の
5//! `web_get`/`web_post`/`web_get_binary` を単一の入口として提供する。
6//!
7//! **設計意図**: アプリ(ブラウザ等)とカーネル内部(DNS/TCP/TLS/HTTP パーサ)を疎結合にし、
8//! この入口で host/path を検証する(`validate_web_request`)ことで、カーネル内部は
9//! 「検証済みの入力しか来ない」前提で実装できる。エラーは [`TcpIpError`](層情報付き)で
10//! 返り、不正入力でパニックしない。詳細は `spec/DESIGN.md` §2「API 境界の事前条件検証」参照。
11#![allow(dead_code)]
12
13use alloc::string::String;
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum TcpIpLayer {
17    Link,
18    Internet,
19    Transport,
20    Application,
21}
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub struct TcpIpError {
25    pub layer: TcpIpLayer,
26    pub message: &'static str,
27}
28
29impl TcpIpError {
30    pub const fn new(layer: TcpIpLayer, message: &'static str) -> Self {
31        Self { layer, message }
32    }
33}
34
35pub trait LinkLayerApi {
36    fn link_up(&self) -> bool;
37}
38
39pub trait InternetLayerApi {
40    fn resolve_a(&self, host: &str) -> Result<[u8; 4], TcpIpError>;
41}
42
43pub trait TransportLayerApi {
44    fn tcp_connect(&self, remote_ip: [u8; 4], remote_port: u16) -> Result<u16, TcpIpError>;
45    fn tcp_send(&self, local_port: u16, data: &[u8]) -> Result<(), TcpIpError>;
46    fn tcp_recv(
47        &self,
48        local_port: u16,
49        timeout_iters: usize,
50    ) -> Result<alloc::vec::Vec<u8>, TcpIpError>;
51    fn tcp_close(&self, local_port: u16) -> Result<(), TcpIpError>;
52}
53
54pub trait ApplicationLayerApi {
55    fn http_get(&self, host: &str, path: &str) -> Result<AppHttpResponse, TcpIpError>;
56    fn https_get(&self, host: &str, path: &str) -> Result<AppHttpResponse, TcpIpError>;
57    fn http_post(
58        &self,
59        host: &str,
60        path: &str,
61        content_type: &str,
62        body: &[u8],
63    ) -> Result<AppHttpResponse, TcpIpError>;
64}
65
66#[derive(Clone)]
67pub struct AppHttpResponse {
68    pub remote_ip: [u8; 4],
69    pub local_port: u16,
70    pub status_code: u16,
71    pub reason: String,
72    pub headers: alloc::collections::BTreeMap<String, String>,
73    pub body: String,
74    /// ボディの生バイト列(バイナリ応答。`body` は lossy な文字列)。
75    pub body_bytes: alloc::vec::Vec<u8>,
76    pub tls: Option<TlsSessionMeta>,
77}
78
79#[derive(Clone)]
80pub struct TlsSessionMeta {
81    pub version: String,
82    pub cipher_suite: String,
83    pub cert_subject: String,
84    pub cert_issuer: String,
85    pub cert_not_after: String,
86}
87
88pub struct TcpIpStack;
89
90impl Default for TcpIpStack {
91    fn default() -> Self {
92        Self::new()
93    }
94}
95
96/// アプリ⇄カーネルのネットワーク API 境界での事前条件検証。
97///
98/// 方針: アプリからの要求はすべてこの関数を通った後にカーネル内部(DNS/TCP/TLS/HTTP パーサ)
99/// へ渡す。これにより内部関数は「検証済みの host/path しか来ない」前提で動ける(疎結合)。
100/// パニックではなく `Result` で弾くため、不正入力で OS が落ちることはない。
101/// - host: `名前[:ポート]`。名前は 1..=253 バイト、`A-Za-z0-9.-` のみ(空白・制御文字・
102///   CR/LF を排除)、ポートは 1..=65535(`net::addr_util::is_valid_request_host`)。
103///   HTTPS は接続先が 443 固定のため、ポート付きは DNS で失敗する(TODO.md)。
104/// - path: `/` 始まり、1..=4096 バイト、制御文字・CR/LF を含まない
105///   (CR/LF 混入による HTTP ヘッダインジェクションを防ぐ)
106pub fn validate_web_request(host: &str, path: &str) -> Result<(), TcpIpError> {
107    if !crate::kernel::net::addr_util::is_valid_request_host(host) {
108        return Err(TcpIpError::new(
109            TcpIpLayer::Application,
110            "invalid host",
111        ));
112    }
113    if !path.starts_with('/') || path.len() > 4096 {
114        return Err(TcpIpError::new(TcpIpLayer::Application, "invalid path"));
115    }
116    if path.bytes().any(|b| b < 0x20 || b == 0x7f) {
117        return Err(TcpIpError::new(
118            TcpIpLayer::Application,
119            "path contains control characters",
120        ));
121    }
122    Ok(())
123}
124
125/// POST ボディの上限(メモリ枯渇・異常要求の防御)。
126const MAX_REQUEST_BODY: usize = 8 * 1024 * 1024;
127
128impl TcpIpStack {
129    pub const fn new() -> Self {
130        Self
131    }
132
133    pub fn web_get(
134        &self,
135        is_https: bool,
136        host: &str,
137        path: &str,
138    ) -> Result<AppHttpResponse, TcpIpError> {
139        self.web_get_with_referer(is_https, host, path, None)
140    }
141
142    /// `Referer` ヘッダ付きの `web_get`(ページの読み込み・iframe の文書の読み込み用)。
143    pub fn web_get_with_referer(
144        &self,
145        is_https: bool,
146        host: &str,
147        path: &str,
148        referer: Option<&str>,
149    ) -> Result<AppHttpResponse, TcpIpError> {
150        validate_web_request(host, path)?;
151        if is_https {
152            self.https_get_ref(host, path, referer)
153        } else {
154            self.http_get_ref(host, path, referer)
155        }
156    }
157
158    fn http_get_ref(&self, host: &str, path: &str, referer: Option<&str>) -> Result<AppHttpResponse, TcpIpError> {
159        let resp = crate::kernel::net::http_get_real_with_referer(host, path, referer)
160            .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
161        Ok(AppHttpResponse {
162            remote_ip: resp.remote_ip,
163            local_port: resp.local_port,
164            status_code: resp.status_code,
165            reason: resp.reason,
166            headers: resp.headers,
167            body: resp.body,
168            body_bytes: resp.body_bytes,
169            tls: None,
170        })
171    }
172
173    fn https_get_ref(&self, host: &str, path: &str, referer: Option<&str>) -> Result<AppHttpResponse, TcpIpError> {
174        let resp = crate::kernel::tls::https_get_with_referer(host, path, referer)
175            .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
176        Ok(AppHttpResponse {
177            remote_ip: resp.remote_ip,
178            local_port: resp.local_port,
179            status_code: resp.status_code,
180            reason: resp.reason,
181            headers: resp.headers,
182            body: resp.body,
183            body_bytes: resp.body_bytes,
184            tls: Some(TlsSessionMeta {
185                version: resp.tls_version,
186                cipher_suite: resp.cipher_suite,
187                cert_subject: resp.cert_subject,
188                cert_issuer: resp.cert_issuer,
189                cert_not_after: resp.cert_not_after,
190            }),
191        })
192    }
193
194    /// is_https に応じて HTTPS(TLS) / HTTP の POST を振り分ける。
195    /// HTTPS フォーム送信が平文 HTTP に落ちてボディが届かない不具合を防ぐ。
196    pub fn web_post(
197        &self,
198        is_https: bool,
199        host: &str,
200        path: &str,
201        content_type: &str,
202        body: &[u8],
203    ) -> Result<AppHttpResponse, TcpIpError> {
204        validate_web_request(host, path)?;
205        if body.len() > MAX_REQUEST_BODY {
206            return Err(TcpIpError::new(
207                TcpIpLayer::Application,
208                "request body too large",
209            ));
210        }
211        if is_https {
212            let resp = crate::kernel::tls::https_post(host, path, content_type, body)
213                .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
214            Ok(AppHttpResponse {
215                remote_ip: resp.remote_ip,
216                local_port: resp.local_port,
217                status_code: resp.status_code,
218                reason: resp.reason,
219                headers: resp.headers,
220                body: resp.body,
221                body_bytes: resp.body_bytes,
222                tls: None,
223            })
224        } else {
225            self.http_post(host, path, content_type, body)
226        }
227    }
228
229    /// `web_get_binary` の厳格版(HTTPS で途中切れの応答を Err にする)。画像用。
230    /// 理由は `tls::https_get_binary_complete` を参照。
231    pub fn web_get_binary_complete(
232        &self,
233        is_https: bool,
234        host: &str,
235        path: &str,
236    ) -> Result<alloc::vec::Vec<u8>, TcpIpError> {
237        validate_web_request(host, path)?;
238        if is_https {
239            crate::kernel::tls::https_get_binary_complete(host, path)
240                .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))
241        } else {
242            crate::kernel::net::http_get_binary(host, path)
243                .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))
244        }
245    }
246
247    pub fn web_get_binary(
248        &self,
249        is_https: bool,
250        host: &str,
251        path: &str,
252    ) -> Result<alloc::vec::Vec<u8>, TcpIpError> {
253        validate_web_request(host, path)?;
254        if is_https {
255            crate::kernel::tls::https_get_binary(host, path)
256                .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))
257        } else {
258            crate::kernel::net::http_get_binary(host, path)
259                .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))
260        }
261    }
262
263    /// 任意メソッド(GET/POST/PUT/DELETE/PATCH/HEAD 等)の HTTP/HTTPS リクエスト。
264    /// method は英大文字トークンへ正規化し、未知の文字は弾く(ヘッダインジェクション防止)。
265    /// is_https に応じて TLS 経路(tls::https_request)と平文経路(http_request_real)を振り分ける。
266    pub fn web_request(
267        &self,
268        is_https: bool,
269        host: &str,
270        path: &str,
271        method: &str,
272        content_type: &str,
273        body: &[u8],
274        headers: &[(&str, &str)],
275    ) -> Result<AppHttpResponse, TcpIpError> {
276        validate_web_request(host, path)?;
277        if body.len() > MAX_REQUEST_BODY {
278            return Err(TcpIpError::new(
279                TcpIpLayer::Application,
280                "request body too large",
281            ));
282        }
283        // メソッドは A-Z のみ・1..=16 バイトに制限(CR/LF やスペース混入を排除)。
284        let m = method.to_ascii_uppercase();
285        if m.is_empty() || m.len() > 16 || !m.bytes().all(|b| b.is_ascii_uppercase()) {
286            return Err(TcpIpError::new(
287                TcpIpLayer::Application,
288                "invalid HTTP method",
289            ));
290        }
291        if is_https {
292            // body を取り得るメソッドのみ Some を渡す(GET/HEAD/DELETE は None)。
293            let body_opt = if !body.is_empty() || m == "POST" || m == "PUT" || m == "PATCH" {
294                Some(body)
295            } else {
296                None
297            };
298            let resp =
299                crate::kernel::tls::https_request(&m, host, path, headers, content_type, body_opt)
300                    .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
301            Ok(AppHttpResponse {
302                remote_ip: resp.remote_ip,
303                local_port: resp.local_port,
304                status_code: resp.status_code,
305                reason: resp.reason,
306                headers: resp.headers,
307                body: resp.body,
308                body_bytes: resp.body_bytes,
309                tls: Some(TlsSessionMeta {
310                    version: resp.tls_version,
311                    cipher_suite: resp.cipher_suite,
312                    cert_subject: resp.cert_subject,
313                    cert_issuer: resp.cert_issuer,
314                    cert_not_after: resp.cert_not_after,
315                }),
316            })
317        } else {
318            let resp = crate::kernel::net::http_request_real(&m, host, path, content_type, body)
319                .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
320            Ok(AppHttpResponse {
321                remote_ip: resp.remote_ip,
322                local_port: resp.local_port,
323                status_code: resp.status_code,
324                reason: resp.reason,
325                headers: resp.headers,
326                body: resp.body,
327                body_bytes: resp.body_bytes,
328                tls: None,
329            })
330        }
331    }
332}
333
334impl LinkLayerApi for TcpIpStack {
335    fn link_up(&self) -> bool {
336        crate::kernel::net::status().link_up
337    }
338}
339
340impl InternetLayerApi for TcpIpStack {
341    fn resolve_a(&self, host: &str) -> Result<[u8; 4], TcpIpError> {
342        crate::kernel::net::dns_query_a_via_udp(host)
343            .map_err(|e| TcpIpError::new(TcpIpLayer::Internet, e))
344    }
345}
346
347impl TransportLayerApi for TcpIpStack {
348    fn tcp_connect(&self, remote_ip: [u8; 4], remote_port: u16) -> Result<u16, TcpIpError> {
349        crate::kernel::net::tcp_connect_real(remote_ip, remote_port)
350            .map_err(|e| TcpIpError::new(TcpIpLayer::Transport, e))
351    }
352
353    fn tcp_send(&self, local_port: u16, data: &[u8]) -> Result<(), TcpIpError> {
354        crate::kernel::net::tcp_send_real(local_port, data)
355            .map_err(|e| TcpIpError::new(TcpIpLayer::Transport, e))
356    }
357
358    fn tcp_recv(
359        &self,
360        local_port: u16,
361        timeout_iters: usize,
362    ) -> Result<alloc::vec::Vec<u8>, TcpIpError> {
363        crate::kernel::net::tcp_recv_real(local_port, timeout_iters)
364            .map_err(|e| TcpIpError::new(TcpIpLayer::Transport, e))
365    }
366
367    fn tcp_close(&self, local_port: u16) -> Result<(), TcpIpError> {
368        crate::kernel::net::tcp_close_real(local_port)
369            .map_err(|e| TcpIpError::new(TcpIpLayer::Transport, e))
370    }
371}
372
373impl ApplicationLayerApi for TcpIpStack {
374    fn http_get(&self, host: &str, path: &str) -> Result<AppHttpResponse, TcpIpError> {
375        self.http_get_ref(host, path, None)
376    }
377
378    fn https_get(&self, host: &str, path: &str) -> Result<AppHttpResponse, TcpIpError> {
379        self.https_get_ref(host, path, None)
380    }
381
382    fn http_post(
383        &self,
384        host: &str,
385        path: &str,
386        content_type: &str,
387        body: &[u8],
388    ) -> Result<AppHttpResponse, TcpIpError> {
389        let resp = crate::kernel::net::http_post_real(host, path, content_type, body)
390            .map_err(|e| TcpIpError::new(TcpIpLayer::Application, e))?;
391        Ok(AppHttpResponse {
392            remote_ip: resp.remote_ip,
393            local_port: resp.local_port,
394            status_code: resp.status_code,
395            reason: resp.reason,
396            headers: resp.headers,
397            body: resp.body,
398            body_bytes: resp.body_bytes,
399            tls: None,
400        })
401    }
402}