use std::cell::RefCell;
use std::fmt;
use crate::net::packet_sender::{BackendPacketSender, PnetBackend, PnetIoBackend};
use crate::net::{
Result as NetResult, SendMode, SendOptions, SendPlan, SendReport, SendTarget, SocketSender,
};
use crate::Packet;
use crate::{LinkType, NetworkLayer};
use super::super::record::{BackendKind, PacketRecord};
use super::super::writer::{PacketWriter, WriteReport};
use super::super::Result;
pub struct RawSocketWriter {
inner: StatefulRawSocketWriter<PnetIoBackend>,
}
impl RawSocketWriter {
pub fn new(options: impl Into<SendOptions>) -> Self {
Self::from(SocketSender::new(options))
}
pub fn dry_run(interface: impl Into<String>) -> Self {
Self::new(SendOptions::new().interface(interface).dry_run())
}
pub fn live(interface: impl Into<String>) -> Self {
Self::new(SendOptions::new().interface(interface).live())
}
pub const fn sender(&self) -> &SocketSender {
self.inner.sender()
}
pub const fn options(&self) -> &SendOptions {
self.inner.options()
}
pub fn plan_packet(&self, packet: &Packet) -> NetResult<SendPlan> {
self.inner.plan_packet(packet)
}
pub fn send_packet(&self, packet: &Packet) -> NetResult<SendReport> {
self.inner.send_packet(packet)
}
}
impl fmt::Debug for RawSocketWriter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RawSocketWriter")
.field("sender", self.sender())
.finish_non_exhaustive()
}
}
impl Clone for RawSocketWriter {
fn clone(&self) -> Self {
Self::from(self.sender().clone())
}
}
impl PartialEq for RawSocketWriter {
fn eq(&self, other: &Self) -> bool {
self.sender() == other.sender()
}
}
impl Eq for RawSocketWriter {}
impl From<SocketSender> for RawSocketWriter {
fn from(sender: SocketSender) -> Self {
Self {
inner: StatefulRawSocketWriter::from_sender_with_backend(sender, PnetIoBackend),
}
}
}
impl PacketWriter for RawSocketWriter {
fn write_record(&mut self, record: &PacketRecord) -> Result<WriteReport> {
self.inner.write_record(record)
}
}
struct StatefulRawSocketWriter<B>
where
B: PnetBackend + Clone,
{
sender: SocketSender,
stateful_sender: RefCell<StatefulPacketSender<B>>,
}
impl<B> StatefulRawSocketWriter<B>
where
B: PnetBackend + Clone,
{
#[cfg(test)]
fn new_with_backend(options: impl Into<SendOptions>, backend: B) -> Self {
Self::from_sender_with_backend(SocketSender::new(options), backend)
}
fn from_sender_with_backend(sender: SocketSender, backend: B) -> Self {
Self {
sender,
stateful_sender: RefCell::new(StatefulPacketSender::new(backend)),
}
}
const fn sender(&self) -> &SocketSender {
&self.sender
}
const fn options(&self) -> &SendOptions {
self.sender.options()
}
fn plan_packet(&self, packet: &Packet) -> NetResult<SendPlan> {
self.sender.plan(packet)
}
fn send_packet(&self, packet: &Packet) -> NetResult<SendReport> {
self.stateful_sender
.borrow_mut()
.send(self.sender.options(), packet)
}
}
impl<B> PacketWriter for StatefulRawSocketWriter<B>
where
B: PnetBackend + Clone,
{
fn write_record(&mut self, record: &PacketRecord) -> Result<WriteReport> {
let send_report = self.send_packet(record.packet())?;
Ok(write_report_from_send_report(&send_report))
}
}
struct StatefulPacketSender<B>
where
B: PnetBackend + Clone,
{
backend: B,
sender: Option<BackendPacketSender<B>>,
}
impl<B> StatefulPacketSender<B>
where
B: PnetBackend + Clone,
{
fn new(backend: B) -> Self {
Self {
backend,
sender: None,
}
}
fn send(&mut self, options: &SendOptions, packet: &Packet) -> NetResult<SendReport> {
if self.sender.is_none() {
self.sender = Some(BackendPacketSender::open_with_backend(
options.clone(),
self.backend.clone(),
)?);
}
self.sender
.as_mut()
.expect("stateful raw socket sender is initialized before send")
.send(packet)
}
}
fn write_report_from_send_report(report: &SendReport) -> WriteReport {
WriteReport::new(
BackendKind::RawSocket,
report.plan().len(),
report.bytes_sent(),
report.is_dry_run(),
)
.with_target_details(send_target_details(report))
}
fn send_target_details(report: &SendReport) -> String {
let plan = report.plan();
match plan.target() {
SendTarget::LinkLayer { link_type } => format!(
"interface={} mode={} target=link-layer:{}",
plan.interface(),
send_mode_name(plan.requested_mode()),
link_type_name(link_type)
),
SendTarget::NetworkLayer {
network_layer,
destination,
protocol,
} => format!(
"interface={} mode={} target=network-layer:{} destination={} protocol={}",
plan.interface(),
send_mode_name(plan.requested_mode()),
network_layer_name(network_layer),
destination,
protocol
),
}
}
const fn send_mode_name(mode: SendMode) -> &'static str {
match mode {
SendMode::Auto => "auto",
SendMode::LinkLayer => "link-layer",
SendMode::NetworkLayer => "network-layer",
}
}
const fn link_type_name(link_type: LinkType) -> &'static str {
match link_type {
LinkType::Raw => "raw",
LinkType::Ethernet => "ethernet",
LinkType::Ieee80211 => "ieee80211",
LinkType::Radiotap => "radiotap",
LinkType::BluetoothLeLl => "bluetooth-le-ll",
LinkType::Ieee802154 => "ieee802154",
LinkType::Ieee802154Tap => "ieee802154-tap",
LinkType::LinuxCooked => "linux-cooked",
LinkType::LinuxSll => "linux-sll",
LinkType::NullLoopback => "null-loopback",
}
}
const fn network_layer_name(network_layer: NetworkLayer) -> &'static str {
match network_layer {
NetworkLayer::Raw => "raw",
NetworkLayer::Ipv4 => "ipv4",
NetworkLayer::Ipv6 => "ipv6",
}
}
#[cfg(test)]
mod raw_socket_writer {
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use super::*;
use crate::net::packet_sender::{FakePnetBackend, IPPROTO_RAW_SOCKET};
use crate::net::{NetError, SendMode, SendOptions, SendTarget};
use crate::{
Dot11, Ethernet, Ipv4, Ipv6, LlcSnap, MacAddr, Packet, PacketWire, PacketWireTarget,
Radiotap, Raw, Tcp, Udp,
};
use crate::{DuplicateTransform, PacketRecord, Transmitter, WireError};
fn ipv4_packet() -> Packet {
Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(Ipv4Addr::new(198, 51, 100, 20))
/ Udp::new().sport(1111).dport(2222)
/ Raw::from("hello")
}
fn ethernet_packet() -> Packet {
Ethernet::new()
.src(MacAddr::new([0x02, 0, 0, 0, 0, 1]))
.dst(MacAddr::BROADCAST)
/ ipv4_packet()
}
fn radiotap_dot11_packet() -> Packet {
Radiotap::new() / Dot11::data() / LlcSnap::new() / ipv4_packet()
}
fn ipv6_packet() -> Packet {
Ipv6::new()
.src(Ipv6Addr::LOCALHOST)
.dst(Ipv6Addr::LOCALHOST)
/ Tcp::new().sport(1234).dport(443)
}
fn assert_raw_socket_wire_target(wire: &PacketWire, interface: &str) {
assert_eq!(
wire.target(),
&PacketWireTarget::RawSocketInterface {
interface: interface.to_string()
}
);
assert_eq!(wire.target().interface(), Some(interface));
assert!(!wire.has_source());
assert!(wire.has_writer());
}
#[test]
fn raw_socket_writer_dry_run_maps_socket_send_report() {
let packet = ipv4_packet();
let expected_len = packet.compile().unwrap().len();
let mut writer = RawSocketWriter::dry_run("eth0");
assert_eq!(writer.options().interface_name(), Some("eth0"));
assert!(writer.options().is_dry_run());
let report = writer
.write_record(&PacketRecord::new(packet.clone()))
.unwrap();
assert_eq!(report.backend(), &BackendKind::RawSocket);
assert_eq!(report.bytes_requested(), expected_len);
assert_eq!(report.bytes_written(), expected_len);
assert!(report.is_dry_run());
let details = report.target_details().unwrap();
assert!(details.contains("interface=eth0"));
assert!(details.contains("mode=auto"));
assert!(details.contains("target=network-layer:ipv4"));
assert!(details.contains("destination=198.51.100.20"));
assert!(details.contains("protocol=17"));
}
#[test]
fn raw_socket_writer_packet_wire_live_requires_explicit_mode() {
let auto_wire = PacketWire::raw_socket_interface("eth0").open().unwrap();
assert_raw_socket_wire_target(&auto_wire, "eth0");
let mut writer = auto_wire.writer().unwrap();
let report = writer
.write_record(&PacketRecord::new(ipv4_packet()))
.unwrap();
assert!(report.is_dry_run());
assert!(report.target_details().unwrap().contains("mode=auto"));
let result = PacketWire::raw_socket_interface("eth0").live().open();
let error = match result {
Ok(_) => panic!("expected live raw socket auto mode rejection"),
Err(error) => error,
};
match error {
WireError::Net(NetError::ExplicitSendModeRequired { mode, reason }) => {
assert_eq!(mode, SendMode::Auto);
assert_eq!(
reason,
"live raw socket packet wires require explicit link_layer() or network_layer() mode"
);
}
other => panic!("expected explicit send mode error, got {other:?}"),
}
}
#[test]
fn raw_socket_writer_packet_wire_builder_is_write_only_and_preserves_mode() {
let wire = PacketWire::raw_socket_interface("eth0")
.network_layer()
.open()
.unwrap();
assert_raw_socket_wire_target(&wire, "eth0");
let mut writer = wire.writer().unwrap();
let report = writer
.write_record(&PacketRecord::new(ipv4_packet()))
.unwrap();
assert_eq!(report.backend(), &BackendKind::RawSocket);
assert!(report.is_dry_run());
assert!(report
.target_details()
.unwrap()
.contains("mode=network-layer"));
let live_network = PacketWire::raw_socket_interface("net-live0")
.network_layer()
.live()
.open()
.unwrap();
assert_raw_socket_wire_target(&live_network, "net-live0");
let live_link = PacketWire::raw_socket_interface("link-live0")
.link_layer()
.live()
.open()
.unwrap();
assert_raw_socket_wire_target(&live_link, "link-live0");
}
#[test]
fn raw_socket_writer_packet_wire_split_reports_write_only_target() {
let result = PacketWire::raw_socket_interface("eth0")
.open()
.unwrap()
.split();
let error = match result {
Ok(_) => panic!("expected unsupported split error"),
Err(error) => error,
};
match error {
WireError::UnsupportedCapability {
capability,
backend: Some(backend),
reason,
} => {
assert_eq!(capability, "split");
assert_eq!(backend, "raw-socket:eth0");
assert_eq!(
reason,
"raw socket interface targets are write-only; use pcap_interface for capture"
);
}
other => panic!("expected unsupported split error, got {other:?}"),
}
}
#[test]
fn raw_socket_writer_dry_run_link_layer_report_keeps_target_shape() {
let packet = ethernet_packet();
let mut writer =
RawSocketWriter::new(SendOptions::new().interface("veth0").link_layer().dry_run());
let report = writer.write_record(&PacketRecord::new(packet)).unwrap();
assert_eq!(report.backend(), &BackendKind::RawSocket);
assert!(report.is_dry_run());
assert_eq!(
report.target_details(),
Some("interface=veth0 mode=link-layer target=link-layer:ethernet")
);
}
#[test]
fn raw_socket_writer_dry_run_radiotap_link_layer_report_keeps_target_shape() {
let packet = radiotap_dot11_packet();
let mut writer = RawSocketWriter::new(
SendOptions::new()
.interface("wifi-dryrun0")
.link_layer()
.dry_run(),
);
let report = writer.write_record(&PacketRecord::new(packet)).unwrap();
assert_eq!(report.backend(), &BackendKind::RawSocket);
assert!(report.is_dry_run());
assert_eq!(
report.target_details(),
Some("interface=wifi-dryrun0 mode=link-layer target=link-layer:radiotap")
);
}
#[test]
fn raw_socket_writer_dry_run_rejects_link_mode_for_ipv4_packet() {
let mut writer =
RawSocketWriter::new(SendOptions::new().interface("eth0").link_layer().dry_run());
let error = writer
.write_record(&PacketRecord::new(ipv4_packet()))
.unwrap_err();
match error {
WireError::Net(NetError::UnsupportedPacketShape {
mode,
summary: _,
reason,
}) => {
assert_eq!(mode, SendMode::LinkLayer);
assert_eq!(
reason,
"link-layer sends require Ethernet, LinuxSll, NullLoopback, or Radiotap / Dot11 Wi-Fi as the first layers"
);
}
other => panic!("expected link-layer shape rejection, got {other:?}"),
}
}
#[test]
fn raw_socket_writer_dry_run_rejects_network_mode_for_ethernet_frame() {
let mut writer = RawSocketWriter::new(
SendOptions::new()
.interface("eth0")
.network_layer()
.dry_run(),
);
let error = writer
.write_record(&PacketRecord::new(ethernet_packet()))
.unwrap_err();
match error {
WireError::Net(NetError::UnsupportedPacketShape {
mode,
summary,
reason,
}) => {
assert_eq!(mode, SendMode::NetworkLayer);
assert!(summary.contains("Ethernet"));
assert_eq!(
reason,
"network-layer sends require IPv4 or IPv6 as the first layer"
);
}
other => panic!("expected network-layer shape rejection, got {other:?}"),
}
}
#[test]
fn raw_socket_writer_live_radiotap_routes_to_layer2_via_socket_sender() {
let mut writer = RawSocketWriter::new(
SendOptions::new()
.interface("missing-crafter-wifi0")
.link_layer()
.live(),
);
let error = writer
.write_record(&PacketRecord::new(radiotap_dot11_packet()))
.unwrap_err();
match error {
WireError::Net(NetError::InterfaceNotFound { name }) => {
assert_eq!(name, "missing-crafter-wifi0");
}
other => panic!("expected raw socket radiotap missing-interface error, got {other:?}"),
}
}
#[test]
fn raw_socket_writer_live_network_layer_reuses_open_sender() {
let backend = FakePnetBackend::default();
let mut writer = StatefulRawSocketWriter::new_with_backend(
SendOptions::new()
.interface("lo")
.network_layer()
.live()
.write_buffer_size(8),
backend.clone(),
);
let packet = ipv4_packet();
let expected_len = packet.compile().unwrap().len();
let first_report = writer
.write_record(&PacketRecord::new(packet.clone()))
.unwrap();
let second_report = writer
.write_record(&PacketRecord::new(packet.clone()))
.unwrap();
for report in [&first_report, &second_report] {
assert_eq!(report.backend(), &BackendKind::RawSocket);
assert_eq!(report.bytes_requested(), expected_len);
assert_eq!(report.bytes_written(), expected_len);
assert!(!report.is_dry_run());
assert_eq!(
report.target_details(),
Some(
"interface=lo mode=network-layer target=network-layer:ipv4 destination=198.51.100.20 protocol=17"
)
);
}
let snapshot = backend.snapshot();
assert_eq!(snapshot.link_opens.len(), 0);
assert_eq!(snapshot.link_sends.len(), 0);
assert_eq!(snapshot.network_opens.len(), 1);
assert_eq!(
snapshot.network_opens[0].socket_protocol,
IPPROTO_RAW_SOCKET
);
assert_eq!(snapshot.network_opens[0].write_buffer_size, expected_len);
assert_eq!(snapshot.network_sends.len(), 2);
assert_eq!(snapshot.network_sends[0].bytes.len(), expected_len);
assert_eq!(snapshot.network_sends[1].bytes.len(), expected_len);
assert_eq!(
snapshot.network_sends[0].destination,
IpAddr::V4(Ipv4Addr::new(198, 51, 100, 20))
);
assert_eq!(snapshot.network_sends[0].protocol, crate::IPPROTO_UDP);
assert_eq!(
snapshot.network_sends[0].target,
snapshot.network_sends[1].target
);
}
#[test]
fn transmitter_live_raw_socket_writer_reuses_sender() {
let backend = FakePnetBackend::default();
let writer = StatefulRawSocketWriter::new_with_backend(
SendOptions::new()
.interface("lo")
.network_layer()
.live()
.write_buffer_size(8),
backend.clone(),
);
let packet = ipv4_packet();
let expected_len = packet.compile().unwrap().len();
let mut transmitter = Transmitter::new(writer).with(DuplicateTransform::new());
let reports = transmitter
.send_record(PacketRecord::new(packet.clone()))
.unwrap();
assert_eq!(reports.len(), 2);
for report in &reports {
assert_eq!(report.backend(), &BackendKind::RawSocket);
assert_eq!(report.bytes_requested(), expected_len);
assert_eq!(report.bytes_written(), expected_len);
assert!(!report.is_dry_run());
assert_eq!(
report.target_details(),
Some(
"interface=lo mode=network-layer target=network-layer:ipv4 destination=198.51.100.20 protocol=17"
)
);
}
let snapshot = backend.snapshot();
assert_eq!(snapshot.link_opens.len(), 0);
assert_eq!(snapshot.link_sends.len(), 0);
assert_eq!(snapshot.network_opens.len(), 1);
assert_eq!(
snapshot.network_opens[0].socket_protocol,
IPPROTO_RAW_SOCKET
);
assert_eq!(snapshot.network_opens[0].write_buffer_size, expected_len);
assert_eq!(snapshot.network_sends.len(), 2);
assert_eq!(snapshot.network_sends[0].bytes.len(), expected_len);
assert_eq!(snapshot.network_sends[1].bytes.len(), expected_len);
assert_eq!(
snapshot.network_sends[0].destination,
IpAddr::V4(Ipv4Addr::new(198, 51, 100, 20))
);
assert_eq!(snapshot.network_sends[0].protocol, crate::IPPROTO_UDP);
assert_eq!(
snapshot.network_sends[0].target,
snapshot.network_sends[1].target
);
}
#[test]
fn raw_socket_writer_dry_run_reports_ipv4_destination_and_protocol() {
let mut writer =
RawSocketWriter::new(SendOptions::new().interface("lo").network_layer().dry_run());
let report = writer
.write_record(&PacketRecord::new(ipv4_packet()))
.unwrap();
assert_eq!(report.backend(), &BackendKind::RawSocket);
assert_eq!(report.bytes_requested(), report.bytes_written());
assert_eq!(
report.target_details(),
Some(
"interface=lo mode=network-layer target=network-layer:ipv4 destination=198.51.100.20 protocol=17"
)
);
let plan = SocketSender::dry_run("lo").plan(&ipv4_packet()).unwrap();
match plan.target() {
SendTarget::NetworkLayer {
destination,
protocol,
..
} => {
assert_eq!(destination, IpAddr::V4(Ipv4Addr::new(198, 51, 100, 20)));
assert_eq!(protocol, crate::IPPROTO_UDP);
}
other => panic!("expected network target, got {other:?}"),
}
}
#[test]
fn raw_socket_writer_dry_run_ipv6_network_layer_stays_plan_only() {
let mut writer =
RawSocketWriter::new(SendOptions::new().interface("lo").network_layer().dry_run());
let report = writer
.write_record(&PacketRecord::new(ipv6_packet()))
.unwrap();
assert_eq!(report.backend(), &BackendKind::RawSocket);
assert_eq!(report.bytes_requested(), report.bytes_written());
assert!(report.is_dry_run());
assert_eq!(
report.target_details(),
Some(
"interface=lo mode=network-layer target=network-layer:ipv6 destination=::1 protocol=6"
)
);
let plan = writer.plan_packet(&ipv6_packet()).unwrap();
match plan.target() {
SendTarget::NetworkLayer {
network_layer,
destination,
protocol,
} => {
assert_eq!(network_layer, NetworkLayer::Ipv6);
assert_eq!(destination, IpAddr::V6(Ipv6Addr::LOCALHOST));
assert_eq!(protocol, crate::IPPROTO_TCP);
}
other => panic!("expected IPv6 network target, got {other:?}"),
}
}
}