use std::fs::File;
use std::io::{self, Read, Write};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
pub const MODE_POINTOPOINT: u32 = 1;
pub const MODE_ETHERNET: u32 = 2;
pub const UNIT_ANY: u32 = 0x7fff_ffff;
const OPENBSD_AF_INET: u32 = 2;
const OPENBSD_AF_INET6: u32 = 24;
const MIN_IP_PACKET: usize = 20;
pub fn parse_unit(s: &str) -> Option<u32> {
if s.eq_ignore_ascii_case("any") {
return Some(UNIT_ANY);
}
s.parse::<u32>().ok().filter(|u| *u < UNIT_ANY)
}
pub fn parse_units(s: &str) -> Option<(u32, u32)> {
match s.split_once(':') {
Some((l, r)) => Some((parse_unit(l)?, parse_unit(r)?)),
None => Some((parse_unit(s)?, UNIT_ANY)),
}
}
pub fn to_wire(mode: u32, packet: &[u8]) -> Option<Vec<u8>> {
if mode != MODE_POINTOPOINT {
return Some(packet.to_vec());
}
if packet.len() <= MIN_IP_PACKET {
return None;
}
let af = if packet[0] >> 4 == 6 {
OPENBSD_AF_INET6
} else {
OPENBSD_AF_INET
};
let mut msg = Vec::with_capacity(4 + packet.len());
msg.extend_from_slice(&af.to_be_bytes());
msg.extend_from_slice(packet);
Some(msg)
}
pub fn from_wire(mode: u32, msg: &[u8]) -> Option<&[u8]> {
if mode != MODE_POINTOPOINT {
return Some(msg);
}
msg.get(4..)
}
pub fn splice<R, S>(dev: File, mode: u32, mut recv: R, mut send: S) -> io::Result<()>
where
R: FnMut() -> Option<Vec<u8>>,
S: FnMut(Vec<u8>) -> bool + Send + 'static,
{
let stop = Arc::new(AtomicBool::new(false));
let mut reader = dev.try_clone()?;
let reader_stop = Arc::clone(&stop);
let device_to_channel = std::thread::spawn(move || {
let mut buf = vec![0u8; 65536];
while !reader_stop.load(Ordering::Relaxed) {
if !readable(&reader, 200) {
continue;
}
let n = match reader.read(&mut buf) {
Ok(0) => break,
Ok(n) => n,
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(_) => break,
};
if let Some(msg) = to_wire(mode, &buf[..n])
&& !send(msg)
{
break;
}
}
reader_stop.store(true, Ordering::Relaxed);
});
let mut writer = dev;
while let Some(msg) = recv() {
if stop.load(Ordering::Relaxed) {
break;
}
if let Some(packet) = from_wire(mode, &msg) {
let _ = writer.write(packet);
}
}
stop.store(true, Ordering::Relaxed);
if READS_TIME_OUT {
let _ = device_to_channel.join();
}
Ok(())
}
const READS_TIME_OUT: bool = cfg!(all(unix, feature = "server"));
#[cfg(all(unix, feature = "server"))]
fn readable(f: &File, timeout_ms: u16) -> bool {
use nix::poll::{PollFd, PollFlags, PollTimeout, poll};
use std::os::fd::AsFd;
let mut fds = [PollFd::new(f.as_fd(), PollFlags::POLLIN)];
matches!(poll(&mut fds, PollTimeout::from(timeout_ms)), Ok(n) if n > 0)
}
#[cfg(not(all(unix, feature = "server")))]
fn readable(_f: &File, _timeout_ms: u16) -> bool {
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn point_to_point_framing() {
let mut v4 = vec![0x45u8; 40];
v4[0] = 0x45;
let msg = to_wire(MODE_POINTOPOINT, &v4).unwrap();
assert_eq!(&msg[..4], &2u32.to_be_bytes());
assert_eq!(from_wire(MODE_POINTOPOINT, &msg).unwrap(), &v4[..]);
let mut v6 = vec![0u8; 60];
v6[0] = 0x60;
let msg = to_wire(MODE_POINTOPOINT, &v6).unwrap();
assert_eq!(&msg[..4], &24u32.to_be_bytes());
assert!(to_wire(MODE_POINTOPOINT, &[0x45; 20]).is_none());
assert!(from_wire(MODE_POINTOPOINT, &[0, 0, 2]).is_none());
}
#[test]
fn ethernet_frames_pass_as_is() {
let frame = [1u8, 2, 3, 4, 5];
assert_eq!(to_wire(MODE_ETHERNET, &frame).unwrap(), frame);
assert_eq!(from_wire(MODE_ETHERNET, &frame).unwrap(), &frame);
}
}