Skip to main content

atmos/kernel/net/
eth_frame.rs

1//! L2: Ethernet フレームの解析と振り分け(純粋ロジック)。
2//!
3//! ## なぜ純粋モジュールにするか
4//! ここを誤ると
5//! - 短いフレームで範囲外を読む(領域外アクセス)
6//! - VLAN タグ付きフレームを「未対応 EtherType」として捨てる
7//! - 自分宛でないフレームを上位へ渡す
8//!
9//! といった形で壊れるが、いずれも**上層では「なぜか通信できない」としか見えない**。
10//! 実際、L1/L2 に単体試験が無いまま L4(TCP)/L7(CSS) を追いかけて
11//! 大きく遠回りした(`spec/l1_l2_usb.md` 参照)。
12//!
13//! ## 典拠
14//! IEEE 802.3(Ethernet II フレーム形式)、IEEE 802.1Q(VLAN タグ)。
15//! 宛先 MAC(6) / 送信元 MAC(6) / EtherType(2) / ペイロード。
16//! EtherType が `0x8100` なら VLAN タグが 4 バイト挿入され、
17//! 本来の EtherType は offset 16 に来る。
18
19/// Ethernet II ヘッダ長。
20pub const ETH_HEADER_LEN: usize = 14;
21/// VLAN タグ(802.1Q)の長さ。
22pub const VLAN_TAG_LEN: usize = 4;
23
24pub const ETHERTYPE_IPV4: u16 = 0x0800;
25pub const ETHERTYPE_ARP: u16 = 0x0806;
26pub const ETHERTYPE_VLAN: u16 = 0x8100;
27pub const ETHERTYPE_IPV6: u16 = 0x86DD;
28
29/// ブロードキャスト MAC。
30pub const MAC_BROADCAST: [u8; 6] = [0xFF; 6];
31
32/// フレームの振り分け結果。
33#[derive(Debug, PartialEq, Eq, Clone, Copy)]
34pub enum Dispatch {
35    /// ARP パケット。既存の解析器はフレーム先頭から読むので範囲は返さない。
36    Arp,
37    /// IPv4 パケット。`(開始, 終了)` はペイロード範囲。
38    Ipv4 { start: usize, end: usize },
39    /// 対応していない EtherType。捨ててよいが、何を捨てたかは記録する。
40    Unsupported { ethertype: u16 },
41}
42
43/// 解析失敗の理由。
44#[derive(Debug, PartialEq, Eq, Clone, Copy)]
45pub enum FrameError {
46    /// Ethernet ヘッダに満たない。
47    TooShort { got: usize },
48    /// VLAN タグ付きだがタグ分の長さが無い。
49    TruncatedVlan { got: usize },
50}
51
52/// フレームの宛先 MAC を返す。長さ不足なら `None`。
53pub fn dst_mac(frame: &[u8]) -> Option<[u8; 6]> {
54    if frame.len() < 6 {
55        return None;
56    }
57    let mut m = [0u8; 6];
58    m.copy_from_slice(&frame[..6]);
59    Some(m)
60}
61
62/// このフレームを受け取るべきか(自分宛/ブロードキャスト/マルチキャスト)。
63///
64/// 他人宛を上位へ渡すと、別ホスト向けの応答を自分のものとして処理しうる。
65pub fn is_for_us(frame: &[u8], my_mac: [u8; 6]) -> bool {
66    match dst_mac(frame) {
67        None => false,
68        Some(d) => {
69            if d == my_mac || d == MAC_BROADCAST {
70                return true;
71            }
72            // マルチキャストは先頭バイトの最下位ビットが 1。
73            d[0] & 0x01 != 0
74        }
75    }
76}
77
78/// EtherType とペイロード開始位置を求める(VLAN タグを剥がす)。
79pub fn ethertype_and_payload(frame: &[u8]) -> Result<(u16, usize), FrameError> {
80    if frame.len() < ETH_HEADER_LEN {
81        return Err(FrameError::TooShort { got: frame.len() });
82    }
83    let mut et = ((frame[12] as u16) << 8) | frame[13] as u16;
84    let mut payload_start = ETH_HEADER_LEN;
85    if et == ETHERTYPE_VLAN {
86        if frame.len() < ETH_HEADER_LEN + VLAN_TAG_LEN {
87            return Err(FrameError::TruncatedVlan { got: frame.len() });
88        }
89        et = ((frame[16] as u16) << 8) | frame[17] as u16;
90        payload_start = ETH_HEADER_LEN + VLAN_TAG_LEN;
91    }
92    Ok((et, payload_start))
93}
94
95/// フレームを振り分ける。
96pub fn dispatch(frame: &[u8]) -> Result<Dispatch, FrameError> {
97    let (et, start) = ethertype_and_payload(frame)?;
98    match et {
99        ETHERTYPE_ARP => Ok(Dispatch::Arp),
100        ETHERTYPE_IPV4 => Ok(Dispatch::Ipv4 {
101            start,
102            end: frame.len(),
103        }),
104        other => Ok(Dispatch::Unsupported { ethertype: other }),
105    }
106}