fix(gateway): reject wireless LAN interfaces
The plan explicitly keeps the physical LAN gateway wired-only for the MVP. Managed Wi-Fi adapters are not reliable for arbitrary source-MAC injection, but the gateway previously accepted any interface that could be opened as an Ethernet-like packet socket. Reject Linux interfaces that sysfs marks as wireless before opening the raw packet socket. The check looks for the common `wireless` and `phy80211` markers under `/sys/class/net/<iface>`, and keeps path separators out of interface names so the sysfs lookup stays scoped to a single netdev name. Document the wired-only enforcement in the gateway README section. Test Plan: - cargo fmt --check - git diff --check - cargo test -p lanparty-gateway - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings Refs: PLAN.md
This commit is contained in:
@@ -2,11 +2,14 @@ use std::{
|
||||
ffi::CString,
|
||||
io,
|
||||
os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use lanparty_proto::MacAddr;
|
||||
|
||||
const ETH_P_ALL: u16 = libc::ETH_P_ALL as u16;
|
||||
const SYS_CLASS_NET: &str = "/sys/class/net";
|
||||
const WIRELESS_INTERFACE_MARKERS: &[&str] = &["wireless", "phy80211"];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PacketSocket {
|
||||
@@ -19,6 +22,7 @@ pub struct PacketSocket {
|
||||
impl PacketSocket {
|
||||
pub fn open(interface: &str) -> io::Result<Self> {
|
||||
let interface_index = interface_index(interface)?;
|
||||
reject_wireless_interface(interface)?;
|
||||
let protocol = i32::from(ETH_P_ALL.to_be());
|
||||
let raw_fd = unsafe {
|
||||
// SAFETY: socket is called with constant domain/type/protocol values and returns
|
||||
@@ -193,6 +197,13 @@ fn interface_name(interface: &str) -> io::Result<CString> {
|
||||
));
|
||||
}
|
||||
|
||||
if interface.contains('/') {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"interface name cannot contain path separators",
|
||||
));
|
||||
}
|
||||
|
||||
let name = CString::new(interface).map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
@@ -209,6 +220,23 @@ fn interface_name(interface: &str) -> io::Result<CString> {
|
||||
Ok(name)
|
||||
}
|
||||
|
||||
fn reject_wireless_interface(interface: &str) -> io::Result<()> {
|
||||
if interface_is_wireless_in_sysfs(Path::new(SYS_CLASS_NET), interface) {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("wireless interface {interface} cannot be used as a LAN gateway"),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn interface_is_wireless_in_sysfs(sys_class_net: &Path, interface: &str) -> bool {
|
||||
WIRELESS_INTERFACE_MARKERS
|
||||
.iter()
|
||||
.any(|marker| sys_class_net.join(interface).join(marker).exists())
|
||||
}
|
||||
|
||||
fn interface_hardware_addr(fd: RawFd, interface: &str) -> io::Result<MacAddr> {
|
||||
let name = interface_name(interface)?;
|
||||
let mut request = unsafe {
|
||||
@@ -269,6 +297,11 @@ fn is_inbound_packet_type(packet_type: u8) -> bool {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{
|
||||
fs,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -281,6 +314,10 @@ mod tests {
|
||||
interface_index("eth0\0bad").unwrap_err().kind(),
|
||||
io::ErrorKind::InvalidInput
|
||||
);
|
||||
assert_eq!(
|
||||
interface_index("eth0/bad").unwrap_err().kind(),
|
||||
io::ErrorKind::InvalidInput
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -307,4 +344,30 @@ mod tests {
|
||||
assert!(is_inbound_packet_type(libc::PACKET_BROADCAST));
|
||||
assert!(is_inbound_packet_type(libc::PACKET_MULTICAST));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_wireless_interfaces_from_sysfs_markers() {
|
||||
let sys_class_net = unique_temp_dir("lanparty-gateway-sysfs");
|
||||
let wired = sys_class_net.join("eth0");
|
||||
let wireless = sys_class_net.join("wlan0");
|
||||
let phy_wireless = sys_class_net.join("wlp1s0");
|
||||
fs::create_dir_all(&wired).unwrap();
|
||||
fs::create_dir_all(wireless.join("wireless")).unwrap();
|
||||
fs::create_dir_all(phy_wireless.join("phy80211")).unwrap();
|
||||
|
||||
assert!(!interface_is_wireless_in_sysfs(&sys_class_net, "eth0"));
|
||||
assert!(interface_is_wireless_in_sysfs(&sys_class_net, "wlan0"));
|
||||
assert!(interface_is_wireless_in_sysfs(&sys_class_net, "wlp1s0"));
|
||||
|
||||
fs::remove_dir_all(sys_class_net).unwrap();
|
||||
}
|
||||
|
||||
fn unique_temp_dir(prefix: &str) -> std::path::PathBuf {
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
|
||||
std::env::temp_dir().join(format!("{prefix}-{}-{nanos}", std::process::id()))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user