1use crate::StreamPrefs;
5use crate::err::ErrorDetail;
6use std::fmt::Display;
7use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
8use std::str::FromStr;
9use thiserror::Error;
10use tor_basic_utils::StrExt;
11use tor_error::{ErrorKind, HasKind};
12
13#[cfg(feature = "onion-service-client")]
14use tor_hscrypto::pk::{HSID_ONION_SUFFIX, HsId};
15
16#[cfg(not(feature = "onion-service-client"))]
18pub(crate) mod hs_dummy {
19 use super::*;
20 use tor_error::internal;
21 use void::Void;
22
23 #[derive(Debug, Clone)]
25 pub(crate) struct HsId(pub(crate) Void);
26
27 impl PartialEq for HsId {
28 fn eq(&self, _other: &Self) -> bool {
29 void::unreachable(self.0)
30 }
31 }
32 impl Eq for HsId {}
33
34 pub(crate) const HSID_ONION_SUFFIX: &str = ".onion";
36
37 impl FromStr for HsId {
39 type Err = ErrorDetail;
40
41 fn from_str(s: &str) -> Result<Self, Self::Err> {
42 if !s.ends_with(HSID_ONION_SUFFIX) {
43 return Err(internal!("non-.onion passed to dummy HsId::from_str").into());
44 }
45
46 Err(ErrorDetail::OnionAddressNotSupported)
47 }
48 }
49}
50#[cfg(not(feature = "onion-service-client"))]
51use hs_dummy::*;
52
53pub trait IntoTorAddr {
78 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError>;
81}
82
83pub trait DangerouslyIntoTorAddr {
95 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError>;
101}
102
103#[derive(Debug, Clone, Eq, PartialEq)]
160pub struct TorAddr {
161 host: Host,
163 port: u16,
165}
166
167#[derive(Debug, PartialEq, Eq)]
173pub(crate) enum StreamInstructions {
174 Exit {
176 hostname: String,
178 port: u16,
180 },
181 Hs {
185 hsid: HsId,
187 hostname: String,
189 port: u16,
191 },
192}
193
194#[derive(PartialEq, Eq, Debug)]
196pub(crate) enum ResolveInstructions {
197 Exit(String),
199 Return(Vec<IpAddr>),
201}
202
203impl TorAddr {
204 fn new(host: Host, port: u16) -> Result<Self, TorAddrError> {
207 if port == 0 {
208 Err(TorAddrError::BadPort)
209 } else {
210 Ok(TorAddr { host, port })
211 }
212 }
213
214 pub fn from<A: IntoTorAddr>(addr: A) -> Result<Self, TorAddrError> {
217 addr.into_tor_addr()
218 }
219 pub fn dangerously_from<A: DangerouslyIntoTorAddr>(addr: A) -> Result<Self, TorAddrError> {
226 addr.into_tor_addr_dangerously()
227 }
228
229 pub fn is_ip_address(&self) -> bool {
231 matches!(&self.host, Host::Ip(_))
232 }
233
234 pub fn as_ip_address(&self) -> Option<&IpAddr> {
236 match &self.host {
237 Host::Ip(a) => Some(a),
238 _ => None,
239 }
240 }
241
242 pub(crate) fn into_stream_instructions(
244 self,
245 cfg: &crate::config::ClientAddrConfig,
246 prefs: &StreamPrefs,
247 ) -> Result<StreamInstructions, ErrorDetail> {
248 self.enforce_config(cfg, prefs)?;
249
250 let port = self.port;
251 Ok(match self.host {
252 Host::Hostname(hostname) => StreamInstructions::Exit { hostname, port },
253 Host::Ip(ip) => StreamInstructions::Exit {
254 hostname: ip.to_string(),
255 port,
256 },
257 Host::Onion(onion) => {
258 let rhs = onion
260 .rmatch_indices('.')
261 .nth(1)
262 .map(|(i, _)| i + 1)
263 .unwrap_or(0);
264 let rhs = onion
265 .get(rhs..)
266 .expect("character index was not a valid index!?");
267 let hsid = rhs.parse()?;
268 StreamInstructions::Hs {
269 hsid,
270 port,
271 hostname: onion,
272 }
273 }
274 })
275 }
276
277 pub(crate) fn into_resolve_instructions(
279 self,
280 cfg: &crate::config::ClientAddrConfig,
281 prefs: &StreamPrefs,
282 ) -> Result<ResolveInstructions, ErrorDetail> {
283 let enforce_config_result = self.enforce_config(cfg, prefs);
288
289 let instructions = (move || {
292 Ok(match self.host {
293 Host::Hostname(hostname) => ResolveInstructions::Exit(hostname),
294 Host::Ip(ip) => ResolveInstructions::Return(vec![ip]),
295 Host::Onion(_) => return Err(ErrorDetail::OnionAddressResolveRequest),
296 })
297 })()?;
298
299 let () = enforce_config_result?;
300
301 Ok(instructions)
302 }
303
304 fn is_local(&self) -> bool {
306 self.host.is_local()
307 }
308
309 fn enforce_config(
312 &self,
313 cfg: &crate::config::ClientAddrConfig,
314 #[allow(unused_variables)] prefs: &StreamPrefs,
316 ) -> Result<(), ErrorDetail> {
317 if !cfg.allow_local_addrs && self.is_local() {
318 return Err(ErrorDetail::LocalAddress);
319 }
320
321 if let Host::Hostname(addr) = &self.host {
322 if !is_valid_hostname(addr) {
323 return Err(ErrorDetail::InvalidHostname);
325 }
326 if addr.ends_with_ignore_ascii_case(HSID_ONION_SUFFIX) {
327 return Err(ErrorDetail::OnionAddressNotSupported);
329 }
330 }
331
332 if let Host::Onion(_name) = &self.host {
333 cfg_if::cfg_if! {
334 if #[cfg(feature = "onion-service-client")] {
335 if !prefs.connect_to_onion_services.as_bool().unwrap_or(cfg.allow_onion_addrs) {
336 return Err(ErrorDetail::OnionAddressDisabled);
337 }
338 } else {
339 return Err(ErrorDetail::OnionAddressNotSupported);
340 }
341 }
342 }
343
344 Ok(())
345 }
346}
347
348impl std::fmt::Display for TorAddr {
349 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
350 match self.host {
351 Host::Ip(IpAddr::V6(addr)) => write!(f, "[{}]:{}", addr, self.port),
352 _ => write!(f, "{}:{}", self.host, self.port),
353 }
354 }
355}
356
357#[derive(Debug, Error, Clone, Eq, PartialEq)]
362#[non_exhaustive]
363pub enum TorAddrError {
364 #[error("String can never be a valid hostname")]
366 InvalidHostname,
367 #[error("No port found in string")]
369 NoPort,
370 #[error("Could not parse port")]
372 BadPort,
373}
374
375impl HasKind for TorAddrError {
376 fn kind(&self) -> ErrorKind {
377 use ErrorKind as EK;
378 use TorAddrError as TAE;
379
380 match self {
381 TAE::InvalidHostname => EK::InvalidStreamTarget,
382 TAE::NoPort => EK::InvalidStreamTarget,
383 TAE::BadPort => EK::InvalidStreamTarget,
384 }
385 }
386}
387
388#[derive(Clone, Debug, Eq, PartialEq)]
398enum Host {
399 Hostname(String),
411 Ip(IpAddr),
413 Onion(String),
418}
419
420impl FromStr for Host {
421 type Err = TorAddrError;
422 fn from_str(s: &str) -> Result<Host, TorAddrError> {
423 if s.ends_with_ignore_ascii_case(".onion") && is_valid_hostname(s) {
424 Ok(Host::Onion(s.to_owned()))
425 } else if let Ok(ip_addr) = s.parse() {
426 Ok(Host::Ip(ip_addr))
427 } else if is_valid_hostname(s) {
428 Ok(Host::Hostname(s.to_owned()))
434 } else {
435 Err(TorAddrError::InvalidHostname)
436 }
437 }
438}
439
440impl Host {
441 fn is_local(&self) -> bool {
444 match self {
445 Host::Hostname(name) => name.eq_ignore_ascii_case("localhost"),
446 Host::Ip(IpAddr::V4(ip)) => ip.is_loopback() || ip.is_private(),
453 Host::Ip(IpAddr::V6(ip)) => ip.is_loopback(),
454 Host::Onion(_) => false,
455 }
456 }
457}
458
459impl std::fmt::Display for Host {
460 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
461 match self {
462 Host::Hostname(s) => Display::fmt(s, f),
463 Host::Ip(ip) => Display::fmt(ip, f),
464 Host::Onion(onion) => Display::fmt(onion, f),
465 }
466 }
467}
468
469impl IntoTorAddr for TorAddr {
470 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
471 Ok(self)
472 }
473}
474
475impl<A: IntoTorAddr + Clone> IntoTorAddr for &A {
476 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
477 self.clone().into_tor_addr()
478 }
479}
480
481impl IntoTorAddr for &str {
482 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
483 if let Ok(sa) = SocketAddr::from_str(self) {
484 TorAddr::new(Host::Ip(sa.ip()), sa.port())
485 } else {
486 let (host, port) = self.rsplit_once(':').ok_or(TorAddrError::NoPort)?;
487 let host = host.parse()?;
488 let port = port.parse().map_err(|_| TorAddrError::BadPort)?;
489 TorAddr::new(host, port)
490 }
491 }
492}
493
494impl IntoTorAddr for String {
495 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
496 self.as_str().into_tor_addr()
497 }
498}
499
500impl FromStr for TorAddr {
501 type Err = TorAddrError;
502 fn from_str(s: &str) -> Result<Self, TorAddrError> {
503 s.into_tor_addr()
504 }
505}
506
507impl IntoTorAddr for (&str, u16) {
508 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
509 let (host, port) = self;
510 let host = host.parse()?;
511 TorAddr::new(host, port)
512 }
513}
514
515impl IntoTorAddr for (String, u16) {
516 fn into_tor_addr(self) -> Result<TorAddr, TorAddrError> {
517 let (host, port) = self;
518 (host.as_str(), port).into_tor_addr()
519 }
520}
521
522impl<T: DangerouslyIntoTorAddr + Clone> DangerouslyIntoTorAddr for &T {
523 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
524 self.clone().into_tor_addr_dangerously()
525 }
526}
527
528impl DangerouslyIntoTorAddr for (IpAddr, u16) {
529 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
530 let (addr, port) = self;
531 TorAddr::new(Host::Ip(addr), port)
532 }
533}
534
535impl DangerouslyIntoTorAddr for (Ipv4Addr, u16) {
536 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
537 let (addr, port) = self;
538 TorAddr::new(Host::Ip(addr.into()), port)
539 }
540}
541
542impl DangerouslyIntoTorAddr for (Ipv6Addr, u16) {
543 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
544 let (addr, port) = self;
545 TorAddr::new(Host::Ip(addr.into()), port)
546 }
547}
548
549impl DangerouslyIntoTorAddr for SocketAddr {
550 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
551 let (addr, port) = (self.ip(), self.port());
552 (addr, port).into_tor_addr_dangerously()
553 }
554}
555
556impl DangerouslyIntoTorAddr for SocketAddrV4 {
557 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
558 let (addr, port) = (self.ip(), self.port());
559 (*addr, port).into_tor_addr_dangerously()
560 }
561}
562
563impl DangerouslyIntoTorAddr for SocketAddrV6 {
564 fn into_tor_addr_dangerously(self) -> Result<TorAddr, TorAddrError> {
565 let (addr, port) = (self.ip(), self.port());
566 (*addr, port).into_tor_addr_dangerously()
567 }
568}
569
570fn is_valid_hostname(hostname: &str) -> bool {
574 hostname_validator::is_valid(hostname)
575}
576
577#[cfg(test)]
578mod test {
579 #![allow(clippy::bool_assert_comparison)]
581 #![allow(clippy::clone_on_copy)]
582 #![allow(clippy::dbg_macro)]
583 #![allow(clippy::mixed_attributes_style)]
584 #![allow(clippy::print_stderr)]
585 #![allow(clippy::print_stdout)]
586 #![allow(clippy::single_char_pattern)]
587 #![allow(clippy::unwrap_used)]
588 #![allow(clippy::unchecked_time_subtraction)]
589 #![allow(clippy::useless_vec)]
590 #![allow(clippy::needless_pass_by_value)]
591 #![allow(clippy::string_slice)] use super::*;
594
595 #[test]
596 fn test_error_kind() {
597 use tor_error::ErrorKind as EK;
598
599 assert_eq!(
600 TorAddrError::InvalidHostname.kind(),
601 EK::InvalidStreamTarget
602 );
603 assert_eq!(TorAddrError::NoPort.kind(), EK::InvalidStreamTarget);
604 assert_eq!(TorAddrError::BadPort.kind(), EK::InvalidStreamTarget);
605 }
606
607 fn mk_stream_prefs() -> StreamPrefs {
609 let prefs = crate::StreamPrefs::default();
610
611 #[cfg(feature = "onion-service-client")]
612 let prefs = {
613 let mut prefs = prefs;
614 prefs.connect_to_onion_services(tor_config::BoolOrAuto::Explicit(true));
615 prefs
616 };
617
618 prefs
619 }
620
621 #[test]
622 fn validate_hostname() {
623 assert!(is_valid_hostname("torproject.org"));
625 assert!(is_valid_hostname("Tor-Project.org"));
626 assert!(is_valid_hostname("example.onion"));
627 assert!(is_valid_hostname("some.example.onion"));
628
629 assert!(!is_valid_hostname("-torproject.org"));
631 assert!(!is_valid_hostname("_torproject.org"));
632 assert!(!is_valid_hostname("tor_project1.org"));
633 assert!(!is_valid_hostname("iwanna$money.org"));
634 }
635
636 #[test]
637 fn validate_addr() {
638 use crate::err::ErrorDetail;
639 fn val<A: IntoTorAddr>(addr: A) -> Result<TorAddr, ErrorDetail> {
640 let toraddr = addr.into_tor_addr()?;
641 toraddr.enforce_config(&Default::default(), &mk_stream_prefs())?;
642 Ok(toraddr)
643 }
644
645 assert!(val("[2001:db8::42]:20").is_ok());
646 assert!(val(("2001:db8::42", 20)).is_ok());
647 assert!(val(("198.151.100.42", 443)).is_ok());
648 assert!(val("198.151.100.42:443").is_ok());
649 assert!(val("www.torproject.org:443").is_ok());
650 assert!(val(("www.torproject.org", 443)).is_ok());
651
652 #[cfg(feature = "onion-service-client")]
654 {
655 assert!(val("example.onion:80").is_ok());
656 assert!(val(("example.onion", 80)).is_ok());
657
658 match val("eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad.onion:443") {
659 Ok(TorAddr {
660 host: Host::Onion(_),
661 ..
662 }) => {}
663 x => panic!("{x:?}"),
664 }
665 }
666
667 assert!(matches!(
668 val("-foobar.net:443"),
669 Err(ErrorDetail::InvalidHostname)
670 ));
671 assert!(matches!(
672 val("www.torproject.org"),
673 Err(ErrorDetail::Address(TorAddrError::NoPort))
674 ));
675
676 assert!(matches!(
677 val("192.168.0.1:80"),
678 Err(ErrorDetail::LocalAddress)
679 ));
680 assert!(matches!(
681 val(TorAddr::new(Host::Hostname("foo@bar".to_owned()), 553).unwrap()),
682 Err(ErrorDetail::InvalidHostname)
683 ));
684 assert!(matches!(
685 val(TorAddr::new(Host::Hostname("foo.onion".to_owned()), 80).unwrap()),
686 Err(ErrorDetail::OnionAddressNotSupported)
687 ));
688 }
689
690 #[test]
691 fn local_addrs() {
692 fn is_local_hostname(s: &str) -> bool {
693 let h: Host = s.parse().unwrap();
694 h.is_local()
695 }
696
697 assert!(is_local_hostname("localhost"));
698 assert!(is_local_hostname("loCALHOST"));
699 assert!(is_local_hostname("127.0.0.1"));
700 assert!(is_local_hostname("::1"));
701 assert!(is_local_hostname("192.168.0.1"));
702
703 assert!(!is_local_hostname("www.example.com"));
704 }
705
706 #[test]
707 fn is_ip_address() {
708 fn ip(s: &str) -> bool {
709 TorAddr::from(s).unwrap().is_ip_address()
710 }
711
712 assert!(ip("192.168.0.1:80"));
713 assert!(ip("[::1]:80"));
714 assert!(ip("[2001:db8::42]:65535"));
715 assert!(!ip("example.com:80"));
716 assert!(!ip("example.onion:80"));
717 }
718
719 #[test]
720 fn stream_instructions() {
721 use StreamInstructions as SI;
722
723 fn sap(s: &str) -> Result<StreamInstructions, ErrorDetail> {
724 TorAddr::from(s)
725 .unwrap()
726 .into_stream_instructions(&Default::default(), &mk_stream_prefs())
727 }
728
729 assert_eq!(
730 sap("[2001:db8::42]:9001").unwrap(),
731 SI::Exit {
732 hostname: "2001:db8::42".to_owned(),
733 port: 9001
734 },
735 );
736 assert_eq!(
737 sap("example.com:80").unwrap(),
738 SI::Exit {
739 hostname: "example.com".to_owned(),
740 port: 80
741 },
742 );
743
744 {
745 let b32 = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad";
746 let onion = format!("sss1234.www.{}.onion", b32);
747 let got = sap(&format!("{}:443", onion));
748
749 #[cfg(feature = "onion-service-client")]
750 assert_eq!(
751 got.unwrap(),
752 SI::Hs {
753 hsid: format!("{}.onion", b32).parse().unwrap(),
754 hostname: onion,
755 port: 443,
756 }
757 );
758
759 #[cfg(not(feature = "onion-service-client"))]
760 assert!(matches!(got, Err(ErrorDetail::OnionAddressNotSupported)));
761 }
762 }
763
764 #[test]
765 fn resolve_instructions() {
766 use ResolveInstructions as RI;
767
768 fn sap(s: &str) -> Result<ResolveInstructions, ErrorDetail> {
769 TorAddr::from(s)
770 .unwrap()
771 .into_resolve_instructions(&Default::default(), &Default::default())
772 }
773
774 assert_eq!(
775 sap("[2001:db8::42]:9001").unwrap(),
776 RI::Return(vec!["2001:db8::42".parse().unwrap()]),
777 );
778 assert_eq!(
779 sap("example.com:80").unwrap(),
780 RI::Exit("example.com".to_owned()),
781 );
782 assert!(matches!(
783 sap("example.onion:80"),
784 Err(ErrorDetail::OnionAddressResolveRequest),
785 ));
786 }
787
788 #[test]
789 fn bad_ports() {
790 assert_eq!(
791 TorAddr::from("www.example.com:squirrel"),
792 Err(TorAddrError::BadPort)
793 );
794 assert_eq!(
795 TorAddr::from("www.example.com:0"),
796 Err(TorAddrError::BadPort)
797 );
798 }
799
800 #[test]
801 fn prefs_onion_services() {
802 use crate::err::ErrorDetailDiscriminants;
803 use ErrorDetailDiscriminants as EDD;
804 use ErrorKind as EK;
805 use tor_error::{ErrorKind, HasKind as _};
806
807 #[allow(clippy::redundant_closure)] let prefs_def = || StreamPrefs::default();
809
810 let addr: TorAddr = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad.onion:443"
811 .parse()
812 .unwrap();
813
814 fn map(
815 got: Result<impl Sized, ErrorDetail>,
816 ) -> Result<(), (ErrorDetailDiscriminants, ErrorKind)> {
817 got.map(|_| ())
818 .map_err(|e| (ErrorDetailDiscriminants::from(&e), e.kind()))
819 }
820
821 let check_stream = |prefs, expected| {
822 let got = addr
823 .clone()
824 .into_stream_instructions(&Default::default(), &prefs);
825 assert_eq!(map(got), expected, "{prefs:?}");
826 };
827 let check_resolve = |prefs| {
828 let got = addr
829 .clone()
830 .into_resolve_instructions(&Default::default(), &prefs);
831 let expected = Err((EDD::OnionAddressResolveRequest, EK::NotImplemented));
834 assert_eq!(map(got), expected, "{prefs:?}");
835 };
836
837 cfg_if::cfg_if! {
838 if #[cfg(feature = "onion-service-client")] {
839 use tor_config::BoolOrAuto as B;
840 let prefs_of = |yn| {
841 let mut prefs = StreamPrefs::default();
842 prefs.connect_to_onion_services(yn);
843 prefs
844 };
845 check_stream(prefs_def(), Ok(()));
846 check_stream(prefs_of(B::Auto), Ok(()));
847 check_stream(prefs_of(B::Explicit(true)), Ok(()));
848 check_stream(prefs_of(B::Explicit(false)), Err((EDD::OnionAddressDisabled, EK::ForbiddenStreamTarget)));
849
850 check_resolve(prefs_def());
851 check_resolve(prefs_of(B::Auto));
852 check_resolve(prefs_of(B::Explicit(true)));
853 check_resolve(prefs_of(B::Explicit(false)));
854 } else {
855 check_stream(prefs_def(), Err((EDD::OnionAddressNotSupported, EK::FeatureDisabled)));
856
857 check_resolve(prefs_def());
858 }
859 }
860 }
861
862 #[test]
863 fn convert_safe() {
864 fn check<A: IntoTorAddr>(a: A, s: &str) {
865 let a1 = TorAddr::from(a).unwrap();
866 let a2 = s.parse().unwrap();
867 assert_eq!(a1, a2);
868 assert_eq!(&a1.to_string(), s);
869 }
870
871 check(("www.example.com", 8000), "www.example.com:8000");
872 check(
873 TorAddr::from(("www.example.com", 8000)).unwrap(),
874 "www.example.com:8000",
875 );
876 check(
877 TorAddr::from(("www.example.com", 8000)).unwrap(),
878 "www.example.com:8000",
879 );
880 let addr = "[2001:db8::0042]:9001".to_owned();
881 check(&addr, "[2001:db8::42]:9001");
882 check(addr, "[2001:db8::42]:9001");
883 check(("2001:db8::0042".to_owned(), 9001), "[2001:db8::42]:9001");
884 check(("example.onion", 80), "example.onion:80");
885 }
886
887 #[test]
888 fn convert_dangerous() {
889 fn check<A: DangerouslyIntoTorAddr>(a: A, s: &str) {
890 let a1 = TorAddr::dangerously_from(a).unwrap();
891 let a2 = TorAddr::from(s).unwrap();
892 assert_eq!(a1, a2);
893 assert_eq!(&a1.to_string(), s);
894 }
895
896 let ip: IpAddr = "203.0.133.6".parse().unwrap();
897 let ip4: Ipv4Addr = "203.0.133.7".parse().unwrap();
898 let ip6: Ipv6Addr = "2001:db8::42".parse().unwrap();
899 let sa: SocketAddr = "203.0.133.8:80".parse().unwrap();
900 let sa4: SocketAddrV4 = "203.0.133.8:81".parse().unwrap();
901 let sa6: SocketAddrV6 = "[2001:db8::43]:82".parse().unwrap();
902
903 #[allow(clippy::needless_borrow)]
905 #[allow(clippy::needless_borrows_for_generic_args)]
906 check(&(ip, 443), "203.0.133.6:443");
907 check((ip, 443), "203.0.133.6:443");
908 check((ip4, 444), "203.0.133.7:444");
909 check((ip6, 445), "[2001:db8::42]:445");
910 check(sa, "203.0.133.8:80");
911 check(sa4, "203.0.133.8:81");
912 check(sa6, "[2001:db8::43]:82");
913 }
914}