Skip to content

Commit

Permalink
Merge pull request #5 from pendulum-project/l2transport
Browse files Browse the repository at this point in the history
Implemented raw ethernet socket support.
  • Loading branch information
rnijveld committed Oct 12, 2023
2 parents f8fd767 + 39d30b1 commit d0e4ed7
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 4 deletions.
7 changes: 6 additions & 1 deletion src/control_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ impl InterfaceName {
None => Ok(None),
}
}

pub fn get_index(self) -> Option<libc::c_uint> {
// Temporary implementation until great refactor
InterfaceDescriptor {
interface_name: Some(self),
// doesn't matter
mode: LinuxNetworkMode::Ipv4,
}
.get_index()
}
}

impl std::fmt::Debug for InterfaceName {
Expand Down Expand Up @@ -219,7 +229,7 @@ fn interface_does_not_exist() -> std::io::Error {
}

impl InterfaceDescriptor {
pub fn get_index(&self) -> Option<u32> {
pub fn get_index(&self) -> Option<libc::c_uint> {
let name = self.interface_name.as_ref()?;

// # SAFETY
Expand Down
195 changes: 195 additions & 0 deletions src/networkaddress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<libc::sockaddr_storage>()
>= std::mem::size_of::<libc::sockaddr_ll>()
);
const _: () = assert!(
std::mem::align_of::<libc::sockaddr_storage>()
>= std::mem::align_of::<libc::sockaddr_ll>()
);

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<Self> {
const _: () = assert!(
std::mem::size_of::<libc::sockaddr_storage>()
>= std::mem::size_of::<libc::sockaddr_ll>()
);
const _: () = assert!(
std::mem::align_of::<libc::sockaddr_storage>()
>= std::mem::align_of::<libc::sockaddr_ll>()
);

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(())
}
}
57 changes: 55 additions & 2 deletions src/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -498,6 +500,57 @@ pub fn open_interface_udp6(
})
}

pub fn open_interface_ethernet(
interface: InterfaceName,
protocol: u16,
timestamping: InterfaceTimestampMode,
) -> std::io::Result<Socket<EthernetAddress, Open>> {
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::*;
Expand Down

0 comments on commit d0e4ed7

Please sign in to comment.