1use 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#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct RawSocketWriter {
21 sender: SocketSender,
22}
23
24impl RawSocketWriter {
25 pub fn new(options: impl Into<SendOptions>) -> Self {
32 Self {
33 sender: SocketSender::new(options),
34 }
35 }
36
37 pub fn dry_run(interface: impl Into<String>) -> Self {
39 Self::new(SendOptions::new().interface(interface).dry_run())
40 }
41
42 pub fn live(interface: impl Into<String>) -> Self {
44 Self::new(SendOptions::new().interface(interface).live())
45 }
46
47 pub const fn sender(&self) -> &SocketSender {
49 &self.sender
50 }
51
52 pub const fn options(&self) -> &SendOptions {
54 self.sender.options()
55 }
56
57 pub fn plan_packet(&self, packet: &Packet) -> NetResult<SendPlan> {
59 self.sender.plan(packet)
60 }
61
62 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}