Skip to content

Latest commit

 

History

History
133 lines (115 loc) · 4.55 KB

17-tcp-stack.org

File metadata and controls

133 lines (115 loc) · 4.55 KB

TCP network stack

In the last section we implemented a simple Address Resolution Protocol (ARP) program, which used the network driver to get hardware addresses from IP addresses. That is one part of a network stack, but developing a robust and reasonably complete network stack is a huge undertaking of its own. Fortunately someone has already done this in no_std Rust, so we’ll use the smoltcp crate to add a network stack to EuraliOS.

We’ll create a new program tcp

cargo new tcp

The traits to implement a physical layer which smoltcp can use are defined here. A smoltcp::phy::Device needs to implement capabilities(), receive() and transmit() functions. Those should return objects which implement smoltcp::phy::RxToken and smoltcp::phy::TxToken traits. RxToken represents received data which can be obtained with a consume() method; TxToken’s consume() function takes data and sends it.

The TCP program will communicate with the network card driver, so the only thing the Device needs to contain is a communication handle:

struct EthernetDevice {
    handle: Arc<syscalls::CommHandle>
}

impl EthernetDevice {
  fn new(handle: syscalls::CommHandle) -> Self {
      EthernetDevice{handle:Arc::new(handle)}
  }
}

We wrap the handle in an Arc because we’re going to need multiple references to it in the tokens. We can initialise it by first opening a handle for the NIC and passing in the handle:

let handle = syscalls::open("/dev/nic").expect("Couldn't open /dev/nic");
let device = EthernetDevice::new(handle);

The Device trait is

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

    fn capabilities(&self) -> DeviceCapabilities {
        ...
    }

    fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
        ...
    }

    fn transmit(&'a mut self) -> Option<Self::TxToken> {
        ...
    }

The capabilities method should tell TCP something about the capabilities of the specific network card being used. For now we’ll just hard-wire some safe defaults for the maximum packet size, and maximum number of packets in a “burst”:

fn capabilities(&self) -> DeviceCapabilities {
    let mut caps = DeviceCapabilities::default();
    caps.max_transmission_unit = 1500;
    caps.max_burst_size = Some(1);
    caps
}

Transmitting data

To transmit data the caller uses transmit() to get a TxToken, and then the consume() method on the TxToken. Since we don’t know how much data will be sent, all we can do in transmit() is copy the communication handle:

fn transmit(&'a mut self) -> Option<Self::TxToken> {
    Some(TxToken{handle: self.handle.clone()})
}

where TxToken is just:

struct TxToken {
    handle: Arc<syscalls::CommHandle>
}

The actual communication is performed when the consume() method is called. That is given the length (in u8 chars) and a function to be called to fill the buffer:

impl smoltcp::phy::TxToken for TxToken {
    fn consume<R, F>(mut self,
                     _timestamp: Instant,
                     length: usize, f: F
    ) -> smoltcp::Result<R> where F: FnOnce(&mut [u8]) -> smoltcp::Result<R> {
        // Allocate memory buffer
        let (mut buffer, _) = syscalls::malloc(length as u64, 0).unwrap();

        // Call function to fill buffer
        let res = f(buffer.as_mut_slice::<u8>(length));

        if res.is_ok() {
            // Transmit, sending buffer to NIC driver
            syscalls::send(
                self.handle.as_ref(),
                message::Message::Long(
                    message::WRITE,
                    (length as u64).into(),
                    buffer.into()));
        }
        res
    }
}

Dynamic Host Configuration Protocol (DHCP)

The DHCP protocol is a standard way to configure devices on a local network. It provides a way to discover the network gateway, DNS server, and be assigned an IP address.

./img/17-01-dhcp.png

In the next section we’ll try out TCP by writing a simple Gopher protocol browser.