Skip to main content

dope_uring/sockets/
source.rs

1use std::{mem, net, ptr, slice};
2
3use dope_sys::io::socket::msg::source;
4
5use crate::{Uring, sockets::controls};
6
7// SAFETY: CMSG_SPACE only aligns these fixed native packet-info payload sizes.
8const V4: usize = unsafe { libc::CMSG_SPACE(mem::size_of::<libc::in_pktinfo>() as u32) as usize };
9const V6: usize = unsafe { libc::CMSG_SPACE(mem::size_of::<libc::in6_pktinfo>() as u32) as usize };
10
11/// Precomputed source selection retained through each datagram submission.
12#[derive(Clone, Copy, Debug)]
13#[repr(C, align(8))]
14pub struct Control {
15    bytes: [u8; V6],
16}
17
18/// One source with its optional retained segmentation capability.
19#[repr(transparent)]
20pub struct Combined(Kind);
21
22enum Kind {
23    Source(Control),
24    Segmented(Segmented),
25}
26
27/// One contiguous native GSO message followed by its selected source message.
28#[repr(C)]
29struct Segmented {
30    gso: controls::Control,
31    source: Control,
32}
33
34impl source::Source for Uring {
35    type Control = Control;
36    type Combined = Combined;
37
38    fn control(source: source::Address) -> Self::Control {
39        let address = source.get();
40        let mut control = Control { bytes: [0; V6] };
41        let header = control.bytes.as_mut_ptr().cast::<libc::cmsghdr>();
42        // SAFETY: Control owns aligned, zero-initialized storage for either
43        // complete native message. Both packet-info layouts have no padding.
44        unsafe {
45            let payload = libc::CMSG_DATA(header);
46            let length = match address {
47                net::SocketAddr::V4(address) => {
48                    (*header).cmsg_level = libc::IPPROTO_IP;
49                    (*header).cmsg_type = libc::IP_PKTINFO;
50                    payload.cast::<libc::in_pktinfo>().write(libc::in_pktinfo {
51                        ipi_ifindex: 0,
52                        ipi_spec_dst: libc::in_addr {
53                            s_addr: u32::from_ne_bytes(address.ip().octets()),
54                        },
55                        ipi_addr: libc::in_addr { s_addr: 0 },
56                    });
57                    mem::size_of::<libc::in_pktinfo>()
58                }
59                net::SocketAddr::V6(address) => {
60                    (*header).cmsg_level = libc::IPPROTO_IPV6;
61                    (*header).cmsg_type = libc::IPV6_PKTINFO;
62                    payload
63                        .cast::<libc::in6_pktinfo>()
64                        .write(libc::in6_pktinfo {
65                            ipi6_addr: libc::in6_addr {
66                                s6_addr: address.ip().octets(),
67                            },
68                            ipi6_ifindex: address.scope_id(),
69                        });
70                    mem::size_of::<libc::in6_pktinfo>()
71                }
72            };
73            (*header).cmsg_len = libc::CMSG_LEN(length as u32) as _;
74        }
75        control
76    }
77
78    fn compose(source: Control, gso: Option<controls::Control>) -> Combined {
79        Combined(match gso {
80            Some(gso) => Kind::Segmented(Segmented { gso, source }),
81            None => Kind::Source(source),
82        })
83    }
84
85    fn release(control: Combined) -> (Control, Option<controls::Control>) {
86        match control.0 {
87            Kind::Source(source) => (source, None),
88            Kind::Segmented(control) => (control.source, Some(control.gso)),
89        }
90    }
91
92    fn bytes(control: &mut Combined) -> &[u8] {
93        match &control.0 {
94            Kind::Source(source) => source.as_ref(),
95            Kind::Segmented(control) => {
96                let length = mem::size_of::<controls::Control>() + control.source.as_ref().len();
97                // SAFETY: this repr(C) payload, excluding the enclosing enum, keeps
98                // the initialized GSO message and padding immediately before the
99                // source message. Its final CMSG_LEN bounds this exact owner.
100                unsafe { slice::from_raw_parts(ptr::from_ref(control).cast(), length) }
101            }
102        }
103    }
104}
105
106impl AsRef<[u8]> for Control {
107    fn as_ref(&self) -> &[u8] {
108        // SAFETY: the private constructor writes this aligned header's length
109        // once, bounded by the initialized native message storage. A final
110        // control message does not need trailing CMSG_SPACE padding.
111        unsafe {
112            let length = (*self.bytes.as_ptr().cast::<libc::cmsghdr>()).cmsg_len;
113            slice::from_raw_parts(self.bytes.as_ptr(), length)
114        }
115    }
116}
117
118const _: () = {
119    assert!(V4 <= V6);
120    assert!(mem::size_of::<libc::in_pktinfo>() == 12);
121    assert!(mem::size_of::<libc::in6_pktinfo>() == 20);
122    assert!(mem::align_of::<Control>() >= mem::align_of::<libc::cmsghdr>());
123    assert!(mem::align_of::<Control>() >= mem::align_of::<libc::in_pktinfo>());
124    assert!(mem::align_of::<Control>() >= mem::align_of::<libc::in6_pktinfo>());
125    assert!(mem::size_of::<Control>() == 40);
126    assert!(mem::offset_of!(Segmented, gso) == 0);
127    assert!(mem::offset_of!(Segmented, source) == mem::size_of::<controls::Control>());
128    assert!(
129        mem::size_of::<Segmented>()
130            == mem::size_of::<controls::Control>() + mem::size_of::<Control>()
131    );
132    assert!(mem::align_of::<Segmented>() == mem::align_of::<Control>());
133};