relay.rs (27287B)
1 //! Nostr relay identifiers and network policy. 2 3 use crate::{Error, RelayEndpoint}; 4 use async_wsocket::Message; 5 use async_wsocket::futures_util::stream::SplitSink; 6 use async_wsocket::futures_util::{Sink, SinkExt, StreamExt}; 7 use core::fmt; 8 use core::pin::Pin; 9 use nostr_relay_pool::ConnectionMode; 10 use nostr_relay_pool::transport::error::TransportError; 11 use nostr_relay_pool::transport::websocket::{WebSocketSink, WebSocketStream, WebSocketTransport}; 12 use radroots_transport::{BoxFuture, Target, TargetNetworkPolicy, TransportId}; 13 use std::collections::BTreeMap; 14 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; 15 use std::sync::Arc; 16 use std::task::{Context, Poll}; 17 use std::time::Duration; 18 use tokio::net::TcpStream; 19 use tokio_tungstenite::tungstenite::Message as WireMessage; 20 use tokio_tungstenite::{MaybeTlsStream, WebSocketStream as TokioWebSocketStream}; 21 use url::Url; 22 23 const MAX_RESOLVED_ADDRESSES: usize = 32; 24 pub(crate) const MAX_WIRE_MESSAGE_BYTES: usize = 512 * 1024; 25 26 /// Validated canonical Nostr relay URL. 27 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 28 pub struct RelayUrl(String); 29 30 impl RelayUrl { 31 /// Parses, canonicalizes, and applies an explicit destination policy. 32 pub fn parse(value: impl AsRef<str>, policy: RelayUrlPolicy) -> Result<Self, Error> { 33 let original = value.as_ref(); 34 let target = match policy { 35 RelayUrlPolicy::PrivateNetwork => { 36 Target::nostr_relay_with_policy(original, TargetNetworkPolicy::PrivateDevice) 37 } 38 RelayUrlPolicy::Public | RelayUrlPolicy::Local => Target::nostr_relay(original), 39 } 40 .map_err(|_| Error::InvalidRelayUrl)?; 41 let canonical = target.uri().as_str(); 42 let parsed = Url::parse(canonical).map_err(|_| Error::InvalidRelayUrl)?; 43 let host = parsed.host_str().ok_or(Error::InvalidRelayUrl)?; 44 validate_scheme(canonical, parsed.scheme(), policy)?; 45 validate_host(canonical, host, policy)?; 46 Ok(Self(canonical.to_owned())) 47 } 48 49 /// Converts a validated relay URL into the generic Nostr target model. 50 pub fn to_target(&self) -> Result<Target, Error> { 51 Target::nostr_relay(self.as_str()) 52 .or_else(|_| { 53 Target::nostr_relay_with_policy(self.as_str(), TargetNetworkPolicy::PrivateDevice) 54 }) 55 .map_err(|_| Error::Target) 56 } 57 58 /// Validates and converts a generic target under the selected policy. 59 pub fn from_target(target: &Target, policy: RelayUrlPolicy) -> Result<Self, Error> { 60 if *target.kind() != TransportId::NOSTR { 61 return Err(Error::UnexpectedTransport); 62 } 63 Self::parse(target.uri().as_str(), policy) 64 } 65 66 /// Revalidates every address returned by DNS before a connection is made. 67 pub fn validate_resolved_addresses( 68 &self, 69 policy: RelayUrlPolicy, 70 addresses: impl IntoIterator<Item = IpAddr>, 71 ) -> Result<(), Error> { 72 let mut resolved = false; 73 for address in addresses { 74 resolved = true; 75 if !policy.accepts_address(address) { 76 return Err(Error::ResolvedAddressDenied); 77 } 78 } 79 if !resolved { 80 return Err(Error::EmptyResolution); 81 } 82 Ok(()) 83 } 84 85 /// Returns the canonical relay URL. 86 pub fn as_str(&self) -> &str { 87 self.0.as_str() 88 } 89 } 90 91 impl fmt::Display for RelayUrl { 92 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 93 formatter.write_str(self.as_str()) 94 } 95 } 96 97 /// WebSocket connector that validates and pins DNS results before opening a 98 /// socket while retaining the original host name for TLS verification. 99 #[derive(Clone, Debug)] 100 pub(crate) struct HardenedWebsocketTransport { 101 policies: Arc<BTreeMap<String, RelayUrlPolicy>>, 102 pub(crate) writers: crate::socket_write::WriterRegistry, 103 pub(crate) ingress: crate::source_ingress::IngressRegistry, 104 } 105 106 impl HardenedWebsocketTransport { 107 pub(crate) fn new(endpoints: &[RelayEndpoint]) -> Self { 108 Self { 109 ingress: crate::source_ingress::IngressRegistry::new( 110 endpoints 111 .iter() 112 .map(|endpoint| endpoint.url().as_str().to_owned()), 113 ), 114 writers: crate::socket_write::WriterRegistry::new( 115 endpoints 116 .iter() 117 .map(|endpoint| endpoint.url().as_str().to_owned()), 118 ), 119 policies: Arc::new( 120 endpoints 121 .iter() 122 .map(|endpoint| (endpoint.url().as_str().to_owned(), endpoint.policy())) 123 .collect(), 124 ), 125 } 126 } 127 } 128 129 impl WebSocketTransport for HardenedWebsocketTransport { 130 fn support_ping(&self) -> bool { 131 true 132 } 133 134 // Direct DNS/socket/TLS behavior is verified by the network-hardening 135 // integration suite; deterministic coverage owns the surrounding policy. 136 #[cfg_attr(coverage_nightly, coverage(off))] 137 fn connect<'a>( 138 &'a self, 139 url: &'a Url, 140 mode: &'a ConnectionMode, 141 timeout: Duration, 142 ) -> BoxFuture<'a, Result<(WebSocketSink, WebSocketStream), TransportError>> { 143 Box::pin(async move { 144 if !matches!(mode, ConnectionMode::Direct) { 145 return Err(policy_error( 146 "proxy and Tor connection modes are not configured", 147 )); 148 } 149 let policy = self 150 .policies 151 .get(configured_policy_key(url)) 152 .copied() 153 .ok_or_else(|| policy_error("relay URL is not configured"))?; 154 let relay = RelayUrl::parse(url.as_str(), policy) 155 .map_err(|_| policy_error("relay URL is denied by network policy"))?; 156 let parsed = 157 Url::parse(relay.as_str()).map_err(|_| policy_error("relay URL is invalid"))?; 158 let host = parsed 159 .host_str() 160 .ok_or_else(|| policy_error("relay URL host is missing"))?; 161 let port = parsed 162 .port_or_known_default() 163 .ok_or_else(|| policy_error("relay URL port is missing"))?; 164 165 let connect = async { 166 let ingress = self 167 .ingress 168 .connection(relay.as_str()) 169 .ok_or_else(|| policy_error("relay ingress unavailable"))?; 170 let addresses = resolve_bounded(host, port).await?; 171 relay 172 .validate_resolved_addresses(policy, addresses.iter().map(SocketAddr::ip)) 173 .map_err(|_| policy_error("relay DNS result is denied by network policy"))?; 174 let tcp = connect_pinned(addresses.as_slice()).await?; 175 let plaintext = if parsed.scheme() == "wss" { 176 let mut roots = rustls::RootCertStore::empty(); 177 roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); 178 let tls_config = rustls::ClientConfig::builder() 179 .with_root_certificates(roots) 180 .with_no_client_auth(); 181 let server_name = rustls::pki_types::ServerName::try_from( 182 host.trim_matches(['[', ']']).to_owned(), 183 ) 184 .map_err(|_| policy_error("relay TLS server name is invalid"))?; 185 let tls = tokio_rustls::TlsConnector::from(Arc::new(tls_config)) 186 .connect(server_name, tcp) 187 .await 188 .map_err(TransportError::backend)?; 189 MaybeTlsStream::Rustls(tls) 190 } else { 191 MaybeTlsStream::Plain(tcp) 192 }; 193 let metered = crate::source_wire::MeteredIo::new(plaintext, ingress); 194 let config = tokio_tungstenite::tungstenite::protocol::WebSocketConfig::default() 195 .max_message_size(Some(MAX_WIRE_MESSAGE_BYTES)) 196 .max_frame_size(Some(MAX_WIRE_MESSAGE_BYTES)); 197 let (stream, _) = tokio_tungstenite::client_async_with_config( 198 relay.as_str(), 199 metered, 200 Some(config), 201 ) 202 .await 203 .map_err(TransportError::backend)?; 204 let (tx, rx) = stream.split(); 205 let writer = 206 crate::socket_write::SocketWriter::new(Box::new(HardenedTransportSink(tx))); 207 self.writers.install(relay.as_str(), &writer)?; 208 let sink: WebSocketSink = 209 Box::new(crate::socket_write::SharedSocketSink::new(writer)); 210 let stream: WebSocketStream = Box::pin( 211 rx.map(|result| result.map_err(TransportError::backend).and_then(from_wire)), 212 ); 213 Ok((sink, stream)) 214 }; 215 216 tokio::time::timeout(timeout, connect) 217 .await 218 .map_err(|_| policy_error("relay connection deadline elapsed"))? 219 }) 220 } 221 } 222 223 fn configured_policy_key(url: &Url) -> &str { 224 if url.path() == "/" && url.query().is_none() && url.fragment().is_none() { 225 url.as_str().strip_suffix('/').unwrap_or(url.as_str()) 226 } else { 227 url.as_str() 228 } 229 } 230 231 #[cfg_attr(coverage_nightly, coverage(off))] 232 async fn resolve_bounded(host: &str, port: u16) -> Result<Vec<SocketAddr>, TransportError> { 233 let mut addresses = tokio::net::lookup_host((host, port)) 234 .await 235 .map_err(|_| policy_error("relay DNS resolution failed"))?; 236 let mut bounded = Vec::new(); 237 for address in addresses.by_ref().take(MAX_RESOLVED_ADDRESSES + 1) { 238 bounded.push(address); 239 } 240 if bounded.is_empty() { 241 return Err(policy_error("relay DNS resolution returned no addresses")); 242 } 243 if bounded.len() > MAX_RESOLVED_ADDRESSES { 244 return Err(policy_error( 245 "relay DNS resolution exceeded its address limit", 246 )); 247 } 248 Ok(bounded) 249 } 250 251 #[cfg_attr(coverage_nightly, coverage(off))] 252 async fn connect_pinned(addresses: &[SocketAddr]) -> Result<TcpStream, TransportError> { 253 for address in addresses { 254 if let Ok(stream) = TcpStream::connect(address).await { 255 return Ok(stream); 256 } 257 } 258 Err(policy_error("relay connection failed")) 259 } 260 261 #[derive(Debug)] 262 struct NetworkPolicyError(&'static str); 263 264 impl fmt::Display for NetworkPolicyError { 265 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 266 formatter.write_str(self.0) 267 } 268 } 269 270 impl std::error::Error for NetworkPolicyError {} 271 272 pub(crate) fn policy_error(message: &'static str) -> TransportError { 273 TransportError::backend(NetworkPolicyError(message)) 274 } 275 276 type MeteredSocket = TokioWebSocketStream<crate::source_wire::MeteredIo<MaybeTlsStream<TcpStream>>>; 277 278 struct HardenedTransportSink(SplitSink<MeteredSocket, WireMessage>); 279 280 fn from_wire(message: WireMessage) -> Result<Message, TransportError> { 281 Ok(match message { 282 WireMessage::Text(text) => Message::Text(text.to_string()), 283 WireMessage::Binary(bytes) => Message::Binary(bytes.to_vec()), 284 WireMessage::Ping(bytes) => Message::Ping(bytes.to_vec()), 285 WireMessage::Pong(bytes) => Message::Pong(bytes.to_vec()), 286 WireMessage::Close(frame) => Message::Close(frame.map(Into::into)), 287 WireMessage::Frame(_) => return Err(policy_error("unexpected raw WebSocket frame")), 288 }) 289 } 290 291 impl Sink<Message> for HardenedTransportSink { 292 type Error = TransportError; 293 294 #[cfg_attr(coverage_nightly, coverage(off))] 295 fn poll_ready( 296 mut self: Pin<&mut Self>, 297 context: &mut Context<'_>, 298 ) -> Poll<Result<(), Self::Error>> { 299 Pin::new(&mut self.0) 300 .poll_ready_unpin(context) 301 .map_err(TransportError::backend) 302 } 303 304 #[cfg_attr(coverage_nightly, coverage(off))] 305 fn start_send(mut self: Pin<&mut Self>, item: Message) -> Result<(), Self::Error> { 306 Pin::new(&mut self.0) 307 .start_send_unpin(item.into()) 308 .map_err(TransportError::backend) 309 } 310 311 #[cfg_attr(coverage_nightly, coverage(off))] 312 fn poll_flush( 313 mut self: Pin<&mut Self>, 314 context: &mut Context<'_>, 315 ) -> Poll<Result<(), Self::Error>> { 316 Pin::new(&mut self.0) 317 .poll_flush_unpin(context) 318 .map_err(TransportError::backend) 319 } 320 321 #[cfg_attr(coverage_nightly, coverage(off))] 322 fn poll_close( 323 mut self: Pin<&mut Self>, 324 context: &mut Context<'_>, 325 ) -> Poll<Result<(), Self::Error>> { 326 Pin::new(&mut self.0) 327 .poll_close_unpin(context) 328 .map_err(TransportError::backend) 329 } 330 } 331 332 /// Destination class authorized for relay connections. 333 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 334 #[non_exhaustive] 335 pub enum RelayUrlPolicy { 336 /// TLS-only public Internet endpoints; resolved addresses must be global. 337 Public, 338 /// Exact loopback endpoints; plaintext WebSocket is allowed. 339 Local, 340 /// Exact RFC1918 IPv4 or ULA IPv6 device endpoints. 341 PrivateNetwork, 342 } 343 344 impl RelayUrlPolicy { 345 fn accepts_address(self, address: IpAddr) -> bool { 346 match self { 347 Self::Public => public_address(address), 348 Self::Local => address.is_loopback(), 349 Self::PrivateNetwork => trusted_network_address(address), 350 } 351 } 352 } 353 354 fn validate_scheme(_url: &str, scheme: &str, policy: RelayUrlPolicy) -> Result<(), Error> { 355 if scheme == "wss" 356 || scheme == "ws" 357 && matches!( 358 policy, 359 RelayUrlPolicy::Local | RelayUrlPolicy::PrivateNetwork 360 ) 361 { 362 return Ok(()); 363 } 364 Err(Error::RelaySchemeDenied) 365 } 366 367 fn validate_host(_url: &str, host: &str, policy: RelayUrlPolicy) -> Result<(), Error> { 368 let address = host.trim_matches(['[', ']']).parse::<IpAddr>().ok(); 369 let accepted = match (policy, address) { 370 (RelayUrlPolicy::Public, Some(address)) => public_address(address), 371 (RelayUrlPolicy::Public, None) => public_hostname(host), 372 (RelayUrlPolicy::Local, Some(address)) => address.is_loopback(), 373 (RelayUrlPolicy::Local, None) => host.eq_ignore_ascii_case("localhost"), 374 (RelayUrlPolicy::PrivateNetwork, Some(address)) => trusted_network_address(address), 375 (RelayUrlPolicy::PrivateNetwork, None) => false, 376 }; 377 if accepted { 378 Ok(()) 379 } else { 380 Err(Error::RelayDestinationDenied) 381 } 382 } 383 384 fn public_hostname(host: &str) -> bool { 385 let host = host.trim_end_matches('.').to_ascii_lowercase(); 386 host.contains('.') 387 && host != "localhost" 388 && !host.ends_with(".localhost") 389 && !host.ends_with(".local") 390 && !host.ends_with(".home.arpa") 391 } 392 393 fn public_address(address: IpAddr) -> bool { 394 match address { 395 IpAddr::V4(address) => public_ipv4(address), 396 IpAddr::V6(address) => public_ipv6(address), 397 } 398 } 399 400 fn trusted_network_address(address: IpAddr) -> bool { 401 match address { 402 IpAddr::V4(address) => address.is_private(), 403 IpAddr::V6(address) => address.segments()[0] & 0xfe00 == 0xfc00, 404 } 405 } 406 407 fn public_ipv4(address: Ipv4Addr) -> bool { 408 let octets = address.octets(); 409 !(octets[0] == 0 410 || address.is_loopback() 411 || address.is_private() 412 || address.is_link_local() 413 || address.is_multicast() 414 || address.is_documentation() 415 || octets[0] == 100 && (64..=127).contains(&octets[1]) 416 || octets[0] == 192 && octets[1] == 0 && octets[2] == 0 417 || octets[0] == 192 && octets[1] == 88 && octets[2] == 99 418 || octets[0] == 198 && matches!(octets[1], 18 | 19) 419 || octets[0] >= 240) 420 } 421 422 fn public_ipv6(address: Ipv6Addr) -> bool { 423 if let Some(mapped) = address.to_ipv4_mapped() { 424 return public_ipv4(mapped); 425 } 426 let segments = address.segments(); 427 (segments[0] & 0xe000) == 0x2000 428 && !(segments[0] == 0x2001 && segments[1] <= 0x01ff) 429 && !(segments[0] == 0x2001 && segments[1] == 0x0db8) 430 && segments[0] != 0x2002 431 && !(segments[0] == 0x3fff && (segments[1] & 0xf000) == 0) 432 } 433 434 #[cfg(test)] 435 mod tests { 436 use super::*; 437 438 #[test] 439 fn metered_socket_conversion_preserves_data_control_and_close_messages() { 440 for message in [ 441 WireMessage::Text("text".into()), 442 WireMessage::Binary(vec![0, 255].into()), 443 WireMessage::Ping(vec![1, 2].into()), 444 WireMessage::Pong(vec![3].into()), 445 WireMessage::Close(None), 446 WireMessage::Close(Some(tokio_tungstenite::tungstenite::protocol::CloseFrame { 447 code: tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode::Normal, 448 reason: "finished".into(), 449 })), 450 ] { 451 let restored: WireMessage = from_wire(message.clone()).expect("conversion").into(); 452 assert_eq!(restored, message); 453 } 454 } 455 456 #[tokio::test] 457 async fn secure_relay_requires_tls_before_any_http_upgrade() { 458 use tokio::io::{AsyncReadExt, AsyncWriteExt}; 459 let listener = tokio::net::TcpListener::bind("127.0.0.1:0") 460 .await 461 .expect("listener"); 462 let endpoint = format!("wss://{}", listener.local_addr().expect("address")); 463 let server = tokio::spawn(async move { 464 let (mut socket, _) = listener.accept().await.expect("accept"); 465 let mut prefix = [0; 5]; 466 socket 467 .read_exact(&mut prefix) 468 .await 469 .expect("TLS record prefix"); 470 socket 471 .write_all(b"HTTP/1.1 101 Switching Protocols\r\n\r\n") 472 .await 473 .expect("plaintext response"); 474 prefix 475 }); 476 let profile = crate::profile::test_profile( 477 crate::RelayProfileKind::Simulator, 478 RelayUrlPolicy::Local, 479 [endpoint.as_str()], 480 ) 481 .expect("local TLS profile"); 482 let transport = HardenedWebsocketTransport::new(profile.endpoints()); 483 let url = Url::parse(&endpoint).expect("URL"); 484 let result = transport 485 .connect(&url, &ConnectionMode::Direct, Duration::from_secs(5)) 486 .await; 487 assert!( 488 result.is_err(), 489 "plaintext peer must not complete a secure relay connection" 490 ); 491 let prefix = tokio::time::timeout(Duration::from_secs(5), server) 492 .await 493 .expect("server deadline") 494 .expect("server task"); 495 assert_eq!( 496 prefix[0], 0x16, 497 "client must send a TLS handshake, never an HTTP request" 498 ); 499 assert_eq!(prefix[1], 0x03, "TLS record version family"); 500 } 501 502 #[test] 503 fn policies_classify_literal_and_named_destinations() { 504 assert!(RelayUrl::parse("wss://relay.example.com", RelayUrlPolicy::Public).is_ok()); 505 assert!(RelayUrl::parse("wss://10.0.0.1", RelayUrlPolicy::Public).is_err()); 506 assert!(RelayUrl::parse("wss://10.0.0.1", RelayUrlPolicy::PrivateNetwork).is_ok()); 507 assert!(RelayUrl::parse("ws://127.0.0.1", RelayUrlPolicy::Local).is_ok()); 508 assert!(RelayUrl::parse("ws://relay.example.com", RelayUrlPolicy::Public).is_err()); 509 assert!(RelayUrl::parse("wss://localhost", RelayUrlPolicy::Local).is_ok()); 510 assert!(RelayUrl::parse("wss://localhost", RelayUrlPolicy::Public).is_err()); 511 assert!(!public_hostname("localhost.")); 512 assert!(!public_hostname("intranet")); 513 assert!(!public_hostname("host.localhost")); 514 assert!(RelayUrl::parse("wss://host.local", RelayUrlPolicy::Public).is_err()); 515 assert!(RelayUrl::parse("wss://host.home.arpa", RelayUrlPolicy::Public).is_err()); 516 assert!(RelayUrl::parse("wss://private.example", RelayUrlPolicy::PrivateNetwork).is_err()); 517 assert!(RelayUrl::parse("wss://localhost", RelayUrlPolicy::PrivateNetwork).is_err()); 518 assert!(RelayUrl::parse("ws://10.0.0.1", RelayUrlPolicy::PrivateNetwork).is_ok()); 519 assert!(RelayUrl::parse("ws://8.8.8.8", RelayUrlPolicy::PrivateNetwork).is_err()); 520 521 let relay = 522 RelayUrl::parse("wss://relay.example.com", RelayUrlPolicy::Public).expect("relay"); 523 assert_eq!(relay.to_string(), relay.as_str()); 524 assert_eq!( 525 RelayUrl::from_target(&relay.to_target().expect("target"), RelayUrlPolicy::Public), 526 Ok(relay) 527 ); 528 let local = Target::local("local:device").expect("local target"); 529 assert!(matches!( 530 RelayUrl::from_target(&local, RelayUrlPolicy::Public), 531 Err(Error::UnexpectedTransport) 532 )); 533 let profile = crate::profile::test_profile( 534 crate::RelayProfileKind::Public, 535 RelayUrlPolicy::Public, 536 ["wss://relay.example.com"], 537 ) 538 .expect("profile"); 539 assert!(HardenedWebsocketTransport::new(profile.endpoints()).support_ping()); 540 assert!(!policy_error("denied").to_string().is_empty()); 541 542 let root = Url::parse("wss://relay.example/").expect("root URL"); 543 let path = Url::parse("wss://relay.example/path/").expect("path URL"); 544 let query = Url::parse("wss://relay.example/?scope=write").expect("query URL"); 545 let fragment = Url::parse("wss://relay.example/#status").expect("fragment URL"); 546 assert_eq!(configured_policy_key(&root), "wss://relay.example"); 547 assert_eq!(configured_policy_key(&path), "wss://relay.example/path/"); 548 assert_eq!( 549 configured_policy_key(&query), 550 "wss://relay.example/?scope=write" 551 ); 552 assert_eq!( 553 configured_policy_key(&fragment), 554 "wss://relay.example/#status" 555 ); 556 } 557 558 #[test] 559 fn device_network_policy_matches_the_shared_conformance_vectors() { 560 let document: serde_json::Value = serde_json::from_str(include_str!( 561 "../../../contracts/conformance/vectors/transport/device_network_policy.v1.json" 562 )) 563 .expect("device network policy vectors"); 564 for vector in document["vectors"].as_array().expect("vectors") { 565 let input = &vector["input"]; 566 if input["surface"] != "relay" { 567 continue; 568 } 569 let policy = match input["policy"].as_str().expect("policy") { 570 "public" => RelayUrlPolicy::Public, 571 "loopback" => RelayUrlPolicy::Local, 572 "private_device" => RelayUrlPolicy::PrivateNetwork, 573 other => panic!("unknown relay policy {other}"), 574 }; 575 let accepted = 576 RelayUrl::parse(input["endpoint"].as_str().expect("endpoint"), policy).is_ok(); 577 assert_eq!( 578 accepted, 579 vector["expected"]["accepted"].as_bool().expect("accepted"), 580 "{}", 581 vector["id"].as_str().expect("id") 582 ); 583 } 584 } 585 586 #[test] 587 fn patched_url_parser_rejects_ascii_masking_punycode() { 588 for denied in ["wss://xn--example-.org", "wss://example.org.xn--"] { 589 assert!(RelayUrl::parse(denied, RelayUrlPolicy::Public).is_err()); 590 } 591 } 592 593 #[test] 594 fn resolved_addresses_are_revalidated() { 595 let relay = RelayUrl::parse("wss://relay.example.com", RelayUrlPolicy::Public) 596 .expect("public relay"); 597 assert!( 598 relay 599 .validate_resolved_addresses( 600 RelayUrlPolicy::Public, 601 [IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34))], 602 ) 603 .is_ok() 604 ); 605 assert!( 606 relay 607 .validate_resolved_addresses( 608 RelayUrlPolicy::Public, 609 [IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))], 610 ) 611 .is_err() 612 ); 613 assert!( 614 relay 615 .validate_resolved_addresses( 616 RelayUrlPolicy::Public, 617 [ 618 IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34)), 619 IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), 620 ], 621 ) 622 .is_err() 623 ); 624 assert!( 625 relay 626 .validate_resolved_addresses(RelayUrlPolicy::Public, []) 627 .is_err() 628 ); 629 } 630 631 #[test] 632 fn address_policies_fail_closed_for_special_use_ranges() { 633 for denied in [ 634 Ipv4Addr::new(0, 0, 0, 0), 635 Ipv4Addr::new(0, 1, 1, 1), 636 Ipv4Addr::new(10, 0, 0, 1), 637 Ipv4Addr::new(100, 64, 0, 1), 638 Ipv4Addr::new(127, 0, 0, 1), 639 Ipv4Addr::new(169, 254, 1, 1), 640 Ipv4Addr::new(192, 0, 2, 1), 641 Ipv4Addr::new(198, 18, 0, 1), 642 Ipv4Addr::new(224, 0, 0, 1), 643 Ipv4Addr::new(240, 0, 0, 1), 644 ] { 645 assert!(!RelayUrlPolicy::Public.accepts_address(denied.into())); 646 } 647 for denied in [Ipv6Addr::UNSPECIFIED, Ipv6Addr::LOCALHOST] { 648 assert!(!RelayUrlPolicy::Public.accepts_address(denied.into())); 649 } 650 assert!(RelayUrlPolicy::Local.accepts_address(Ipv4Addr::LOCALHOST.into())); 651 assert!(!RelayUrlPolicy::Local.accepts_address(Ipv4Addr::new(10, 0, 0, 1).into())); 652 assert!(RelayUrlPolicy::PrivateNetwork.accepts_address(Ipv4Addr::new(10, 0, 0, 1).into())); 653 assert!(!RelayUrlPolicy::PrivateNetwork.accepts_address(Ipv4Addr::UNSPECIFIED.into())); 654 assert!(!RelayUrlPolicy::PrivateNetwork.accepts_address(Ipv4Addr::LOCALHOST.into())); 655 assert!(!RelayUrlPolicy::PrivateNetwork.accepts_address(Ipv4Addr::BROADCAST.into())); 656 assert!( 657 !RelayUrlPolicy::PrivateNetwork.accepts_address(Ipv4Addr::new(224, 0, 0, 1).into()) 658 ); 659 assert!( 660 RelayUrlPolicy::PrivateNetwork 661 .accepts_address("fd00::1".parse::<Ipv6Addr>().expect("private v6").into()) 662 ); 663 assert!(!RelayUrlPolicy::PrivateNetwork.accepts_address(Ipv6Addr::UNSPECIFIED.into())); 664 assert!(!RelayUrlPolicy::PrivateNetwork.accepts_address(Ipv6Addr::LOCALHOST.into())); 665 assert!( 666 !RelayUrlPolicy::PrivateNetwork 667 .accepts_address("ff02::1".parse::<Ipv6Addr>().expect("multicast").into()) 668 ); 669 670 for denied in [ 671 "192.0.0.1", 672 "192.88.99.1", 673 "198.19.0.1", 674 "255.0.0.1", 675 "::ffff:10.0.0.1", 676 "2001:db8::1", 677 "2002::1", 678 "3fff::1", 679 "fc00::1", 680 "fe80::1", 681 "ff02::1", 682 ] { 683 let address = denied.parse::<IpAddr>().expect("address"); 684 assert!( 685 !RelayUrlPolicy::Public.accepts_address(address), 686 "accepted {denied}" 687 ); 688 } 689 for allowed in ["8.8.8.8", "2606:4700:4700::1111"] { 690 assert!( 691 RelayUrlPolicy::Public.accepts_address(allowed.parse::<IpAddr>().expect("address")) 692 ); 693 } 694 } 695 }