feat(client): validate TAP Ethernet frame I/O
The future client pump should exchange Ethernet frames with TAP through a narrow API, not raw byte reads and writes. Add TAP frame validation at the adapter boundary so malformed or jumbo frames are rejected before they enter the relay path. Expose `TAP_FRAME_BUFFER_LEN` and `validate_tap_ethernet_frame`, then add Windows helpers that read a TAP frame and validate it, or validate and fully write an Ethernet frame. Raw read/write methods remain available for lower-level adapter work. Test Plan: - cargo fmt --check - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - Windows-target cargo clippy for lanparty-client-tap with -D warnings - git diff --check Refs: PLAN.md one TAP Ethernet frame per datagram
This commit is contained in:
@@ -4,13 +4,15 @@
|
|||||||
//! how to find and open an installed TAP-Windows6 Ethernet adapter; the Windows
|
//! how to find and open an installed TAP-Windows6 Ethernet adapter; the Windows
|
||||||
//! client binary owns when to connect it to QUIC and how to protect routes.
|
//! client binary owns when to connect it to QUIC and how to protect routes.
|
||||||
|
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
|
use lanparty_proto::{EthernetFrame, MAX_STANDARD_ETHERNET_FRAME_LEN};
|
||||||
|
|
||||||
pub const TAP_COMPONENT_ID: &str = "tap0901";
|
pub const TAP_COMPONENT_ID: &str = "tap0901";
|
||||||
pub const TAP_ADAPTER_KEY: &str =
|
pub const TAP_ADAPTER_KEY: &str =
|
||||||
r"SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}";
|
r"SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}";
|
||||||
pub const TAP_DEVICE_PREFIX: &str = r"\\.\Global\";
|
pub const TAP_DEVICE_PREFIX: &str = r"\\.\Global\";
|
||||||
pub const TAP_DEVICE_SUFFIX: &str = ".tap";
|
pub const TAP_DEVICE_SUFFIX: &str = ".tap";
|
||||||
|
pub const TAP_FRAME_BUFFER_LEN: usize = MAX_STANDARD_ETHERNET_FRAME_LEN;
|
||||||
const FILE_DEVICE_UNKNOWN: u32 = 0x0000_0022;
|
const FILE_DEVICE_UNKNOWN: u32 = 0x0000_0022;
|
||||||
const METHOD_BUFFERED: u32 = 0;
|
const METHOD_BUFFERED: u32 = 0;
|
||||||
const FILE_ANY_ACCESS: u32 = 0;
|
const FILE_ANY_ACCESS: u32 = 0;
|
||||||
@@ -70,6 +72,19 @@ pub fn tap_device_path(instance_id: &str) -> String {
|
|||||||
format!("{TAP_DEVICE_PREFIX}{instance_id}{TAP_DEVICE_SUFFIX}")
|
format!("{TAP_DEVICE_PREFIX}{instance_id}{TAP_DEVICE_SUFFIX}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn validate_tap_ethernet_frame(frame: &[u8]) -> Result<()> {
|
||||||
|
let frame = EthernetFrame::parse(frame).context("TAP Ethernet frame is malformed")?;
|
||||||
|
if frame.is_jumbo() {
|
||||||
|
bail!(
|
||||||
|
"TAP Ethernet frame length {} exceeds maximum {}",
|
||||||
|
frame.len(),
|
||||||
|
MAX_STANDARD_ETHERNET_FRAME_LEN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn tap_control_code(request: u32) -> u32 {
|
pub const fn tap_control_code(request: u32) -> u32 {
|
||||||
(FILE_DEVICE_UNKNOWN << 16) | (FILE_ANY_ACCESS << 14) | (request << 2) | METHOD_BUFFERED
|
(FILE_DEVICE_UNKNOWN << 16) | (FILE_ANY_ACCESS << 14) | (request << 2) | METHOD_BUFFERED
|
||||||
@@ -129,6 +144,17 @@ mod tests {
|
|||||||
assert_eq!(tap_ioctl_set_media_status(), 0x0022_0018);
|
assert_eq!(tap_ioctl_set_media_status(), 0x0022_0018);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validates_tap_ethernet_frames() {
|
||||||
|
let mut valid = vec![0; 14];
|
||||||
|
valid[12..14].copy_from_slice(&0x0800_u16.to_be_bytes());
|
||||||
|
|
||||||
|
assert!(validate_tap_ethernet_frame(&valid).is_ok());
|
||||||
|
assert!(validate_tap_ethernet_frame(&valid[..13]).is_err());
|
||||||
|
valid.resize(TAP_FRAME_BUFFER_LEN + 1, 0);
|
||||||
|
assert!(validate_tap_ethernet_frame(&valid).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validates_adapter_info() {
|
fn validates_adapter_info() {
|
||||||
let info =
|
let info =
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::{
|
|||||||
ptr::{null, null_mut},
|
ptr::{null, null_mut},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result, bail};
|
||||||
use lanparty_proto::MacAddr;
|
use lanparty_proto::MacAddr;
|
||||||
use windows_sys::Win32::{
|
use windows_sys::Win32::{
|
||||||
Foundation::{
|
Foundation::{
|
||||||
@@ -26,7 +26,7 @@ use windows_sys::Win32::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
TAP_ADAPTER_KEY, TapAdapterInfo, is_tap_component_id, tap_ioctl_get_mac, tap_ioctl_get_mtu,
|
TAP_ADAPTER_KEY, TapAdapterInfo, is_tap_component_id, tap_ioctl_get_mac, tap_ioctl_get_mtu,
|
||||||
tap_ioctl_set_media_status,
|
tap_ioctl_set_media_status, validate_tap_ethernet_frame,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -128,6 +128,13 @@ impl TapAdapter {
|
|||||||
Ok(bytes_read as usize)
|
Ok(bytes_read as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn read_ethernet_frame(&self, buffer: &mut [u8]) -> Result<usize> {
|
||||||
|
let len = self.read_frame(buffer)?;
|
||||||
|
validate_tap_ethernet_frame(&buffer[..len])?;
|
||||||
|
|
||||||
|
Ok(len)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write_frame(&self, frame: &[u8]) -> Result<usize> {
|
pub fn write_frame(&self, frame: &[u8]) -> Result<usize> {
|
||||||
let mut bytes_written = 0_u32;
|
let mut bytes_written = 0_u32;
|
||||||
let ok = unsafe {
|
let ok = unsafe {
|
||||||
@@ -148,6 +155,16 @@ impl TapAdapter {
|
|||||||
Ok(bytes_written as usize)
|
Ok(bytes_written as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write_ethernet_frame(&self, frame: &[u8]) -> Result<()> {
|
||||||
|
validate_tap_ethernet_frame(frame)?;
|
||||||
|
let written = self.write_frame(frame)?;
|
||||||
|
if written != frame.len() {
|
||||||
|
bail!("partial TAP frame write: {written}/{}", frame.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn device_io_control(
|
fn device_io_control(
|
||||||
&self,
|
&self,
|
||||||
code: u32,
|
code: u32,
|
||||||
|
|||||||
Reference in New Issue
Block a user