Skip to main content

enclavia_protocol/
egress.rs

1//! Wire format shared between the in-enclave egress daemon
2//! (`enclavia-egress`) and the host-side relay (`egress-host`).
3//!
4//! Transport (vsock from inside the enclave, AF_VSOCK or
5//! `vhost-device-vsock` UDS on the host depending on the runtime) is
6//! external to this module: callers hand in any `AsyncRead + AsyncWrite`
7//! and the helpers read or write the opener frame.
8//!
9//! On every new connection the in-enclave daemon writes one
10//! length-prefixed CBOR [`Open`] frame, then the bidirectional byte
11//! stream begins. The host-side relay reads the frame, dials the
12//! requested destination, and splices.
13
14use std::io;
15use std::net::Ipv4Addr;
16
17use serde::{Deserialize, Serialize};
18use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
19
20/// Maximum size (in bytes) of the opener CBOR frame. Plenty of room for
21/// the small `Open` enum we serialize today, but tight enough to reject
22/// obvious junk before allocating.
23pub const MAX_OPEN_FRAME_SIZE: u32 = 4096;
24
25/// Opener frame sent by the in-enclave daemon on every new connection.
26///
27/// Wire format: 4-byte big-endian length prefix, then CBOR-encoded
28/// `Open`, then the bidirectional byte stream begins.
29///
30/// IPv6 is intentionally not representable here. The epic punts IPv6
31/// for v1; the in-enclave filter is expected to reject v6 destinations
32/// before they ever reach the wire.
33#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(tag = "type")]
35pub enum Open {
36    Tcp {
37        #[serde(with = "ipv4_octets")]
38        host: Ipv4Addr,
39        port: u16,
40    },
41}
42
43// Force `Ipv4Addr` onto a stable wire shape (4-byte array) regardless of
44// serde's human-readable mode. CBOR's `is_human_readable()` is true, so
45// the default Ipv4Addr impl would emit a string; we want the compact
46// form and freedom to switch encoders later without a wire break.
47pub(crate) mod ipv4_octets {
48    use std::net::Ipv4Addr;
49
50    use serde::{Deserialize, Deserializer, Serialize, Serializer};
51
52    pub fn serialize<S: Serializer>(addr: &Ipv4Addr, s: S) -> Result<S::Ok, S::Error> {
53        addr.octets().serialize(s)
54    }
55
56    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Ipv4Addr, D::Error> {
57        let bytes = <[u8; 4]>::deserialize(d)?;
58        Ok(Ipv4Addr::from(bytes))
59    }
60}
61
62/// Errors surfaced while reading the opener frame from a relay stream.
63#[derive(Debug, thiserror::Error)]
64pub enum ReadOpenError {
65    #[error("io error: {0}")]
66    Io(#[from] io::Error),
67    #[error("opener frame too large: {0} > {MAX_OPEN_FRAME_SIZE}")]
68    FrameTooLarge(u32),
69    #[error("failed to decode opener frame: {0}")]
70    Decode(#[from] ciborium::de::Error<io::Error>),
71}
72
73/// Read the length-prefixed CBOR `Open` frame from `stream`.
74pub async fn read_open_frame<S>(stream: &mut S) -> Result<Open, ReadOpenError>
75where
76    S: AsyncRead + Unpin,
77{
78    let len = stream.read_u32().await?;
79    if len > MAX_OPEN_FRAME_SIZE {
80        return Err(ReadOpenError::FrameTooLarge(len));
81    }
82    let mut buf = vec![0u8; len as usize];
83    stream.read_exact(&mut buf).await?;
84    let open: Open = ciborium::from_reader(&buf[..])?;
85    Ok(open)
86}
87
88/// Write a length-prefixed CBOR `Open` frame to `stream`.
89pub async fn write_open_frame<S>(stream: &mut S, open: &Open) -> io::Result<()>
90where
91    S: AsyncWrite + Unpin,
92{
93    let mut buf = Vec::new();
94    ciborium::into_writer(open, &mut buf).expect("ciborium encode Open frame");
95    let len: u32 = buf
96        .len()
97        .try_into()
98        .expect("Open frame fits in u32 (CBOR encoding is small)");
99    stream.write_all(&len.to_be_bytes()).await?;
100    stream.write_all(&buf).await?;
101    stream.flush().await?;
102    Ok(())
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn open_roundtrip_cbor() {
111        let open = Open::Tcp {
112            host: Ipv4Addr::new(127, 0, 0, 1),
113            port: 4242,
114        };
115        let mut buf = Vec::new();
116        ciborium::into_writer(&open, &mut buf).unwrap();
117        let decoded: Open = ciborium::from_reader(&buf[..]).unwrap();
118        assert_eq!(open, decoded);
119    }
120}