RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / net / device.rs
4318 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//! The one "device" smoltcp talks to: the virtio-net card (if there is
//! one) *plus* a loopback queue.
//!
//! **Loopback.** Programs talk to themselves over the network all the
//! time: a database on 127.0.0.1, a test client and server, a compositor
//! and its clients. Linux has a separate `lo` interface for that. We keep
//! smoltcp's single Ethernet interface and give it a second address,
//! 127.0.0.1/8, next to DHCP's. Then this device does the trick: a frame
//! meant for *ourselves* never goes to the card, it's put on the `lo`
//! queue, and `receive` hands queued frames back to smoltcp first.
//! "Meant for ourselves" is:
//!   * an ARP request asking who has 127.x.x.x or our own DHCP address
//!     (smoltcp then answers itself: "that's me", with its own MAC), and
//!   * any frame sent to our own MAC address.
//! So 127.0.0.1 works with no network card at all (MAC 02:00:00:00:00:01,
//! a "locally administered" one), and connecting to our own 10.0.2.15
//! doesn't leave the machine either, just like Linux.

use super::virtio_net::{MTU, VirtioNet};
use alloc::collections::VecDeque;
use alloc::vec;
use alloc::vec::Vec;
use smoltcp::phy::{self, DeviceCapabilities, Medium};
use smoltcp::time::Instant;

/// Our MAC when there's no card.
pub const LOOPBACK_MAC: [u8; 6] = [0x02, 0, 0, 0, 0, 1];
/// Frames waiting on lo at most (more are dropped, like a full queue).
const LO_QUEUE: usize = 256;

pub struct NetDevice {
    pub nic: Option<VirtioNet>,
    pub mac: [u8; 6],
    /// Our DHCP address (frames ARPing for it are ours).
    pub own_ip: Option<[u8; 4]>,
    lo: VecDeque<Vec<u8>>,
    pub lo_packets: u64,
}

impl NetDevice {
    pub fn new(nic: Option<VirtioNet>) -> NetDevice {
        let mac = nic.as_ref().map_or(LOOPBACK_MAC, |n| n.mac);
        NetDevice { nic, mac, own_ip: None, lo: VecDeque::new(), lo_packets: 0 }
    }

    /// Frames are waiting on lo (net::poll then polls smoltcp again).
    pub fn lo_pending(&self) -> bool {
        !self.lo.is_empty()
    }

    /// Is this outgoing Ethernet frame addressed to us? (See the top.)
    fn is_for_us(&self, frame: &[u8]) -> bool {
        if frame.len() < 14 {
            return false;
        }
        if frame[0..6] == self.mac {
            return true;
        }
        // ARP (ethertype 0x0806): the asked-for IP is at bytes 38..42.
        if frame[12..14] == [0x08, 0x06] && frame.len() >= 42 {
            let target = [frame[38], frame[39], frame[40], frame[41]];
            return target[0] == 127 || Some(target) == self.own_ip;
        }
        false
    }
}

// smoltcp asks a `Device` for *tokens*: an RxToken is "a received frame you
// may look at", a TxToken is "permission to build one frame to send".

pub struct RxToken(Vec<u8>);
pub struct TxToken<'a>(&'a mut NetDevice);

impl phy::Device for NetDevice {
    type RxToken<'a> = RxToken;
    type TxToken<'a> = TxToken<'a>;

    fn receive(&mut self, _now: Instant) -> Option<(RxToken, TxToken<'_>)> {
        // Loopback first, then the card.
        let frame = match self.lo.pop_front() {
            Some(f) => f,
            None => self.nic.as_mut()?.receive_frame()?,
        };
        Some((RxToken(frame), TxToken(self)))
    }

    fn transmit(&mut self, _now: Instant) -> Option<TxToken<'_>> {
        Some(TxToken(self))
    }

    fn capabilities(&self) -> DeviceCapabilities {
        let mut caps = DeviceCapabilities::default();
        caps.medium = Medium::Ethernet;
        caps.max_transmission_unit = MTU;
        caps
    }
}

impl phy::RxToken for RxToken {
    fn consume<R, F: FnOnce(&[u8]) -> R>(self, f: F) -> R {
        f(&self.0)
    }
}

impl phy::TxToken for TxToken<'_> {
    /// smoltcp builds the frame in `f`; we then decide where it goes.
    fn consume<R, F: FnOnce(&mut [u8]) -> R>(self, len: usize, f: F) -> R {
        let dev = self.0;
        let mut frame = vec![0; len.min(MTU)];
        let result = f(&mut frame);
        if dev.is_for_us(&frame) {
            if dev.lo.len() < LO_QUEUE {
                dev.lo_packets += 1;
                dev.lo.push_back(frame);
            }
        } else if let Some(nic) = dev.nic.as_mut() {
            nic.send_frame(frame.len(), |buf| buf.copy_from_slice(&frame[..buf.len()]));
        } // no card: nowhere to send it
        result
    }
}