mctx_core/raw_ip/
config.rs1use crate::config::PublicationAddressFamily;
2use crate::error::MctxError;
3use std::net::IpAddr;
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub struct RawIpSocketConfig {
12 pub family: Option<PublicationAddressFamily>,
15 pub bind_addr: Option<IpAddr>,
17 pub interface_addr: Option<IpAddr>,
19 pub interface_index: Option<u32>,
21}
22
23pub type RawIpPublicationConfig = RawIpSocketConfig;
25
26impl Default for RawIpSocketConfig {
27 fn default() -> Self {
28 Self::new()
29 }
30}
31
32impl RawIpSocketConfig {
33 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 pub fn ipv4() -> Self {
45 Self::new().with_family(PublicationAddressFamily::Ipv4)
46 }
47
48 pub fn ipv6() -> Self {
50 Self::new().with_family(PublicationAddressFamily::Ipv6)
51 }
52
53 pub fn with_family(mut self, family: PublicationAddressFamily) -> Self {
55 self.family = Some(family);
56 self
57 }
58
59 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 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 pub fn with_interface_index(mut self, interface_index: u32) -> Self {
74 self.interface_index = Some(interface_index);
75 self
76 }
77
78 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}