1use std::net::IpAddr;
4use std::sync::{Arc, Mutex};
5
6use sipx_sip::{Header, Message, Request};
7use tokio::sync::mpsc;
8
9use crate::counters::Meters;
10use crate::{ConnectionKey, Target, TransportKind};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub struct SourcePrefix {
15 network: IpAddr,
16 bits: u8,
17}
18
19impl SourcePrefix {
20 #[must_use]
22 pub fn new(network: IpAddr, bits: u8) -> Option<Self> {
23 let max = if network.is_ipv4() { 32 } else { 128 };
24 (bits <= max).then_some(Self { network, bits })
25 }
26
27 #[must_use]
29 pub const fn address(address: IpAddr) -> Self {
30 let bits = if address.is_ipv4() { 32 } else { 128 };
31 Self {
32 network: address,
33 bits,
34 }
35 }
36
37 #[must_use]
39 pub fn contains(self, candidate: IpAddr) -> bool {
40 match (self.network, candidate) {
41 (IpAddr::V4(network), IpAddr::V4(candidate)) => prefix_matches(
42 u32::from(network).into(),
43 u32::from(candidate).into(),
44 self.bits,
45 32,
46 ),
47 (IpAddr::V6(network), IpAddr::V6(candidate)) => {
48 prefix_matches(u128::from(network), u128::from(candidate), self.bits, 128)
49 }
50 _ => false,
51 }
52 }
53}
54
55fn prefix_matches(network: u128, candidate: u128, bits: u8, width: u8) -> bool {
56 if bits == 0 {
57 return true;
58 }
59 let shift = u32::from(width.saturating_sub(bits));
60 (network >> shift) == (candidate >> shift)
61}
62
63#[derive(Debug, Clone)]
64struct AdmissionGeneration {
65 number: u64,
66 prefixes: Option<Arc<[SourcePrefix]>>,
67}
68
69#[derive(Debug)]
71pub(crate) struct SourceAdmission {
72 current: Mutex<AdmissionGeneration>,
73 limit: usize,
74}
75
76impl Default for SourceAdmission {
77 fn default() -> Self {
78 Self::new(1024)
79 }
80}
81
82impl SourceAdmission {
83 pub(crate) fn new(limit: usize) -> Self {
84 Self {
85 current: Mutex::new(AdmissionGeneration {
86 number: 0,
87 prefixes: None,
88 }),
89 limit,
90 }
91 }
92 pub(crate) fn admit(&self, address: IpAddr) -> Option<u64> {
94 let current = self
95 .current
96 .lock()
97 .unwrap_or_else(std::sync::PoisonError::into_inner);
98 let allowed = current
99 .prefixes
100 .as_ref()
101 .is_none_or(|prefixes| prefixes.iter().any(|prefix| prefix.contains(address)));
102 allowed.then_some(current.number)
103 }
104
105 pub(crate) fn replace(&self, prefixes: Vec<SourcePrefix>) -> crate::Result<u64> {
106 if prefixes.len() > self.limit {
107 return Err(crate::Error::SourceAdmissionCapacity {
108 max: self.limit,
109 attempted: prefixes.len(),
110 });
111 }
112 Ok(self.publish(Some(prefixes.into())))
113 }
114
115 pub(crate) fn clear(&self) -> u64 {
116 self.publish(None)
117 }
118
119 fn publish(&self, prefixes: Option<Arc<[SourcePrefix]>>) -> u64 {
120 let mut current = self
121 .current
122 .lock()
123 .unwrap_or_else(std::sync::PoisonError::into_inner);
124 current.number = current.number.wrapping_add(1).max(1);
125 current.prefixes = prefixes;
126 current.number
127 }
128}
129
130#[derive(Debug)]
132pub enum RequestPolicyDecision {
133 Allow,
135 Reject(String),
137 AddHeaders(Vec<Header>),
139}
140
141pub trait RequestPolicy: Send + Sync {
143 fn decide(&self, request: &Request, target: &Target) -> RequestPolicyDecision;
145}
146
147#[derive(Clone)]
149pub struct RequestPolicyRef(Arc<dyn RequestPolicy>);
150
151impl RequestPolicyRef {
152 #[must_use]
154 pub fn new(policy: impl RequestPolicy + 'static) -> Self {
155 Self(Arc::new(policy))
156 }
157
158 pub(crate) fn decide(&self, request: &Request, target: &Target) -> RequestPolicyDecision {
159 self.0.decide(request, target)
160 }
161}
162
163impl std::fmt::Debug for RequestPolicyRef {
164 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 formatter.write_str("RequestPolicyRef(..)")
166 }
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub enum MessageDirection {
172 Inbound,
174 Outbound,
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
180pub enum TransactionClass {
181 ServerCreated,
183 Matched,
185 Unmatched,
187 ClientCreated,
189 Direct,
191}
192
193#[derive(Debug, Clone)]
195pub struct MessageObservation {
196 pub message: Message,
198 pub local: std::net::SocketAddr,
200 pub peer: std::net::SocketAddr,
202 pub transport: TransportKind,
204 pub direction: MessageDirection,
206 pub transaction: TransactionClass,
208}
209
210#[derive(Debug, Clone, PartialEq, Eq, Hash)]
212pub struct ConnectionId {
213 pub key: ConnectionKey,
215 pub generation: u64,
217 pub admission_generation: Option<u64>,
219}
220
221#[derive(Debug, Clone, Copy, PartialEq, Eq)]
223pub enum ConnectionState {
224 Accepted,
226 Opened,
228 Authenticated,
230 Pooled,
232 Reused,
234 Failed,
236 Closed,
238}
239
240#[derive(Debug, Clone)]
242pub struct ConnectionObservation {
243 pub connection: ConnectionId,
245 pub state: ConnectionState,
247}
248
249#[derive(Debug, Clone)]
251pub enum EndpointObservation {
252 Message(Box<MessageObservation>),
254 Connection(ConnectionObservation),
256}
257
258#[derive(Debug)]
260pub(crate) struct ObservationHub {
261 sink: Mutex<Option<mpsc::Sender<EndpointObservation>>>,
262 meters: Arc<Meters>,
263}
264
265impl ObservationHub {
266 pub(crate) fn new(meters: Arc<Meters>) -> Self {
267 Self {
268 sink: Mutex::new(None),
269 meters,
270 }
271 }
272
273 pub(crate) fn subscribe(&self, capacity: usize) -> mpsc::Receiver<EndpointObservation> {
274 let (sender, receiver) = mpsc::channel(capacity.max(1));
275 *self
276 .sink
277 .lock()
278 .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(sender);
279 receiver
280 }
281
282 pub(crate) fn emit(&self, event: EndpointObservation) {
283 let mut sink = self
284 .sink
285 .lock()
286 .unwrap_or_else(std::sync::PoisonError::into_inner);
287 let Some(sender) = sink.as_ref() else {
288 return;
289 };
290 match sender.try_send(event) {
291 Ok(()) => {}
292 Err(mpsc::error::TrySendError::Full(_)) => self.meters.observation_drop(),
293 Err(mpsc::error::TrySendError::Closed(_)) => *sink = None,
294 }
295 }
296}
297
298pub(crate) fn connection_event(
299 key: ConnectionKey,
300 generation: u64,
301 admission_generation: Option<u64>,
302 state: ConnectionState,
303) -> EndpointObservation {
304 EndpointObservation::Connection(ConnectionObservation {
305 connection: ConnectionId {
306 key,
307 generation,
308 admission_generation,
309 },
310 state,
311 })
312}
313
314pub(crate) fn policy_header(name: &sipx_sip::HeaderName) -> (sipx_sip::HeaderName, bool) {
316 use sipx_sip::HeaderName;
317 let semantic = HeaderName::parse(&bytes::Bytes::copy_from_slice(name.canonical()));
318 let allowed = matches!(
319 semantic,
320 HeaderName::AlertInfo
321 | HeaderName::CallInfo
322 | HeaderName::Organization
323 | HeaderName::Priority
324 | HeaderName::Subject
325 | HeaderName::UserAgent
326 | HeaderName::Other(_)
327 );
328 (semantic, allowed)
329}
330
331pub(crate) fn duplicate_policy_header(request: &Request, semantic: &sipx_sip::HeaderName) -> bool {
332 !matches!(semantic, sipx_sip::HeaderName::Other(_)) && request.headers.get(semantic).is_some()
333}
334
335#[cfg(test)]
336#[allow(
337 clippy::unwrap_used,
338 clippy::expect_used,
339 clippy::panic,
340 clippy::indexing_slicing
341)]
342mod tests {
343 use super::*;
344 use bytes::Bytes;
345 use sipx_sip::HeaderName;
346 use std::net::{Ipv4Addr, Ipv6Addr};
347
348 #[test]
349 fn prefixes_match_only_their_network_and_family() {
350 let v4 = SourcePrefix::new(Ipv4Addr::new(192, 0, 2, 0).into(), 24).unwrap();
351 assert!(v4.contains(Ipv4Addr::new(192, 0, 2, 42).into()));
352 assert!(!v4.contains(Ipv4Addr::new(192, 0, 3, 1).into()));
353 assert!(!v4.contains(Ipv6Addr::LOCALHOST.into()));
354 }
355
356 #[test]
357 fn replacement_publishes_complete_generations() {
358 let admission = SourceAdmission::default();
359 assert_eq!(admission.admit(IpAddr::V4(Ipv4Addr::LOCALHOST)), Some(0));
360 let one = admission
361 .replace(vec![SourcePrefix::address(IpAddr::V4(Ipv4Addr::LOCALHOST))])
362 .unwrap();
363 assert_eq!(admission.admit(IpAddr::V4(Ipv4Addr::LOCALHOST)), Some(one));
364 assert_eq!(admission.admit(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), None);
365 let two = admission.clear();
366 assert!(two > one);
367 assert_eq!(
368 admission.admit(IpAddr::V4(Ipv4Addr::UNSPECIFIED)),
369 Some(two)
370 );
371 }
372
373 #[test]
374 fn oversized_replacement_preserves_the_old_generation() {
375 let admission = SourceAdmission::new(1);
376 let first = admission
377 .replace(vec![SourcePrefix::address(IpAddr::V4(Ipv4Addr::LOCALHOST))])
378 .unwrap();
379 let error = admission
380 .replace(vec![
381 SourcePrefix::address(IpAddr::V4(Ipv4Addr::LOCALHOST)),
382 SourcePrefix::address(IpAddr::V4(Ipv4Addr::UNSPECIFIED)),
383 ])
384 .unwrap_err();
385 assert!(matches!(
386 error,
387 crate::Error::SourceAdmissionCapacity { .. }
388 ));
389 assert_eq!(
390 admission.admit(IpAddr::V4(Ipv4Addr::LOCALHOST)),
391 Some(first)
392 );
393 assert_eq!(admission.admit(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), None);
394 }
395
396 #[test]
397 fn request_policy_allows_only_application_fields_and_unknown_extensions() {
398 for name in [HeaderName::Subject, HeaderName::Organization] {
399 assert!(policy_header(&name).1);
400 }
401 assert!(policy_header(&HeaderName::Other(Bytes::from_static(b"X-Trace"))).1);
402 for name in [
403 HeaderName::Contact,
404 HeaderName::ContentType,
405 HeaderName::Event,
406 ] {
407 assert!(!policy_header(&name).1);
408 }
409 for raw in [b"vIa".as_slice(), b"v".as_slice()] {
410 let (semantic, allowed) =
411 policy_header(&HeaderName::Other(Bytes::copy_from_slice(raw)));
412 assert_eq!(semantic, HeaderName::Via);
413 assert!(!allowed);
414 }
415 }
416}