Skip to content

Commit 75329f2

Browse files
committed
Merge sockaddr reading functionality into luomu-common
We had almost same sockaddr reading code in luomu-getifaddr and luomu-libpcap. Now both places use shared code from luomu-common. The code is behind "libc" feature flag because it requires libc as external dependency and we want to keep luomu-common as dependency free by default.
1 parent 30a1c14 commit 75329f2

7 files changed

Lines changed: 139 additions & 201 deletions

File tree

luomu-common/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ rust-version.workspace = true
1111
homepage.workspace = true
1212
repository.workspace = true
1313

14+
[features]
15+
default = []
16+
17+
[dependencies]
18+
libc = { version = "0.2", optional = true }
19+
1420
[dev-dependencies]
1521
quickcheck.workspace = true
1622

luomu-common/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ pub use tagged_macaddr::TaggedMacAddr;
2121
/// forwardable
2222
pub mod ipaddr;
2323

24+
#[cfg(feature = "libc")]
25+
pub mod sockaddr;
26+
2427
/// Invalid address error
2528
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
2629
pub struct InvalidAddress;

luomu-common/src/sockaddr.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#![allow(unsafe_code)]
2+
//! Utilities to handle [libc::sockaddr] and related structs.
3+
4+
use std::net::{Ipv4Addr, Ipv6Addr};
5+
6+
use crate::Address;
7+
8+
/// Length of MAC address in bytes
9+
const MAC_ADDR_LEN: usize = 6;
10+
11+
/// Reads address information from given socket address pointer.
12+
///
13+
/// Address may contain IP address or Link address, the returned [Address]
14+
/// reflects which one was read. [None] is returned if no address was available,
15+
/// or it could not be read.
16+
// We do our best to validate that the pointer given in `ifa_addr` is valid.
17+
#[allow(clippy::not_unsafe_ptr_arg_deref)]
18+
pub fn from_sockaddr(ifa_addr: *const libc::sockaddr) -> Option<Address> {
19+
if ifa_addr.is_null() {
20+
return None;
21+
}
22+
23+
let family = i32::from(unsafe { (*ifa_addr).sa_family });
24+
25+
#[cfg(target_os = "macos")]
26+
let sa_len = usize::from(unsafe { (*ifa_addr).sa_len });
27+
28+
match family {
29+
#[cfg(target_os = "macos")]
30+
libc::AF_INET if sa_len < 16 => {
31+
debug_assert!((5..=8).contains(&sa_len), "invalid sa_len {sa_len} for AF_INET");
32+
// We keep ifa_addr as sockaddr, but read it like it's sockaddr_in:
33+
//
34+
// pub struct sockaddr_in {
35+
// pub sin_len: u8,
36+
// pub sin_family: u8,
37+
// pub sin_port: u16, \ These two are in sockaddr.sa_data
38+
// pub sin_addr: u32, /
39+
// pub sin_zero: [c_char; 8],
40+
// }
41+
//
42+
// We want to read the partial bytes from sin_addr, the length is
43+
// dictated by sa_len. sockaddr.sa_data contains first sin_port
44+
// which we skip and then sin_addr.
45+
let len = sa_len.saturating_sub(2); // remove length of sin_len and sin_family
46+
let mut iret = [0i8; 4];
47+
let sa_data: &[i8] = unsafe { std::slice::from_raw_parts((*ifa_addr).sa_data.as_ptr(), len) };
48+
iret[0..len - 2].copy_from_slice(&sa_data[2..len]);
49+
let uret: [u8; 4] = unsafe { std::mem::transmute(iret) };
50+
Some(Address::from(Ipv4Addr::from_octets(uret)))
51+
}
52+
53+
libc::AF_INET => {
54+
#[cfg(target_os = "macos")]
55+
debug_assert_eq!(sa_len, 16, "invalid sa_len {sa_len} for AF_INET");
56+
let a: libc::sockaddr_in = unsafe { *(ifa_addr.cast::<libc::sockaddr_in>()) };
57+
Some(Address::from(Ipv4Addr::from_bits(u32::from_be(
58+
a.sin_addr.s_addr,
59+
))))
60+
}
61+
62+
libc::AF_INET6 => {
63+
#[cfg(target_os = "macos")]
64+
debug_assert_eq!(sa_len, 28, "invalid sa_len {sa_len} for AF_INET6");
65+
let a: libc::sockaddr_in6 = unsafe { *(ifa_addr.cast::<libc::sockaddr_in6>()) };
66+
Some(Address::from(Ipv6Addr::from_octets(a.sin6_addr.s6_addr)))
67+
}
68+
69+
#[cfg(target_os = "macos")]
70+
libc::AF_LINK => {
71+
debug_assert!(
72+
sa_len == 20 || sa_len == 24,
73+
"invalid sa_len {sa_len} for AF_LINK"
74+
);
75+
// MAC address for this interface
76+
let a: libc::sockaddr_dl = unsafe { *(ifa_addr.cast::<libc::sockaddr_dl>()) };
77+
// length of the address
78+
let a_len = usize::from(a.sdl_alen);
79+
// length of the name
80+
let n_len = usize::from(a.sdl_nlen);
81+
// If seems that name is stored to sdl_data before the mac
82+
// address of the interface. However, libc::sockaddr_dl::sdl_data has been
83+
// defined to contain 12 bytes. Thus, if name of the interface
84+
// is longer than 6 bytes (characters), we can not read the MAC
85+
// address of that interface.
86+
if a_len != MAC_ADDR_LEN || n_len + a_len > a.sdl_data.len() {
87+
return None;
88+
}
89+
// also, sdl_data has been defined as i8 for whatever reason,
90+
// we need bytes for mac address, thus a bit of unsafery
91+
let data = &a.sdl_data;
92+
let sdl_data_as_u8: &[u8] =
93+
unsafe { std::slice::from_raw_parts(data.as_ptr().cast::<u8>(), data.len()) };
94+
let mut address = [0u8; MAC_ADDR_LEN];
95+
// mac address stored after name
96+
// You may want to look into LLADDR() macro somewhere on Mac OS headers
97+
let offset = usize::from(a.sdl_nlen);
98+
address[..MAC_ADDR_LEN].copy_from_slice(&sdl_data_as_u8[offset..offset + MAC_ADDR_LEN]);
99+
Some(Address::from(address))
100+
}
101+
102+
#[cfg(target_os = "linux")]
103+
libc::AF_PACKET => {
104+
// Mac address of the interface
105+
let a: libc::sockaddr_ll = unsafe { *(ifa_addr.cast::<libc::sockaddr_ll>()) };
106+
let a_len = usize::from(a.sll_halen);
107+
debug_assert!(a_len == MAC_ADDR_LEN);
108+
if a_len != MAC_ADDR_LEN {
109+
return None;
110+
}
111+
let mut address = [0u8; MAC_ADDR_LEN];
112+
address[..MAC_ADDR_LEN].copy_from_slice(&a.sll_addr[..MAC_ADDR_LEN]);
113+
Some(Address::from(address))
114+
}
115+
116+
_ => None,
117+
}
118+
}

luomu-getifaddrs/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ repository.workspace = true
1515
bitflags.workspace = true
1616
cfg-if.workspace = true
1717
libc.workspace = true
18-
luomu-common.workspace = true
18+
luomu-common = { workspace = true, features = [ "libc" ] }
1919

2020
[lints]
2121
workspace = true

luomu-getifaddrs/src/lib.rs

Lines changed: 7 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,17 @@
55
use std::ffi::CStr;
66
use std::io;
77
use std::mem::MaybeUninit;
8-
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
8+
use std::net::IpAddr;
99

10+
use luomu_common::sockaddr::from_sockaddr;
1011
use luomu_common::{Address, MacAddr};
1112

12-
#[cfg(target_os = "macos")]
13-
use std::slice;
14-
1513
mod flags;
1614
pub use flags::Flags;
1715

1816
mod stats;
1917
pub use stats::IfStats;
2018

21-
/// Length of MAC address in bytes
22-
const MAC_ADDR_LEN: usize = 6;
23-
2419
/// Returns a linked list describing the network interfaces of
2520
/// the local system.
2621
pub fn getifaddrs() -> io::Result<IfAddrs> {
@@ -117,27 +112,6 @@ impl Iterator for IfAddrs {
117112
}
118113
}
119114

120-
/// Returns address family value from [libc::sockaddr]
121-
const fn sa_get_family(sa: *const libc::sockaddr) -> i32 {
122-
unsafe { *sa }.sa_family as libc::c_int
123-
}
124-
125-
/// Returns given [libc::sockaddr] pointer as a pointer to [libc::sockaddr_in]
126-
const fn sa_as_sockaddr_in(sa: *const libc::sockaddr) -> libc::sockaddr_in {
127-
unsafe { *(sa.cast::<libc::sockaddr_in>()) }
128-
}
129-
130-
/// Returns given [libc::sockaddr] pointer as a pointer to [libc::sockaddr_in6]
131-
const fn sa_as_sockaddr_in6(sa: *const libc::sockaddr) -> libc::sockaddr_in6 {
132-
unsafe { *(sa.cast::<libc::sockaddr_in6>()) }
133-
}
134-
135-
#[cfg(target_os = "macos")]
136-
/// Returns given [libc::sockaddr] pointer as a pointer to [libc::sockaddr_dl]
137-
const fn sa_as_sockaddr_dl(sa: *const libc::sockaddr) -> libc::sockaddr_dl {
138-
unsafe { *(sa.cast::<libc::sockaddr_dl>()) }
139-
}
140-
141115
/// This struct provides access to information about network interface.
142116
pub struct IfAddr {
143117
/// pointer for the interface data
@@ -179,21 +153,21 @@ impl IfAddr {
179153

180154
/// Interface address
181155
pub fn addr(&self) -> Option<Address> {
182-
IfAddr::read_addr(self.ifa().ifa_addr)
156+
from_sockaddr(self.ifa().ifa_addr)
183157
}
184158

185159
/// Interface netmask
186160
pub fn netmask(&self) -> Option<IpAddr> {
187-
IfAddr::read_addr(self.ifa().ifa_netmask).and_then(|a| a.as_ip())
161+
from_sockaddr(self.ifa().ifa_netmask).and_then(|a| a.as_ip())
188162
}
189163

190164
/// Destination address for Point-to-Point link
191165
pub fn dstaddr(&self) -> Option<IpAddr> {
192166
cfg_if::cfg_if! {
193167
if #[cfg(target_os = "macos")] {
194-
IfAddr::read_addr(self.ifa().ifa_dstaddr).and_then(|a| a.as_ip())
168+
from_sockaddr(self.ifa().ifa_dstaddr).and_then(|a| a.as_ip())
195169
} else if #[cfg(target_os = "linux")] {
196-
IfAddr::read_addr(self.ifa().ifa_ifu).and_then(|a| a.as_ip())
170+
from_sockaddr(self.ifa().ifa_ifu).and_then(|a| a.as_ip())
197171
}
198172
}
199173
}
@@ -204,7 +178,7 @@ impl IfAddr {
204178
return None;
205179
}
206180

207-
let family = sa_get_family(self.ifa().ifa_addr);
181+
let family = i32::from(unsafe { *self.ifa().ifa_addr }.sa_family);
208182

209183
#[cfg(target_os = "linux")]
210184
if family != libc::AF_PACKET {
@@ -220,96 +194,6 @@ impl IfAddr {
220194
let link_stats = unsafe { &*(ifa_data as *const stats::LinkStats) };
221195
Some(*link_stats)
222196
}
223-
224-
/// Reads address information from given socket address pointer.
225-
///
226-
/// Address may contain IP address or Link address, the returned
227-
/// [Addr] reflects which one was read. [None] is returned if no address
228-
/// was available, or it could not be read.
229-
fn read_addr(ifa_addr: *const libc::sockaddr) -> Option<Address> {
230-
if ifa_addr.is_null() {
231-
return None;
232-
}
233-
234-
let family = sa_get_family(ifa_addr);
235-
236-
match family {
237-
#[cfg(target_os = "macos")]
238-
libc::AF_INET if unsafe { *ifa_addr }.sa_len < 16 => {
239-
let len = unsafe { *ifa_addr }.sa_len;
240-
debug_assert!(len >= 5 && len <= 8, "invalid sa_len {len} for AF_INET");
241-
let sa_data = unsafe { *ifa_addr }.sa_data;
242-
let mut ret = 0;
243-
if len >= 5 {
244-
ret += u32::from(sa_data[2] as u8) << 24;
245-
}
246-
if len >= 6 {
247-
ret += u32::from(sa_data[3] as u8) << 16;
248-
}
249-
if len >= 7 {
250-
ret += u32::from(sa_data[4] as u8) << 8;
251-
}
252-
if len == 8 {
253-
ret += u32::from(sa_data[5] as u8);
254-
}
255-
Some(Address::from(Ipv4Addr::from_bits(ret)))
256-
}
257-
libc::AF_INET => {
258-
#[cfg(target_os = "macos")]
259-
debug_assert_eq!(unsafe { *ifa_addr }.sa_len, 16, "invalid sa_len for AF_INET");
260-
let a: libc::sockaddr_in = sa_as_sockaddr_in(ifa_addr);
261-
Some(Address::from(Ipv4Addr::from(u32::from_be(a.sin_addr.s_addr))))
262-
}
263-
libc::AF_INET6 => {
264-
#[cfg(target_os = "macos")]
265-
debug_assert_eq!(unsafe { *ifa_addr }.sa_len, 28, "invalid sa_len for AF_INET6");
266-
let a: libc::sockaddr_in6 = sa_as_sockaddr_in6(ifa_addr);
267-
Some(Address::from(Ipv6Addr::from(a.sin6_addr.s6_addr)))
268-
}
269-
#[cfg(target_os = "macos")]
270-
libc::AF_LINK => {
271-
// MAC address for this interface
272-
let a: libc::sockaddr_dl = sa_as_sockaddr_dl(ifa_addr);
273-
// length of the address
274-
let a_len = usize::from(a.sdl_alen);
275-
// length of the name
276-
let n_len = usize::from(a.sdl_nlen);
277-
// If seems that name is stored to sdl_data before the mac
278-
// address of the interface. However, libc::sockaddr_dl::sdl_data has been
279-
// defined to contain 12 bytes. Thus, if name of the interface
280-
// is longer than 6 bytes (characters), we can not read the MAC
281-
// address of that interface.
282-
if a_len != MAC_ADDR_LEN || n_len + a_len > a.sdl_data.len() {
283-
return None;
284-
}
285-
// also, sdl_data has been defined as i8 for whatever reason,
286-
// we need bytes for mac address, thus a bit of unsafery
287-
let data = &a.sdl_data;
288-
let sdl_data_as_u8: &[u8] =
289-
unsafe { slice::from_raw_parts(data.as_ptr().cast::<u8>(), data.len()) };
290-
let mut address = [0u8; MAC_ADDR_LEN];
291-
// mac address stored after name
292-
// You may want to look into LLADDR() macro somewhere on Mac OS headers
293-
let offset = usize::from(a.sdl_nlen);
294-
address[..MAC_ADDR_LEN].copy_from_slice(&sdl_data_as_u8[offset..offset + MAC_ADDR_LEN]);
295-
Some(Address::from(address))
296-
}
297-
#[cfg(target_os = "linux")]
298-
libc::AF_PACKET => {
299-
// Mac address of the interface
300-
let a: libc::sockaddr_ll = unsafe { *(ifa_addr.cast::<libc::sockaddr_ll>()) };
301-
let a_len = usize::from(a.sll_halen);
302-
debug_assert!(a_len == MAC_ADDR_LEN);
303-
if a_len != MAC_ADDR_LEN {
304-
return None;
305-
}
306-
let mut address = [0u8; MAC_ADDR_LEN];
307-
address[..MAC_ADDR_LEN].copy_from_slice(&a.sll_addr[..MAC_ADDR_LEN]);
308-
Some(Address::from(address))
309-
}
310-
_ => None,
311-
}
312-
}
313197
}
314198

315199
/// A view into interface data

luomu-libpcap/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ async-tokio = [ "dep:futures-core", "dep:tokio" ]
1818

1919
[dependencies]
2020
libc.workspace = true
21-
luomu-common.workspace = true
21+
luomu-common = { workspace = true, features = [ "libc" ] }
2222
luomu-libpcap-sys.workspace = true
2323
tracing.workspace = true
2424

0 commit comments

Comments
 (0)