use std::net::IpAddr;
use std::time::Duration;
use crate::wire::RawSocketWriter;
use crate::{
CompiledPacket, Ethernet, Icmpv4, Icmpv6, Ipv4, Ipv6, Layer, LinkType, NetworkLayer, Packet,
Radiotap, Tcp, Udp, IPPROTO_ICMP, IPPROTO_ICMPV6, IPPROTO_TCP, IPPROTO_UDP,
};
use super::error::{NetError, Result};
use super::packet_sender;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub enum SendMode {
#[default]
Auto,
LinkLayer,
NetworkLayer,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SendTarget {
LinkLayer {
link_type: LinkType,
},
NetworkLayer {
network_layer: NetworkLayer,
destination: IpAddr,
protocol: u8,
},
}
impl SendTarget {
pub const fn is_link_layer(self) -> bool {
matches!(self, Self::LinkLayer { .. })
}
pub const fn is_network_layer(self) -> bool {
matches!(self, Self::NetworkLayer { .. })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SendOptions {
interface: Option<String>,
mode: SendMode,
dry_run: bool,
write_timeout: Option<Duration>,
write_buffer_size: usize,
}
impl SendOptions {
pub const fn new() -> Self {
Self {
interface: None,
mode: SendMode::Auto,
dry_run: false,
write_timeout: None,
write_buffer_size: 4096,
}
}
pub fn interface(mut self, interface: impl Into<String>) -> Self {
self.interface = Some(interface.into());
self
}
pub fn iface(self, interface: impl Into<String>) -> Self {
self.interface(interface)
}
pub const fn mode(mut self, mode: SendMode) -> Self {
self.mode = mode;
self
}
pub const fn link_layer(self) -> Self {
self.mode(SendMode::LinkLayer)
}
pub const fn network_layer(self) -> Self {
self.mode(SendMode::NetworkLayer)
}
pub const fn dry_run(mut self) -> Self {
self.dry_run = true;
self
}
pub const fn live(mut self) -> Self {
self.dry_run = false;
self
}
pub const fn write_timeout(mut self, timeout: Duration) -> Self {
self.write_timeout = Some(timeout);
self
}
pub const fn no_write_timeout(mut self) -> Self {
self.write_timeout = None;
self
}
pub const fn write_buffer_size(mut self, size: usize) -> Self {
self.write_buffer_size = size;
self
}
pub fn interface_name(&self) -> Option<&str> {
self.interface.as_deref()
}
pub const fn send_mode(&self) -> SendMode {
self.mode
}
pub const fn is_dry_run(&self) -> bool {
self.dry_run
}
pub(crate) const fn write_timeout_hint(&self) -> Option<Duration> {
self.write_timeout
}
pub(crate) const fn write_buffer_size_hint(&self) -> usize {
self.write_buffer_size
}
}
impl Default for SendOptions {
fn default() -> Self {
Self::new()
}
}
impl From<&str> for SendOptions {
fn from(interface: &str) -> Self {
Self::new().interface(interface)
}
}
impl From<String> for SendOptions {
fn from(interface: String) -> Self {
Self::new().interface(interface)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SendPlan {
interface: String,
mode: SendMode,
target: SendTarget,
packet: CompiledPacket,
}
impl SendPlan {
pub fn from_packet(packet: &Packet, options: impl Into<SendOptions>) -> Result<Self> {
let options = options.into();
let interface = validated_interface(&options)?;
let compiled = packet.compile()?;
let target = infer_send_target(packet, compiled.as_bytes(), options.mode)?;
Ok(Self {
interface,
mode: options.mode,
target,
packet: compiled,
})
}
pub fn interface(&self) -> &str {
&self.interface
}
pub const fn requested_mode(&self) -> SendMode {
self.mode
}
pub const fn target(&self) -> SendTarget {
self.target
}
pub fn bytes(&self) -> &[u8] {
self.packet.as_bytes()
}
pub fn len(&self) -> usize {
self.packet.len()
}
pub fn is_empty(&self) -> bool {
self.packet.is_empty()
}
pub const fn compiled_packet(&self) -> &CompiledPacket {
&self.packet
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SendReport {
plan: SendPlan,
bytes_sent: usize,
dry_run: bool,
}
impl SendReport {
pub const fn new(plan: SendPlan, bytes_sent: usize, dry_run: bool) -> Self {
Self {
plan,
bytes_sent,
dry_run,
}
}
pub const fn plan(&self) -> &SendPlan {
&self.plan
}
pub const fn bytes_sent(&self) -> usize {
self.bytes_sent
}
pub const fn is_dry_run(&self) -> bool {
self.dry_run
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SocketSender {
options: SendOptions,
}
impl SocketSender {
pub fn new(options: impl Into<SendOptions>) -> Self {
Self {
options: options.into(),
}
}
pub fn dry_run(interface: impl Into<String>) -> Self {
Self::new(SendOptions::new().interface(interface).dry_run())
}
pub const fn options(&self) -> &SendOptions {
&self.options
}
pub fn plan(&self, packet: &Packet) -> Result<SendPlan> {
SendPlan::from_packet(packet, self.options.clone())
}
pub fn send(&self, packet: &Packet) -> Result<SendReport> {
let plan = self.plan(packet)?;
if self.options.dry_run {
let len = plan.len();
return Ok(SendReport::new(plan, len, true));
}
let bytes_sent = transmit_packet_once(packet, &plan, &self.options)?;
Ok(SendReport::new(plan, bytes_sent, false))
}
}
pub type SocketSend = SocketSender;
pub type RawSender = SocketSender;
pub trait PacketSendExt {
fn send_plan(&self, options: impl Into<SendOptions>) -> Result<SendPlan>;
fn send_dry_run(&self, options: impl Into<SendOptions>) -> Result<SendPlan>;
fn send(&self, options: impl Into<SendOptions>) -> Result<SendReport>;
}
impl PacketSendExt for Packet {
fn send_plan(&self, options: impl Into<SendOptions>) -> Result<SendPlan> {
RawSocketWriter::new(options).plan_packet(self)
}
fn send_dry_run(&self, options: impl Into<SendOptions>) -> Result<SendPlan> {
RawSocketWriter::new(options.into().dry_run()).plan_packet(self)
}
fn send(&self, options: impl Into<SendOptions>) -> Result<SendReport> {
SocketSender::new(options).send(self)
}
}
pub fn send_packet(packet: &Packet, options: impl Into<SendOptions>) -> Result<SendReport> {
SocketSender::new(options).send(packet)
}
pub fn send_plan(packet: &Packet, options: impl Into<SendOptions>) -> Result<SendPlan> {
RawSocketWriter::new(options.into().dry_run()).plan_packet(packet)
}
pub(crate) fn validated_interface(options: &SendOptions) -> Result<String> {
let interface = options
.interface_name()
.ok_or(NetError::InterfaceRequired)?
.to_string();
if interface.trim().is_empty() {
return Err(NetError::InvalidInterfaceName {
name: interface,
reason: "interface name must not be empty",
});
}
if interface.as_bytes().contains(&0) {
return Err(NetError::InvalidInterfaceName {
name: interface,
reason: "interface name must not contain NUL bytes",
});
}
Ok(interface)
}
fn infer_send_target(packet: &Packet, bytes: &[u8], mode: SendMode) -> Result<SendTarget> {
let Some(first) = packet.get(0) else {
return Err(NetError::UnsupportedPacketShape {
mode,
summary: packet.summary(),
reason: "empty packets cannot be sent",
});
};
match mode {
SendMode::Auto => infer_auto_target(packet, first, bytes),
SendMode::LinkLayer => infer_link_target(packet, first),
SendMode::NetworkLayer => infer_network_target(packet, first, bytes),
}
}
fn infer_auto_target(packet: &Packet, first: &dyn Layer, bytes: &[u8]) -> Result<SendTarget> {
if is_link_layer(first) {
infer_link_target(packet, first)
} else if is_network_layer(first) {
infer_network_target(packet, first, bytes)
} else {
Err(NetError::UnsupportedPacketShape {
mode: SendMode::Auto,
summary: packet.summary(),
reason: "first layer must be Ethernet, LinuxSll, NullLoopback, Radiotap / Dot11 for Wi-Fi, IPv4, or IPv6",
})
}
}
fn infer_link_target(packet: &Packet, first: &dyn Layer) -> Result<SendTarget> {
if first.as_any().is::<Ethernet>() {
Ok(SendTarget::LinkLayer {
link_type: LinkType::Ethernet,
})
} else if first.as_any().is::<crate::LinuxSll>() {
Ok(SendTarget::LinkLayer {
link_type: LinkType::LinuxSll,
})
} else if first.as_any().is::<crate::NullLoopback>() {
Ok(SendTarget::LinkLayer {
link_type: LinkType::NullLoopback,
})
} else if first.as_any().is::<Radiotap>() {
Ok(SendTarget::LinkLayer {
link_type: LinkType::Radiotap,
})
} else {
Err(NetError::UnsupportedPacketShape {
mode: SendMode::LinkLayer,
summary: packet.summary(),
reason: "link-layer sends require Ethernet, LinuxSll, NullLoopback, or Radiotap / Dot11 Wi-Fi as the first layers",
})
}
}
fn infer_network_target(packet: &Packet, first: &dyn Layer, bytes: &[u8]) -> Result<SendTarget> {
if let Some(ipv4) = first.as_any().downcast_ref::<Ipv4>() {
let protocol = bytes
.get(9)
.copied()
.unwrap_or_else(|| ipv4_protocol(packet, ipv4));
Ok(SendTarget::NetworkLayer {
network_layer: NetworkLayer::Ipv4,
destination: IpAddr::V4(ipv4.destination()),
protocol,
})
} else if let Some(ipv6) = first.as_any().downcast_ref::<Ipv6>() {
let protocol = bytes
.get(6)
.copied()
.unwrap_or_else(|| ipv6_next_header(packet, ipv6));
Ok(SendTarget::NetworkLayer {
network_layer: NetworkLayer::Ipv6,
destination: IpAddr::V6(ipv6.destination()),
protocol,
})
} else {
Err(NetError::UnsupportedPacketShape {
mode: SendMode::NetworkLayer,
summary: packet.summary(),
reason: "network-layer sends require IPv4 or IPv6 as the first layer",
})
}
}
fn is_link_layer(layer: &dyn Layer) -> bool {
layer.as_any().is::<Ethernet>()
|| layer.as_any().is::<crate::LinuxSll>()
|| layer.as_any().is::<crate::NullLoopback>()
|| layer.as_any().is::<Radiotap>()
}
fn is_network_layer(layer: &dyn Layer) -> bool {
layer.as_any().is::<Ipv4>() || layer.as_any().is::<Ipv6>()
}
fn ipv4_protocol(packet: &Packet, ipv4: &Ipv4) -> u8 {
if ipv4.protocol_value() != 0 {
return ipv4.protocol_value();
}
packet.get(1).map(layer_protocol).unwrap_or(0)
}
fn ipv6_next_header(packet: &Packet, ipv6: &Ipv6) -> u8 {
if ipv6.next_header_value() != 0 {
return ipv6.next_header_value();
}
packet.get(1).map(layer_protocol).unwrap_or(0)
}
fn layer_protocol(layer: &dyn Layer) -> u8 {
if layer.as_any().is::<Icmpv4>() {
IPPROTO_ICMP
} else if layer.as_any().is::<Icmpv6>() {
IPPROTO_ICMPV6
} else if layer.as_any().is::<Tcp>() {
IPPROTO_TCP
} else if layer.as_any().is::<Udp>() {
IPPROTO_UDP
} else if layer.as_any().is::<Ipv6>() {
crate::IPPROTO_IPV6
} else {
0
}
}
fn transmit_packet_once(packet: &Packet, plan: &SendPlan, options: &SendOptions) -> Result<usize> {
let mut sender =
packet_sender::PacketSender::open(one_shot_stateful_options(options, plan.target()))?;
let report = sender.send(packet)?;
Ok(report.bytes_sent())
}
fn one_shot_stateful_options(options: &SendOptions, target: SendTarget) -> SendOptions {
if options.send_mode() != SendMode::Auto {
return options.clone();
}
match target {
SendTarget::LinkLayer { .. } => options.clone().link_layer(),
SendTarget::NetworkLayer { .. } => options.clone().network_layer(),
}
}