Skip to main content

crafter/wire/backend/
raw_socket.rs

1//! Raw socket packet writer adapter.
2
3use crate::net::{
4    Result as NetResult, SendMode, SendOptions, SendPlan, SendReport, SendTarget, SocketSender,
5};
6use crate::Packet;
7use crate::{LinkType, NetworkLayer};
8
9use super::super::record::{BackendKind, PacketRecord};
10use super::super::writer::{PacketWriter, WriteReport};
11use super::super::Result;
12
13/// Packet writer that adapts [`SocketSender`] into the [`PacketWriter`] API.
14///
15/// The adapter delegates planning, dry-run behavior, live transmission,
16/// interface validation, send-mode handling, and unsupported target checks to
17/// `SocketSender`. In particular, live radiotap injection is routed through the
18/// same Layer-2 datalink writer `SocketSender` uses for Ethernet frames.
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct RawSocketWriter {
21    sender: SocketSender,
22}
23
24impl RawSocketWriter {
25    /// Create a raw socket writer from existing send options.
26    ///
27    /// This preserves the behavior of [`SendOptions`] exactly. Use
28    /// [`PacketWire::raw_socket_interface`](crate::wire::PacketWire::raw_socket_interface)
29    /// when you want the packet-wire constructor that defaults to dry-run
30    /// planning and requires `.live()` for live transmission.
31    pub fn new(options: impl Into<SendOptions>) -> Self {
32        Self {
33            sender: SocketSender::new(options),
34        }
35    }
36
37    /// Create a compile-only raw socket writer for an interface.
38    pub fn dry_run(interface: impl Into<String>) -> Self {
39        Self::new(SendOptions::new().interface(interface).dry_run())
40    }
41
42    /// Create a live raw socket writer for an interface.
43    pub fn live(interface: impl Into<String>) -> Self {
44        Self::new(SendOptions::new().interface(interface).live())
45    }
46
47    /// Borrow the adapted sender.
48    pub const fn sender(&self) -> &SocketSender {
49        &self.sender
50    }
51
52    /// Borrow the adapted sender options.
53    pub const fn options(&self) -> &SendOptions {
54        self.sender.options()
55    }
56
57    /// Build the send plan this writer would use for a packet.
58    pub fn plan_packet(&self, packet: &Packet) -> NetResult<SendPlan> {
59        self.sender.plan(packet)
60    }
61
62    /// Send a packet through the adapted raw socket sender.
63    pub fn send_packet(&self, packet: &Packet) -> NetResult<SendReport> {
64        self.sender.send(packet)
65    }
66}
67
68impl From<SocketSender> for RawSocketWriter {
69    fn from(sender: SocketSender) -> Self {
70        Self { sender }
71    }
72}
73
74impl PacketWriter for RawSocketWriter {
75    fn write_record(&mut self, record: &PacketRecord) -> Result<WriteReport> {
76        let send_report = self.send_packet(record.packet())?;
77        Ok(write_report_from_send_report(&send_report))
78    }
79}
80
81fn write_report_from_send_report(report: &SendReport) -> WriteReport {
82    WriteReport::new(
83        BackendKind::RawSocket,
84        report.plan().len(),
85        report.bytes_sent(),
86        report.is_dry_run(),
87    )
88    .with_target_details(send_target_details(report))
89}
90
91fn send_target_details(report: &SendReport) -> String {
92    let plan = report.plan();
93    match plan.target() {
94        SendTarget::LinkLayer { link_type } => format!(
95            "interface={} mode={} target=link-layer:{}",
96            plan.interface(),
97            send_mode_name(plan.requested_mode()),
98            link_type_name(link_type)
99        ),
100        SendTarget::NetworkLayer {
101            network_layer,
102            destination,
103            protocol,
104        } => format!(
105            "interface={} mode={} target=network-layer:{} destination={} protocol={}",
106            plan.interface(),
107            send_mode_name(plan.requested_mode()),
108            network_layer_name(network_layer),
109            destination,
110            protocol
111        ),
112    }
113}
114
115const fn send_mode_name(mode: SendMode) -> &'static str {
116    match mode {
117        SendMode::Auto => "auto",
118        SendMode::LinkLayer => "link-layer",
119        SendMode::NetworkLayer => "network-layer",
120    }
121}
122
123const fn link_type_name(link_type: LinkType) -> &'static str {
124    match link_type {
125        LinkType::Raw => "raw",
126        LinkType::Ethernet => "ethernet",
127        LinkType::Ieee80211 => "ieee80211",
128        LinkType::Radiotap => "radiotap",
129        LinkType::BluetoothLeLl => "bluetooth-le-ll",
130        LinkType::Ieee802154 => "ieee802154",
131        LinkType::Ieee802154Tap => "ieee802154-tap",
132        LinkType::LinuxCooked => "linux-cooked",
133        LinkType::LinuxSll => "linux-sll",
134        LinkType::NullLoopback => "null-loopback",
135    }
136}
137
138const fn network_layer_name(network_layer: NetworkLayer) -> &'static str {
139    match network_layer {
140        NetworkLayer::Raw => "raw",
141        NetworkLayer::Ipv4 => "ipv4",
142        NetworkLayer::Ipv6 => "ipv6",
143    }
144}
145
146#[cfg(test)]
147mod raw_socket_writer {
148    use std::net::{IpAddr, Ipv4Addr};
149
150    use super::*;
151    use crate::net::{NetError, SendOptions, SendTarget};
152    use crate::{
153        Dot11, Ethernet, Ipv4, LlcSnap, MacAddr, Packet, PacketWire, PacketWireTarget, Radiotap,
154        Raw, Udp,
155    };
156    use crate::{PacketRecord, WireError};
157
158    fn ipv4_packet() -> Packet {
159        Ipv4::new()
160            .src(Ipv4Addr::new(192, 0, 2, 10))
161            .dst(Ipv4Addr::new(198, 51, 100, 20))
162            / Udp::new().sport(1111).dport(2222)
163            / Raw::from("hello")
164    }
165
166    fn ethernet_packet() -> Packet {
167        Ethernet::new()
168            .src(MacAddr::new([0x02, 0, 0, 0, 0, 1]))
169            .dst(MacAddr::BROADCAST)
170            / ipv4_packet()
171    }
172
173    fn radiotap_dot11_packet() -> Packet {
174        Radiotap::new() / Dot11::data() / LlcSnap::new() / ipv4_packet()
175    }
176
177    #[test]
178    fn raw_socket_writer_dry_run_maps_socket_send_report() {
179        let packet = ipv4_packet();
180        let expected_len = packet.compile().unwrap().len();
181        let mut writer = RawSocketWriter::dry_run("eth0");
182
183        assert_eq!(writer.options().interface_name(), Some("eth0"));
184        assert!(writer.options().is_dry_run());
185
186        let report = writer
187            .write_record(&PacketRecord::new(packet.clone()))
188            .unwrap();
189
190        assert_eq!(report.backend(), &BackendKind::RawSocket);
191        assert_eq!(report.bytes_requested(), expected_len);
192        assert_eq!(report.bytes_written(), expected_len);
193        assert!(report.is_dry_run());
194
195        let details = report.target_details().unwrap();
196        assert!(details.contains("interface=eth0"));
197        assert!(details.contains("mode=auto"));
198        assert!(details.contains("target=network-layer:ipv4"));
199        assert!(details.contains("destination=198.51.100.20"));
200        assert!(details.contains("protocol=17"));
201    }
202
203    #[test]
204    fn raw_socket_writer_packet_wire_builder_is_write_only_and_preserves_mode() {
205        let wire = PacketWire::raw_socket_interface("eth0")
206            .network_layer()
207            .open()
208            .unwrap();
209
210        assert_eq!(
211            wire.target(),
212            &PacketWireTarget::RawSocketInterface {
213                interface: "eth0".to_string()
214            }
215        );
216        assert_eq!(wire.target().interface(), Some("eth0"));
217        assert!(!wire.has_source());
218        assert!(wire.has_writer());
219
220        let mut writer = wire.writer().unwrap();
221        let report = writer
222            .write_record(&PacketRecord::new(ipv4_packet()))
223            .unwrap();
224
225        assert_eq!(report.backend(), &BackendKind::RawSocket);
226        assert!(report.is_dry_run());
227        assert!(report
228            .target_details()
229            .unwrap()
230            .contains("mode=network-layer"));
231    }
232
233    #[test]
234    fn raw_socket_writer_packet_wire_split_reports_write_only_target() {
235        let result = PacketWire::raw_socket_interface("eth0")
236            .open()
237            .unwrap()
238            .split();
239        let error = match result {
240            Ok(_) => panic!("expected unsupported split error"),
241            Err(error) => error,
242        };
243
244        match error {
245            WireError::UnsupportedCapability {
246                capability,
247                backend: Some(backend),
248                reason,
249            } => {
250                assert_eq!(capability, "split");
251                assert_eq!(backend, "raw-socket:eth0");
252                assert_eq!(
253                    reason,
254                    "raw socket interface targets are write-only; use pcap_interface for capture"
255                );
256            }
257            other => panic!("expected unsupported split error, got {other:?}"),
258        }
259    }
260
261    #[test]
262    fn raw_socket_writer_dry_run_link_layer_report_keeps_target_shape() {
263        let packet = ethernet_packet();
264        let mut writer =
265            RawSocketWriter::new(SendOptions::new().interface("veth0").link_layer().dry_run());
266
267        let report = writer.write_record(&PacketRecord::new(packet)).unwrap();
268
269        assert_eq!(report.backend(), &BackendKind::RawSocket);
270        assert!(report.is_dry_run());
271        assert_eq!(
272            report.target_details(),
273            Some("interface=veth0 mode=link-layer target=link-layer:ethernet")
274        );
275    }
276
277    #[test]
278    fn raw_socket_writer_live_radiotap_routes_to_layer2_via_socket_sender() {
279        let mut writer =
280            RawSocketWriter::new(SendOptions::new().interface("missing-crafter-wifi0").live());
281        let error = writer
282            .write_record(&PacketRecord::new(radiotap_dot11_packet()))
283            .unwrap_err();
284
285        match error {
286            WireError::Net(NetError::InterfaceNotFound { name }) => {
287                assert_eq!(name, "missing-crafter-wifi0");
288            }
289            other => panic!("expected raw socket radiotap missing-interface error, got {other:?}"),
290        }
291    }
292
293    #[test]
294    fn raw_socket_writer_dry_run_reports_ipv4_destination_and_protocol() {
295        let mut writer =
296            RawSocketWriter::new(SendOptions::new().interface("lo").network_layer().dry_run());
297        let report = writer
298            .write_record(&PacketRecord::new(ipv4_packet()))
299            .unwrap();
300
301        assert_eq!(report.backend(), &BackendKind::RawSocket);
302        assert_eq!(report.bytes_requested(), report.bytes_written());
303        assert_eq!(
304            report.target_details(),
305            Some(
306                "interface=lo mode=network-layer target=network-layer:ipv4 destination=198.51.100.20 protocol=17"
307            )
308        );
309
310        let plan = SocketSender::dry_run("lo").plan(&ipv4_packet()).unwrap();
311        match plan.target() {
312            SendTarget::NetworkLayer {
313                destination,
314                protocol,
315                ..
316            } => {
317                assert_eq!(destination, IpAddr::V4(Ipv4Addr::new(198, 51, 100, 20)));
318                assert_eq!(protocol, crate::IPPROTO_UDP);
319            }
320            other => panic!("expected network target, got {other:?}"),
321        }
322    }
323}