From 39d30b19798d858f693a4f8d0bdb0e1f9fcc9ead Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Fri, 6 Oct 2023 13:46:58 +0200 Subject: [PATCH] Implemented raw ethernet socket support. --- src/control_message.rs | 7 +- src/interface.rs | 12 ++- src/networkaddress.rs | 195 +++++++++++++++++++++++++++++++++++++++++ src/socket.rs | 57 +++++++++++- 4 files changed, 267 insertions(+), 4 deletions(-) diff --git a/src/control_message.rs b/src/control_message.rs index ec8b2f0..7d4f111 100644 --- a/src/control_message.rs +++ b/src/control_message.rs @@ -60,6 +60,9 @@ const SCM_TIMESTAMP_NS: libc::c_int = libc::SCM_TIMESTAMPNS; #[cfg(target_os = "freebsd")] const SCM_TIMESTAMP_NS: libc::c_int = libc::SCM_REALTIME; +#[cfg(target_os = "linux")] +const PACKET_TX_TIMESTAMP: libc::c_int = 16; + impl<'a> Iterator for ControlMessageIterator<'a> { type Item = ControlMessage; @@ -136,7 +139,9 @@ impl<'a> Iterator for ControlMessageIterator<'a> { } #[cfg(target_os = "linux")] - (libc::SOL_IP, libc::IP_RECVERR) | (libc::SOL_IPV6, libc::IPV6_RECVERR) => { + (libc::SOL_IP, libc::IP_RECVERR) + | (libc::SOL_IPV6, libc::IPV6_RECVERR) + | (libc::SOL_PACKET, PACKET_TX_TIMESTAMP) => { // this is part of how timestamps are reported. // Safety: // current_msg was constructed from a pointer that pointed to a valid diff --git a/src/interface.rs b/src/interface.rs index c66a329..27e464a 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -137,6 +137,16 @@ impl InterfaceName { None => Ok(None), } } + + pub fn get_index(self) -> Option { + // Temporary implementation until great refactor + InterfaceDescriptor { + interface_name: Some(self), + // doesn't matter + mode: LinuxNetworkMode::Ipv4, + } + .get_index() + } } impl std::fmt::Debug for InterfaceName { @@ -219,7 +229,7 @@ fn interface_does_not_exist() -> std::io::Error { } impl InterfaceDescriptor { - pub fn get_index(&self) -> Option { + pub fn get_index(&self) -> Option { let name = self.interface_name.as_ref()?; // # SAFETY diff --git a/src/networkaddress.rs b/src/networkaddress.rs index 3fe77fc..a442bce 100644 --- a/src/networkaddress.rs +++ b/src/networkaddress.rs @@ -316,3 +316,198 @@ impl NetworkAddress for SocketAddr { } } } + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct MacAddress([u8; 6]); + +impl From<[u8; 6]> for MacAddress { + fn from(value: [u8; 6]) -> Self { + MacAddress(value) + } +} + +impl AsRef<[u8]> for MacAddress { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl MacAddress { + pub const fn new(address: [u8; 6]) -> Self { + MacAddress(address) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct EthernetAddress { + protocol: u16, + mac_address: MacAddress, + if_index: libc::c_int, +} + +impl EthernetAddress { + pub const fn new(protocol: u16, mac_address: MacAddress, if_index: libc::c_int) -> Self { + EthernetAddress { + protocol, + mac_address, + if_index, + } + } + + pub const fn mac(&self) -> MacAddress { + self.mac_address + } + + pub const fn protocol(&self) -> u16 { + self.protocol + } + + pub const fn interface(&self) -> libc::c_int { + self.if_index + } +} + +impl SealedNA for EthernetAddress {} + +impl NetworkAddress for EthernetAddress { + fn to_sockaddr(&self, _token: PrivateToken) -> libc::sockaddr_storage { + const _: () = assert!( + std::mem::size_of::() + >= std::mem::size_of::() + ); + const _: () = assert!( + std::mem::align_of::() + >= std::mem::align_of::() + ); + + let mut result = zeroed_sockaddr_storage(); + // Safety: the above assertions guarantee that alignment and size are correct. + // the resulting reference won't outlast the function, and result lives the entire + // duration of the function + let out = unsafe { &mut (*(&mut result as *mut _ as *mut libc::sockaddr_ll)) }; + + out.sll_family = libc::AF_PACKET as _; + out.sll_addr[..6].copy_from_slice(&self.mac_address.0); + out.sll_halen = 6; + out.sll_protocol = u16::from_ne_bytes(self.protocol.to_be_bytes()); + out.sll_ifindex = self.if_index; + + result + } + + fn from_sockaddr(addr: libc::sockaddr_storage, _token: PrivateToken) -> Option { + const _: () = assert!( + std::mem::size_of::() + >= std::mem::size_of::() + ); + const _: () = assert!( + std::mem::align_of::() + >= std::mem::align_of::() + ); + + if addr.ss_family != libc::AF_PACKET as _ { + return None; + } + + // Safety: the above assertions guarantee that alignment and size are correct + // the resulting reference won't outlast the function, and addr lives the entire + // duration of the function + let input = unsafe { &(*(&addr as *const _ as *const libc::sockaddr_ll)) }; + + if input.sll_halen != 6 { + return None; + } + + Some(EthernetAddress::new( + u16::from_be_bytes(input.sll_protocol.to_ne_bytes()), + MacAddress::new(input.sll_addr[..6].try_into().unwrap()), + input.sll_ifindex, + )) + } +} + +impl SealedMC for EthernetAddress {} + +impl MulticastJoinable for EthernetAddress { + fn join_multicast( + &self, + socket: RawFd, + interface: InterfaceName, + _token: PrivateToken, + ) -> std::io::Result<()> { + let request = libc::packet_mreq { + mr_ifindex: InterfaceDescriptor { + interface_name: Some(interface), + // Just need a mode, which doesnt matter for index + mode: crate::interface::LinuxNetworkMode::Ipv6, + } + .get_index() + .ok_or(std::io::ErrorKind::InvalidInput)? as _, + mr_type: libc::PACKET_MR_MULTICAST as _, + mr_alen: 6, + mr_address: [ + self.mac_address.0[0], + self.mac_address.0[1], + self.mac_address.0[2], + self.mac_address.0[3], + self.mac_address.0[4], + self.mac_address.0[5], + 0, + 0, + ], + }; + // Safety: + // value points to a struct of length option_len, of type ip_mreq as expected for IPPROTO_IPV6/IPV6_ADD_MEMBERSHIP + cerr(unsafe { + libc::setsockopt( + socket, + libc::SOL_PACKET, + libc::PACKET_ADD_MEMBERSHIP, + &request as *const _ as *const _, + std::mem::size_of_val(&request) as _, + ) + })?; + Ok(()) + } + + fn leave_multicast( + &self, + socket: RawFd, + interface: InterfaceName, + _token: PrivateToken, + ) -> std::io::Result<()> { + let request = libc::packet_mreq { + mr_ifindex: InterfaceDescriptor { + interface_name: Some(interface), + // Just need a mode, which doesnt matter for index + mode: crate::interface::LinuxNetworkMode::Ipv6, + } + .get_index() + .ok_or(std::io::ErrorKind::InvalidInput)? as _, + mr_type: libc::PACKET_MR_MULTICAST as _, + mr_alen: 6, + mr_address: [ + self.mac_address.0[0], + self.mac_address.0[1], + self.mac_address.0[2], + self.mac_address.0[3], + self.mac_address.0[4], + self.mac_address.0[5], + 0, + 0, + ], + }; + // Safety: + // value points to a struct of length option_len, of type ip_mreq as expected for IPPROTO_IPV6/IPV6_ADD_MEMBERSHIP + cerr(unsafe { + libc::setsockopt( + socket, + libc::SOL_PACKET, + libc::PACKET_DROP_MEMBERSHIP, + &request as *const _ as *const _, + std::mem::size_of_val(&request) as _, + ) + })?; + Ok(()) + } +} diff --git a/src/socket.rs b/src/socket.rs index 6c87dba..ac71ee0 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -9,8 +9,10 @@ use tokio::io::{unix::AsyncFd, Interest}; use crate::{ control_message::{control_message_space, ControlMessage, MessageQueue}, - interface::InterfaceName, - networkaddress::{sealed::PrivateToken, MulticastJoinable, NetworkAddress}, + interface::{InterfaceDescriptor, InterfaceName}, + networkaddress::{ + sealed::PrivateToken, EthernetAddress, MacAddress, MulticastJoinable, NetworkAddress, + }, raw_socket::RawSocket, }; @@ -498,6 +500,57 @@ pub fn open_interface_udp6( }) } +pub fn open_interface_ethernet( + interface: InterfaceName, + protocol: u16, + timestamping: InterfaceTimestampMode, +) -> std::io::Result> { + let socket = RawSocket::open( + libc::AF_PACKET, + libc::SOCK_DGRAM, + u16::from_ne_bytes(protocol.to_be_bytes()) as _, + )?; + socket.bind( + EthernetAddress::new( + u16::from_ne_bytes(protocol.to_le_bytes()), + MacAddress::new([0; 6]), + InterfaceDescriptor { + interface_name: Some(interface), + // Just need a mode, which doesnt matter for index + mode: crate::interface::LinuxNetworkMode::Ipv6, + } + .get_index() + .ok_or(std::io::ErrorKind::InvalidInput)? as _, + ) + .to_sockaddr(PrivateToken), + )?; + configure_timestamping(&socket, timestamping)?; + #[cfg(target_os = "linux")] + if matches!( + timestamping, + InterfaceTimestampMode::HardwarePTPAll | InterfaceTimestampMode::HardwarePTPRecv + ) { + socket.driver_enable_hardware_timestamping( + interface, + libc::HWTSTAMP_FILTER_PTP_V2_L2_EVENT as _, + )?; + } + socket.set_nonblocking(true)?; + + #[cfg(target_os = "linux")] + let errqueue_waiter = crate::raw_socket::err_queue_waiter::ErrQueueWaiter::new(&socket)?; + + Ok(Socket { + timestamp_mode: timestamping, + socket: AsyncFd::new(socket)?, + #[cfg(target_os = "linux")] + errqueue_waiter, + send_counter: 0, + _addr: PhantomData, + _state: PhantomData, + }) +} + #[cfg(test)] mod tests { use super::*;