Skip to main content

timestamped_socket/networkaddress/
linux.rs

1use std::{
2    net::{Ipv4Addr, SocketAddrV4, SocketAddrV6},
3    os::fd::RawFd,
4};
5
6use crate::{
7    cerr, control_message::zeroed_sockaddr_storage, interface::InterfaceName,
8    networkaddress::NetworkAddress,
9};
10
11use super::{
12    sealed::{PrivateToken, SealedMC, SealedNA},
13    MulticastJoinable,
14};
15
16/// A mac address.
17#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub struct MacAddress([u8; 6]);
19
20impl From<[u8; 6]> for MacAddress {
21    fn from(value: [u8; 6]) -> Self {
22        MacAddress(value)
23    }
24}
25
26impl AsRef<[u8]> for MacAddress {
27    fn as_ref(&self) -> &[u8] {
28        &self.0
29    }
30}
31
32impl MacAddress {
33    /// Create a mac address from raw bytes.
34    #[must_use]
35    pub const fn new(address: [u8; 6]) -> Self {
36        MacAddress(address)
37    }
38}
39
40/// An ethernet address, which is a mac address together with an ethertype and interface index.
41#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
42pub struct EthernetAddress {
43    protocol: u16,
44    mac_address: MacAddress,
45    if_index: libc::c_int,
46}
47
48impl EthernetAddress {
49    /// Create an ethernet address from its raw components.
50    #[must_use]
51    pub const fn new(protocol: u16, mac_address: MacAddress, if_index: libc::c_int) -> Self {
52        EthernetAddress {
53            protocol,
54            mac_address,
55            if_index,
56        }
57    }
58
59    /// The mac address portion of the ethernet address.
60    #[must_use]
61    pub const fn mac(&self) -> MacAddress {
62        self.mac_address
63    }
64
65    /// The protocol type in the ethernet address.
66    #[must_use]
67    pub const fn protocol(&self) -> u16 {
68        self.protocol
69    }
70
71    /// The index of the interface associated with this ethernet address.
72    #[must_use]
73    pub const fn interface(&self) -> libc::c_int {
74        self.if_index
75    }
76}
77
78impl SealedNA for EthernetAddress {}
79
80impl NetworkAddress for EthernetAddress {
81    #[expect(
82        clippy::cast_possible_truncation,
83        reason = "libc constants are of inconsistent sizes."
84    )]
85    fn to_sockaddr(&self, _token: PrivateToken) -> libc::sockaddr_storage {
86        const _: () = assert!(
87            std::mem::size_of::<libc::sockaddr_storage>()
88                >= std::mem::size_of::<libc::sockaddr_ll>()
89        );
90        const _: () = assert!(
91            std::mem::align_of::<libc::sockaddr_storage>()
92                >= std::mem::align_of::<libc::sockaddr_ll>()
93        );
94
95        let mut result = zeroed_sockaddr_storage();
96        // Safety: the above assertions guarantee that alignment and size are correct.
97        // the resulting reference won't outlast the function, and result lives the entire
98        // duration of the function
99        let out = unsafe { &mut (*(&raw mut result).cast::<libc::sockaddr_ll>()) };
100
101        out.sll_family = libc::AF_PACKET as _;
102        out.sll_addr[..6].copy_from_slice(&self.mac_address.0);
103        out.sll_halen = 6;
104        out.sll_protocol = u16::from_ne_bytes(self.protocol.to_be_bytes());
105        out.sll_ifindex = self.if_index;
106
107        result
108    }
109
110    #[expect(
111        clippy::cast_possible_truncation,
112        reason = "libc constants are of inconsistent sizes."
113    )]
114    fn from_sockaddr(addr: libc::sockaddr_storage, _token: PrivateToken) -> Option<Self> {
115        const _: () = assert!(
116            std::mem::size_of::<libc::sockaddr_storage>()
117                >= std::mem::size_of::<libc::sockaddr_ll>()
118        );
119        const _: () = assert!(
120            std::mem::align_of::<libc::sockaddr_storage>()
121                >= std::mem::align_of::<libc::sockaddr_ll>()
122        );
123
124        if addr.ss_family != libc::AF_PACKET as _ {
125            return None;
126        }
127
128        // Safety: the above assertions guarantee that alignment and size are correct
129        // the resulting reference won't outlast the function, and addr lives the entire
130        // duration of the function
131        let input = unsafe { &(*(&raw const addr).cast::<libc::sockaddr_ll>()) };
132
133        if input.sll_halen != 6 {
134            return None;
135        }
136
137        Some(EthernetAddress::new(
138            u16::from_be_bytes(input.sll_protocol.to_ne_bytes()),
139            MacAddress::new(input.sll_addr[..6].try_into().unwrap()),
140            input.sll_ifindex,
141        ))
142    }
143
144    fn from_ip_and_port(_addr: std::net::IpAddr, _port: u16) -> Option<Self> {
145        None
146    }
147
148    fn port(&self) -> u16 {
149        // Ethernet doesn't have a port, zero is a decent sentinal value to cover that.
150        0
151    }
152}
153
154impl SealedMC for EthernetAddress {}
155
156impl MulticastJoinable for EthernetAddress {
157    #[expect(
158        clippy::cast_possible_truncation,
159        reason = "a packet_mreq is small enough for its size to fit in an u32."
160    )]
161    #[expect(
162        clippy::cast_possible_wrap,
163        reason = "interface indices will always fit in i32s"
164    )]
165    fn join_multicast(
166        &self,
167        socket: RawFd,
168        interface: InterfaceName,
169        _token: PrivateToken,
170    ) -> std::io::Result<()> {
171        let request = libc::packet_mreq {
172            mr_ifindex: interface
173                .get_index()
174                .ok_or(std::io::ErrorKind::InvalidInput)? as _,
175            mr_type: libc::PACKET_MR_MULTICAST as _,
176            mr_alen: 6,
177            mr_address: [
178                self.mac_address.0[0],
179                self.mac_address.0[1],
180                self.mac_address.0[2],
181                self.mac_address.0[3],
182                self.mac_address.0[4],
183                self.mac_address.0[5],
184                0,
185                0,
186            ],
187        };
188        // Safety:
189        // value points to a struct of length option_len, of type ip_mreq as expected for IPPROTO_IPV6/IPV6_ADD_MEMBERSHIP
190        cerr(unsafe {
191            libc::setsockopt(
192                socket,
193                libc::SOL_PACKET,
194                libc::PACKET_ADD_MEMBERSHIP,
195                (&raw const request).cast(),
196                std::mem::size_of_val(&request) as _,
197            )
198        })?;
199        Ok(())
200    }
201
202    #[expect(
203        clippy::cast_possible_truncation,
204        reason = "a packet_mreq is small enough for its size to fit in an u32."
205    )]
206    #[expect(
207        clippy::cast_possible_wrap,
208        reason = "interface indices will always fit in i32s"
209    )]
210    fn leave_multicast(
211        &self,
212        socket: RawFd,
213        interface: InterfaceName,
214        _token: PrivateToken,
215    ) -> std::io::Result<()> {
216        let request = libc::packet_mreq {
217            mr_ifindex: interface
218                .get_index()
219                .ok_or(std::io::ErrorKind::InvalidInput)? as _,
220            mr_type: libc::PACKET_MR_MULTICAST as _,
221            mr_alen: 6,
222            mr_address: [
223                self.mac_address.0[0],
224                self.mac_address.0[1],
225                self.mac_address.0[2],
226                self.mac_address.0[3],
227                self.mac_address.0[4],
228                self.mac_address.0[5],
229                0,
230                0,
231            ],
232        };
233        // Safety:
234        // value points to a struct of length option_len, of type ip_mreq as expected for IPPROTO_IPV6/IPV6_ADD_MEMBERSHIP
235        cerr(unsafe {
236            libc::setsockopt(
237                socket,
238                libc::SOL_PACKET,
239                libc::PACKET_DROP_MEMBERSHIP,
240                (&raw const request).cast(),
241                std::mem::size_of_val(&request) as _,
242            )
243        })?;
244        Ok(())
245    }
246}
247
248impl SealedMC for SocketAddrV4 {}
249
250impl MulticastJoinable for SocketAddrV4 {
251    #[expect(
252        clippy::cast_possible_truncation,
253        reason = "an ip_mreqn is small enough for its size to fit in an u32."
254    )]
255    #[expect(
256        clippy::cast_possible_wrap,
257        reason = "interface indices will always fit in i32s"
258    )]
259    fn join_multicast(
260        &self,
261        socket: RawFd,
262        interface: InterfaceName,
263        _token: PrivateToken,
264    ) -> std::io::Result<()> {
265        let request = libc::ip_mreqn {
266            imr_multiaddr: libc::in_addr {
267                s_addr: u32::from_ne_bytes(self.ip().octets()),
268            },
269            imr_address: libc::in_addr {
270                s_addr: u32::from_ne_bytes(Ipv4Addr::UNSPECIFIED.octets()),
271            },
272            imr_ifindex: interface
273                .get_index()
274                .ok_or(std::io::ErrorKind::InvalidInput)? as _,
275        };
276        // Safety:
277        // value points to a struct of length option_len, of type ip_mreq as expected for IPPROTO_IP/IP_ADD_MEMBERSHIP
278        cerr(unsafe {
279            libc::setsockopt(
280                socket,
281                libc::IPPROTO_IP,
282                libc::IP_ADD_MEMBERSHIP,
283                (&raw const request).cast(),
284                std::mem::size_of_val(&request) as _,
285            )
286        })?;
287        Ok(())
288    }
289
290    #[expect(
291        clippy::cast_possible_truncation,
292        reason = "an ip_mreqn is small enough for its size to fit in an u32."
293    )]
294    #[expect(
295        clippy::cast_possible_wrap,
296        reason = "interface indices will always fit in i32s"
297    )]
298    fn leave_multicast(
299        &self,
300        socket: RawFd,
301        interface: InterfaceName,
302        _token: PrivateToken,
303    ) -> std::io::Result<()> {
304        let request = libc::ip_mreqn {
305            imr_multiaddr: libc::in_addr {
306                s_addr: u32::from_ne_bytes(self.ip().octets()),
307            },
308            imr_address: libc::in_addr {
309                s_addr: u32::from_ne_bytes(Ipv4Addr::UNSPECIFIED.octets()),
310            },
311            imr_ifindex: interface
312                .get_index()
313                .ok_or(std::io::ErrorKind::InvalidInput)? as _,
314        };
315        // Safety:
316        // value points to a struct of length option_len, of type ip_mreq as expected for IPPROTO_IP/IP_DROP_MEMBERSHIP
317        cerr(unsafe {
318            libc::setsockopt(
319                socket,
320                libc::IPPROTO_IP,
321                libc::IP_DROP_MEMBERSHIP,
322                (&raw const request).cast(),
323                std::mem::size_of_val(&request) as _,
324            )
325        })?;
326        Ok(())
327    }
328}
329
330impl SealedMC for SocketAddrV6 {}
331
332impl MulticastJoinable for SocketAddrV6 {
333    #[expect(
334        clippy::cast_possible_truncation,
335        reason = "an ipv6_mreq is small enough for its size to fit in an u32."
336    )]
337    fn join_multicast(
338        &self,
339        socket: RawFd,
340        interface: InterfaceName,
341        _token: PrivateToken,
342    ) -> std::io::Result<()> {
343        let request = libc::ipv6_mreq {
344            ipv6mr_multiaddr: libc::in6_addr {
345                s6_addr: self.ip().octets(),
346            },
347            ipv6mr_interface: interface
348                .get_index()
349                .ok_or(std::io::ErrorKind::InvalidInput)? as _,
350        };
351        // Safety:
352        // value points to a struct of length option_len, of type ip_mreq as expected for IPPROTO_IPV6/IPV6_ADD_MEMBERSHIP
353        cerr(unsafe {
354            libc::setsockopt(
355                socket,
356                libc::IPPROTO_IPV6,
357                libc::IPV6_ADD_MEMBERSHIP,
358                (&raw const request).cast(),
359                std::mem::size_of_val(&request) as _,
360            )
361        })?;
362        Ok(())
363    }
364
365    #[expect(
366        clippy::cast_possible_truncation,
367        reason = "an ipv6_mreq is small enough for its size to fit in an u32."
368    )]
369    fn leave_multicast(
370        &self,
371        socket: RawFd,
372        interface: InterfaceName,
373        _token: PrivateToken,
374    ) -> std::io::Result<()> {
375        let request = libc::ipv6_mreq {
376            ipv6mr_multiaddr: libc::in6_addr {
377                s6_addr: self.ip().octets(),
378            },
379            ipv6mr_interface: interface
380                .get_index()
381                .ok_or(std::io::ErrorKind::InvalidInput)? as _,
382        };
383        // Safety:
384        // value points to a struct of length option_len, of type ip_mreq as expected for IPPROTO_IPV6/IPV6_DROP_MEMBERSHIP
385        cerr(unsafe {
386            libc::setsockopt(
387                socket,
388                libc::IPPROTO_IPV6,
389                libc::IPV6_DROP_MEMBERSHIP,
390                (&raw const request).cast(),
391                std::mem::size_of_val(&request) as _,
392            )
393        })?;
394        Ok(())
395    }
396}