fix(proto): reject reserved overlay flags
The MVP overlay reserves its flags field for later features such as fragmentation or payload encryption, but version 1 does not define any flag semantics. Accepting nonzero flags would let unknown behavior silently traverse the relay and reach the tunnel endpoints. Make zero the only valid v1 flag value. Overlay encoding and decoding now reject reserved nonzero flags, production send paths use the explicit OVERLAY_FLAGS_NONE constant, and the relay emits forwarded datagrams with the same zero-flag policy instead of preserving peer-supplied bits. Document the reserved-flag rule in the protocol crate overview. Test Plan: - cargo test -p lanparty-proto overlay - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - cargo fmt --check - git diff --check Refs: PLAN.md no-fragmentation MVP overlay format
This commit is contained in:
@@ -3,6 +3,7 @@ use thiserror::Error;
|
||||
pub const OVERLAY_MAGIC: u32 = 0x534c_414e; // "SLAN"
|
||||
pub const OVERLAY_VERSION: u8 = 1;
|
||||
pub const OVERLAY_HEADER_LEN: usize = 22;
|
||||
pub const OVERLAY_FLAGS_NONE: u16 = 0;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
|
||||
#[repr(u8)]
|
||||
@@ -45,6 +46,7 @@ impl OverlayHeader {
|
||||
flags: u16,
|
||||
payload_len: usize,
|
||||
) -> Result<Self, ProtoError> {
|
||||
validate_overlay_flags(flags)?;
|
||||
let payload_len = u16::try_from(payload_len).map_err(|_| ProtoError::PayloadTooLarge {
|
||||
len: payload_len,
|
||||
max: u16::MAX as usize,
|
||||
@@ -115,11 +117,14 @@ impl OverlayHeader {
|
||||
return Err(ProtoError::UnsupportedVersion { actual: version });
|
||||
}
|
||||
|
||||
let flags = u16::from_be_bytes(bytes[18..20].try_into().expect("flags slice length"));
|
||||
validate_overlay_flags(flags)?;
|
||||
|
||||
Ok(Self {
|
||||
frame_type: FrameType::from_u8(bytes[5])?,
|
||||
room_id: u64::from_be_bytes(bytes[6..14].try_into().expect("room id slice length")),
|
||||
peer_id: u32::from_be_bytes(bytes[14..18].try_into().expect("peer id slice length")),
|
||||
flags: u16::from_be_bytes(bytes[18..20].try_into().expect("flags slice length")),
|
||||
flags,
|
||||
payload_len: u16::from_be_bytes(
|
||||
bytes[20..22].try_into().expect("payload len slice length"),
|
||||
),
|
||||
@@ -168,6 +173,8 @@ pub enum ProtoError {
|
||||
UnsupportedVersion { actual: u8 },
|
||||
#[error("unknown overlay frame type {0}")]
|
||||
UnknownFrameType(u8),
|
||||
#[error("unsupported overlay flags 0x{actual:04x}")]
|
||||
UnsupportedFlags { actual: u16 },
|
||||
#[error("payload length {len} exceeds wire maximum {max}")]
|
||||
PayloadTooLarge { len: usize, max: usize },
|
||||
#[error("encoded datagram length {len} exceeds negotiated QUIC datagram budget {max}")]
|
||||
@@ -178,6 +185,14 @@ pub enum ProtoError {
|
||||
EthernetFrameTooShort { actual: usize, minimum: usize },
|
||||
}
|
||||
|
||||
fn validate_overlay_flags(flags: u16) -> Result<(), ProtoError> {
|
||||
if flags != OVERLAY_FLAGS_NONE {
|
||||
return Err(ProtoError::UnsupportedFlags { actual: flags });
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn encode_datagram(
|
||||
frame_type: FrameType,
|
||||
room_id: u64,
|
||||
@@ -224,7 +239,7 @@ mod tests {
|
||||
FrameType::Ethernet,
|
||||
0x0102_0304_0506_0708,
|
||||
0x0a0b_0c0d,
|
||||
0x1001,
|
||||
OVERLAY_FLAGS_NONE,
|
||||
&payload,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -239,7 +254,7 @@ mod tests {
|
||||
assert_eq!(header.frame_type(), FrameType::Ethernet);
|
||||
assert_eq!(header.room_id(), 0x0102_0304_0506_0708);
|
||||
assert_eq!(header.peer_id(), 0x0a0b_0c0d);
|
||||
assert_eq!(header.flags(), 0x1001);
|
||||
assert_eq!(header.flags(), OVERLAY_FLAGS_NONE);
|
||||
assert_eq!(header.payload_len(), 4);
|
||||
assert_eq!(packet.payload(), payload);
|
||||
}
|
||||
@@ -288,6 +303,22 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_reserved_overlay_flags() {
|
||||
assert_eq!(
|
||||
OverlayHeader::new(FrameType::Ethernet, 1, 2, 1, 0).unwrap_err(),
|
||||
ProtoError::UnsupportedFlags { actual: 1 }
|
||||
);
|
||||
|
||||
let mut datagram = encode_datagram(FrameType::Ethernet, 1, 2, 0, &[]).unwrap();
|
||||
datagram[18..20].copy_from_slice(&0x8000_u16.to_be_bytes());
|
||||
|
||||
assert_eq!(
|
||||
decode_datagram(&datagram).unwrap_err(),
|
||||
ProtoError::UnsupportedFlags { actual: 0x8000 }
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_payloads_too_large_for_header() {
|
||||
let payload = vec![0; usize::from(u16::MAX) + 1];
|
||||
|
||||
Reference in New Issue
Block a user