socket2_ext/
binds.rs

1use std::borrow::Cow;
2
3use socket2::{Domain, Socket};
4
5/// Options to bind device.
6pub struct BindDeviceOption<'a> {
7    /// Device name.
8    name: Cow<'a, str>,
9    /// Device socket domain.
10    #[allow(dead_code)]
11    domain: Domain,
12}
13
14impl<'a> BindDeviceOption<'a> {
15    pub fn v4(name: &'a str) -> Self {
16        Self {
17            name: Cow::Borrowed(name),
18            domain: Domain::IPV4,
19        }
20    }
21
22    pub fn v6(name: &'a str) -> Self {
23        Self {
24            name: Cow::Borrowed(name),
25            domain: Domain::IPV6,
26        }
27    }
28}
29
30pub trait AddressBinding {
31    /// Bind socket to device with name:
32    ///
33    /// Use name to find the adapter address and bind it on Windows.
34    ///
35    /// Call system builtin socket bind device method on non-Windows.
36    fn bind_to_device(&self, opt: BindDeviceOption) -> std::io::Result<()>;
37}
38
39impl AddressBinding for Socket {
40    #[cfg(not(target_os = "windows"))]
41    #[allow(unreachable_code)]
42    fn bind_to_device(&self, opt: BindDeviceOption) -> std::io::Result<()> {
43        #[cfg(any(
44            target_os = "ios",
45            target_os = "macos",
46            target_os = "tvos",
47            target_os = "watchos"
48        ))]
49        {
50            let ifindex = std::num::NonZeroU32::new(unsafe {
51                libc::if_nametoindex(opt.name.as_ptr() as *const _)
52            });
53            match opt.domain {
54                Domain::IPV4 => self.bind_device_by_index_v4(ifindex)?,
55                Domain::IPV6 => self.bind_device_by_index_v6(ifindex)?,
56                _ => {}
57            }
58            return Ok(());
59        }
60
61        #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
62        {
63            self.bind_device(Some(opt.name.as_bytes()))?;
64            return Ok(());
65        }
66
67        #[cfg(feature = "log")]
68        tracing::warn!(
69            "bind device name [{}] on unsupported OS [{}]",
70            opt.name,
71            std::env::consts::OS,
72        );
73        Ok(())
74    }
75
76    #[cfg(target_os = "windows")]
77    fn bind_to_device(&self, opt: BindDeviceOption) -> std::io::Result<()> {
78        let ip = crate::utils::win_ifname_to_addr(&opt.name)?;
79
80        #[cfg(feature = "log")]
81        tracing::trace!(
82            "bind adapter by name [{}] and ip [{}] on Windows",
83            opt.name,
84            ip,
85        );
86
87        let addr = std::net::SocketAddr::new(ip, 0);
88        self.bind(&socket2::SockAddr::from(addr))?;
89        Ok(())
90    }
91}
92
93#[test]
94fn test_bind_iface() {
95    use crate::binds::{AddressBinding, BindDeviceOption};
96    let iface = "your/interface/name";
97    match socket2::Socket::new(socket2::Domain::IPV4, socket2::Type::DGRAM, None) {
98        Err(e) => println!("create socket error: {:?}", e),
99        Ok(socket) => {
100            if let Err(e) = socket.bind_to_device(BindDeviceOption::v4(iface)) {
101                println!("bind device error: {:?}", e);
102            }
103        }
104    }
105}