文档地址:https://man7.org/linux/man-pages/man7/packet.7.html。
packet - 设备级数据包接口。
/* the L2 protocols */
packet_socket = socket(AF_PACKET, int socket_type, int protocol);Packet 套接字用于在设备驱动级别(OSI 二层)接收或发送原始数据包。它支持用户在用户空间在物理层上实现协议模块。
socket_type 要么为 SOCK_RAW(用于包含链路层头部的原始数据包),要么为 SOCK_DGRAM(用于移除链路层头部的 cooked 数据包)。sockaddr_ll 结构体是链路层头部信息的通用格式。
protocol 是网络字节序表示的 IEEE 802.3 协议号。查看 <linux/if_ether.h> 头文件,获取支持的协议列表。如果将协议设置为 htons(ETH_P_ALL),那么接收全部协议。在将进入的特定协议类型的数据包传递给内核实现的协议之前,数据包将被传递到 Packet 套接字。如果将 protocol 设置为 0,那么不接收任何数据包。可以使用非零的 sll_protocol 调用 bind(2),以开始接收指定协议的数据包。
如果想创建 Packet 套接字,那么进程在管理其网络命名空间的用户命名空间中,必须拥有 CAP_NET_RAW 能力。
向和从设备驱动传递 SOCK_RAW 数据包时,不会更改数据包数据。接收数据包时,仍然解析地址,并且通过标准的 sockaddr_ll 地址结构体传递。传输数据包时,用户提供的缓冲区应该包含物理层头部。然后将该数据包放进由目的地址指定的接口的网络驱动的队列中。一些设备驱动也添加其它头部。SOCK_RAW 与废弃的 Linux 2.0 AF_INET/SOCK_PACKET 相似,但不兼容。
SOCK_DGRAM 工作在稍高层次上。在将数据包传递给用户之前,移除物理头部。通过 SOCK_DGRAM Packet 套接字发送的数据包,在排队之前,将根据 sockaddr_ll 目的地址中的信息获得适当的物理层头部。
默认情况下,指定协议类型的全部数据包都被传递给 Packet 套接字。如果想仅从特定的接口获取数据包,那么使用在 struct sockaddr_ll 中指定地址的 bind(2) ,将 Packet 套接字绑定到接口上。用于绑定的字段包括 sll_family(应为 AF_PACKET)、sll_protocol 和 sll_ifindex。
Packet 套接字不支持 connect(2) 操作。
当将 MSG_TRUNC 标记传递给 recvmsg(2)、recv(2) 或 recvfrom(2) 时,无论数据包的长度是否超过缓冲区,始终返回其在链路上的实际长度。
sockaddr_ll 结构体是与设备无关的物理层地址。
xxxxxxxxxx struct sockaddr_ll { unsigned short sll_family; /* Always AF_PACKET */ unsigned short sll_protocol; /* Physical-layer protocol */ int sll_ifindex; /* Interface number */ unsigned short sll_hatype; /* ARP hardware type */ unsigned char sll_pkttype; /* Packet type */ unsigned char sll_halen; /* Length of address */ unsigned char sll_addr[8]; /* Physical-layer address */ };该结构体的字段如下所示:
sll_protocol
以网络字节顺序表示的标准以太网协议类型,其定义在 <linux/if_ether.h> 头文件中。它默认为套接字的协议。
sll_ifindex
接口的接口索引(参考 netdevice(7));0 表示匹配任何接口(仅允许绑定)。
sll_hatype
在 <linux/if_arp.h> 头文件中定义的 ARP 类型
sll_pkttype
包含数据包类型。有效类型包括:
PACKET_HOST:发送给本地主机的数据包、
PACKET_BROADCAST:物理层广播数据包、
PACKET_MULTICAST:发送到物理层组播地址的数据包、
PACKET_OTHERHOST:通过设备驱动的混杂模式捕获到的发送给其它主机的数据包、
PACKET_OUTGOING:从本地主机发起,被回送到 Packet 套接字的数据包。
这些类型只在接收时有意义。
sll_addr
sll_halen
包含物理层(比如,IEEE 802.3)地址及其长度。具体解释取决于设备。
发送数据包时,需要指定 sll_family、sll_addr、sll_halen、sll_ifindex、sll_protocol。其它字段应该为 0。sll_hatype 和 sll_pkttype 是在接收到的数据包上设置的,用于提供信息。
通过使用级别 SOL_PACKET 调用 setsockopt(2) 的方式,配置 Packet 套接字选项。
为在多个线程之间扩展处理能力,Packet 套接字可以构成扇出(fanout)组。在该模式中,每个匹配的数据包只被放入组内的一个套接字的队列中。通过使用级别 SOL_PACKET 和选项 PACKET_FANOUT 调用 setsockopt(2) 的方式,将套接字加入扇出组。每个网络命名空间最多可以拥有 65536 个独立的组。套接字通过在整型选项值的前 16 位中编码 ID 的方式,选择组。第一个加入组的 Packet 套接字隐式地创建组。如果想加入已存在的组,后续的 Packet 套接字必须使用相同的协议、设备设置、扇出模式和标记。Packet 套接字只能通过关闭套接字的方式离开扇出组。当最后一个套接字关闭时,组被删除。
扇出支持多种算法在套接字间分发流量,如下所示:
默认模式,PACKET_FANOUT_HASH,将相同流量的数据包发送到相同的套接字,以保持每条流量的顺序。对于每个数据包,通过将数据包的流哈希值对组中的套接字数量取模的方式,选择套接字,其中流哈希值由对网络层地址和可选的传输层端口字段进行哈希计算得到。
负载均衡模式 PACKET_FANOUT_LB 实现轮询算法。
PACKET_FANOUT_CPU 根据数据包到达的 CPU 选择套接字。
PACKET_FANOUT_ROLLOVER 在单个套接字上处理所有数据,当套接字积压时,切换到下一个。
PACKET_FANOUT_RND 使用伪随机数生成器选择套接字。
PACKET_FANOUT_QM(自 Linux 3.14 起可用)使用记录的接收到的 skb 的 queue_mapping 选择套接字。
扇出模式可以使用其它选项。IP 分片导致同一流量的数据包具有不同的流哈希值。如果设置标记 PACKET_FANOUT_FLAG_DEFRAG,那么在应用扇出之前,将对数据包进行 defragmentation(重新组装),以保留顺序。扇出模式和选项通过整型选项值的第二个 16 位进行传递。标记 PACKET_FANOUT_FLAG_ROLLOVER 启用回滚机制作为备用策略:如果原始扇出算法选择的是积压套接字,那么数据包将滚动到下一个可用的套接字。
xxxxxxxxxx[package]name = "packet-socket-test"version = "0.1.0"edition = "2021"
[dependencies]libc = "0.2.147"ipnetwork = "0.20.0"pnet = "0.34.0"xxxxxxxxxxuse core::fmt;use ipnetwork::{ip_mask_to_prefix, IpNetwork};use std::cmp;use std::ffi::{CStr, CString};use std::io;use std::mem::{self, MaybeUninit};use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};use std::os::raw::c_char;use std::ptr;use std::str::from_utf8_unchecked;use std::sync::Arc;use std::time::Duration;
pub use libc::{IFF_BROADCAST, IFF_LOOPBACK, IFF_MULTICAST, IFF_POINTOPOINT, IFF_RUNNING, IFF_UP};pub use libc::{IFF_DORMANT, IFF_LOWER_UP};
pub type SockAddrStorage = libc::sockaddr_storage;pub type SockAddr = libc::sockaddr;pub type SockAddrIn = libc::sockaddr_in;pub type SockAddrIn6 = libc::sockaddr_in6;pub type InAddr = libc::in_addr;pub type In6Addr = libc::in6_addr;pub type InAddrType = libc::c_uint;pub type InAddrType = libc::c_ulonglong;pub type CSocket = libc::c_int;pub type SockLen = libc::socklen_t;pub type Buf = *const libc::c_void;pub type BufLen = libc::size_t;pub type CouldFail = libc::ssize_t;pub type MutBuf = *mut libc::c_void;pub type TvUsecType = libc::c_long;pub type TvUsecType = libc::c_int;pub type InterfaceType = u32;pub type InterfaceType = u64;pub type EtherType = u16;
pub const AF_INET: libc::c_int = libc::AF_INET;pub const AF_INET6: libc::c_int = libc::AF_INET6;pub const SOL_PACKET: libc::c_int = 263;pub const PACKET_ADD_MEMBERSHIP: libc::c_int = 1;pub const PACKET_MR_PROMISC: libc::c_int = 1;pub const PACKET_FANOUT: libc::c_int = 18;pub const PACKET_FANOUT_HASH: libc::c_int = 0;pub const PACKET_FANOUT_LB: libc::c_int = 1;pub const PACKET_FANOUT_CPU: libc::c_int = 2;pub const PACKET_FANOUT_ROLLOVER: libc::c_int = 3;pub const PACKET_FANOUT_RND: libc::c_int = 4;pub const PACKET_FANOUT_QM: libc::c_int = 5;pub const PACKET_FANOUT_CBPF: libc::c_int = 6;pub const PACKET_FANOUT_EBPF: libc::c_int = 7;pub const PACKET_FANOUT_FLAG_ROLLOVER: libc::c_uint = 0x1000; // 如下标记尚未使用pub const PACKET_FANOUT_FLAG_UNIQUEID: libc::c_uint = 0x2000;pub const PACKET_FANOUT_FLAG_DEFRAG: libc::c_uint = 0x8000;
pub struct packet_mreq { pub mr_ifindex: libc::c_int, pub mr_type: libc::c_ushort, pub mr_alen: libc::c_ushort, pub mr_address: [libc::c_uchar; 8],}
pub fn ntohs(u: u16) -> u16 { u16::from_be(u)}
/// MAC 地址pub struct MacAddr(pub u8, pub u8, pub u8, pub u8, pub u8, pub u8);
impl fmt::Debug for MacAddr { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, fmt) }}
impl fmt::Display for MacAddr { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!( fmt, "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", self.0, self.1, self.2, self.3, self.4, self.5 ) }}
/// 表示网络接口及其相关地址pub struct NetworkInterface { /// 接口名称 pub name: String, /// 接口描述 pub description: String, /// 接口索引(操作系统特定的) pub index: u32, /// 接口的 MAC 地址 pub mac: Option<MacAddr>, /// 接口的 IP 地址和掩码 pub ips: Vec<IpNetwork>, /// 接口的操作系统特定的标记 pub flags: u32, pub flags: u64,}
impl NetworkInterface { /// UP 状态表示网络接口已经启用或激活,Running 状态表示网络接口正在处理网络数据 pub fn is_up(&self) -> bool { self.flags & (IFF_UP as InterfaceType) != 0 }
pub fn is_broadcast(&self) -> bool { self.flags & (IFF_BROADCAST as InterfaceType) != 0 }
/// 接口是 LoopBack 接口? pub fn is_loopback(&self) -> bool { self.flags & (IFF_LOOPBACK as InterfaceType) != 0 }
pub fn is_point_to_point(&self) -> bool { self.flags & (IFF_POINTOPOINT as InterfaceType) != 0 }
pub fn is_multicast(&self) -> bool { self.flags & (IFF_MULTICAST as InterfaceType) != 0 }
/// "NETIF_CARRIER_ON" 是网络编程中的标记位,用于表示网络接口(Network Interface)的链路状态。 /// 链路状态表示网络接口是否连接到活动的物理链路,并且能够进行数据传输。 /// 当网络接口连接到活动的链路时,链路状态被认为是 "UP" 或 "ON",而当链路断开或不可用时,链路状态被认为是 "DOWN" 或 "OFF"。 /// 查看 <https://www.kernel.org/doc/html/latest/networking/operstates.html> 获取更多信息。 pub fn is_lower_up(&self) -> bool { self.flags & (IFF_LOWER_UP as InterfaceType) != 0 }
/// 网络接口处于 L1(物理层)UP 状态,但在等待外部事件,比如建立协议(802.1X)。 /// 查看 <https://www.kernel.org/doc/html/latest/networking/operstates.html> 获取更多信息。 pub fn is_dormant(&self) -> bool { self.flags & (IFF_DORMANT as InterfaceType) != 0 }
pub fn is_running(&self) -> bool { self.flags & (IFF_RUNNING as InterfaceType) != 0 }}
/// 获取本机可用网络接口的列表pub fn interfaces() -> Vec<NetworkInterface> { fn merge(old: &mut NetworkInterface, new: &NetworkInterface) { old.mac = match new.mac { None => old.mac, _ => new.mac, }; old.ips.extend_from_slice(&new.ips[..]); old.flags = old.flags | new.flags; }
let mut ifaces: Vec<NetworkInterface> = Vec::new(); let mut addrs: MaybeUninit<*mut libc::ifaddrs> = MaybeUninit::uninit();
// 安全性:addrs.as_mut_ptr() 可用,它指向 addrs。 if unsafe { libc::getifaddrs(addrs.as_mut_ptr()) } != 0 { return ifaces; }
// 安全性:如果有错误,那么应该已经返回。 // 因此,getifaddrs 已经初始化 `addrs`。 let addrs = unsafe { addrs.assume_init() };
let mut addr = addrs; while !addr.is_null() { // 安全性:假定 addr 在该循环体的生命周期内可用,并且不可变。 let addr_ref: &libc::ifaddrs = unsafe { &*addr };
let c_str = addr_ref.ifa_name as *const c_char;
// 安全性:ifa_name 是以 Null 为终止的接口名称 let bytes = unsafe { CStr::from_ptr(c_str).to_bytes() };
// 安全性:在 Unix 上,接口必须是合法的 UTF-8 let name = unsafe { from_utf8_unchecked(bytes).to_owned() }; let (mac, ip) = sockaddr_to_network_addr(addr_ref.ifa_addr as *const libc::sockaddr); let (_, netmask) = sockaddr_to_network_addr(addr_ref.ifa_netmask as *const libc::sockaddr); let prefix = netmask .and_then(|netmask| ip_mask_to_prefix(netmask).ok()) .unwrap_or(0); let network = ip.and_then(|ip| IpNetwork::new(ip, prefix).ok()); let ni = NetworkInterface { name: name.clone(), description: "".to_string(), index: 0, mac: mac, ips: network.into_iter().collect(), flags: addr_ref.ifa_flags, }; let mut found: bool = false; for iface in &mut ifaces { if name == iface.name { merge(iface, &ni); found = true; } } if !found { ifaces.push(ni); }
addr = addr_ref.ifa_next; }
// 安全性:前面已通过 getifaddrs 申请 addrs unsafe { libc::freeifaddrs(addrs); }
for iface in &mut ifaces { let name = CString::new(iface.name.as_bytes()).unwrap();
// 安全性:name.as_ptr() 是有效指针 unsafe { iface.index = libc::if_nametoindex(name.as_ptr()); } }
ifaces}
fn sockaddr_to_network_addr(sa: *const libc::sockaddr) -> (Option<MacAddr>, Option<IpAddr>) { unsafe { if sa.is_null() { (None, None) } else if (*sa).sa_family as libc::c_int == libc::AF_PACKET { let sll: *const libc::sockaddr_ll = mem::transmute(sa); let mac = MacAddr( (*sll).sll_addr[0], (*sll).sll_addr[1], (*sll).sll_addr[2], (*sll).sll_addr[3], (*sll).sll_addr[4], (*sll).sll_addr[5], );
(Some(mac), None) } else { let addr = sockaddr_to_addr(mem::transmute(sa), mem::size_of::<libc::sockaddr_storage>());
match addr { Ok(SocketAddr::V4(sa)) => (None, Some(IpAddr::V4(*sa.ip()))), Ok(SocketAddr::V6(sa)) => (None, Some(IpAddr::V6(*sa.ip()))), Err(_) => (None, None), } } }}
pub fn sockaddr_to_addr(storage: &SockAddrStorage, len: usize) -> io::Result<SocketAddr> { match storage.ss_family as libc::c_int { AF_INET => { assert!(len as usize >= mem::size_of::<SockAddrIn>()); let storage: &SockAddrIn = unsafe { mem::transmute(storage) }; let ip = ipv4_addr(storage.sin_addr); let a = (ip >> 24) as u8; let b = (ip >> 16) as u8; let c = (ip >> 8) as u8; let d = ip as u8; let sockaddrv4 = SocketAddrV4::new(Ipv4Addr::new(a, b, c, d), ntohs(storage.sin_port)); Ok(SocketAddr::V4(sockaddrv4)) } AF_INET6 => { assert!(len as usize >= mem::size_of::<SockAddrIn6>()); let storage: &SockAddrIn6 = unsafe { mem::transmute(storage) }; let arr: [u16; 8] = unsafe { mem::transmute(storage.sin6_addr.s6_addr) }; let a = ntohs(arr[0]); let b = ntohs(arr[1]); let c = ntohs(arr[2]); let d = ntohs(arr[3]); let e = ntohs(arr[4]); let f = ntohs(arr[5]); let g = ntohs(arr[6]); let h = ntohs(arr[7]); let ip = Ipv6Addr::new(a, b, c, d, e, f, g, h);
Ok(SocketAddr::V6(SocketAddrV6::new( ip, ntohs(storage.sin6_port), u32::from_be(storage.sin6_flowinfo), storage.sin6_scope_id, ))) } _ => Err(io::Error::new( io::ErrorKind::InvalidData, "expected IPv4 or IPv6 socket", )), }}
pub fn ipv4_addr(addr: InAddr) -> InAddrType { (addr.s_addr as InAddrType).to_be()}
/// 数据链路层通道类型pub enum ChannelType { /// 直接发送和接收 2 层数据包,包含头部。 Layer2, /// 发送和接收 "cooked" 数据包 - 发送和接收网络层数据包。 Layer3(EtherType),}
/// 套接字扇出类型。pub enum FanoutType { HASH, LB, CPU, ROLLOVER, RND, QM, CBPF, EBPF,}
/// 扇出设置。pub struct FanoutOption { pub group_id: u16, pub fanout_type: FanoutType, pub defrag: bool, pub rollover: bool,}
/// datalink 后端的配置pub struct Config { /// 写数据包时,使用的缓冲区大小。默认为 4096。 pub write_buffer_size: usize,
/// 读取数据包时,使用的缓冲区大小。默认为 4096。 pub read_buffer_size: usize,
/// 读超时。默认为 None。 pub read_timeout: Option<Duration>,
/// 写超时。默认为 None。 pub write_timeout: Option<Duration>,
/// 指定从数据链路层,还是网络层读取数据包。 /// 默认为 Layer2。 pub channel_type: ChannelType,
/// 如果需要,指定数据包扇出选项。默认为 None。 pub fanout: Option<FanoutOption>,
/// 混杂模式。 pub promiscuous: bool,}
impl Default for Config { fn default() -> Config { Config { write_buffer_size: 4096, read_buffer_size: 4096, read_timeout: None, write_timeout: None, channel_type: ChannelType::Layer2, fanout: None, promiscuous: true, } }}
/// 用于在数据链路层发送和接收数据包的通道。pub enum Channel { /// 发送/接收以太网数据包的数据链路通道。 Ethernet(Box<dyn DataLinkSender>, Box<dyn DataLinkReceiver>),}
/// 最近发生的错误代码。fn errno() -> i32 { io::Error::last_os_error().raw_os_error().unwrap()}
pub fn retry<F>(f: &mut F) -> libc::ssize_twhere F: FnMut() -> libc::ssize_t,{ loop { let ret = f(); // eintr 代表 Interrupted System Call,即中断的系统调用。它是特定类型的错误,通常在使用系统调用或库函数进行阻塞操作时出现。 // 当进程执行阻塞操作(比如读取、写入、等待信号)时,如果收到信号,可能中断该阻塞操作。 // 当操作被中断时,将返回 eintr 错误。 // 当发生 eintr 错误时,开发人员需要检查该错误,确定是由于系统调用被中断而引起的,然后决定如何处理这种情况。 // 此处重新调用被中断的系统调用 if ret != -1 || errno() as isize != libc::EINTR as isize { return ret; } }}
pub unsafe fn recvfrom( socket: CSocket, buf: MutBuf, len: BufLen, flags: libc::c_int, addr: *mut SockAddr, addrlen: *mut SockLen,) -> CouldFail { libc::recvfrom(socket, buf, len, flags, addr, addrlen)}
pub fn recv_from( socket: CSocket, buffer: &mut [u8], caddr: *mut SockAddrStorage,) -> io::Result<usize> { let mut caddrlen = mem::size_of::<SockAddrStorage>() as SockLen; let len = retry(&mut || unsafe { recvfrom( socket, buffer.as_ptr() as MutBuf, buffer.len() as BufLen, 0, caddr as *mut SockAddr, &mut caddrlen, ) });
if len < 0 { Err(io::Error::last_os_error()) } else { Ok(len as usize) }}
pub unsafe fn sendto( socket: CSocket, buf: Buf, len: BufLen, flags: libc::c_int, addr: *const SockAddr, addrlen: SockLen,) -> CouldFail { libc::sendto(socket, buf, len, flags, addr, addrlen)}
pub fn send_to( socket: CSocket, buffer: &[u8], dst: *const SockAddr, slen: SockLen,) -> io::Result<usize> { let send_len = retry(&mut || unsafe { sendto( socket, buffer.as_ptr() as Buf, buffer.len() as BufLen, 0, dst, slen, ) });
if send_len < 0 { Err(io::Error::last_os_error()) } else { Ok(send_len as usize) }}
/// 启用发送 `$packet` 数据包的 Trait。pub trait DataLinkSender: Send { /// 创建并且发送多个数据包。 /// /// 该方法将调用 `func` `num_packets` 次。该函数将被提供可变的数据包,以供操作,然后将其发送。 /// 该方式支持原地构建数据包,避免 `send` 所需的拷贝操作。 /// 如果缓冲区没有足够的容量,那么返回 None。 fn build_and_send( &mut self, num_packets: usize, packet_size: usize, func: &mut dyn FnMut(&mut [u8]), ) -> Option<io::Result<()>>;
/// 与 `build_and_send` 相比,根据使用的操作系统,该方法可能需要额外的拷贝操作。 /// 当前忽略第二个参数,但应该传递 `None`。 fn send_to(&mut self, packet: &[u8], dst: Option<NetworkInterface>) -> Option<io::Result<()>>;}
/// 该结构体用于在数据链路层接收数据包。应该使用 `datalink_channel()` 构造。pub trait DataLinkReceiver: Send { /// 获取通道中的下一个以太网帧。 fn next(&mut self) -> io::Result<&[u8]>;}
struct DataLinkSenderImpl { socket: Arc<FileDesc>, fd_set: libc::fd_set, write_buffer: Vec<u8>, _channel_type: ChannelType, send_addr: libc::sockaddr_ll, send_addr_len: usize, timeout: Option<libc::timespec>,}
impl DataLinkSender for DataLinkSenderImpl { // FIXME Layer 3 fn build_and_send( &mut self, num_packets: usize, packet_size: usize, func: &mut dyn FnMut(&mut [u8]), ) -> Option<io::Result<()>> { let len = num_packets * packet_size; if len <= self.write_buffer.len() { let min = cmp::min(self.write_buffer[..].len(), len); let mut_slice = &mut self.write_buffer; for chunk in mut_slice[..min].chunks_mut(packet_size) { func(chunk); let send_addr = (&self.send_addr as *const libc::sockaddr_ll) as *const libc::sockaddr;
unsafe { libc::FD_ZERO(&mut self.fd_set as *mut libc::fd_set); libc::FD_SET(self.socket.fd, &mut self.fd_set as *mut libc::fd_set); } let ret = unsafe { libc::pselect( self.socket.fd + 1, ptr::null_mut(), &mut self.fd_set as *mut libc::fd_set, ptr::null_mut(), self.timeout .as_ref() .map(|to| to as *const libc::timespec) .unwrap_or(ptr::null()), ptr::null(), ) }; if ret == -1 { return Some(Err(io::Error::last_os_error())); } else if ret == 0 { return Some(Err(io::Error::new(io::ErrorKind::TimedOut, "Timed out"))); } else { if let Err(e) = send_to( self.socket.fd, chunk, // BUG:对于 ChannelType::Layer3,未正确设置目的地址 send_addr, self.send_addr_len as libc::socklen_t, ) { return Some(Err(e)); } } }
Some(Ok(())) } else { None } }
fn send_to(&mut self, packet: &[u8], _dst: Option<NetworkInterface>) -> Option<io::Result<()>> { unsafe { libc::FD_ZERO(&mut self.fd_set as *mut libc::fd_set); libc::FD_SET(self.socket.fd, &mut self.fd_set as *mut libc::fd_set); } let ret = unsafe { libc::pselect( self.socket.fd + 1, ptr::null_mut(), &mut self.fd_set as *mut libc::fd_set, ptr::null_mut(), self.timeout .as_ref() .map(|to| to as *const libc::timespec) .unwrap_or(ptr::null()), ptr::null(), ) }; if ret == -1 { Some(Err(io::Error::last_os_error())) } else if ret == 0 { Some(Err(io::Error::new(io::ErrorKind::TimedOut, "Timed out"))) } else { match send_to( self.socket.fd, packet, // BUG:对于 ChannelType::Layer3,未正确设置目的地址 (&self.send_addr as *const libc::sockaddr_ll) as *const _, self.send_addr_len as libc::socklen_t, ) { Err(e) => Some(Err(e)), Ok(_) => Some(Ok(())), } } }}
struct DataLinkReceiverImpl { socket: Arc<FileDesc>, fd_set: libc::fd_set, read_buffer: Vec<u8>, _channel_type: ChannelType, timeout: Option<libc::timespec>,}
impl DataLinkReceiver for DataLinkReceiverImpl { fn next(&mut self) -> io::Result<&[u8]> { let mut caddr: libc::sockaddr_storage = unsafe { mem::zeroed() }; unsafe { libc::FD_ZERO(&mut self.fd_set as *mut libc::fd_set); libc::FD_SET(self.socket.fd, &mut self.fd_set as *mut libc::fd_set); } let ret = unsafe { libc::pselect( self.socket.fd + 1, &mut self.fd_set as *mut libc::fd_set, ptr::null_mut(), ptr::null_mut(), self.timeout .as_ref() .map(|to| to as *const libc::timespec) .unwrap_or(ptr::null()), ptr::null(), ) }; if ret == -1 { Err(io::Error::last_os_error()) } else if ret == 0 { Err(io::Error::new(io::ErrorKind::TimedOut, "Timed out")) } else { let res = recv_from(self.socket.fd, &mut self.read_buffer, &mut caddr); match res { Ok(len) => Ok(&self.read_buffer[0..len]), Err(e) => Err(e), } } }}
pub unsafe fn close(sock: CSocket) { let _ = libc::close(sock);}
pub struct FileDesc { pub fd: CSocket,}
impl Drop for FileDesc { fn drop(&mut self) { unsafe { close(self.fd); } }}
fn network_addr_to_sockaddr( ni: &NetworkInterface, storage: *mut libc::sockaddr_storage, proto: libc::c_int,) -> usize { unsafe { let sll: *mut libc::sockaddr_ll = mem::transmute(storage); (*sll).sll_family = libc::AF_PACKET as libc::sa_family_t; if let Some(MacAddr(a, b, c, d, e, f)) = ni.mac { (*sll).sll_addr = [a, b, c, d, e, f, 0, 0]; } (*sll).sll_protocol = (proto as u16).to_be(); (*sll).sll_halen = 6; (*sll).sll_ifindex = ni.index as i32; mem::size_of::<libc::sockaddr_ll>() }}
/// 将 Duration 转换成平台特定(platform-specific)的 `timespec`。pub fn duration_to_timespec(dur: Duration) -> libc::timespec { libc::timespec { tv_sec: dur.as_secs() as libc::time_t, tv_nsec: (dur.subsec_nanos() as TvUsecType).into(), }}
/// 使用 Linux 的 `AF_PACKET` 套接字类型,创建数据链路层通道。pub fn channel(network_interface: &NetworkInterface, config: Config) -> io::Result<Channel> { let eth_p_all = 0x0003; let (typ, proto) = match config.channel_type { ChannelType::Layer2 => (libc::SOCK_RAW, eth_p_all), ChannelType::Layer3(proto) => (libc::SOCK_DGRAM, proto), }; let socket = unsafe { libc::socket(libc::AF_PACKET, typ, proto.to_be() as i32) }; if socket == -1 { return Err(io::Error::last_os_error()); } let mut addr: libc::sockaddr_storage = unsafe { mem::zeroed() }; let len = network_addr_to_sockaddr(network_interface, &mut addr, proto as i32);
let send_addr = (&addr as *const libc::sockaddr_storage) as *const libc::sockaddr;
// 绑定到接口 if unsafe { libc::bind(socket, send_addr, len as libc::socklen_t) } == -1 { let err = io::Error::last_os_error(); unsafe { close(socket); } return Err(err); }
let mut pmr: packet_mreq = unsafe { mem::zeroed() }; pmr.mr_ifindex = network_interface.index as i32; pmr.mr_type = PACKET_MR_PROMISC as u16;
// 启用混杂模式 if config.promiscuous { if unsafe { libc::setsockopt( socket, SOL_PACKET, PACKET_ADD_MEMBERSHIP, (&pmr as *const packet_mreq) as *const libc::c_void, mem::size_of::<packet_mreq>() as libc::socklen_t, ) } == -1 { let err = io::Error::last_os_error(); unsafe { close(socket); } return Err(err); } } // 启用数据包 FANOUT if let Some(fanout) = config.fanout { let mut typ = match fanout.fanout_type { FanoutType::HASH => PACKET_FANOUT_HASH, FanoutType::LB => PACKET_FANOUT_LB, FanoutType::CPU => PACKET_FANOUT_CPU, FanoutType::ROLLOVER => PACKET_FANOUT_ROLLOVER, FanoutType::RND => PACKET_FANOUT_RND, FanoutType::QM => PACKET_FANOUT_QM, FanoutType::CBPF => PACKET_FANOUT_CBPF, FanoutType::EBPF => PACKET_FANOUT_EBPF, } as u32; // 设置 defrag 标记 if fanout.defrag { typ = typ | PACKET_FANOUT_FLAG_DEFRAG; } // 设置 rollover 标记 if fanout.rollover { typ = typ | PACKET_FANOUT_FLAG_ROLLOVER; }
// 整型选项值的前 16 位编码扇出组 ID,第 2 个 16 位编码扇出模式和选项 let arg: libc::c_uint = fanout.group_id as u32 | (typ << 16); if unsafe { libc::setsockopt( socket, SOL_PACKET, PACKET_FANOUT, (&arg as *const libc::c_uint) as *const libc::c_void, mem::size_of::<libc::c_uint>() as libc::socklen_t, ) } == -1 { let err = io::Error::last_os_error(); unsafe { close(socket); } return Err(err); } }
// 开启非阻塞模式 if unsafe { libc::fcntl(socket, libc::F_SETFL, libc::O_NONBLOCK) } == -1 { let err = io::Error::last_os_error(); unsafe { close(socket); } return Err(err); } let fd = Arc::new(FileDesc { fd: socket }); let sender = Box::new(DataLinkSenderImpl { socket: fd.clone(), fd_set: unsafe { mem::zeroed() }, write_buffer: vec![0; config.write_buffer_size], _channel_type: config.channel_type, send_addr: unsafe { *(send_addr as *const libc::sockaddr_ll) }, send_addr_len: len, timeout: config.write_timeout.map(|to| duration_to_timespec(to)), }); let receiver = Box::new(DataLinkReceiverImpl { socket: fd.clone(), fd_set: unsafe { mem::zeroed() }, read_buffer: vec![0; config.read_buffer_size], _channel_type: config.channel_type, timeout: config.read_timeout.map(|to| duration_to_timespec(to)), });
Ok(Channel::Ethernet(sender, receiver))}xxxxxxxxxxuse packet_socket_test::Channel::Ethernet;use packet_socket_test::{self, channel, interfaces, NetworkInterface};use pnet::packet::arp::ArpPacket;use pnet::packet::ethernet::{EtherTypes, EthernetPacket, MutableEthernetPacket};use pnet::packet::icmp::{echo_reply, echo_request, IcmpPacket, IcmpTypes};use pnet::packet::icmpv6::Icmpv6Packet;use pnet::packet::ip::{IpNextHeaderProtocol, IpNextHeaderProtocols};use pnet::packet::ipv4::Ipv4Packet;use pnet::packet::ipv6::Ipv6Packet;use pnet::packet::tcp::TcpPacket;use pnet::packet::udp::UdpPacket;use pnet::packet::Packet;use pnet::util::MacAddr;use std::env;use std::io::{self, Write};use std::net::IpAddr;use std::process;
fn handle_udp_packet(interface_name: &str, source: IpAddr, destination: IpAddr, packet: &[u8]) { let udp = UdpPacket::new(packet);
if let Some(udp) = udp { println!( "[{}]: UDP Packet: {}:{} > {}:{}; length: {}", interface_name, source, udp.get_source(), destination, udp.get_destination(), udp.get_length() ); } else { println!("[{}]: Malformed UDP Packet", interface_name); }}
fn handle_icmp_packet(interface_name: &str, source: IpAddr, destination: IpAddr, packet: &[u8]) { let icmp_packet = IcmpPacket::new(packet); if let Some(icmp_packet) = icmp_packet { match icmp_packet.get_icmp_type() { IcmpTypes::EchoReply => { let echo_reply_packet = echo_reply::EchoReplyPacket::new(packet).unwrap(); println!( "[{}]: ICMP echo reply {} -> {} (seq={:?}, id={:?})", interface_name, source, destination, echo_reply_packet.get_sequence_number(), echo_reply_packet.get_identifier() ); } IcmpTypes::EchoRequest => { let echo_request_packet = echo_request::EchoRequestPacket::new(packet).unwrap(); println!( "[{}]: ICMP echo request {} -> {} (seq={:?}, id={:?})", interface_name, source, destination, echo_request_packet.get_sequence_number(), echo_request_packet.get_identifier() ); } _ => println!( "[{}]: ICMP packet {} -> {} (type={:?})", interface_name, source, destination, icmp_packet.get_icmp_type() ), } } else { println!("[{}]: Malformed ICMP Packet", interface_name); }}
fn handle_icmpv6_packet(interface_name: &str, source: IpAddr, destination: IpAddr, packet: &[u8]) { let icmpv6_packet = Icmpv6Packet::new(packet); if let Some(icmpv6_packet) = icmpv6_packet { println!( "[{}]: ICMPv6 packet {} -> {} (type={:?})", interface_name, source, destination, icmpv6_packet.get_icmpv6_type() ) } else { println!("[{}]: Malformed ICMPv6 Packet", interface_name); }}
fn handle_tcp_packet(interface_name: &str, source: IpAddr, destination: IpAddr, packet: &[u8]) { let tcp = TcpPacket::new(packet); if let Some(tcp) = tcp { println!( "[{}]: TCP Packet: {}:{} > {}:{}; length: {}", interface_name, source, tcp.get_source(), destination, tcp.get_destination(), packet.len() ); } else { println!("[{}]: Malformed TCP Packet", interface_name); }}
fn handle_transport_protocol( interface_name: &str, source: IpAddr, destination: IpAddr, protocol: IpNextHeaderProtocol, packet: &[u8],) { match protocol { IpNextHeaderProtocols::Udp => { handle_udp_packet(interface_name, source, destination, packet) } IpNextHeaderProtocols::Tcp => { handle_tcp_packet(interface_name, source, destination, packet) } IpNextHeaderProtocols::Icmp => { handle_icmp_packet(interface_name, source, destination, packet) } IpNextHeaderProtocols::Icmpv6 => { handle_icmpv6_packet(interface_name, source, destination, packet) } _ => println!( "[{}]: Unknown {} packet: {} > {}; protocol: {:?} length: {}", interface_name, match source { IpAddr::V4(..) => "IPv4", _ => "IPv6", }, source, destination, protocol, packet.len() ), }}
fn handle_ipv4_packet(interface_name: &str, ethernet: &EthernetPacket) { let header = Ipv4Packet::new(ethernet.payload()); if let Some(header) = header { handle_transport_protocol( interface_name, IpAddr::V4(header.get_source()), IpAddr::V4(header.get_destination()), header.get_next_level_protocol(), header.payload(), ); } else { println!("[{}]: Malformed IPv4 Packet", interface_name); }}
fn handle_ipv6_packet(interface_name: &str, ethernet: &EthernetPacket) { let header = Ipv6Packet::new(ethernet.payload()); if let Some(header) = header { handle_transport_protocol( interface_name, IpAddr::V6(header.get_source()), IpAddr::V6(header.get_destination()), header.get_next_header(), header.payload(), ); } else { println!("[{}]: Malformed IPv6 Packet", interface_name); }}
fn handle_arp_packet(interface_name: &str, ethernet: &EthernetPacket) { let header = ArpPacket::new(ethernet.payload()); if let Some(header) = header { println!( "[{}]: ARP packet: {}({}) > {}({}); operation: {:?}", interface_name, ethernet.get_source(), header.get_sender_proto_addr(), ethernet.get_destination(), header.get_target_proto_addr(), header.get_operation() ); } else { println!("[{}]: Malformed ARP Packet", interface_name); }}
fn handle_ethernet_frame(interface: &NetworkInterface, ethernet: &EthernetPacket) { let interface_name = &interface.name[..]; match ethernet.get_ethertype() { EtherTypes::Ipv4 => handle_ipv4_packet(interface_name, ethernet), EtherTypes::Ipv6 => handle_ipv6_packet(interface_name, ethernet), EtherTypes::Arp => handle_arp_packet(interface_name, ethernet), _ => println!( "[{}]: Unknown packet: {} > {}; ethertype: {:?} length: {}", interface_name, ethernet.get_source(), ethernet.get_destination(), ethernet.get_ethertype(), ethernet.packet().len() ), }}
fn main() { let iface_name = match env::args().nth(1) { Some(n) => n, None => { writeln!(io::stderr(), "USAGE: packetdump <NETWORK INTERFACE>").unwrap(); process::exit(1); } }; let interface_names_match = |iface: &NetworkInterface| iface.name == iface_name;
// 寻找具有指定名称的网络接口 let interfaces = interfaces(); let interface = interfaces .into_iter() .filter(interface_names_match) .next() .unwrap_or_else(|| panic!("No such network interface: {}", iface_name));
// 创建用于接收数据包的通道 let (mut _tx, mut rx) = match channel(&interface, Default::default()) { Ok(Ethernet(tx, rx)) => (tx, rx), Ok(_) => panic!("packetdump: unhandled channel type"), Err(e) => panic!("packetdump: unable to create channel: {}", e), };
loop { let mut buf: [u8; 1600] = [0u8; 1600]; let mut fake_ethernet_frame = MutableEthernetPacket::new(&mut buf[..]).unwrap(); match rx.next() { Ok(packet) => { let payload_offset; if cfg!(any(target_os = "macos", target_os = "ios")) && interface.is_up() && !interface.is_broadcast() && ((!interface.is_loopback() && interface.is_point_to_point()) || interface.is_loopback()) { if interface.is_loopback() { // 用于 BPF LoopBack 的 pnet 代码添加全零的以太网头部 payload_offset = 14; } else { // 可能是 TUN 接口 payload_offset = 0; } if packet.len() > payload_offset { let version = Ipv4Packet::new(&packet[payload_offset..]) .unwrap() .get_version(); if version == 4 { fake_ethernet_frame.set_destination(MacAddr(0, 0, 0, 0, 0, 0)); fake_ethernet_frame.set_source(MacAddr(0, 0, 0, 0, 0, 0)); fake_ethernet_frame.set_ethertype(EtherTypes::Ipv4); fake_ethernet_frame.set_payload(&packet[payload_offset..]); handle_ethernet_frame(&interface, &fake_ethernet_frame.to_immutable()); continue; } else if version == 6 { fake_ethernet_frame.set_destination(MacAddr(0, 0, 0, 0, 0, 0)); fake_ethernet_frame.set_source(MacAddr(0, 0, 0, 0, 0, 0)); fake_ethernet_frame.set_ethertype(EtherTypes::Ipv6); fake_ethernet_frame.set_payload(&packet[payload_offset..]); handle_ethernet_frame(&interface, &fake_ethernet_frame.to_immutable()); continue; } } } handle_ethernet_frame(&interface, &EthernetPacket::new(packet).unwrap()); } Err(e) => panic!("packetdump: unable to receive packet: {}", e), } }}packet_mreq 是在 C 网络编程中使用的结构体,用于设置和配置网络套接字的多播(Multicast)参数。
在 POSIX 操作系统(比如 Linux、Unix)中,多播是一种网络通信模式,允许一个发送方将数据包发送给一组接收方,而不是仅发送给单个目标。多播通常用于在局域网或广域网上同时向多个主机传送数据。
packet_mreq 结构体定义如下:
xxxxxxxxxx// man 7 packetpub struct packet_mreq { pub mr_ifindex: libc::c_int, // 网络接口的索引,用于指定使用哪个网络接口进行多播通信 pub mr_type: libc::c_ushort, // 多播组地址的类型 pub mr_alen: libc::c_ushort, // 多播组地址的长度 pub mr_address: [libc::c_uchar; 8], // 长度为 8 字节的字符数组,用于存储多播组的地址}通过使用 packet_mreq 结构体,可以设置和配置网络套接字,以加入或离开指定的多播组,以及指定网络接口进行多播通信。
libc::ifaddrs 用于表示网络接口的地址信息。它是与 C 标准库进行交互时,使用的类型。
以下是 libc::ifaddrs 结构体的定义:
xxxxxxxxxxpub struct ifaddrs { pub ifa_next: *mut ifaddrs, // 指向下一个 ifaddrs 结构体的指针,用于构建链表 pub ifa_name: *mut c_char, // 网络接口的名称 pub ifa_flags: c_uint, // 网络接口的标记,比如接口是否是活动的、是否支持广播等 pub ifa_addr: *mut sockaddr, // 指向 sockaddr 结构体的指针,表示网络接口的地址 pub ifa_netmask: *mut sockaddr, // 指向 sockaddr 结构体的指针,表示网络接口的子网掩码 pub ifa_ifu: *mut ifa_ifu, // 联合体指针,用于包含特定于接口类型的数据 pub ifa_data: *mut c_void, // 用于存储自定义的接口信息}libc::sockaddr 结构体用于表示通用的套接字地址。
以下是 libc::sockaddr 结构体的定义:
xxxxxxxxxxpub struct sockaddr { pub sa_family: sa_family_t, // 地址家族(Address Family),用于指定套接字地址的类型,比如 IPv4、IPv6 等。 // 其类型为 sa_family_t,通常是整数类型 pub sa_data: [c_char; 14], // 长度为 14 的固定大小数组,用于存储地址的具体数据。 // 在实际使用中,该数组的内容随着不同的地址家族而有所变化}sockaddr 结构体共 16 个字节,是 Socket 编程中的标准地址结构体。几乎所有 Socket API 都使用 sockaddr 作为其地址信息存储结构。但由于定义它时还处于 IPv4 年代,没有预料到 IPv6 的诞生,sockaddr 大小只有 16 字节,无法存储 IPv6 128 位的 IP 地址,因此引入第二种通用地址结构 sockaddr_storage。
libc::getifaddrs 函数用于获取当前系统上可用网络接口的地址信息列表。
以下是 libc::getifaddrs 函数的签名:
xxxxxxxxxxpub unsafe extern "C" fn getifaddrs(ifap: *mut *mut ifaddrs) -> c_int该函数接受指向 libc::ifaddrs 结构体指针的指针作为参数,返回整数值。
ifap:指向指针的指针,用于接收指向 ifaddrs 结构体链表的头指针
返回值:如果函数调用成功,返回 0;否则返回非零值,表示错误发生
libc::sockaddr_storage 结构体提供通用的套接字地址存储结构,可以用于容纳各种类型的套接字地址。根据不同的地址家族,套接字地址的具体信息可能存储在 sockaddr_storage 结构体的其它字段中。比如,对于 IPv4 地址家族,可以将 sockaddr_in 结构体的实例强制转换为 sockaddr_storage 类型进行存储和操作。
以下是 libc::sockaddr_storage 结构体的定义:
xxxxxxxxxx pub struct sockaddr_storage { pub ss_family: sa_family_t, // 地址家族(Address Family),用于指定套接字地址的类型,比如 IPv4、IPv6 等 __ss_pad2: [u8; 128 - 2 - 4], __ss_pad2: [u8; 128 - 2 - 8], __ss_align: ::size_t, // 用于对齐 }libc::sockaddr_in 结构体用于表示 IPv4 套接字地址。
以下是 libc::sockaddr_in 结构体的定义:
xxxxxxxxxx pub struct sockaddr_in { pub sin_family: sa_family_t, // 地址家族(Address Family),用于指定套接字地址的类型,对于 IPv4 地址,为 AF_INET pub sin_port: ::in_port_t, // 套接字的端口号,使用网络字节序(Big-Endian)表示 pub sin_addr: ::in_addr, // IPv4 地址,是 in_addr 结构体的实例 pub sin_zero: [u8; 8], // 长度为 8 的数组,用于填充结构体,以满足对齐要求 }libc::fd_set 结构体用于表示文件描述符集合。
以下是 libc::fd_set 结构体的定义:
xxxxxxxxxxpub struct fd_set { fds_bits: [libc::c_ulong; FD_SETSIZE / (8 * std::mem::size_of::<libc::c_ulong>())],}该结构体包含一个名为 fds_bits 的字段,它是固定大小的数组。该数组的长度根据 FD_SETSIZE 宏的值动态计算,以适应文件描述符集的大小。
通过设置或清除 fds_bits 数组中的相应位,向集合中添加或删除文件描述符。
libc::pselect 函数用于在指定的文件描述符集上,进行阻塞式选择操作。
libc::pselect 函数的定义如下:
xxxxxxxxxxpub unsafe fn pselect( nfds: libc::c_int, readfds: *mut libc::fd_set, writefds: *mut libc::fd_set, errorfds: *mut libc::fd_set, timeout: *const libc::timespec, sigmask: *const libc::sigset_t,) -> libc::c_int该函数接受以下参数:
nfds:待监视的最大文件描述符值加 1
readfds:指向读取文件描述符集合的指针
writefds:指向写入文件描述符集合的指针
errorfds:指向错误文件描述符集合的指针
timeout:指向指定超时时间的 timespec 结构体的指针。如果为NULL,表示没有超时限制
sigmask:指向信号掩码(sigset_t)的指针,用于指定需要阻塞的信号集。如果为 NULL,表示不阻塞任何信号
libc::pselect 函数将阻塞程序的执行,直到满足以下条件之一:
有一个或多个文件描述符读取就绪、写入就绪或出现错误
超时时间到达
接收到阻塞的信号
在函数返回时,可以根据返回值确定选择操作的结果。如果返回值为负数,那么表示选择操作出错;如果返回值为 0,那么表示选择操作超时;如果返回值大于 0,那么表示就绪的文件描述符数量。
libc::timespec 是在 Rust 中表示时间的结构体,通常用于与系统调用、时间相关的函数和库一起使用。
timespec 结构体定义一个时间点,包含两个字段:
xxxxxxxxxxpub struct timespec { pub tv_sec: time_t, // 秒数 pub tv_nsec: c_long, // 纳秒数}其中,tv_sec 字段是 time_t 类型的整数,用于表示秒数。time_t 是长整型,通常为 32 位或 64 位整数,用于表示自 1970 年 1 月 1 日 00:00:00 UTC 起经过的秒数。
tv_nsec 字段是 c_long 类型的长整数,用于表示剩余的纳秒数。它的取值范围是 0 到 999,999,999。
通过结合 tv_sec 和 tv_nsec 字段,timespec 结构体可以表示精确到纳秒级别的时间点。
libc::sendto 函数用于向指定的目的地址发送数据。
libc::sendto 函数的定义如下:
xxxxxxxxxxpub fn sendto( sockfd: libc::c_int, buf: *const libc::c_void, len: libc::size_t, flags: libc::c_int, dest_addr: *const libc::sockaddr, addrlen: libc::socklen_t,) -> libc::ssize_t该函数接受以下参数:
sockfd:指定用于发送数据的套接字描述符
buf:指向包含待发送数据的缓冲区的指针
len:待发送数据的长度
flags:指定发送操作的选项标记,比如是否设置为非阻塞、发送超时等
dest_addr:指向目的地址的 sockaddr 结构体的指针,用于指定数据的接收方
addrlen:指定目的地址结构体的长度
libc::sendto 函数将指定的数据发送到目的地址。从缓冲区 buf 中读取待发送数据,长度为 len 字节。可以通过设置 flags 参数控制发送操作的行为,比如设置非阻塞模式、启用发送超时等。
在函数返回时,可以根据返回值确定发送操作的结果。如果返回值为负数,那么表示发送操作出错;如果返回值为零或正数,那么表示成功发送的字节数。
libc::recvfrom 函数用于从指定的源地址接收数据。
libc::recvfrom 函数的定义如下:
xxxxxxxxxxpub fn recvfrom( sockfd: libc::c_int, buf: *mut libc::c_void, len: libc::size_t, flags: libc::c_int, src_addr: *mut libc::sockaddr, addrlen: *mut libc::socklen_t,) -> libc::ssize_t该函数接受以下参数:
sockfd:指定用于接收数据的套接字描述符
buf:指向用于存储接收数据的缓冲区的指针
len:指定接收缓冲区的长度
flags:指定接收操作的选项标记,比如是否设置为非阻塞、接收超时等
src_addr:指向用于存储源地址的 sockaddr 结构体的指针,用于获取发送方的地址信息
addrlen:指向 socklen_t 类型的指针,用于获取源地址结构体的长度
libc::recvfrom 函数从指定的套接字接收数据,将接收到的数据存储到缓冲区 buf 中,最多存储 len 字节的数据。可以通过设置 flags 参数,控制接收操作的行为,比如设置非阻塞模式、启用接收超时等。
在函数返回时,可以根据返回值确定接收操作的结果。如果返回值为负数,那么表示接收操作出错;如果返回值为零或正数,那么表示成功接收的字节数。
同时,src_addr 和 addrlen 参数用于获取发送方的地址信息。src_addr 指向的 sockaddr 结构体将被填充为发送方的地址信息,addrlen 用于指定 src_addr 结构体的长度。
libc::fcntl 函数用于在文件描述符上执行各种控制操作。
libc::fcntl 函数的定义如下:
xxxxxxxxxxpub fn fcntl( fd: libc::c_int, cmd: libc::c_int, ...) -> libc::c_int该函数接受以下参数:
fd:指定要进行控制操作的文件描述符
cmd:指定要执行的控制命令
...:根据不同的命令,需要传递额外的参数
比如 libc::fcntl(socket, libc::F_SETFL, libc::O_NONBLOCK) 将套接字(socket)设置为非阻塞模式。
内核(Kernel)利用文件描述符(File Descriptor)访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核将返回一个文件描述符。读写文件时,也需要使用文件描述符指定待读写的文件。实际上,它是一个索引值,指向内核为每个进程所维护的该进程打开的文件的记录表。
$ sudo strace tcpdump -i eth0可以在输出中看到:
socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) = 3
可见在 Linux 下,tcpdump 是基于 Packet 套接字的。