1pub mod discovery;
9pub mod socket;
10pub mod stats;
11
12use super::{
13 DiscoveredPeer, PacketBuffer, PacketTx, ReceivedPacket, Transport, TransportAddr,
14 TransportError, TransportId, TransportState, TransportType,
15};
16use crate::config::EthernetConfig;
17use discovery::{
18 DiscoveryBuffer, FRAME_TYPE_BEACON, FRAME_TYPE_DATA, build_scoped_beacon, parse_beacon_record,
19};
20use socket::{AsyncPacketSocket, ETHERNET_BROADCAST, PacketSocket};
21use stats::EthernetStats;
22
23use secp256k1::XOnlyPublicKey;
24use std::sync::Arc;
25use tokio::task::JoinHandle;
26use tracing::{debug, info, trace, warn};
27
28pub struct EthernetTransport {
34 transport_id: TransportId,
36 name: Option<String>,
38 config: EthernetConfig,
40 state: TransportState,
42 socket: Option<Arc<AsyncPacketSocket>>,
44 packet_tx: PacketTx,
46 recv_task: Option<JoinHandle<()>>,
48 beacon_task: Option<JoinHandle<()>>,
50 local_mac: Option<[u8; 6]>,
52 interface: String,
54 effective_mtu: u16,
56 discovery_buffer: Arc<DiscoveryBuffer>,
58 stats: Arc<EthernetStats>,
60 local_pubkey: Option<XOnlyPublicKey>,
62}
63
64impl EthernetTransport {
65 pub fn new(
67 transport_id: TransportId,
68 name: Option<String>,
69 config: EthernetConfig,
70 packet_tx: PacketTx,
71 ) -> Self {
72 let interface = config.interface.clone();
73 let discovery_buffer = Arc::new(DiscoveryBuffer::new(
74 transport_id,
75 config.discovery_scope().map(str::to_string),
76 ));
77 let stats = Arc::new(EthernetStats::new());
78
79 Self {
80 transport_id,
81 name,
82 config,
83 state: TransportState::Configured,
84 socket: None,
85 packet_tx,
86 recv_task: None,
87 beacon_task: None,
88 local_mac: None,
89 interface,
90 effective_mtu: 1497, discovery_buffer,
92 stats,
93 local_pubkey: None,
94 }
95 }
96
97 pub fn name(&self) -> Option<&str> {
99 self.name.as_deref()
100 }
101
102 pub fn interface_name(&self) -> &str {
104 &self.interface
105 }
106
107 pub fn local_mac(&self) -> Option<[u8; 6]> {
109 self.local_mac
110 }
111
112 pub fn set_local_pubkey(&mut self, pubkey: XOnlyPublicKey) {
116 self.local_pubkey = Some(pubkey);
117 }
118
119 pub fn stats(&self) -> &Arc<EthernetStats> {
121 &self.stats
122 }
123
124 pub async fn start_async(&mut self) -> Result<(), TransportError> {
129 if !self.state.can_start() {
130 return Err(TransportError::AlreadyStarted);
131 }
132
133 self.state = TransportState::Starting;
134
135 let raw_socket = PacketSocket::open(&self.config.interface, self.config.ethertype())?;
137
138 let local_mac = raw_socket.local_mac()?;
140 let if_mtu = raw_socket.interface_mtu()?;
141
142 let effective_mtu = if let Some(configured_mtu) = self.config.mtu {
145 configured_mtu.min(if_mtu.saturating_sub(3))
147 } else {
148 if_mtu.saturating_sub(3)
149 };
150 self.effective_mtu = effective_mtu;
151 self.local_mac = Some(local_mac);
152
153 raw_socket.set_recv_buffer_size(self.config.recv_buf_size())?;
155 raw_socket.set_send_buffer_size(self.config.send_buf_size())?;
156
157 let async_socket = raw_socket.into_async()?;
159 let socket = Arc::new(async_socket);
160 self.socket = Some(socket.clone());
161
162 let recv_task = tokio::spawn(ethernet_receive_loop(EthernetReceiveContext {
163 socket: socket.clone(),
164 transport_id: self.transport_id,
165 packet_tx: self.packet_tx.clone(),
166 mtu: self.effective_mtu,
167 discovery_enabled: self.config.discovery(),
168 discovery_buffer: self.discovery_buffer.clone(),
169 stats: self.stats.clone(),
170 local_mac,
171 }));
172 self.recv_task = Some(recv_task);
173
174 if self.config.announce() {
176 if let Some(pubkey) = self.local_pubkey {
177 let beacon_task = tokio::spawn(beacon_sender_loop(EthernetBeaconContext {
178 socket: socket.clone(),
179 pubkey,
180 discovery_scope: self.config.discovery_scope().map(str::to_string),
181 interval_secs: self.config.beacon_interval_secs(),
182 stats: self.stats.clone(),
183 transport_id: self.transport_id,
184 interface: self.config.interface.clone(),
185 ethertype: self.config.ethertype(),
186 }));
187 self.beacon_task = Some(beacon_task);
188 } else {
189 warn!(
190 transport_id = %self.transport_id,
191 "Announce enabled but no local pubkey set; beacons disabled"
192 );
193 }
194 }
195
196 self.state = TransportState::Up;
197
198 if let Some(ref name) = self.name {
199 info!(
200 name = %name,
201 interface = %self.interface,
202 mac = %format_mac(&local_mac),
203 mtu = effective_mtu,
204 if_mtu = if_mtu,
205 "Ethernet transport started"
206 );
207 } else {
208 info!(
209 interface = %self.interface,
210 mac = %format_mac(&local_mac),
211 mtu = effective_mtu,
212 if_mtu = if_mtu,
213 "Ethernet transport started"
214 );
215 }
216
217 Ok(())
218 }
219
220 pub async fn stop_async(&mut self) -> Result<(), TransportError> {
222 if !self.state.is_operational() {
223 return Err(TransportError::NotStarted);
224 }
225
226 if let Some(ref socket) = self.socket {
230 socket.shutdown();
231 }
232
233 if let Some(task) = self.beacon_task.take() {
238 task.abort();
239 #[cfg(not(target_os = "macos"))]
240 {
241 let _ = task.await;
242 }
243 }
244 if let Some(task) = self.recv_task.take() {
245 task.abort();
246 #[cfg(not(target_os = "macos"))]
247 {
248 let _ = task.await;
249 }
250 }
251
252 self.socket.take();
254 self.local_mac = None;
255
256 self.state = TransportState::Down;
257
258 info!(
259 transport_id = %self.transport_id,
260 interface = %self.interface,
261 "Ethernet transport stopped"
262 );
263
264 Ok(())
265 }
266
267 pub async fn send_async(
272 &self,
273 addr: &TransportAddr,
274 data: &[u8],
275 ) -> Result<usize, TransportError> {
276 if !self.state.is_operational() {
277 return Err(TransportError::NotStarted);
278 }
279
280 if data.len() > self.effective_mtu as usize {
281 return Err(TransportError::MtuExceeded {
282 packet_size: data.len(),
283 mtu: self.effective_mtu,
284 });
285 }
286
287 let dest_mac = parse_mac_addr(addr)?;
288 let socket = self.socket.as_ref().ok_or(TransportError::NotStarted)?;
289
290 let mut frame = Vec::with_capacity(3 + data.len());
295 frame.push(FRAME_TYPE_DATA);
296 frame.extend_from_slice(&(data.len() as u16).to_le_bytes());
297 frame.extend_from_slice(data);
298
299 let bytes_sent = socket.send_to(&frame, &dest_mac).await?;
300 self.stats.record_send(bytes_sent);
301
302 trace!(
303 transport_id = %self.transport_id,
304 remote_mac = %format_mac(&dest_mac),
305 bytes = bytes_sent,
306 "Ethernet frame sent"
307 );
308
309 Ok(bytes_sent.saturating_sub(3))
311 }
312}
313
314impl Transport for EthernetTransport {
315 fn transport_id(&self) -> TransportId {
316 self.transport_id
317 }
318
319 fn transport_type(&self) -> &TransportType {
320 &TransportType::ETHERNET
321 }
322
323 fn state(&self) -> TransportState {
324 self.state
325 }
326
327 fn mtu(&self) -> u16 {
328 self.effective_mtu
329 }
330
331 fn start(&mut self) -> Result<(), TransportError> {
332 Err(TransportError::NotSupported(
333 "use start_async() for Ethernet transport".into(),
334 ))
335 }
336
337 fn stop(&mut self) -> Result<(), TransportError> {
338 Err(TransportError::NotSupported(
339 "use stop_async() for Ethernet transport".into(),
340 ))
341 }
342
343 fn send(&self, _addr: &TransportAddr, _data: &[u8]) -> Result<(), TransportError> {
344 Err(TransportError::NotSupported(
345 "use send_async() for Ethernet transport".into(),
346 ))
347 }
348
349 fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError> {
350 Ok(self.discovery_buffer.take())
351 }
352
353 fn auto_connect(&self) -> bool {
354 self.config.auto_connect()
355 }
356
357 fn accept_connections(&self) -> bool {
358 self.config.accept_connections()
359 }
360}
361
362struct EthernetReceiveContext {
367 socket: Arc<AsyncPacketSocket>,
368 transport_id: TransportId,
369 packet_tx: PacketTx,
370 mtu: u16,
371 discovery_enabled: bool,
372 discovery_buffer: Arc<DiscoveryBuffer>,
373 stats: Arc<EthernetStats>,
374 local_mac: [u8; 6],
375}
376
377async fn ethernet_receive_loop(ctx: EthernetReceiveContext) {
379 let EthernetReceiveContext {
380 socket,
381 transport_id,
382 packet_tx,
383 mtu,
384 discovery_enabled,
385 discovery_buffer,
386 stats,
387 local_mac,
388 } = ctx;
389
390 let mut buf = vec![0u8; mtu as usize + 100];
392
393 debug!(transport_id = %transport_id, "Ethernet receive loop starting");
394
395 loop {
396 match socket.recv_from(&mut buf).await {
397 Ok((len, src_mac)) => {
398 if len == 0 {
399 continue;
400 }
401 if src_mac == local_mac {
402 trace!(
403 transport_id = %transport_id,
404 local_mac = %format_mac(&local_mac),
405 "Ignoring self-echoed Ethernet frame"
406 );
407 continue;
408 }
409
410 stats.record_recv(len);
411
412 let frame_type = buf[0];
413 match frame_type {
414 FRAME_TYPE_DATA => {
415 if len < 3 {
418 trace!("Data frame too short ({len} bytes), ignoring");
419 continue;
420 }
421 let payload_len = u16::from_le_bytes([buf[1], buf[2]]) as usize;
422 if payload_len > len - 3 {
423 trace!(
424 "Data frame length field ({payload_len}) exceeds \
425 available bytes ({}), ignoring",
426 len - 3
427 );
428 continue;
429 }
430 let data = buf[3..3 + payload_len].to_vec();
431 let addr = TransportAddr::from_bytes(&src_mac);
432 let packet = ReceivedPacket::with_timestamp(
433 transport_id,
434 addr,
435 PacketBuffer::new(data),
436 crate::time::now_ms(),
437 );
438
439 trace!(
440 transport_id = %transport_id,
441 remote_mac = %format_mac(&src_mac),
442 bytes = payload_len,
443 "Ethernet data frame received"
444 );
445
446 if packet_tx.send(packet).is_err() {
447 debug!(
448 transport_id = %transport_id,
449 "Packet channel closed, stopping receive loop"
450 );
451 break;
452 }
453 }
454 FRAME_TYPE_BEACON => {
455 stats.record_beacon_recv();
456
457 if discovery_enabled && let Some(beacon) = parse_beacon_record(&buf[..len])
458 {
459 discovery_buffer.add_peer(src_mac, beacon);
460 trace!(
461 transport_id = %transport_id,
462 remote_mac = %format_mac(&src_mac),
463 "Discovery beacon received"
464 );
465 }
466 }
467 _ => {
468 trace!(
470 transport_id = %transport_id,
471 frame_type = frame_type,
472 "Unknown frame type, dropping"
473 );
474 }
475 }
476 }
477 Err(e) => {
478 stats.record_recv_error();
479 warn!(
480 transport_id = %transport_id,
481 error = %e,
482 "Ethernet receive error"
483 );
484 }
485 }
486 }
487
488 debug!(transport_id = %transport_id, "Ethernet receive loop stopped");
489}
490
491struct EthernetBeaconContext {
502 socket: Arc<AsyncPacketSocket>,
503 pubkey: XOnlyPublicKey,
504 discovery_scope: Option<String>,
505 interval_secs: u64,
506 stats: Arc<EthernetStats>,
507 transport_id: TransportId,
508 interface: String,
509 ethertype: u16,
510}
511
512async fn beacon_sender_loop(ctx: EthernetBeaconContext) {
513 let EthernetBeaconContext {
514 mut socket,
515 pubkey,
516 discovery_scope,
517 interval_secs,
518 stats,
519 transport_id,
520 interface,
521 ethertype,
522 } = ctx;
523
524 const REOPEN_THRESHOLD: u32 = 3;
526
527 let beacon = build_scoped_beacon(&pubkey, discovery_scope.as_deref());
528 let interval = tokio::time::Duration::from_secs(interval_secs);
529
530 debug!(
531 transport_id = %transport_id,
532 interval_secs,
533 "Beacon sender starting"
534 );
535
536 if let Err(e) = socket.send_to(&beacon, ÐERNET_BROADCAST).await {
538 warn!(
539 transport_id = %transport_id,
540 error = %e,
541 "Failed to send initial beacon"
542 );
543 } else {
544 stats.record_beacon_sent();
545 }
546
547 let mut interval_timer = tokio::time::interval(interval);
548 interval_timer.tick().await; let mut consecutive_errors: u32 = 0;
550
551 loop {
552 interval_timer.tick().await;
553
554 match socket.send_to(&beacon, ÐERNET_BROADCAST).await {
555 Ok(_) => {
556 if consecutive_errors > 0 {
557 debug!(
558 transport_id = %transport_id,
559 "Beacon send recovered after {} errors", consecutive_errors,
560 );
561 }
562 consecutive_errors = 0;
563 stats.record_beacon_sent();
564 trace!(
565 transport_id = %transport_id,
566 "Beacon sent"
567 );
568 }
569 Err(e) => {
570 consecutive_errors += 1;
571 stats.record_send_error();
572
573 let is_enxio = format!("{e}").contains("os error 6");
574
575 if consecutive_errors == 1 {
577 warn!(
578 transport_id = %transport_id,
579 error = %e,
580 "Failed to send beacon"
581 );
582 }
583
584 if is_enxio && consecutive_errors >= REOPEN_THRESHOLD {
585 info!(
586 transport_id = %transport_id,
587 consecutive_errors,
588 interface = %interface,
589 "Stale veth detected (ENXIO), attempting socket reopen"
590 );
591 match reopen_beacon_socket(&interface, ethertype) {
592 Ok(new_socket) => {
593 socket = Arc::new(new_socket);
594 consecutive_errors = 0;
595 info!(
596 transport_id = %transport_id,
597 interface = %interface,
598 "Beacon socket reopened successfully"
599 );
600 }
601 Err(e) => {
602 warn!(
603 transport_id = %transport_id,
604 error = %e,
605 interface = %interface,
606 "Failed to reopen beacon socket, will retry"
607 );
608 }
609 }
610 }
611 }
612 }
613 }
614}
615
616fn reopen_beacon_socket(
621 interface: &str,
622 ethertype: u16,
623) -> Result<AsyncPacketSocket, TransportError> {
624 let raw_socket = PacketSocket::open(interface, ethertype)?;
625 raw_socket.into_async()
626}
627
628fn parse_mac_addr(addr: &TransportAddr) -> Result<[u8; 6], TransportError> {
634 let bytes = addr.as_bytes();
635 if bytes.len() != 6 {
636 return Err(TransportError::InvalidAddress(format!(
637 "expected 6-byte MAC, got {} bytes",
638 bytes.len()
639 )));
640 }
641 if bytes == [0, 0, 0, 0, 0, 0] {
642 return Err(TransportError::InvalidAddress(
643 "destination MAC is all zeros".into(),
644 ));
645 }
646 let mut mac = [0u8; 6];
647 mac.copy_from_slice(bytes);
648 Ok(mac)
649}
650
651pub fn format_mac(mac: &[u8; 6]) -> String {
653 format!(
654 "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
655 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
656 )
657}
658
659pub fn parse_mac_string(s: &str) -> Result<[u8; 6], TransportError> {
661 let parts: Vec<&str> = s.split(':').collect();
662 if parts.len() != 6 {
663 return Err(TransportError::InvalidAddress(format!(
664 "invalid MAC format: expected 6 colon-separated hex bytes, got '{}'",
665 s
666 )));
667 }
668 let mut mac = [0u8; 6];
669 for (i, part) in parts.iter().enumerate() {
670 mac[i] = u8::from_str_radix(part, 16).map_err(|_| {
671 TransportError::InvalidAddress(format!("invalid hex byte '{}' in MAC address", part))
672 })?;
673 }
674 Ok(mac)
675}
676
677#[cfg(test)]
682mod tests {
683 use super::*;
684
685 #[test]
686 fn test_parse_mac_addr_valid() {
687 let addr = TransportAddr::from_bytes(&[0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
688 let mac = parse_mac_addr(&addr).unwrap();
689 assert_eq!(mac, [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
690 }
691
692 #[test]
693 fn test_parse_mac_addr_wrong_length() {
694 let addr = TransportAddr::from_bytes(&[0xaa, 0xbb, 0xcc]);
695 assert!(parse_mac_addr(&addr).is_err());
696
697 let addr = TransportAddr::from_string("192.168.1.1:2121");
698 assert!(parse_mac_addr(&addr).is_err());
699 }
700
701 #[test]
702 fn test_parse_mac_addr_all_zeros() {
703 let addr = TransportAddr::from_bytes(&[0, 0, 0, 0, 0, 0]);
704 assert!(parse_mac_addr(&addr).is_err());
705 }
706
707 #[test]
708 fn test_format_mac() {
709 let mac = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff];
710 assert_eq!(format_mac(&mac), "aa:bb:cc:dd:ee:ff");
711 }
712
713 #[test]
714 fn test_format_mac_leading_zeros() {
715 let mac = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
716 assert_eq!(format_mac(&mac), "01:02:03:04:05:06");
717 }
718
719 #[test]
720 fn test_parse_mac_string_valid() {
721 let mac = parse_mac_string("aa:bb:cc:dd:ee:ff").unwrap();
722 assert_eq!(mac, [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
723 }
724
725 #[test]
726 fn test_parse_mac_string_uppercase() {
727 let mac = parse_mac_string("AA:BB:CC:DD:EE:FF").unwrap();
728 assert_eq!(mac, [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
729 }
730
731 #[test]
732 fn test_parse_mac_string_invalid() {
733 assert!(parse_mac_string("aa:bb:cc").is_err());
734 assert!(parse_mac_string("not:a:mac:at:all:x").is_err());
735 assert!(parse_mac_string("").is_err());
736 assert!(parse_mac_string("aa-bb-cc-dd-ee-ff").is_err());
737 }
738
739 #[test]
740 fn test_frame_type_data_prefix() {
741 let data = vec![1, 2, 3, 4];
743 let mut frame = Vec::with_capacity(3 + data.len());
744 frame.push(FRAME_TYPE_DATA);
745 frame.extend_from_slice(&(data.len() as u16).to_le_bytes());
746 frame.extend_from_slice(&data);
747
748 assert_eq!(frame[0], 0x00); assert_eq!(u16::from_le_bytes([frame[1], frame[2]]), 4); assert_eq!(&frame[3..], &[1, 2, 3, 4]); }
752
753 #[test]
754 fn test_data_frame_padding_trimmed() {
755 let payload = vec![0xAA, 0xBB, 0xCC, 0xDD];
758 let payload_len = payload.len() as u16;
759
760 let mut frame = Vec::with_capacity(3 + payload.len());
762 frame.push(FRAME_TYPE_DATA);
763 frame.extend_from_slice(&payload_len.to_le_bytes());
764 frame.extend_from_slice(&payload);
765
766 frame.resize(46, 0x00);
768
769 let recv_len = u16::from_le_bytes([frame[1], frame[2]]) as usize;
771 let extracted = &frame[3..3 + recv_len];
772 assert_eq!(extracted, &[0xAA, 0xBB, 0xCC, 0xDD]);
773 }
774
775 #[test]
776 fn test_beacon_size() {
777 assert_eq!(discovery::BEACON_SIZE, 34);
778 }
779}