Skip to main content

mctx_core/raw_ip/
config.rs

1use crate::config::PublicationAddressFamily;
2use crate::error::MctxError;
3use std::net::IpAddr;
4
5/// Configuration for one generic raw-IP transmit socket.
6///
7/// An egress selector is mandatory. Supplying a local bind address or an
8/// interface address resolves a concrete interface index; supplying only an
9/// interface index requires an explicit address family.
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub struct RawIpSocketConfig {
12    /// Expected IP family. It is inferred from an IP-address selector when
13    /// omitted, but is required for an index-only configuration.
14    pub family: Option<PublicationAddressFamily>,
15    /// Optional exact local address used to bind the raw socket.
16    pub bind_addr: Option<IpAddr>,
17    /// Optional local address identifying the required egress interface.
18    pub interface_addr: Option<IpAddr>,
19    /// Optional required egress interface index.
20    pub interface_index: Option<u32>,
21}
22
23/// Compatibility name for callers that use the publication terminology.
24pub type RawIpPublicationConfig = RawIpSocketConfig;
25
26impl Default for RawIpSocketConfig {
27    fn default() -> Self {
28        Self::new()
29    }
30}
31
32impl RawIpSocketConfig {
33    /// Creates a generic raw-IP socket configuration with inferred family.
34    pub fn new() -> Self {
35        Self {
36            family: None,
37            bind_addr: None,
38            interface_addr: None,
39            interface_index: None,
40        }
41    }
42
43    /// Creates an IPv4 raw-IP socket configuration.
44    pub fn ipv4() -> Self {
45        Self::new().with_family(PublicationAddressFamily::Ipv4)
46    }
47
48    /// Creates an IPv6 raw-IP socket configuration.
49    pub fn ipv6() -> Self {
50        Self::new().with_family(PublicationAddressFamily::Ipv6)
51    }
52
53    /// Pins the expected family for supplied datagrams.
54    pub fn with_family(mut self, family: PublicationAddressFamily) -> Self {
55        self.family = Some(family);
56        self
57    }
58
59    /// Binds the raw socket to this exact local IP address when the platform
60    /// supports it.
61    pub fn with_bind_addr(mut self, bind_addr: impl Into<IpAddr>) -> Self {
62        self.bind_addr = Some(bind_addr.into());
63        self
64    }
65
66    /// Selects the required egress interface by one of its local addresses.
67    pub fn with_interface_addr(mut self, interface_addr: impl Into<IpAddr>) -> Self {
68        self.interface_addr = Some(interface_addr.into());
69        self
70    }
71
72    /// Selects the required egress interface by its operating-system index.
73    pub fn with_interface_index(mut self, interface_index: u32) -> Self {
74        self.interface_index = Some(interface_index);
75        self
76    }
77
78    /// Validates the structural configuration before privileged socket setup.
79    pub fn validate(&self) -> Result<(), MctxError> {
80        if self.bind_addr.is_none()
81            && self.interface_addr.is_none()
82            && self.interface_index.is_none()
83        {
84            return Err(MctxError::RawInterfaceRequired);
85        }
86
87        if self.interface_index == Some(0) {
88            return Err(MctxError::RawInterfaceRequired);
89        }
90
91        if let Some(bind_addr) = self.bind_addr {
92            validate_unicast_selector(bind_addr, MctxError::InvalidRawBindAddress)?;
93            if let Some(family) = self.family
94                && !family_matches_ip(family, bind_addr)
95            {
96                return Err(MctxError::RawBindAddressFamilyMismatch);
97            }
98        }
99
100        if let Some(interface_addr) = self.interface_addr {
101            validate_unicast_selector(interface_addr, MctxError::InvalidInterfaceAddress)?;
102            if let Some(family) = self.family
103                && !family_matches_ip(family, interface_addr)
104            {
105                return Err(MctxError::OutgoingInterfaceFamilyMismatch);
106            }
107        }
108
109        if let (Some(bind_addr), Some(interface_addr)) = (self.bind_addr, self.interface_addr)
110            && ip_family(bind_addr) != ip_family(interface_addr)
111        {
112            return Err(MctxError::OutgoingInterfaceFamilyMismatch);
113        }
114
115        if self.family.is_none() && self.bind_addr.is_none() && self.interface_addr.is_none() {
116            return Err(MctxError::RawPacketTransmitUnsupported(
117                "raw IP interface-index selection requires an explicit address family".to_string(),
118            ));
119        }
120
121        Ok(())
122    }
123
124    pub(crate) fn resolved_family(&self) -> Result<PublicationAddressFamily, MctxError> {
125        self.validate()?;
126        self.family
127            .or_else(|| self.bind_addr.map(ip_family))
128            .or_else(|| self.interface_addr.map(ip_family))
129            .ok_or_else(|| {
130                MctxError::RawPacketTransmitUnsupported(
131                    "raw IP socket address family could not be inferred".to_string(),
132                )
133            })
134    }
135}
136
137fn validate_unicast_selector(ip: IpAddr, error: MctxError) -> Result<(), MctxError> {
138    if ip.is_multicast() || ip.is_unspecified() {
139        return Err(error);
140    }
141
142    Ok(())
143}
144
145pub(crate) fn ip_family(ip: IpAddr) -> PublicationAddressFamily {
146    match ip {
147        IpAddr::V4(_) => PublicationAddressFamily::Ipv4,
148        IpAddr::V6(_) => PublicationAddressFamily::Ipv6,
149    }
150}
151
152pub(crate) fn family_matches_ip(family: PublicationAddressFamily, ip: IpAddr) -> bool {
153    matches!(
154        (family, ip),
155        (PublicationAddressFamily::Ipv4, IpAddr::V4(_))
156            | (PublicationAddressFamily::Ipv6, IpAddr::V6(_))
157    )
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use std::net::{Ipv4Addr, Ipv6Addr};
164
165    #[test]
166    fn address_selector_infers_family() {
167        let config = RawIpSocketConfig::new().with_interface_addr(Ipv4Addr::new(192, 0, 2, 10));
168
169        assert_eq!(
170            config.resolved_family().unwrap(),
171            PublicationAddressFamily::Ipv4
172        );
173    }
174
175    #[test]
176    fn index_only_selector_requires_family() {
177        let config = RawIpSocketConfig::new().with_interface_index(7);
178
179        assert!(matches!(
180            config.validate(),
181            Err(MctxError::RawPacketTransmitUnsupported(_))
182        ));
183    }
184
185    #[test]
186    fn zero_interface_index_is_rejected_explicitly() {
187        let config = RawIpSocketConfig::ipv4().with_interface_index(0);
188
189        assert!(matches!(
190            config.validate(),
191            Err(MctxError::RawInterfaceRequired)
192        ));
193    }
194
195    #[test]
196    fn mismatched_selectors_are_rejected() {
197        let config = RawIpSocketConfig::ipv6()
198            .with_bind_addr(Ipv6Addr::LOCALHOST)
199            .with_interface_addr(Ipv4Addr::LOCALHOST);
200
201        assert!(matches!(
202            config.validate(),
203            Err(MctxError::OutgoingInterfaceFamilyMismatch)
204        ));
205    }
206
207    #[test]
208    fn configuration_requires_a_concrete_egress_selector() {
209        assert!(matches!(
210            RawIpSocketConfig::ipv4().validate(),
211            Err(MctxError::RawInterfaceRequired)
212        ));
213    }
214}