|
1 | 1 | #![cfg(any(target_os = "linux", target_os = "macos"))] |
2 | 2 |
|
3 | | -#[cfg(target_os = "linux")] |
4 | | -use std::ffi::c_ulong; |
5 | | -use std::{ffi::c_uint, io, os::fd::AsRawFd}; |
| 3 | +use std::ffi::c_uint; |
| 4 | +use std::io; |
| 5 | +use std::mem; |
| 6 | +use std::os::fd::AsRawFd; |
| 7 | +use std::ptr; |
6 | 8 |
|
7 | | -use nix::{errno::Errno, net::if_::if_nametoindex}; |
8 | | -use socket2::Domain; |
| 9 | +use nix::errno::Errno; |
| 10 | +use nix::libc::ifreq; |
| 11 | +use nix::net::if_::if_nametoindex; |
| 12 | +use socket2::{Domain, Protocol, Socket, Type}; |
9 | 13 | use talpid_types::ErrorExt; |
10 | 14 |
|
11 | | -/// Converts an interface name into the corresponding index. |
12 | | -pub fn iface_index(name: &str) -> Result<c_uint, IfaceIndexLookupError> { |
13 | | - if_nametoindex(name).map_err(|error| IfaceIndexLookupError { |
14 | | - interface_name: name.to_owned(), |
15 | | - error, |
16 | | - }) |
17 | | -} |
18 | | - |
19 | 15 | #[derive(Debug, thiserror::Error)] |
20 | 16 | #[error("Failed to get index for interface {interface_name}: {error}")] |
21 | 17 | pub struct IfaceIndexLookupError { |
22 | 18 | pub interface_name: String, |
23 | 19 | pub error: Errno, |
24 | 20 | } |
25 | 21 |
|
26 | | -#[cfg(target_os = "macos")] |
27 | | -const SIOCSIFMTU: u64 = 0x80206934; |
28 | | -#[cfg(target_os = "macos")] |
29 | | -const SIOCGIFMTU: u64 = 0xc0206933; |
30 | | -#[cfg(target_os = "linux")] |
31 | | -const SIOCSIFMTU: c_ulong = libc::SIOCSIFMTU; |
32 | | -#[cfg(target_os = "linux")] |
33 | | -const SIOCGIFMTU: c_ulong = libc::SIOCSIFMTU; |
| 22 | +/// Converts an interface name into the corresponding index. |
| 23 | +pub fn iface_index(name: &str) -> Result<c_uint, IfaceIndexLookupError> { |
| 24 | + if_nametoindex(name).map_err(|error| IfaceIndexLookupError { |
| 25 | + interface_name: name.to_owned(), |
| 26 | + error, |
| 27 | + }) |
| 28 | +} |
34 | 29 |
|
35 | 30 | pub fn set_mtu(interface_name: &str, mtu: u16) -> Result<(), io::Error> { |
36 | | - let sock = socket2::Socket::new( |
37 | | - Domain::IPV4, |
38 | | - socket2::Type::STREAM, |
39 | | - Some(socket2::Protocol::TCP), |
40 | | - )?; |
41 | | - |
42 | | - // SAFETY: ifreq is a C struct, these can safely be zeroed. |
43 | | - let mut ifr: libc::ifreq = unsafe { std::mem::zeroed() }; |
44 | | - if interface_name.len() >= ifr.ifr_name.len() { |
45 | | - return Err(io::Error::new( |
46 | | - io::ErrorKind::InvalidInput, |
47 | | - "Interface name too long", |
48 | | - )); |
49 | | - } |
50 | | - |
51 | | - // SAFETY: `interface_name.len()` is less than `ifr.ifr_name.len()` |
52 | | - unsafe { |
53 | | - std::ptr::copy_nonoverlapping( |
54 | | - interface_name.as_ptr() as *const libc::c_char, |
55 | | - ifr.ifr_name.as_mut_ptr(), |
56 | | - interface_name.len(), |
57 | | - ) |
58 | | - }; |
59 | | - ifr.ifr_ifru.ifru_mtu = mtu as i32; |
60 | | - |
61 | | - // For some reason, libc crate defines ioctl to take a c_int (which is defined as i32), but the c_ulong type is defined as u64: |
62 | | - // https://docs.rs/libc/latest/x86_64-unknown-linux-musl/libc/fn.ioctl.html |
63 | | - // https://docs.rs/libc/latest/x86_64-unknown-linux-musl/libc/type.c_ulong.html |
64 | | - // https://docs.rs/libc/latest/x86_64-unknown-linux-musl/libc/constant.SIOCSIFMTU.html |
65 | | - #[allow(clippy::useless_conversion)] |
66 | | - let request = SIOCSIFMTU.try_into().unwrap(); |
67 | | - // SAFETY: SIOCSIFMTU expects an ifreq with an MTU and interface set |
68 | | - if unsafe { libc::ioctl(sock.as_raw_fd(), request, &ifr) } < 0 { |
69 | | - let e = std::io::Error::last_os_error(); |
| 31 | + IfReq::new(interface_name)?.set_mtu(mtu).inspect_err(|e| { |
70 | 32 | log::error!("{}", e.display_chain_with_msg("SIOCSIFMTU failed")); |
71 | | - return Err(e); |
72 | | - } |
73 | | - Ok(()) |
| 33 | + }) |
74 | 34 | } |
75 | 35 |
|
76 | 36 | pub fn get_mtu(interface_name: &str) -> Result<u16, io::Error> { |
77 | | - let sock = socket2::Socket::new( |
78 | | - Domain::IPV4, |
79 | | - socket2::Type::STREAM, |
80 | | - Some(socket2::Protocol::TCP), |
81 | | - )?; |
| 37 | + IfReq::new(interface_name)?.get_mtu().inspect_err(|e| { |
| 38 | + log::error!("{}", e.display_chain_with_msg("SIOCGIFMTU failed")); |
| 39 | + }) |
| 40 | +} |
82 | 41 |
|
83 | | - // SAFETY: ifreq is a C struct, these can safely be zeroed. |
84 | | - let mut ifr: libc::ifreq = unsafe { std::mem::zeroed() }; |
85 | | - if interface_name.len() >= ifr.ifr_name.len() { |
86 | | - return Err(io::Error::new( |
87 | | - io::ErrorKind::InvalidInput, |
88 | | - "Interface name too long", |
89 | | - )); |
| 42 | +/// An [`ifreq`] initialized with an interface name. |
| 43 | +struct IfReq { |
| 44 | + interface_request: ifreq, |
| 45 | + socket: Socket, |
| 46 | +} |
| 47 | + |
| 48 | +impl IfReq { |
| 49 | + /// Returns an [`ifreq`] refering to `interface`. |
| 50 | + /// |
| 51 | + /// - `interface`: Name of the interface (e.g. `eth0`). |
| 52 | + fn new(interface: &str) -> Result<Self, io::Error> { |
| 53 | + let invalid_input = |msg| io::Error::new(io::ErrorKind::InvalidInput, msg); |
| 54 | + if !interface.is_ascii() { |
| 55 | + return Err(invalid_input("Interface name contains UTF-8")); |
| 56 | + }; |
| 57 | + let interface_name = interface.as_bytes(); |
| 58 | + // `ifreq.ifr_name` may only contain max IF_NAMESIZE ASCII characters, including a trailing |
| 59 | + // null terminator. |
| 60 | + if interface_name.len() >= nix::libc::IF_NAMESIZE { |
| 61 | + return Err(invalid_input("Interface name too long")); |
| 62 | + }; |
| 63 | + // SAFETY: ifreq is a C struct, these can safely be zeroed. |
| 64 | + let mut ifr: ifreq = unsafe { mem::zeroed() }; |
| 65 | + // SAFETY: `interface_name.len()` does not exceed IF_NAMESIZE (+ a trailing null terminator) and `interface_name` only |
| 66 | + // contains ASCII. |
| 67 | + unsafe { |
| 68 | + ptr::copy_nonoverlapping( |
| 69 | + interface_name.as_ptr().cast::<libc::c_char>(), |
| 70 | + ifr.ifr_name.as_mut_ptr(), |
| 71 | + interface_name.len(), |
| 72 | + ) |
| 73 | + }; |
| 74 | + let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP))?; |
| 75 | + Ok(Self { |
| 76 | + interface_request: ifr, |
| 77 | + socket, |
| 78 | + }) |
90 | 79 | } |
91 | 80 |
|
92 | | - // SAFETY: `interface_name.len()` is less than `ifr.ifr_name.len()` |
93 | | - unsafe { |
94 | | - std::ptr::copy_nonoverlapping( |
95 | | - interface_name.as_ptr() as *const libc::c_char, |
96 | | - ifr.ifr_name.as_mut_ptr(), |
97 | | - interface_name.len(), |
98 | | - ) |
99 | | - }; |
| 81 | + /// Set MTU for this interface. |
| 82 | + // SIOCSIFMTU in a trenchcoat. |
| 83 | + fn set_mtu(mut self, mtu: u16) -> Result<(), io::Error> { |
| 84 | + self.interface_request.ifr_ifru.ifru_mtu = i32::from(mtu); |
| 85 | + let socket = self.socket.as_raw_fd(); |
| 86 | + #[cfg(target_os = "macos")] |
| 87 | + const SIOCSIFMTU: u64 = 0x80206934; |
| 88 | + #[cfg(target_os = "linux")] |
| 89 | + const SIOCSIFMTU: libc::c_ulong = libc::SIOCSIFMTU; |
| 90 | + // For some reason, libc crate defines ioctl to take a c_int (which is defined as i32), but the c_ulong type is defined as u64: |
| 91 | + // https://docs.rs/libc/latest/x86_64-unknown-linux-musl/libc/fn.ioctl.html |
| 92 | + // https://docs.rs/libc/latest/x86_64-unknown-linux-musl/libc/type.c_ulong.html |
| 93 | + // https://docs.rs/libc/latest/x86_64-unknown-linux-musl/libc/constant.SIOCSIFMTU.html |
| 94 | + #[allow(clippy::useless_conversion)] |
| 95 | + let request = SIOCSIFMTU.try_into().unwrap(); |
| 96 | + // SAFETY: SIOCSIFMTU expects an ifreq with an MTU and interface set. The interface is set |
| 97 | + // by [Self::new]. |
| 98 | + match unsafe { libc::ioctl(socket, request, &self.interface_request) } { |
| 99 | + n if n < 0 => Err(io::Error::last_os_error()), |
| 100 | + _ => Ok(()), |
| 101 | + } |
| 102 | + } |
100 | 103 |
|
101 | | - // For some reason, libc crate defines ioctl to take a c_int (which is defined as i32), but the c_ulong type is defined as u64: |
102 | | - // https://docs.rs/libc/latest/x86_64-unknown-linux-musl/libc/fn.ioctl.html |
103 | | - // https://docs.rs/libc/latest/x86_64-unknown-linux-musl/libc/type.c_ulong.html |
104 | | - // https://docs.rs/libc/latest/x86_64-unknown-linux-musl/libc/constant.SIOCGIFMTU.html |
105 | | - #[allow(clippy::useless_conversion)] |
106 | | - let request = SIOCGIFMTU.try_into().unwrap(); |
107 | | - // SAFETY: SIOCGIFMTU expects an ifreq with an interface set |
108 | | - if unsafe { libc::ioctl(sock.as_raw_fd(), request, &ifr) } < 0 { |
109 | | - let e = std::io::Error::last_os_error(); |
110 | | - log::error!("{}", e.display_chain_with_msg("SIOCGIFMTU failed")); |
111 | | - return Err(e); |
| 104 | + /// Get MTU of this interface. |
| 105 | + // SIOCGIFMTU in a trenchcoat. |
| 106 | + fn get_mtu(self) -> Result<u16, io::Error> { |
| 107 | + let socket = self.socket.as_raw_fd(); |
| 108 | + #[cfg(target_os = "macos")] |
| 109 | + const SIOCGIFMTU: u64 = 0xc0206933; |
| 110 | + #[cfg(target_os = "linux")] |
| 111 | + const SIOCGIFMTU: libc::c_ulong = libc::SIOCSIFMTU; |
| 112 | + // For some reason, libc crate defines ioctl to take a c_int (which is defined as i32), but the c_ulong type is defined as u64: |
| 113 | + // https://docs.rs/libc/latest/x86_64-unknown-linux-musl/libc/fn.ioctl.html |
| 114 | + // https://docs.rs/libc/latest/x86_64-unknown-linux-musl/libc/type.c_ulong.html |
| 115 | + // https://docs.rs/libc/latest/x86_64-unknown-linux-musl/libc/constant.SIOCGIFMTU.html |
| 116 | + #[allow(clippy::useless_conversion)] |
| 117 | + let request = SIOCGIFMTU.try_into().unwrap(); |
| 118 | + // SAFETY: SIOCGIFMTU expects an ifreq with an interface set, which is guaranteed by |
| 119 | + // [Self::new]. |
| 120 | + match unsafe { libc::ioctl(socket, request, &self.interface_request) } { |
| 121 | + n if n < 0 => Err(io::Error::last_os_error()), |
| 122 | + _ => { |
| 123 | + // SAFETY: ifru_mtu is initialized by SIOCGIFMTU |
| 124 | + let mtu = unsafe { self.interface_request.ifr_ifru.ifru_mtu }; |
| 125 | + let mtu = u16::try_from(mtu).expect("MTU of interface to be less than u16::MAX"); |
| 126 | + Ok(mtu) |
| 127 | + } |
| 128 | + } |
112 | 129 | } |
113 | | - // SAFETY: ifru_mtu is initialized by SIOCGIFMTU |
114 | | - Ok(u16::try_from(unsafe { ifr.ifr_ifru.ifru_mtu }).unwrap()) |
115 | 130 | } |
0 commit comments