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;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RawSocketWriter {
sender: SocketSender,
}
impl RawSocketWriter {
pub fn new(options: impl Into<SendOptions>) -> Self {
Self {
sender: 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.sender
}
pub const fn options(&self) -> &SendOptions {
self.sender.options()
}
pub fn plan_packet(&self, packet: &Packet) -> NetResult<SendPlan> {
self.sender.plan(packet)
}
pub fn send_packet(&self, packet: &Packet) -> NetResult<SendReport> {
self.sender.send(packet)
}
}
impl From<SocketSender> for RawSocketWriter {
fn from(sender: SocketSender) -> Self {
Self { sender }
}
}
impl PacketWriter for RawSocketWriter {
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))
}
}
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::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};
use super::*;
use crate::net::{NetError, SendOptions, SendTarget};
use crate::{
Dot11, Ethernet, Ipv4, LlcSnap, MacAddr, Packet, PacketWire, PacketWireTarget, Radiotap,
Raw, Udp,
};
use crate::{PacketRecord, 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()
}
#[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_builder_is_write_only_and_preserves_mode() {
let wire = PacketWire::raw_socket_interface("eth0")
.network_layer()
.open()
.unwrap();
assert_eq!(
wire.target(),
&PacketWireTarget::RawSocketInterface {
interface: "eth0".to_string()
}
);
assert_eq!(wire.target().interface(), Some("eth0"));
assert!(!wire.has_source());
assert!(wire.has_writer());
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"));
}
#[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_live_radiotap_routes_to_layer2_via_socket_sender() {
let mut writer =
RawSocketWriter::new(SendOptions::new().interface("missing-crafter-wifi0").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_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:?}"),
}
}
}