Skip to main content

sipx_transport/
target.rs

1//! Where a message goes, and how a response finds its way back.
2
3use std::net::{IpAddr, SocketAddr};
4use std::sync::Arc;
5
6use sipx_sip::headers::Via;
7use sipx_sip::transaction::Reliability;
8
9/// Which transport a message travels over.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum TransportKind {
12    /// UDP: retransmits, one message per datagram.
13    Udp,
14    /// TCP: reliable, `Content-Length` framing.
15    Tcp,
16    /// TLS over TCP.
17    Tls,
18    /// SIP over WebSocket (RFC 7118).
19    Ws,
20    /// SIP over secure WebSocket.
21    Wss,
22    /// SIP over QUIC using sipx's experimental mapping.
23    Quic,
24}
25
26impl TransportKind {
27    /// Whether the transport delivers reliably, which decides half the transaction timers.
28    #[must_use]
29    pub fn reliability(self) -> Reliability {
30        match self {
31            Self::Udp => Reliability::Unreliable,
32            _ => Reliability::Reliable,
33        }
34    }
35
36    /// The token this transport is spelled with in a `Via`.
37    #[must_use]
38    pub fn as_str(self) -> &'static str {
39        match self {
40            Self::Udp => "UDP",
41            Self::Tcp => "TCP",
42            Self::Tls => "TLS",
43            Self::Ws => "WS",
44            Self::Wss => "WSS",
45            Self::Quic => "QUIC",
46        }
47    }
48
49    /// Whether this transport protects the signalling itself.
50    ///
51    /// The question SDES turns on. RFC 4568 §7.1 makes a secure signalling path a *condition* of
52    /// carrying a key in SDP, because the key travels in the body — so this decides whether sipx
53    /// may offer encrypted media at all.
54    #[must_use]
55    pub fn is_secure(self) -> bool {
56        matches!(self, Self::Tls | Self::Wss | Self::Quic)
57    }
58
59    /// The default port, per RFC 3261 §19.1.2 and RFC 7118.
60    #[must_use]
61    pub fn default_port(self) -> u16 {
62        match self {
63            Self::Udp | Self::Tcp => 5060,
64            Self::Tls | Self::Quic => 5061,
65            Self::Ws => 80,
66            Self::Wss => 443,
67        }
68    }
69
70    /// Resolve a transport token from a `Via` or a URI parameter.
71    #[must_use]
72    pub fn parse(token: &[u8]) -> Option<Self> {
73        match token.to_ascii_uppercase().as_slice() {
74            b"UDP" => Some(Self::Udp),
75            b"TCP" => Some(Self::Tcp),
76            b"TLS" => Some(Self::Tls),
77            b"WS" => Some(Self::Ws),
78            b"WSS" => Some(Self::Wss),
79            b"QUIC" => Some(Self::Quic),
80            _ => None,
81        }
82    }
83}
84
85/// A destination.
86///
87/// Not `Copy`, because of `verify_as`. That field is the reason this type exists rather than a
88/// bare `(SocketAddr, TransportKind)`: the address says where to send, while the original URI host
89/// says which TLS identity to verify and which HTTP authority a WebSocket handshake must name.
90/// Deriving either from the resolved address loses authority information before the connection.
91#[derive(Debug, Clone, PartialEq, Eq, Hash)]
92pub struct Target {
93    /// Where to send.
94    pub addr: SocketAddr,
95    /// How to send.
96    pub transport: TransportKind,
97    /// Original URI host before resolution: TLS/WSS verification name and WS/WSS HTTP authority.
98    /// For clear WS it is authority only; no certificate verification is implied.
99    pub verify_as: Option<Arc<str>>,
100    /// The resource the WebSocket handshake asks for. `None` means `/`, and `None` outside
101    /// WebSocket, where there is no handshake to ask anything of.
102    pub path: Option<Arc<str>>,
103}
104
105impl Target {
106    /// A destination.
107    #[must_use]
108    pub fn new(addr: SocketAddr, transport: TransportKind) -> Self {
109        Self {
110            addr,
111            transport,
112            verify_as: None,
113            path: None,
114        }
115    }
116
117    /// A UDP destination.
118    #[must_use]
119    pub fn udp(addr: SocketAddr) -> Self {
120        Self::new(addr, TransportKind::Udp)
121    }
122
123    /// The same destination, with its pre-resolution URI host.
124    ///
125    /// TLS/WSS verify certificates against it. WS/WSS also put it in the HTTP `Host` authority;
126    /// clear WS uses that authority without implying authentication.
127    #[must_use]
128    pub fn verifying(mut self, name: impl AsRef<str>) -> Self {
129        self.verify_as = Some(Arc::from(name.as_ref()));
130        self
131    }
132
133    /// The same destination, with the resource its WebSocket handshake asks for.
134    ///
135    /// RFC 7118 §5 registers a subprotocol and a set of framing rules; it says nothing at all
136    /// about where on a server SIP lives. A server is therefore entitled to serve it from `/`,
137    /// from `/ws`, or from its own HTTP server on another port, and a client that can only ask
138    /// for `/` reaches the first kind and none of the others.
139    ///
140    /// A leading `/` is supplied when it is missing, because a resource name that lacks one is
141    /// not a relative path in a request-target — it runs into the authority and silently sends
142    /// the upgrade somewhere nobody meant. Anything after the path is kept as given: a server
143    /// that wants a query string gets the one it was handed.
144    #[must_use]
145    pub fn at_path(mut self, path: impl AsRef<str>) -> Self {
146        let path = path.as_ref();
147        self.path = Some(if path.starts_with('/') {
148            Arc::from(path)
149        } else {
150            Arc::from(format!("/{path}"))
151        });
152        self
153    }
154
155    /// Which pooled connection carries traffic for this destination.
156    #[must_use]
157    pub fn connection(&self) -> ConnectionKey {
158        ConnectionKey {
159            peer: self.addr,
160            transport: self.transport,
161            identity: self.verify_as.clone(),
162            path: if matches!(self.transport, TransportKind::Ws | TransportKind::Wss) {
163                self.path.clone()
164            } else {
165                None
166            },
167        }
168    }
169}
170
171/// What makes two connections the same connection.
172///
173/// Not the address alone, and each of the other two fields earns its place.
174///
175/// **The transport**, because TCP, TLS and QUIC to one address are not interchangeable: a `sips:`
176/// request riding a cleartext socket has silently become what it asked not to be. With
177/// WebSocket in the mix the case stops being theoretical — WS and TCP can and do share a port.
178///
179/// **The URI authority/verified identity**, because two names resolving to one address are still
180/// distinct TLS identities and distinct WebSocket HTTP authorities. Reusing one for another either
181/// throws away certificate verification or sends traffic on an upgrade granted to a different
182/// virtual host. `None` on a connection a peer opened: sipx selected no outbound authority.
183///
184/// **The WebSocket resource**, for the same reason one step down: a socket upgraded at `/ws` was
185/// accepted by whatever serves `/ws`, and handing it traffic that asked for `/other` ignores the
186/// only thing the target said about where it wanted to go. `None` everywhere the question does
187/// not arise — every other transport, and every connection a peer opened.
188#[derive(Debug, Clone, PartialEq, Eq, Hash)]
189pub struct ConnectionKey {
190    /// The far end.
191    pub peer: SocketAddr,
192    /// Which transport it speaks.
193    pub transport: TransportKind,
194    /// Original URI host: verified for TLS/WSS and used as authority for WS/WSS.
195    pub identity: Option<Arc<str>>,
196    /// The resource the upgrade asked for, for WebSocket connections sipx opened.
197    pub path: Option<Arc<str>>,
198}
199
200impl ConnectionKey {
201    /// A connection with nothing verified about it and no resource named — anything a peer
202    /// opened, and every cleartext transport.
203    #[must_use]
204    pub fn new(peer: SocketAddr, transport: TransportKind) -> Self {
205        Self {
206            peer,
207            transport,
208            identity: None,
209            path: None,
210        }
211    }
212
213    /// The resource a WebSocket handshake for this connection asks for.
214    ///
215    /// `/` when the target named none, which is what RFC 6455 §3 requires of a request-target
216    /// that would otherwise be empty and what every server serving SIP at its root expects.
217    #[must_use]
218    pub fn ws_path(&self) -> &str {
219        self.path.as_deref().unwrap_or("/")
220    }
221}
222
223/// Where a response to this request must be sent (RFC 3261 §18.2.2).
224///
225/// The order is the RFC's and each step exists for a reason: `maddr` is an explicit override,
226/// `received` is where the request actually came from as opposed to where the sender believed
227/// it was, and the sent-by is what the sender claims. Behind a NAT only `received` is true,
228/// which is why the fallback order matters more than it looks.
229#[must_use]
230pub fn response_destination(via: &Via, source: SocketAddr, transport: TransportKind) -> Target {
231    // 1. An explicit maddr wins.
232    if let Some(maddr) = via.maddr()
233        && let Some(addr) = parse_host(maddr)
234    {
235        let port = via.port.unwrap_or_else(|| transport.default_port());
236        return Target::new(SocketAddr::new(addr, port), transport);
237    }
238
239    // RFC 3581 §4: an observed `rport` names the port the response has to go to, whichever
240    // address the steps below settle on. It is not tied to `received` — a client whose
241    // sent-by host is right but whose port was rewritten, or which simply sent from an
242    // ephemeral socket, has its pinhole open here and nothing listening on the claimed port.
243    let observed_port = via
244        .rport()
245        .flatten()
246        .and_then(|v| std::str::from_utf8(v).ok())
247        .and_then(|v| v.parse::<u16>().ok());
248
249    // 2. received, at the rport if the sender asked us to observe one.
250    if let Some(received) = via.received()
251        && let Some(addr) = parse_host(received)
252    {
253        let port = observed_port
254            .or(via.port)
255            .unwrap_or_else(|| transport.default_port());
256        return Target::new(SocketAddr::new(addr, port), transport);
257    }
258
259    // 3. The sent-by, if it is an address we can use directly.
260    if let sipx_sip::Host::Ip(ip) = &via.host {
261        let port = observed_port
262            .or(via.port)
263            .unwrap_or_else(|| transport.default_port());
264        return Target::new(SocketAddr::new(*ip, port), transport);
265    }
266
267    // A hostname sent-by needs resolution, which the caller does. Falling back to the source
268    // address is both the safest answer and, behind a NAT, the only one that works.
269    Target::new(source, transport)
270}
271
272fn parse_host(raw: &[u8]) -> Option<IpAddr> {
273    std::str::from_utf8(raw).ok()?.parse().ok()
274}
275
276#[cfg(test)]
277#[allow(
278    clippy::unwrap_used,
279    clippy::expect_used,
280    clippy::panic,
281    clippy::indexing_slicing
282)]
283mod tests {
284    use super::*;
285
286    fn via(text: &str) -> Via {
287        Via::parse_one(text.as_bytes()).expect("a valid Via")
288    }
289
290    fn source() -> SocketAddr {
291        "203.0.113.9:41234".parse().expect("a valid address")
292    }
293
294    #[test]
295    fn a_plain_via_goes_to_its_sent_by() {
296        let target = response_destination(
297            &via("SIP/2.0/UDP 192.0.2.1:5060;branch=z9hG4bKx"),
298            source(),
299            TransportKind::Udp,
300        );
301        assert_eq!(target.addr.to_string(), "192.0.2.1:5060");
302    }
303
304    #[test]
305    fn a_sent_by_without_a_port_uses_the_transport_default() {
306        assert_eq!(
307            response_destination(
308                &via("SIP/2.0/UDP 192.0.2.1;branch=z9hG4bKx"),
309                source(),
310                TransportKind::Udp
311            )
312            .addr
313            .port(),
314            5060
315        );
316        assert_eq!(
317            response_destination(
318                &via("SIP/2.0/TLS 192.0.2.1;branch=z9hG4bKx"),
319                source(),
320                TransportKind::Tls
321            )
322            .addr
323            .port(),
324            5061
325        );
326    }
327
328    /// RFC 3581 §4: when the topmost `Via` carries an `rport`, the response goes to the source
329    /// IP address *and port* the request came from. The port matters on its own — a client
330    /// whose sent-by names the right host but the wrong port (an ephemeral socket, or a NAT
331    /// that rewrote only the port) has a pinhole open on the observed port and nothing
332    /// listening on the claimed one.
333    #[test]
334    fn an_observed_rport_is_used_even_without_a_received() {
335        let target = response_destination(
336            &via("SIP/2.0/UDP 203.0.113.9:5060;rport=41234;branch=z9hG4bKx"),
337            source(),
338            TransportKind::Udp,
339        );
340        assert_eq!(target.addr.to_string(), "203.0.113.9:41234");
341    }
342
343    /// The NAT case, and the reason this function is not one line. The sender believes it is
344    /// at 10.0.0.5:5060; it is actually behind a NAT and reachable only at the observed
345    /// address and port.
346    #[test]
347    fn received_and_rport_override_the_sent_by() {
348        let target = response_destination(
349            &via("SIP/2.0/UDP 10.0.0.5:5060;received=203.0.113.9;rport=41234;branch=z9hG4bKx"),
350            source(),
351            TransportKind::Udp,
352        );
353        assert_eq!(target.addr.to_string(), "203.0.113.9:41234");
354    }
355
356    #[test]
357    fn received_without_rport_uses_the_sent_by_port() {
358        let target = response_destination(
359            &via("SIP/2.0/UDP 10.0.0.5:5070;received=203.0.113.9;branch=z9hG4bKx"),
360            source(),
361            TransportKind::Udp,
362        );
363        assert_eq!(target.addr.to_string(), "203.0.113.9:5070");
364    }
365
366    #[test]
367    fn maddr_wins_over_everything() {
368        let target = response_destination(
369            &via("SIP/2.0/UDP 10.0.0.5:5060;maddr=192.0.2.99;received=203.0.113.9;branch=z9hG4bKx"),
370            source(),
371            TransportKind::Udp,
372        );
373        assert_eq!(target.addr.ip().to_string(), "192.0.2.99");
374    }
375
376    /// A hostname sent-by cannot be used without resolving it, and the source address is both
377    /// the safest fallback and the only one that works behind a NAT.
378    #[test]
379    fn a_hostname_sent_by_falls_back_to_the_source() {
380        let target = response_destination(
381            &via("SIP/2.0/UDP client.example.com;branch=z9hG4bKx"),
382            source(),
383            TransportKind::Udp,
384        );
385        assert_eq!(target.addr, source());
386    }
387
388    #[test]
389    fn transports_have_their_rfc_default_ports() {
390        assert_eq!(TransportKind::Udp.default_port(), 5060);
391        assert_eq!(TransportKind::Tcp.default_port(), 5060);
392        assert_eq!(TransportKind::Tls.default_port(), 5061);
393        assert_eq!(TransportKind::Ws.default_port(), 80);
394        assert_eq!(TransportKind::Wss.default_port(), 443);
395        assert_eq!(TransportKind::Quic.default_port(), 5061);
396    }
397
398    fn peer() -> SocketAddr {
399        "127.0.0.1:8088".parse().expect("a valid address")
400    }
401
402    /// Nothing that works today changes: a target that names no resource asks for the root,
403    /// which is where every server sipx has ever reached serves SIP.
404    #[test]
405    fn a_target_asks_for_the_root_unless_it_says_otherwise() {
406        let target = Target::new(peer(), TransportKind::Ws);
407        assert_eq!(target.path, None);
408        assert_eq!(target.connection().ws_path(), "/");
409    }
410
411    #[test]
412    fn a_target_can_name_the_resource_it_wants() {
413        let target = Target::new(peer(), TransportKind::Ws).at_path("/ws");
414        assert_eq!(target.path.as_deref(), Some("/ws"));
415        assert_eq!(target.connection().ws_path(), "/ws");
416    }
417
418    /// A resource name without a leading slash is not a relative path in a request-target — it
419    /// runs into the authority, and `ws://127.0.0.1:8088ws` is a request to somewhere nobody
420    /// meant. Supplying the slash is the difference between a typo and a silent misdirection.
421    #[test]
422    fn a_resource_name_missing_its_leading_slash_gets_one() {
423        for named in ["ws", "/ws"] {
424            assert_eq!(
425                Target::new(peer(), TransportKind::Ws)
426                    .at_path(named)
427                    .connection()
428                    .ws_path(),
429                "/ws"
430            );
431        }
432        assert_eq!(
433            Target::new(peer(), TransportKind::Ws)
434                .at_path("")
435                .connection()
436                .ws_path(),
437            "/",
438            "naming nothing is naming the root"
439        );
440    }
441
442    /// The same argument the verified identity makes, one step down: a socket upgraded at `/ws`
443    /// was accepted by whatever serves `/ws`, so handing it traffic that asked for somewhere
444    /// else throws away the only thing the target said about where it was going.
445    #[test]
446    fn two_resources_on_one_address_are_two_connections() {
447        let one = Target::new(peer(), TransportKind::Ws).at_path("/ws");
448        let other = Target::new(peer(), TransportKind::Ws).at_path("/sip");
449        assert_ne!(one.connection(), other.connection());
450        assert_ne!(
451            one.connection(),
452            Target::new(peer(), TransportKind::Ws).connection(),
453            "the root is a resource like any other"
454        );
455    }
456
457    #[test]
458    fn only_udp_is_unreliable() {
459        assert_eq!(TransportKind::Udp.reliability(), Reliability::Unreliable);
460        for t in [
461            TransportKind::Tcp,
462            TransportKind::Tls,
463            TransportKind::Ws,
464            TransportKind::Wss,
465            TransportKind::Quic,
466        ] {
467            assert_eq!(t.reliability(), Reliability::Reliable);
468        }
469    }
470
471    #[test]
472    fn quic_is_a_secure_reliable_via_transport() {
473        assert_eq!(TransportKind::parse(b"QUIC"), Some(TransportKind::Quic));
474        assert_eq!(TransportKind::Quic.as_str(), "QUIC");
475        assert!(TransportKind::Quic.is_secure());
476    }
477
478    #[test]
479    fn quic_pool_keys_keep_verified_names_and_transports_separate() {
480        let one = Target::new(peer(), TransportKind::Quic)
481            .verifying("one.example")
482            .connection();
483        let two = Target::new(peer(), TransportKind::Quic)
484            .verifying("two.example")
485            .connection();
486        let tls = Target::new(peer(), TransportKind::Tls)
487            .verifying("one.example")
488            .connection();
489        assert_ne!(one, two, "Q15: two authenticated names are two connections");
490        assert_ne!(one, tls, "Q16: QUIC and TLS cannot share a connection");
491    }
492
493    #[test]
494    fn quic_pool_keys_never_include_a_websocket_resource() {
495        let plain = Target::new(peer(), TransportKind::Quic)
496            .verifying("one.example")
497            .connection();
498        let with_irrelevant_path = Target::new(peer(), TransportKind::Quic)
499            .verifying("one.example")
500            .at_path("/ws")
501            .connection();
502        assert_eq!(plain, with_irrelevant_path);
503        assert_eq!(plain.path, None);
504    }
505}