1use std::collections::VecDeque;
10#[cfg(any(target_os = "linux", target_os = "android"))]
11use std::fs;
12use std::io;
13use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket as StdUdpSocket};
14use std::sync::Mutex;
15
16use bytes::BytesMut;
17use if_addrs::IfAddr;
18use socket2::{Domain, Protocol, SockRef, Socket, Type};
19use tokio::net::UdpSocket;
20use tracing::{debug, info};
21
22#[cfg(any(target_os = "linux", target_os = "android", windows))]
23use tracing::warn;
24
25pub const DEFAULT_RCVBUF_BYTES: usize = 4 << 20; #[cfg(not(target_os = "windows"))]
35const IFACE_NAME_MAX: usize = 15;
36#[cfg(target_os = "windows")]
37const IFACE_NAME_MAX: usize = 64;
38
39#[cfg(any(target_os = "linux", target_os = "android"))]
41fn iface_name_to_index(name: &str) -> io::Result<u32> {
42 let index_path = format!("/sys/class/net/{name}/ifindex");
43 fs::read_to_string(&index_path)
44 .map_err(|err| io::Error::new(err.kind(), format!("{index_path}: {err}")))?
45 .trim()
46 .parse::<u32>()
47 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
48}
49
50#[cfg(all(unix, not(any(target_os = "linux", target_os = "android"))))]
52fn iface_name_to_index(name: &str) -> io::Result<u32> {
53 use std::ffi::CString;
54
55 let c_name = CString::new(name).map_err(|_| {
56 io::Error::new(
57 io::ErrorKind::InvalidInput,
58 format!("interface name contains null byte: '{name}'"),
59 )
60 })?;
61 let index = unsafe { libc::if_nametoindex(c_name.as_ptr()) };
63 if index == 0 {
64 Err(io::Error::new(
65 io::ErrorKind::NotFound,
66 format!("interface '{name}' not found"),
67 ))
68 } else {
69 Ok(index)
70 }
71}
72
73#[cfg(target_os = "windows")]
75fn iface_name_to_index(name: &str) -> io::Result<u32> {
76 for (idx, iface) in if_addrs::get_if_addrs()?.iter().enumerate() {
81 if iface.name == name {
82 return Ok((idx + 1) as u32);
83 }
84 }
85 Err(io::Error::new(
86 io::ErrorKind::NotFound,
87 format!("interface '{name}' not found"),
88 ))
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct Iface {
94 name: String,
95 index: u32,
96 ipv4: Option<Ipv4Addr>,
97 ipv6: Option<Ipv6Addr>,
98}
99
100impl Iface {
101 pub fn from_system(name: &str) -> io::Result<Self> {
106 Self::resolve(name, None)
107 }
108
109 pub fn from_ipv4(addr: Ipv4Addr) -> io::Result<Self> {
117 for iface in if_addrs::get_if_addrs()? {
118 if let IfAddr::V4(v4) = iface.addr
119 && v4.ip == addr
120 {
121 return Self::resolve(&iface.name, Some(addr));
122 }
123 }
124 Err(io::Error::new(
125 io::ErrorKind::NotFound,
126 format!("no interface with IPv4 {addr}"),
127 ))
128 }
129
130 pub fn from_remote_ipv4(remote: Ipv4Addr) -> io::Result<Self> {
133 const GVCP_PORT: u16 = 3956;
134
135 let socket = StdUdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0)).map_err(|err| {
136 io::Error::new(
137 err.kind(),
138 format!("failed to create route probe for remote IPv4 {remote}: {err}"),
139 )
140 })?;
141 socket.connect((remote, GVCP_PORT)).map_err(|err| {
142 io::Error::new(
143 err.kind(),
144 format!("failed to resolve route to remote IPv4 {remote}: {err}"),
145 )
146 })?;
147 let local = match socket.local_addr()?.ip() {
148 IpAddr::V4(local) => local,
149 IpAddr::V6(_) => {
150 return Err(io::Error::new(
151 io::ErrorKind::AddrNotAvailable,
152 format!("route to remote IPv4 {remote} selected an IPv6 address"),
153 ));
154 }
155 };
156
157 Self::from_ipv4(local).map_err(|err| {
158 io::Error::new(
159 err.kind(),
160 format!(
161 "route to remote IPv4 {remote} selected local IPv4 {local}, \
162 but its interface could not be resolved: {err}"
163 ),
164 )
165 })
166 }
167
168 pub fn list() -> io::Result<Vec<Self>> {
175 let mut names: Vec<String> = if_addrs::get_if_addrs()?
176 .into_iter()
177 .map(|iface| iface.name)
178 .collect();
179 names.sort();
180 names.dedup();
181 Ok(names
182 .into_iter()
183 .filter_map(|name| Self::from_system(&name).ok())
184 .collect())
185 }
186
187 pub fn all_ipv4(&self) -> io::Result<Vec<Ipv4Addr>> {
193 Ok(if_addrs::get_if_addrs()?
194 .into_iter()
195 .filter(|iface| iface.name == self.name)
196 .filter_map(|iface| match iface.addr {
197 IfAddr::V4(v4) => Some(v4.ip),
198 IfAddr::V6(_) => None,
199 })
200 .collect())
201 }
202
203 fn resolve(name: &str, preferred: Option<Ipv4Addr>) -> io::Result<Self> {
204 if name.is_empty() || name.len() > IFACE_NAME_MAX {
205 return Err(io::Error::new(
206 io::ErrorKind::InvalidInput,
207 format!("invalid interface name '{name}'"),
208 ));
209 }
210
211 let mut ipv4: Option<Ipv4Addr> = None;
212 let mut ipv6 = None;
213 let mut index: Option<u32> = None;
216 let mut found = false;
217 for iface in if_addrs::get_if_addrs()? {
218 if iface.name != name {
219 continue;
220 }
221 found = true;
222 index = index.or(iface.index);
223 match iface.addr {
224 IfAddr::V4(v4) => {
225 if Some(v4.ip) == preferred || ipv4.is_none() {
228 ipv4 = Some(v4.ip);
229 }
230 }
231 IfAddr::V6(v6) => {
232 if ipv6.is_none() {
233 ipv6 = Some(v6.ip);
234 }
235 }
236 }
237 }
238
239 let index = match index {
241 Some(index) => index,
242 None => iface_name_to_index(name)?,
243 };
244 if !found && ipv4.is_none() && ipv6.is_none() {
245 debug!(name, "interface has no assigned IP address");
249 }
250
251 Ok(Self {
252 name: name.to_string(),
253 index,
254 ipv4,
255 ipv6,
256 })
257 }
258
259 pub fn name(&self) -> &str {
261 &self.name
262 }
263
264 pub fn index(&self) -> u32 {
267 self.index
268 }
269
270 pub fn ipv4(&self) -> Option<Ipv4Addr> {
272 self.ipv4
273 }
274
275 #[allow(dead_code)]
277 pub fn ipv6(&self) -> Option<Ipv6Addr> {
278 self.ipv6
279 }
280}
281
282#[derive(Debug, Clone, PartialEq, Eq)]
296pub enum IfaceSelector {
297 Ipv4(Ipv4Addr),
299 Name(String),
301}
302
303impl IfaceSelector {
304 pub fn resolve(&self) -> io::Result<Iface> {
313 let resolved = match self {
314 Self::Ipv4(addr) => Iface::from_ipv4(*addr),
315 Self::Name(name) => Iface::from_system(name),
316 };
317 resolved.map_err(|err| io::Error::new(err.kind(), format!("{err}{}", available())))
318 }
319}
320
321impl std::str::FromStr for IfaceSelector {
322 type Err = std::convert::Infallible;
323
324 fn from_str(s: &str) -> Result<Self, Self::Err> {
325 Ok(match s.parse::<Ipv4Addr>() {
326 Ok(addr) => Self::Ipv4(addr),
327 Err(_) => Self::Name(s.to_string()),
328 })
329 }
330}
331
332impl std::fmt::Display for IfaceSelector {
333 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
334 match self {
335 Self::Ipv4(addr) => write!(f, "{addr}"),
336 Self::Name(name) => f.write_str(name),
337 }
338 }
339}
340
341fn available() -> String {
346 let Ok(ifaces) = Iface::list() else {
347 return String::new();
348 };
349 if ifaces.is_empty() {
350 return String::new();
351 }
352 let mut out = String::from("\n interfaces this host reports:");
353 for iface in ifaces {
354 let addrs = iface.all_ipv4().unwrap_or_default();
355 out.push_str(&format!("\n {}", iface.name()));
356 if !addrs.is_empty() {
357 let list: Vec<String> = addrs.iter().map(|a| a.to_string()).collect();
358 out.push_str(&format!(" {}", list.join(", ")));
359 }
360 }
361 out
362}
363
364pub fn mtu(_iface: &Iface) -> io::Result<u32> {
370 #[cfg(target_os = "linux")]
371 {
372 let path = format!("/sys/class/net/{}/mtu", _iface.name());
373 match fs::read_to_string(path) {
374 Ok(contents) => {
375 let mtu = contents
376 .trim()
377 .parse::<u32>()
378 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
379 tracing::debug!(name = _iface.name(), mtu, "resolved interface MTU");
380 return Ok(mtu);
381 }
382 Err(err) => {
383 warn!(name = _iface.name(), error = %err, "failed to read MTU, using default");
384 }
385 }
386 }
387
388 #[cfg(windows)]
389 {
390 use windows_sys::Win32::NetworkManagement::IpHelper::{GetIfEntry2, MIB_IF_ROW2};
391
392 let mut row = MIB_IF_ROW2 {
393 InterfaceIndex: _iface.index(),
394 ..Default::default()
395 };
396 let status = unsafe { GetIfEntry2(&mut row) };
397 if status == 0 {
398 tracing::debug!(
399 name = _iface.name(),
400 mtu = row.Mtu,
401 "resolved interface MTU"
402 );
403 return Ok(row.Mtu);
404 }
405
406 let err = io::Error::from_raw_os_error(status as i32);
407 warn!(
408 name = _iface.name(),
409 error = %err,
410 "failed to read MTU, using default"
411 );
412 }
413
414 Ok(1500)
415}
416
417const MAX_IPV4_PACKET_SIZE: u32 = 65535;
423
424pub fn best_packet_size(mtu: u32) -> u32 {
438 mtu.min(MAX_IPV4_PACKET_SIZE)
439}
440
441#[derive(Debug, Clone)]
443pub struct McOptions {
444 pub loopback: bool,
446 pub ttl: u32,
448 pub rcvbuf_bytes: usize,
450 pub reuse_addr: bool,
452}
453
454impl Default for McOptions {
455 fn default() -> Self {
456 Self {
457 loopback: false,
458 ttl: 1,
459 rcvbuf_bytes: DEFAULT_RCVBUF_BYTES,
460 reuse_addr: true,
461 }
462 }
463}
464
465pub async fn bind_udp(
467 bind: IpAddr,
468 port: u16,
469 iface: Option<Iface>,
470 recv_buffer: Option<usize>,
471) -> io::Result<UdpSocket> {
472 let recv_buffer = recv_buffer.unwrap_or(DEFAULT_RCVBUF_BYTES);
473 if let Some(ipv4) = iface.as_ref().and_then(|iface| iface.ipv4()) {
474 info!(name = iface.as_ref().map(Iface::name), %ipv4, port, "binding GVSP socket");
475 } else {
476 info!(%bind, port, "binding GVSP socket");
477 }
478
479 let domain = match bind {
480 IpAddr::V4(_) => Domain::IPV4,
481 IpAddr::V6(_) => Domain::IPV6,
482 };
483 let socket = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP))?;
484
485 socket.set_reuse_address(true)?;
486 #[cfg(all(unix, not(target_os = "solaris")))]
487 socket.set_reuse_port(true)?;
488
489 socket.set_recv_buffer_size(recv_buffer)?;
490 let actual_recv_buffer = socket.recv_buffer_size()?;
491 debug!(
492 requested_recv_buffer = recv_buffer,
493 actual_recv_buffer, "configured GVSP receive buffer"
494 );
495
496 #[cfg(any(target_os = "linux", target_os = "android"))]
497 if let Some(iface) = iface.as_ref()
498 && let Err(err) = socket.bind_device(Some(iface.name().as_bytes()))
499 {
500 warn!(name = iface.name(), error = %err, "SO_BINDTODEVICE failed");
501 }
502
503 let addr = SocketAddr::new(bind, port);
504 socket.bind(&addr.into())?;
505
506 let std_socket: std::net::UdpSocket = socket.into();
507 std_socket.set_nonblocking(true)?;
508 UdpSocket::from_std(std_socket)
509}
510
511fn validate_multicast_inputs(group: Ipv4Addr, ttl: u32) -> io::Result<()> {
512 if ttl > 255 {
513 return Err(io::Error::new(
514 io::ErrorKind::InvalidInput,
515 "multicast TTL must be <= 255",
516 ));
517 }
518 if (group.octets()[0] & 0xF0) != 0xE0 {
519 return Err(io::Error::new(
520 io::ErrorKind::InvalidInput,
521 "multicast group must be within 224.0.0.0/4",
522 ));
523 }
524 Ok(())
525}
526
527pub async fn bind_multicast(
529 iface: &Iface,
530 group: Ipv4Addr,
531 port: u16,
532 opts: &McOptions,
533) -> io::Result<UdpSocket> {
534 validate_multicast_inputs(group, opts.ttl)?;
535 let iface_addr = iface
536 .ipv4()
537 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "interface lacks IPv4"))?;
538
539 info!(name = iface.name(), %group, port, "binding multicast GVSP socket");
540
541 let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?;
542
543 if opts.reuse_addr {
544 socket.set_reuse_address(true)?;
545 #[cfg(all(unix, not(target_os = "solaris")))]
546 socket.set_reuse_port(true)?;
547 }
548
549 socket.set_recv_buffer_size(opts.rcvbuf_bytes)?;
550 socket.set_multicast_loop_v4(opts.loopback)?;
551 socket.set_multicast_ttl_v4(opts.ttl)?;
552 socket.set_multicast_if_v4(&iface_addr)?;
553
554 #[cfg(any(target_os = "linux", target_os = "android"))]
555 if let Err(err) = socket.bind_device(Some(iface.name().as_bytes())) {
556 warn!(name = iface.name(), error = %err, "SO_BINDTODEVICE failed");
557 }
558
559 let bind_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port);
560 socket.bind(&bind_addr.into())?;
561 socket.join_multicast_v4(&group, &iface_addr)?;
562
563 let std_socket: std::net::UdpSocket = socket.into();
564 std_socket.set_nonblocking(true)?;
565 UdpSocket::from_std(std_socket)
566}
567
568pub fn join_multicast(sock: &UdpSocket, group: Ipv4Addr, iface: &Iface) -> io::Result<()> {
570 let socket = SockRef::from(sock);
571 let iface_addr = iface.ipv4().unwrap_or(Ipv4Addr::UNSPECIFIED);
572 socket.join_multicast_v4(&group, &iface_addr)?;
573 Ok(())
574}
575
576#[derive(Debug)]
578pub struct BufferPool {
579 buffers: Mutex<VecDeque<BytesMut>>,
580 size: usize,
581}
582
583impl BufferPool {
584 pub fn new(capacity: usize, size: usize) -> Self {
586 let mut buffers = VecDeque::with_capacity(capacity);
587 for _ in 0..capacity {
588 buffers.push_back(BytesMut::with_capacity(size));
589 }
590 Self {
591 buffers: Mutex::new(buffers),
592 size,
593 }
594 }
595
596 pub fn acquire(&self) -> Option<BytesMut> {
598 self.buffers
599 .lock()
600 .ok()
601 .and_then(|mut guard| guard.pop_front())
602 }
603
604 pub fn release(&self, mut buffer: BytesMut) {
606 buffer.truncate(0);
607 buffer.reserve(self.size);
608 if let Ok(mut guard) = self.buffers.lock() {
609 guard.push_back(buffer);
610 }
611 }
612}
613
614pub fn default_bind_addr() -> IpAddr {
616 IpAddr::V4(Ipv4Addr::UNSPECIFIED)
617}
618
619#[cfg(test)]
620mod tests {
621 use super::*;
622
623 #[test]
624 fn reject_invalid_ttl() {
625 let err = validate_multicast_inputs(Ipv4Addr::new(239, 0, 0, 1), 512).unwrap_err();
626 assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
627 }
628
629 #[test]
630 fn reject_non_multicast_group() {
631 let err = validate_multicast_inputs(Ipv4Addr::new(192, 168, 1, 1), 1).unwrap_err();
632 assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
633 }
634
635 #[test]
636 fn accept_valid_group() {
637 assert!(validate_multicast_inputs(Ipv4Addr::new(239, 192, 1, 10), 1).is_ok());
638 }
639
640 #[test]
641 #[cfg(not(target_os = "windows"))]
642 fn from_system_loopback() {
643 let lo_name = if cfg!(target_os = "macos") {
644 "lo0"
645 } else {
646 "lo"
647 };
648 let iface = Iface::from_system(lo_name).expect("loopback interface should exist");
649 assert!(iface.ipv4().unwrap().is_loopback());
650 }
651
652 #[test]
653 fn from_remote_ipv4_uses_route_selected_local_address() {
654 let remote = Ipv4Addr::new(127, 0, 0, 2);
655 let iface =
656 Iface::from_remote_ipv4(remote).expect("loopback route should select an interface");
657
658 assert_eq!(iface.ipv4(), Some(Ipv4Addr::LOCALHOST));
659 assert_ne!(iface.ipv4(), Some(remote));
660 }
661
662 #[test]
663 fn packet_size_matches_link_mtu() {
664 let mtu = 1500;
665 let size = best_packet_size(mtu);
666 assert_eq!(size, mtu);
667 }
668
669 #[test]
670 fn packet_size_is_clamped_to_the_ipv4_maximum() {
671 assert_eq!(best_packet_size(65536), MAX_IPV4_PACKET_SIZE);
675 assert_eq!(best_packet_size(u32::MAX), MAX_IPV4_PACKET_SIZE);
676
677 const UDP_MAX_PAYLOAD: u32 = 65535 - 20 - 8;
679 let gvsp_payload = best_packet_size(65536) - 20 - 8 - 8;
680 assert!(gvsp_payload + 8 <= UDP_MAX_PAYLOAD);
681 }
682
683 #[test]
684 fn selector_parses_an_ipv4_literal_as_an_address_and_anything_else_as_a_name() {
685 use std::str::FromStr;
686
687 assert_eq!(
688 IfaceSelector::from_str("169.254.105.106").unwrap(),
689 IfaceSelector::Ipv4(Ipv4Addr::new(169, 254, 105, 106))
690 );
691 assert_eq!(
692 IfaceSelector::from_str("en0").unwrap(),
693 IfaceSelector::Name("en0".into())
694 );
695 let guid = "{6394C55F-F630-4BC7-92D2-7AC320C73D1C}";
697 assert_eq!(
698 IfaceSelector::from_str(guid).unwrap(),
699 IfaceSelector::Name(guid.into())
700 );
701 assert_eq!(
704 IfaceSelector::from_str("").unwrap(),
705 IfaceSelector::Name(String::new())
706 );
707 assert_eq!(
710 IfaceSelector::from_str("169.254").unwrap(),
711 IfaceSelector::Name("169.254".into())
712 );
713 }
714
715 #[test]
716 #[cfg(not(target_os = "windows"))]
717 fn both_spellings_of_loopback_resolve_to_the_same_interface() {
718 use std::str::FromStr;
719
720 let lo_name = if cfg!(target_os = "macos") {
721 "lo0"
722 } else {
723 "lo"
724 };
725 let by_name = IfaceSelector::from_str(lo_name).unwrap().resolve().unwrap();
726 let by_addr = IfaceSelector::from_str("127.0.0.1")
727 .unwrap()
728 .resolve()
729 .unwrap();
730
731 assert_eq!(by_name.name(), by_addr.name());
732 assert_eq!(by_addr.ipv4(), Some(Ipv4Addr::LOCALHOST));
733 }
734
735 #[test]
736 fn an_unresolvable_selector_names_the_interfaces_we_can_see() {
737 use std::str::FromStr;
738
739 let err = IfaceSelector::from_str("no-such-iface")
743 .unwrap()
744 .resolve()
745 .expect_err("a name no host has must not resolve");
746 let msg = err.to_string();
747 assert!(
748 msg.contains("interfaces this host reports:"),
749 "expected the interface listing, got: {msg}"
750 );
751 assert!(
752 Iface::list()
753 .unwrap()
754 .iter()
755 .any(|iface| msg.contains(iface.name())),
756 "expected a real interface name in: {msg}"
757 );
758 }
759
760 #[test]
761 fn buffer_pool_recycles() {
762 let pool = BufferPool::new(2, 1024);
763 let mut buf = pool.acquire().expect("buffer");
764 buf.extend_from_slice(&[1, 2, 3]);
765 pool.release(buf);
766 let buf2 = pool.acquire().expect("buffer");
767 assert!(buf2.is_empty());
768 assert!(buf2.capacity() >= 1024);
769 }
770}