diff --git a/Cargo.lock b/Cargo.lock index 78a7f32..608117f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -453,8 +453,10 @@ name = "lanparty-gateway" version = "0.1.0" dependencies = [ "anyhow", + "bytes", "clap", "lanparty-ctrl", + "lanparty-proto", "libc", "quinn", "rcgen", diff --git a/README.md b/README.md index f562c1b..2cec12b 100644 --- a/README.md +++ b/README.md @@ -82,4 +82,5 @@ cargo run -p lanparty-gateway -- \ The gateway currently connects to the relay as `role = gateway`, completes the control-stream hello/welcome handshake, opens an AF_PACKET socket on the LAN -interface, and then waits for shutdown. The frame bridge loop is not wired yet. +interface, and has relay Ethernet datagram send/receive helpers. The frame +bridge loop is not wired yet. diff --git a/crates/lanparty-gateway/Cargo.toml b/crates/lanparty-gateway/Cargo.toml index 96aa363..a99fa8f 100644 --- a/crates/lanparty-gateway/Cargo.toml +++ b/crates/lanparty-gateway/Cargo.toml @@ -5,8 +5,10 @@ edition.workspace = true [dependencies] anyhow.workspace = true +bytes.workspace = true clap.workspace = true lanparty-ctrl = { path = "../lanparty-ctrl" } +lanparty-proto = { path = "../lanparty-proto" } libc.workspace = true quinn.workspace = true rustls.workspace = true diff --git a/crates/lanparty-gateway/src/lib.rs b/crates/lanparty-gateway/src/lib.rs index 3ab8e8b..7eb24aa 100644 --- a/crates/lanparty-gateway/src/lib.rs +++ b/crates/lanparty-gateway/src/lib.rs @@ -15,11 +15,13 @@ use std::{ }; use anyhow::{Context, Result, bail}; +use bytes::Bytes; use clap::Parser; use lanparty_ctrl::{ CONTROL_LENGTH_PREFIX_LEN, ControlMessage, EndpointHello, MAX_CONTROL_MESSAGE_LEN, RELAY_ALPN, RoomCode, ServerWelcome, decode_control_frame, encode_control_message, }; +use lanparty_proto::{EthernetFrame, FrameType, decode_datagram, encode_datagram}; use quinn::{ClientConfig, Endpoint, crypto::rustls::QuicClientConfig}; use rustls::pki_types::CertificateDer; @@ -164,6 +166,24 @@ pub struct GatewayConnection { welcome: ServerWelcome, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ReceivedEthernetFrame { + source_peer_id: u32, + payload: Bytes, +} + +impl ReceivedEthernetFrame { + #[must_use] + pub const fn source_peer_id(&self) -> u32 { + self.source_peer_id + } + + #[must_use] + pub fn payload(&self) -> &[u8] { + &self.payload + } +} + impl GatewayConnection { #[must_use] pub const fn config(&self) -> &GatewayConfig { @@ -175,6 +195,47 @@ impl GatewayConnection { &self.welcome } + pub fn send_ethernet(&self, frame: &[u8]) -> Result<()> { + let datagram = encode_datagram( + FrameType::Ethernet, + self.welcome.room_id(), + self.welcome.peer_id(), + 0, + frame, + ) + .context("failed to encode gateway Ethernet datagram")?; + + self.connection + .send_datagram(Bytes::from(datagram)) + .context("failed to send gateway Ethernet datagram")?; + + Ok(()) + } + + pub async fn recv_ethernet(&self) -> Result { + loop { + let datagram = self.connection.read_datagram().await?; + let Ok(packet) = decode_datagram(&datagram) else { + continue; + }; + let header = packet.header(); + if header.frame_type() != FrameType::Ethernet + || header.room_id() != self.welcome.room_id() + || header.peer_id() == self.welcome.peer_id() + { + continue; + } + if EthernetFrame::parse(packet.payload()).is_err() { + continue; + } + + return Ok(ReceivedEthernetFrame { + source_peer_id: header.peer_id(), + payload: Bytes::copy_from_slice(packet.payload()), + }); + } + } + pub async fn shutdown(self, reason: &str) { self.connection.close(0_u32.into(), reason.as_bytes()); self.endpoint.wait_idle().await; @@ -258,6 +319,7 @@ async fn request_control_message( mod tests { use std::time::Duration; + use bytes::Bytes; use lanparty_ctrl::Role; use quinn::{ServerConfig, TransportConfig, crypto::rustls::QuicServerConfig}; use rustls::pki_types::{PrivateKeyDer, PrivatePkcs8KeyDer}; @@ -340,6 +402,24 @@ mod tests { send.write_all(&response).await.unwrap(); send.finish().unwrap(); + let datagram = connection.read_datagram().await.unwrap(); + let packet = decode_datagram(&datagram).unwrap(); + let header = packet.header(); + assert_eq!(header.frame_type(), FrameType::Ethernet); + assert_eq!(header.room_id(), 7); + assert_eq!(header.peer_id(), 1); + assert_eq!(packet.payload(), ethernet_frame(b"to relay").as_slice()); + + let response = encode_datagram( + FrameType::Ethernet, + 7, + 99, + 0, + ðernet_frame(b"from relay"), + ) + .unwrap(); + connection.send_datagram(Bytes::from(response)).unwrap(); + connection.closed().await; endpoint.close(0_u32.into(), b"test complete"); endpoint.wait_idle().await; @@ -360,6 +440,14 @@ mod tests { assert_eq!(gateway.welcome().room_id(), 7); assert_eq!(gateway.welcome().peer_id(), 1); + gateway.send_ethernet(ðernet_frame(b"to relay")).unwrap(); + let received = tokio::time::timeout(Duration::from_secs(5), gateway.recv_ethernet()) + .await + .unwrap() + .unwrap(); + assert_eq!(received.source_peer_id(), 99); + assert_eq!(received.payload(), ethernet_frame(b"from relay").as_slice()); + gateway.shutdown("test complete").await; tokio::time::timeout(Duration::from_secs(5), server_task) .await @@ -390,4 +478,13 @@ mod tests { (server_config, certificate) } + + fn ethernet_frame(payload: &[u8]) -> Vec { + let mut frame = Vec::new(); + frame.extend_from_slice(&[0x02, 0, 0, 0, 0, 2]); + frame.extend_from_slice(&[0x02, 0, 0, 0, 0, 1]); + frame.extend_from_slice(&0x0800_u16.to_be_bytes()); + frame.extend_from_slice(payload); + frame + } }