1use core::net::SocketAddr;
13use std::fmt;
14use std::future::Future;
15use std::net::IpAddr;
16use std::net::Ipv6Addr;
17#[cfg(target_os = "linux")]
18use std::os::linux::net::SocketAddrExt;
19use std::os::unix::io::FromRawFd;
20use std::os::unix::io::RawFd;
21use std::panic::Location;
22use std::pin::Pin;
23use std::str::FromStr;
24use std::sync::Arc;
25use std::sync::Mutex;
26use std::sync::atomic::AtomicU8;
27use std::sync::atomic::AtomicUsize;
28use std::sync::atomic::Ordering;
29use std::task::Context;
30use std::task::Poll;
31
32use async_trait::async_trait;
33use enum_as_inner::EnumAsInner;
34use futures::task::AtomicWaker;
35use hyperactor_config::attrs::AttrValue;
36use serde::Deserialize;
37use serde::Serialize;
38use tokio::sync::mpsc;
39use tokio::sync::watch;
40use tokio::time::Instant;
41use tokio_util::sync::CancellationToken;
42
43use crate as hyperactor;
44use crate::RemoteMessage;
45pub(crate) mod local;
46pub(crate) mod net;
47
48pub use net::ServerError;
52pub use net::try_tls_acceptor_with_pem_bundle;
53pub use net::try_tls_connector;
54pub use net::try_tls_pem_bundle;
55
56pub mod duplex {
58 pub use super::net::duplex::DuplexClient;
59 pub use super::net::duplex::DuplexRx;
60 pub use super::net::duplex::DuplexServer;
61 pub use super::net::duplex::DuplexTx;
62 pub use super::net::duplex::dial;
63 pub use super::net::duplex::serve;
64}
65
66#[derive(thiserror::Error, Debug)]
68pub enum ChannelError {
69 #[error("channel closed")]
71 Closed,
72
73 #[error("send: {0}")]
75 Send(#[source] anyhow::Error),
76
77 #[error(transparent)]
79 Client(#[from] net::ClientError),
80
81 #[error("invalid address {0:?}")]
83 InvalidAddress(String),
84
85 #[error(transparent)]
87 Server(#[from] net::ServerError),
88
89 #[error(transparent)]
91 BincodeEncode(#[from] bincode::error::EncodeError),
92
93 #[error(transparent)]
95 BincodeDecode(#[from] bincode::error::DecodeError),
96
97 #[error(transparent)]
99 Data(#[from] wirevalue::Error),
100
101 #[error(transparent)]
103 Other(#[from] anyhow::Error),
104
105 #[error("operation timed out after {0:?}")]
107 Timeout(std::time::Duration),
108}
109
110#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
112pub enum SendErrorReason {
113 #[error(
115 "rejecting oversize frame: len={len} > max={max}. \
116 ack will not arrive before timeout; increase CODEC_MAX_FRAME_LENGTH to allow."
117 )]
118 OversizedFrame {
119 len: usize,
121
122 max: usize,
124 },
125
126 #[error("{0}")]
128 Other(String),
129}
130
131#[derive(thiserror::Error, Debug)]
133#[error("{error} for reason {reason:?}")]
134pub struct SendError<M: RemoteMessage> {
135 #[source]
137 pub error: ChannelError,
138 pub message: M,
140 pub reason: Option<SendErrorReason>,
142}
143
144#[repr(u8)]
145#[derive(Clone, Copy, Debug, PartialEq, Eq)]
146enum CompletionStatus {
147 Pending = 0,
148 Accepted = 1,
149 Rejected = 2,
150}
151
152impl CompletionStatus {
153 fn from_u8(value: u8) -> Self {
154 match value {
155 value if value == Self::Pending as u8 => Self::Pending,
156 value if value == Self::Accepted as u8 => Self::Accepted,
157 value if value == Self::Rejected as u8 => Self::Rejected,
158 _ => panic!("invalid completion state"),
159 }
160 }
161}
162
163struct CompletionState<M: RemoteMessage> {
164 state: AtomicU8,
165 waker: AtomicWaker,
166 rejected: Mutex<Option<Box<SendError<M>>>>,
167}
168
169pub(crate) struct CompletionSender<M: RemoteMessage> {
170 inner: Arc<CompletionState<M>>,
171}
172
173pub struct CompletionReceipt<M: RemoteMessage> {
175 inner: Arc<CompletionState<M>>,
176}
177
178impl<M: RemoteMessage> CompletionSender<M> {
179 fn pair() -> (Self, CompletionReceipt<M>) {
180 let inner = Arc::new(CompletionState {
181 state: AtomicU8::new(CompletionStatus::Pending as u8),
182 waker: AtomicWaker::new(),
183 rejected: Mutex::new(None),
184 });
185
186 (
187 Self {
188 inner: Arc::clone(&inner),
189 },
190 CompletionReceipt { inner },
191 )
192 }
193
194 fn accept(self) {
195 self.inner
196 .state
197 .store(CompletionStatus::Accepted as u8, Ordering::Release);
198 self.inner.waker.wake();
199 }
200
201 fn reject(self, error: SendError<M>) {
202 *self.inner.rejected.lock().unwrap() = Some(Box::new(error));
203 self.inner
204 .state
205 .store(CompletionStatus::Rejected as u8, Ordering::Release);
206 self.inner.waker.wake();
207 }
208}
209
210impl<M: RemoteMessage> Drop for CompletionSender<M> {
211 fn drop(&mut self) {
212 if CompletionStatus::from_u8(self.inner.state.load(Ordering::Acquire))
213 == CompletionStatus::Pending
214 {
215 self.inner
216 .state
217 .store(CompletionStatus::Accepted as u8, Ordering::Release);
218 self.inner.waker.wake();
219 }
220 }
221}
222
223impl<M: RemoteMessage> Future for CompletionReceipt<M> {
224 type Output = Result<(), SendError<M>>;
225
226 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
227 match self.poll_ready() {
228 Poll::Ready(result) => Poll::Ready(result),
229 Poll::Pending => {
230 self.inner.waker.register(cx.waker());
231 self.poll_ready()
232 }
233 }
234 }
235}
236
237impl<M: RemoteMessage> CompletionReceipt<M> {
238 fn poll_ready(&self) -> Poll<Result<(), SendError<M>>> {
239 match CompletionStatus::from_u8(self.inner.state.load(Ordering::Acquire)) {
240 CompletionStatus::Accepted => Poll::Ready(Ok(())),
241 CompletionStatus::Rejected => {
242 let error = self
243 .inner
244 .rejected
245 .lock()
246 .unwrap()
247 .take()
248 .expect("rejected completion should store send error");
249 Poll::Ready(Err(*error))
250 }
251 CompletionStatus::Pending => Poll::Pending,
252 }
253 }
254}
255
256pub(crate) struct CompletionTracker {
258 completed: Arc<AtomicUsize>,
259 completed_notify: Arc<tokio::sync::Notify>,
260}
261
262impl CompletionTracker {
263 pub(crate) fn new(
264 completed: Arc<AtomicUsize>,
265 completed_notify: Arc<tokio::sync::Notify>,
266 ) -> Self {
267 Self {
268 completed,
269 completed_notify,
270 }
271 }
272
273 fn complete(&self) {
274 self.completed.fetch_add(1, Ordering::Relaxed);
275 self.completed_notify.notify_waiters();
276 }
277}
278
279enum CompletionSinkInner<M: RemoteMessage> {
280 Ignore,
281 Receipt(CompletionSender<M>),
282 OnReject(Box<dyn FnOnce(SendError<M>) + Send + Sync>),
283 Tracked {
284 tracker: CompletionTracker,
285 on_reject: Box<dyn FnOnce(SendError<M>) + Send + Sync>,
286 },
287}
288
289pub struct CompletionSink<M: RemoteMessage>(CompletionSinkInner<M>);
291
292impl<M: RemoteMessage> CompletionSink<M> {
293 pub fn ignore() -> Self {
295 Self(CompletionSinkInner::Ignore)
296 }
297
298 pub fn on_reject(f: impl FnOnce(SendError<M>) + Send + Sync + 'static) -> Self {
300 Self(CompletionSinkInner::OnReject(Box::new(f)))
301 }
302
303 pub fn receipt() -> (Self, CompletionReceipt<M>) {
305 let (sender, receipt) = CompletionSender::pair();
306 (Self(CompletionSinkInner::Receipt(sender)), receipt)
307 }
308
309 pub(crate) fn tracked(
311 tracker: CompletionTracker,
312 on_reject: impl FnOnce(SendError<M>) + Send + Sync + 'static,
313 ) -> Self {
314 Self(CompletionSinkInner::Tracked {
315 tracker,
316 on_reject: Box::new(on_reject),
317 })
318 }
319
320 pub fn contramap_rejected<N: RemoteMessage>(
322 self,
323 f: impl FnOnce(SendError<N>) -> Option<SendError<M>> + Send + Sync + 'static,
324 ) -> CompletionSink<N> {
325 match self.0 {
326 CompletionSinkInner::Ignore => CompletionSink::ignore(),
327 CompletionSinkInner::Receipt(sender) => CompletionSink::on_reject(move |error| {
328 if let Some(error) = f(error) {
329 sender.reject(error);
330 } else {
331 sender.accept();
332 }
333 }),
334 CompletionSinkInner::OnReject(on_reject) => CompletionSink::on_reject(move |error| {
335 if let Some(error) = f(error) {
336 on_reject(error);
337 }
338 }),
339 CompletionSinkInner::Tracked { tracker, on_reject } => {
340 CompletionSink::tracked(tracker, move |error| {
341 if let Some(error) = f(error) {
342 on_reject(error);
343 }
344 })
345 }
346 }
347 }
348
349 pub fn accept(self) {
351 match self.0 {
352 CompletionSinkInner::Ignore => {}
353 CompletionSinkInner::Receipt(sender) => sender.accept(),
354 CompletionSinkInner::OnReject(_) => {}
355 CompletionSinkInner::Tracked { tracker, .. } => tracker.complete(),
356 }
357 }
358
359 pub fn reject(self, error: SendError<M>) {
361 match self.0 {
362 CompletionSinkInner::Ignore => {}
363 CompletionSinkInner::Receipt(sender) => sender.reject(error),
364 CompletionSinkInner::OnReject(on_reject) => on_reject(error),
365 CompletionSinkInner::Tracked { tracker, on_reject } => {
366 on_reject(error);
367 tracker.complete();
368 }
369 }
370 }
371}
372
373impl<M: RemoteMessage> From<SendError<M>> for ChannelError {
374 fn from(error: SendError<M>) -> Self {
375 error.error
376 }
377}
378
379#[derive(Debug, Clone, PartialEq)]
384pub enum CloseReason {
385 SequenceMismatch(String),
391 OversizedFrame {
395 size: usize,
397 max: usize,
399 },
400 Other(String),
404}
405
406impl fmt::Display for CloseReason {
407 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
408 match self {
409 Self::SequenceMismatch(s) => write!(f, "stale session: {}", s),
410 Self::OversizedFrame { size, max } => {
411 write!(f, "oversized frame: len={size} > max={max}")
412 }
413 Self::Other(s) => f.write_str(s),
414 }
415 }
416}
417
418#[derive(Debug, Clone, PartialEq, EnumAsInner)]
420pub enum TxStatus {
421 Active,
423 Closed(CloseReason),
425}
426
427#[async_trait]
429pub trait Tx<M: RemoteMessage> {
430 fn do_post(&self, message: M, completion: CompletionSink<M>);
434
435 fn try_post(&self, message: M) -> CompletionReceipt<M> {
438 let (completion, receipt) = CompletionSink::receipt();
439 self.do_post(message, completion);
440 receipt
441 }
442
443 #[hyperactor::instrument_infallible]
445 fn post(&self, message: M) {
446 self.do_post(message, CompletionSink::ignore());
447 }
448
449 async fn send(&self, message: M) -> Result<(), SendError<M>> {
452 self.try_post(message).await
453 }
454
455 fn addr(&self) -> ChannelAddr;
457
458 fn status(&self) -> &watch::Receiver<TxStatus>;
460}
461
462#[async_trait]
464pub trait Rx<M: RemoteMessage> {
465 async fn recv(&mut self) -> Result<M, ChannelError>;
468
469 fn addr(&self) -> ChannelAddr;
471
472 async fn join(self)
476 where
477 Self: Sized;
478}
479
480#[derive(
482 Clone,
483 Debug,
484 PartialEq,
485 Eq,
486 Hash,
487 Serialize,
488 Deserialize,
489 strum::EnumIter,
490 strum::Display,
491 strum::EnumString
492)]
493pub enum TcpMode {
494 Localhost,
496 Hostname,
498}
499
500#[derive(
502 Clone,
503 Debug,
504 PartialEq,
505 Eq,
506 Hash,
507 Serialize,
508 Deserialize,
509 strum::EnumIter,
510 strum::Display,
511 strum::EnumString
512)]
513pub enum TlsMode {
514 IpV6,
516 Hostname,
518 }
520
521#[derive(
523 Clone,
524 Debug,
525 PartialEq,
526 Eq,
527 Hash,
528 Serialize,
529 Deserialize,
530 Ord,
531 PartialOrd
532)]
533pub struct TlsAddr {
534 pub hostname: Hostname,
536 pub port: Port,
538}
539
540impl TlsAddr {
541 pub fn new(hostname: impl Into<Hostname>, port: Port) -> Self {
543 Self {
544 hostname: normalize_host(&hostname.into()),
545 port,
546 }
547 }
548
549 pub fn port(&self) -> Port {
551 self.port
552 }
553
554 pub fn hostname(&self) -> &str {
556 &self.hostname
557 }
558}
559
560impl FromStr for TlsAddr {
561 type Err = anyhow::Error;
562
563 fn from_str(addr: &str) -> Result<Self, Self::Err> {
564 let (hostname, port_str) = addr
565 .rsplit_once(':')
566 .ok_or_else(|| anyhow::anyhow!("invalid TLS address: {}", addr))?;
567 let port = port_str
568 .parse()
569 .map_err(|_| anyhow::anyhow!("invalid TLS address port: {}", port_str))?;
570 Ok(Self::new(hostname, port))
571 }
572}
573
574impl fmt::Display for TlsAddr {
575 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
576 write!(f, "{}:{}", self.hostname, self.port)
577 }
578}
579
580#[derive(
582 Clone,
583 Debug,
584 PartialEq,
585 Eq,
586 Hash,
587 Serialize,
588 Deserialize,
589 typeuri::Named
590)]
591pub enum ChannelTransport {
592 Tcp(TcpMode),
594
595 MetaTls(TlsMode),
597
598 Tls,
600
601 Quic,
603
604 MetaQuic(TlsMode),
606
607 Local,
610
611 Unix,
613}
614
615impl fmt::Display for ChannelTransport {
616 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
617 match self {
618 Self::Tcp(mode) => write!(f, "tcp({:?})", mode),
619 Self::MetaTls(mode) => write!(f, "metatls({:?})", mode),
620 Self::Tls => write!(f, "tls"),
621 Self::Quic => write!(f, "quic"),
622 Self::MetaQuic(mode) => write!(f, "metaquic({:?})", mode),
623 Self::Local => write!(f, "local"),
624 Self::Unix => write!(f, "unix"),
625 }
626 }
627}
628
629impl FromStr for ChannelTransport {
630 type Err = anyhow::Error;
631
632 fn from_str(s: &str) -> Result<Self, Self::Err> {
633 match s {
634 "tcp" => Ok(ChannelTransport::Tcp(TcpMode::Hostname)),
636 s if s.starts_with("tcp(") => {
637 let inner = &s["tcp(".len()..s.len() - 1];
638 let mode = inner.parse()?;
639 Ok(ChannelTransport::Tcp(mode))
640 }
641 "local" => Ok(ChannelTransport::Local),
642 "unix" => Ok(ChannelTransport::Unix),
643 "tls" => Ok(ChannelTransport::Tls),
644 "quic" => Ok(ChannelTransport::Quic),
645 s if s.starts_with("metatls(") && s.ends_with(")") => {
646 let inner = &s["metatls(".len()..s.len() - 1];
647 let mode = inner.parse()?;
648 Ok(ChannelTransport::MetaTls(mode))
649 }
650 s if s.starts_with("metaquic(") && s.ends_with(")") => {
651 let inner = &s["metaquic(".len()..s.len() - 1];
652 let mode = inner.parse()?;
653 Ok(ChannelTransport::MetaQuic(mode))
654 }
655 unknown => Err(anyhow::anyhow!("unknown channel transport: {}", unknown)),
656 }
657 }
658}
659
660impl ChannelTransport {
661 pub fn all() -> [ChannelTransport; 3] {
663 [
664 ChannelTransport::Tcp(TcpMode::Hostname),
667 ChannelTransport::Local,
668 ChannelTransport::Unix,
669 ]
672 }
673
674 pub fn any(&self) -> ChannelAddr {
676 ChannelAddr::any(self.clone())
677 }
678
679 pub fn is_remote(&self) -> bool {
681 match self {
682 ChannelTransport::Tcp(_) => true,
683 ChannelTransport::MetaTls(_) => true,
684 ChannelTransport::Tls => true,
685 ChannelTransport::Quic => true,
686 ChannelTransport::MetaQuic(_) => true,
687 ChannelTransport::Local => false,
688 ChannelTransport::Unix => false,
689 }
690 }
691
692 pub fn is_net(&self) -> bool {
697 match self {
698 ChannelTransport::Tcp(_) => true,
699 ChannelTransport::MetaTls(_) => true,
700 ChannelTransport::Tls => true,
701 ChannelTransport::Quic => false,
702 ChannelTransport::MetaQuic(_) => false,
703 ChannelTransport::Unix => true,
704 ChannelTransport::Local => false,
705 }
706 }
707
708 pub fn is_tls(&self) -> bool {
710 matches!(self, ChannelTransport::Tls | ChannelTransport::MetaTls(_))
711 }
712
713 pub fn supports_duplex(&self) -> bool {
720 match self {
721 ChannelTransport::Tcp(_) => true,
722 ChannelTransport::MetaTls(_) => true,
723 ChannelTransport::Tls => true,
724 ChannelTransport::Quic => false,
726 ChannelTransport::MetaQuic(_) => false,
727 ChannelTransport::Unix => true,
728 ChannelTransport::Local => true,
729 }
730 }
731}
732
733impl AttrValue for ChannelTransport {
734 fn display(&self) -> String {
735 self.to_string()
736 }
737
738 fn parse(s: &str) -> Result<Self, anyhow::Error> {
739 s.parse()
740 }
741}
742
743#[derive(
745 Clone,
746 Debug,
747 PartialEq,
748 Eq,
749 Hash,
750 Serialize,
751 Deserialize,
752 typeuri::Named
753)]
754pub enum BindSpec {
755 Any(ChannelTransport),
757
758 Addr(ChannelAddr),
760}
761
762impl BindSpec {
763 pub fn binding_addr(&self) -> ChannelAddr {
765 match self {
766 BindSpec::Any(transport) => ChannelAddr::any(transport.clone()),
767 BindSpec::Addr(addr) => addr.clone(),
768 }
769 }
770}
771
772impl From<ChannelTransport> for BindSpec {
773 fn from(transport: ChannelTransport) -> Self {
774 BindSpec::Any(transport)
775 }
776}
777
778impl From<ChannelAddr> for BindSpec {
779 fn from(addr: ChannelAddr) -> Self {
780 BindSpec::Addr(addr)
781 }
782}
783
784impl fmt::Display for BindSpec {
785 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
786 match self {
787 Self::Any(transport) => write!(f, "{}", transport),
788 Self::Addr(addr) => write!(f, "{}", addr),
789 }
790 }
791}
792
793impl FromStr for BindSpec {
794 type Err = anyhow::Error;
795
796 fn from_str(s: &str) -> Result<Self, Self::Err> {
797 if let Ok(transport) = ChannelTransport::from_str(s) {
798 Ok(BindSpec::Any(transport))
799 } else if let Ok(addr) = ChannelAddr::from_zmq_url(s) {
800 Ok(BindSpec::Addr(addr))
801 } else if let Ok(addr) = ChannelAddr::from_str(s) {
802 Ok(BindSpec::Addr(addr))
803 } else {
804 Err(anyhow::anyhow!("invalid bind spec: {}", s))
805 }
806 }
807}
808
809impl AttrValue for BindSpec {
810 fn display(&self) -> String {
811 self.to_string()
812 }
813
814 fn parse(s: &str) -> Result<Self, anyhow::Error> {
815 Self::from_str(s)
816 }
817}
818
819pub type Hostname = String;
821
822pub type Port = u16;
824
825#[derive(
849 Clone,
850 Debug,
851 PartialEq,
852 Eq,
853 Ord,
854 PartialOrd,
855 Serialize,
856 Deserialize,
857 Hash,
858 typeuri::Named
859)]
860pub enum ChannelAddr {
861 Tcp(SocketAddr),
864
865 MetaTls(TlsAddr),
868
869 Tls(TlsAddr),
872
873 Quic(TlsAddr),
876
877 MetaQuic(TlsAddr),
880
881 Local(u64),
884
885 Unix(net::unix::SocketAddr),
888
889 Alias {
910 dial_to: Box<ChannelAddr>,
912 bind_to: Box<ChannelAddr>,
914 },
915}
916
917impl From<SocketAddr> for ChannelAddr {
918 fn from(value: SocketAddr) -> Self {
919 Self::Tcp(value)
920 }
921}
922
923impl From<net::unix::SocketAddr> for ChannelAddr {
924 fn from(value: net::unix::SocketAddr) -> Self {
925 Self::Unix(value)
926 }
927}
928
929impl From<std::os::unix::net::SocketAddr> for ChannelAddr {
930 fn from(value: std::os::unix::net::SocketAddr) -> Self {
931 Self::Unix(net::unix::SocketAddr::new(value))
932 }
933}
934
935impl From<tokio::net::unix::SocketAddr> for ChannelAddr {
936 fn from(value: tokio::net::unix::SocketAddr) -> Self {
937 std::os::unix::net::SocketAddr::from(value).into()
938 }
939}
940
941fn is_routable_address(addr: &IpAddr) -> bool {
942 !addr.is_loopback()
943 && match addr {
944 IpAddr::V6(v6) => !v6.is_unicast_link_local(),
945 IpAddr::V4(v4) => !v4.is_link_local(),
946 }
947}
948
949fn find_routable_address(addresses: &[IpAddr]) -> Option<IpAddr> {
951 addresses
952 .iter()
953 .find(|addr| is_routable_address(addr))
954 .cloned()
955}
956
957impl ChannelAddr {
958 pub fn any(transport: ChannelTransport) -> Self {
967 match transport {
968 ChannelTransport::Tcp(mode) => {
969 let ip = match mode {
970 TcpMode::Localhost => IpAddr::V6(Ipv6Addr::LOCALHOST),
971 TcpMode::Hostname => {
972 let hostname = hostname::get().expect("failed to retrieve hostname");
973 let hostname = hostname.to_str().expect("hostname is not valid UTF-8");
974 let addresses =
976 dns_lookup::lookup_host(hostname).expect("failed to resolve hostname");
977 find_routable_address(&addresses).expect(
978 "hostname resolved to no non-loopback, non-link-local address; configure an explicit transport address",
979 )
980 }
981 };
982 Self::Tcp(SocketAddr::new(ip, 0))
983 }
984 ChannelTransport::MetaTls(mode) => {
985 let host_address = match mode {
986 TlsMode::Hostname => hostname::get()
987 .ok()
988 .and_then(|hostname| hostname.to_str().map(|s| s.to_string()))
989 .unwrap_or("unknown_host".to_string()),
990 TlsMode::IpV6 => {
991 get_host_ipv6_address().expect("failed to retrieve ipv6 address")
992 }
993 };
994 Self::MetaTls(TlsAddr::new(host_address, 0))
995 }
996 ChannelTransport::MetaQuic(mode) => {
997 let host_address = match mode {
998 TlsMode::Hostname => hostname::get()
999 .ok()
1000 .and_then(|hostname| hostname.to_str().map(|s| s.to_string()))
1001 .unwrap_or("unknown_host".to_string()),
1002 TlsMode::IpV6 => {
1003 get_host_ipv6_address().expect("failed to retrieve ipv6 address")
1004 }
1005 };
1006 Self::MetaQuic(TlsAddr::new(host_address, 0))
1007 }
1008 ChannelTransport::Local => Self::Local(0),
1009 ChannelTransport::Tls => {
1010 let host_address = hostname::get()
1011 .ok()
1012 .and_then(|hostname| hostname.to_str().map(|s| s.to_string()))
1013 .unwrap_or("localhost".to_string());
1014 Self::Tls(TlsAddr::new(host_address, 0))
1015 }
1016 ChannelTransport::Quic => {
1017 let host_address = hostname::get()
1018 .ok()
1019 .and_then(|hostname| hostname.to_str().map(|s| s.to_string()))
1020 .unwrap_or("localhost".to_string());
1021 Self::Quic(TlsAddr::new(host_address, 0))
1022 }
1023 ChannelTransport::Unix => Self::Unix(net::unix::SocketAddr::from_str("").unwrap()),
1025 }
1026 }
1027
1028 pub fn transport(&self) -> ChannelTransport {
1030 match self {
1031 Self::Tcp(addr) => {
1032 if addr.ip().is_loopback() {
1033 ChannelTransport::Tcp(TcpMode::Localhost)
1034 } else {
1035 ChannelTransport::Tcp(TcpMode::Hostname)
1036 }
1037 }
1038 Self::MetaTls(addr) => match addr.hostname.parse::<IpAddr>() {
1039 Ok(IpAddr::V6(_)) => ChannelTransport::MetaTls(TlsMode::IpV6),
1040 Ok(IpAddr::V4(_)) => ChannelTransport::MetaTls(TlsMode::Hostname),
1041 Err(_) => ChannelTransport::MetaTls(TlsMode::Hostname),
1042 },
1043 Self::Tls(_) => ChannelTransport::Tls,
1044 Self::Quic(_) => ChannelTransport::Quic,
1045 Self::MetaQuic(addr) => match addr.hostname.parse::<IpAddr>() {
1046 Ok(IpAddr::V6(_)) => ChannelTransport::MetaQuic(TlsMode::IpV6),
1047 Ok(IpAddr::V4(_)) => ChannelTransport::MetaQuic(TlsMode::Hostname),
1048 Err(_) => ChannelTransport::MetaQuic(TlsMode::Hostname),
1049 },
1050 Self::Local(_) => ChannelTransport::Local,
1051 Self::Unix(_) => ChannelTransport::Unix,
1052 Self::Alias { bind_to, .. } => bind_to.transport(),
1055 }
1056 }
1057}
1058
1059#[cfg(fbcode_build)]
1060fn get_host_ipv6_address() -> anyhow::Result<String> {
1061 crate::meta::host_ip::host_ipv6_address()
1062}
1063
1064#[cfg(not(fbcode_build))]
1065fn get_host_ipv6_address() -> anyhow::Result<String> {
1066 Ok(local_ip_address::local_ipv6()?.to_string())
1067}
1068
1069impl fmt::Display for ChannelAddr {
1070 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1071 match self {
1072 Self::Tcp(addr) => write!(f, "tcp:{}", addr),
1073 Self::MetaTls(addr) => write!(f, "metatls:{}", addr),
1074 Self::Tls(addr) => write!(f, "tls:{}", addr),
1075 Self::Quic(addr) => write!(f, "quic:{}", addr),
1076 Self::MetaQuic(addr) => write!(f, "metaquic:{}", addr),
1077 Self::Local(index) => write!(f, "local:{}", index),
1078 Self::Unix(addr) => write!(f, "unix:{}", addr),
1079 Self::Alias { dial_to, bind_to } => {
1080 write!(f, "alias:dial_to={};bind_to={}", dial_to, bind_to)
1081 }
1082 }
1083 }
1084}
1085
1086impl FromStr for ChannelAddr {
1087 type Err = anyhow::Error;
1088
1089 fn from_str(addr: &str) -> Result<Self, Self::Err> {
1090 match addr.split_once('!').or_else(|| addr.split_once(':')) {
1091 Some(("local", rest)) => rest
1092 .parse::<u64>()
1093 .map(Self::Local)
1094 .map_err(anyhow::Error::from),
1095 Some(("tcp", rest)) => rest
1096 .parse::<SocketAddr>()
1097 .map(Self::Tcp)
1098 .map_err(anyhow::Error::from),
1099 Some(("metatls", rest)) => net::meta::parse(rest).map_err(|e| e.into()),
1100 Some(("tls", rest)) => net::tls::parse(rest).map_err(|e| e.into()),
1101 Some(("quic", rest)) => TlsAddr::from_str(rest).map(Self::Quic),
1102 Some(("metaquic", rest)) => TlsAddr::from_str(rest).map(Self::MetaQuic),
1103 Some(("unix", rest)) => Ok(Self::Unix(net::unix::SocketAddr::from_str(rest)?)),
1104 Some(("alias", _)) => Err(anyhow::anyhow!(
1105 "detect possible alias address, but we currently do not support \
1106 parsing alias' string representation since we only want to \
1107 support parsing its zmq url format."
1108 )),
1109 Some((r#type, _)) => Err(anyhow::anyhow!("no such channel type: {type}")),
1110 None => Err(anyhow::anyhow!("no channel type specified")),
1111 }
1112 }
1113}
1114
1115pub(crate) fn normalize_host(host: &str) -> String {
1118 let host_clean = host
1121 .strip_prefix('[')
1122 .and_then(|h| h.strip_suffix(']'))
1123 .unwrap_or(host);
1124
1125 if let Ok(ip_addr) = host_clean.parse::<IpAddr>() {
1126 ip_addr.to_string()
1127 } else {
1128 host.to_string()
1129 }
1130}
1131
1132impl ChannelAddr {
1133 pub fn into_dial_addr(self) -> Self {
1140 match self {
1141 Self::Alias { dial_to, .. } => (*dial_to).into_dial_addr(),
1142 addr => addr,
1143 }
1144 }
1145
1146 pub fn from_zmq_url(address: &str) -> Result<Self, anyhow::Error> {
1161 let (addr, _listener) = Self::from_zmq_url_with_listener(address)?;
1162 Ok(addr)
1163 }
1164
1165 pub fn from_zmq_url_with_listener(
1178 address: &str,
1179 ) -> Result<(Self, Option<std::net::TcpListener>), anyhow::Error> {
1180 if let Some(at_pos) = address
1183 .find('@')
1184 .filter(|&pos| address[..pos].starts_with("tcp://"))
1185 {
1186 let dial_to_str = &address[..at_pos];
1187 let bind_to_str = &address[at_pos + 1..];
1188
1189 if !dial_to_str.starts_with("tcp://") {
1191 return Err(anyhow::anyhow!(
1192 "alias format is only supported for TCP addresses, got dial_to: {}",
1193 dial_to_str
1194 ));
1195 }
1196 if !bind_to_str.starts_with("tcp://") {
1197 return Err(anyhow::anyhow!(
1198 "alias format is only supported for TCP addresses, got bind_to: {}",
1199 bind_to_str
1200 ));
1201 }
1202
1203 let dial_to = Self::from_zmq_url(dial_to_str)?;
1204 let bind_to = Self::from_zmq_url(bind_to_str)?;
1205
1206 return Ok((
1207 Self::Alias {
1208 dial_to: Box::new(dial_to),
1209 bind_to: Box::new(bind_to),
1210 },
1211 None,
1212 ));
1213 }
1214
1215 let (scheme, address) = address.split_once("://").ok_or_else(|| {
1217 anyhow::anyhow!("address must be in url form scheme://endppoint {}", address)
1218 })?;
1219
1220 match scheme {
1221 "tcp" => {
1222 let (host, port, listener) = Self::parse_host_port_or_fd(address)?;
1223 let socket_addr = if host == "*" {
1224 SocketAddr::new("::".parse().unwrap(), port)
1225 } else {
1226 Self::resolve_hostname_to_socket_addr(host, port)?
1227 };
1228 Ok((Self::Tcp(socket_addr), listener))
1229 }
1230 "inproc" => {
1231 let port = address.parse::<u64>().map_err(|_| {
1232 anyhow::anyhow!("inproc endpoint must be a valid port number: {}", address)
1233 })?;
1234 Ok((Self::Local(port), None))
1235 }
1236 "ipc" => Ok((Self::Unix(net::unix::SocketAddr::from_str(address)?), None)),
1237 "metatls" | "tls" | "quic" | "metaquic" => {
1238 let (host, port, listener) = Self::parse_host_port_or_fd(address)?;
1239 let hostname = if host == "*" {
1240 std::net::Ipv6Addr::UNSPECIFIED.to_string()
1241 } else {
1242 host.to_string()
1243 };
1244 let addr = match scheme {
1245 "metatls" => Self::MetaTls(TlsAddr::new(hostname, port)),
1246 "metaquic" => Self::MetaQuic(TlsAddr::new(hostname, port)),
1247 "quic" => Self::Quic(TlsAddr::new(hostname, port)),
1248 _ => Self::Tls(TlsAddr::new(hostname, port)),
1249 };
1250 Ok((addr, listener))
1251 }
1252 scheme => Err(anyhow::anyhow!("unsupported ZMQ scheme: {}", scheme)),
1253 }
1254 }
1255
1256 fn parse_host_port_or_fd(
1259 address: &str,
1260 ) -> Result<(&str, u16, Option<std::net::TcpListener>), anyhow::Error> {
1261 let (host, port_str) = address
1262 .rsplit_once(':')
1263 .ok_or_else(|| anyhow::anyhow!("invalid address format: {}", address))?;
1264
1265 if let Some(fd_str) = port_str.strip_prefix("fd") {
1266 let fd_num: RawFd = fd_str
1267 .parse()
1268 .map_err(|_| anyhow::anyhow!("invalid file descriptor number: {}", port_str))?;
1269 let borrowed = unsafe { std::os::unix::io::BorrowedFd::borrow_raw(fd_num) };
1273 nix::sys::socket::listen(&borrowed, nix::sys::socket::Backlog::new(128)?)?;
1274 let std_listener = unsafe { std::net::TcpListener::from_raw_fd(fd_num) };
1276 let local_addr = std_listener.local_addr()?;
1277 Ok((host, local_addr.port(), Some(std_listener)))
1278 } else {
1279 let port: u16 = port_str
1280 .parse()
1281 .map_err(|_| anyhow::anyhow!("invalid port: {}", port_str))?;
1282 Ok((host, port, None))
1283 }
1284 }
1285
1286 pub fn to_zmq_url(&self) -> String {
1288 match self {
1289 Self::Tcp(addr) => format!("tcp://{}", addr),
1290 Self::MetaTls(addr) => format!("metatls://{}:{}", addr.hostname, addr.port),
1291 Self::Tls(addr) => format!("tls://{}:{}", addr.hostname, addr.port),
1292 Self::Quic(addr) => format!("quic://{}:{}", addr.hostname, addr.port),
1293 Self::MetaQuic(addr) => format!("metaquic://{}:{}", addr.hostname, addr.port),
1294 Self::Local(index) => format!("inproc://{}", index),
1295 Self::Unix(addr) => format!("ipc://{}", addr),
1296 Self::Alias { dial_to, bind_to } => {
1297 format!("{}@{}", dial_to.to_zmq_url(), bind_to.to_zmq_url())
1298 }
1299 }
1300 }
1301
1302 fn resolve_hostname_to_socket_addr(host: &str, port: u16) -> Result<SocketAddr, anyhow::Error> {
1304 let host_clean = if host.starts_with('[') && host.ends_with(']') {
1306 &host[1..host.len() - 1]
1307 } else {
1308 host
1309 };
1310
1311 if let Ok(ip_addr) = host_clean.parse::<IpAddr>() {
1313 return Ok(SocketAddr::new(ip_addr, port));
1314 }
1315
1316 use std::net::ToSocketAddrs;
1318 let mut addrs = (host_clean, port)
1319 .to_socket_addrs()
1320 .map_err(|e| anyhow::anyhow!("failed to resolve hostname '{}': {}", host_clean, e))?;
1321
1322 addrs
1323 .next()
1324 .ok_or_else(|| anyhow::anyhow!("no addresses found for hostname '{}'", host_clean))
1325 }
1326}
1327
1328pub struct ChannelTx<M: RemoteMessage> {
1331 sender: mpsc::UnboundedSender<(M, CompletionSink<M>, Instant)>,
1332 dest: ChannelAddr,
1333 status: watch::Receiver<TxStatus>,
1334}
1335
1336impl<M: RemoteMessage> fmt::Debug for ChannelTx<M> {
1337 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1338 f.debug_struct("ChannelTx")
1339 .field("addr", &self.addr())
1340 .finish()
1341 }
1342}
1343
1344#[async_trait]
1345impl<M: RemoteMessage> Tx<M> for ChannelTx<M> {
1346 fn do_post(&self, message: M, completion: CompletionSink<M>) {
1350 tracing::trace!(
1351 name = "post",
1352 dest = %self.dest,
1353 "sending message"
1354 );
1355
1356 if let Err(mpsc::error::SendError((message, completion, _))) =
1357 self.sender.send((message, completion, Instant::now()))
1358 {
1359 let reason = self
1360 .status
1361 .borrow()
1362 .as_closed()
1363 .map(|r| SendErrorReason::Other(r.to_string()));
1364 completion.reject(SendError {
1365 error: ChannelError::Closed,
1366 message,
1367 reason,
1368 });
1369 }
1370 }
1371
1372 fn addr(&self) -> ChannelAddr {
1373 self.dest.clone()
1374 }
1375
1376 fn status(&self) -> &watch::Receiver<TxStatus> {
1377 &self.status
1378 }
1379}
1380
1381pub struct ChannelRx<M: RemoteMessage> {
1383 receiver: mpsc::Receiver<M>,
1384 dest: ChannelAddr,
1385 server: net::ServerHandle,
1386}
1387
1388impl<M: RemoteMessage> fmt::Debug for ChannelRx<M> {
1389 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1390 f.debug_struct("ChannelRx")
1391 .field("addr", &self.addr())
1392 .finish()
1393 }
1394}
1395
1396impl<M: RemoteMessage> ChannelRx<M> {
1397 fn stop(&self, trigger: &str) {
1399 self.server.stop(&format!(
1400 "ChannelRx {trigger}; channel address: {}",
1401 self.dest
1402 ));
1403 }
1404}
1405
1406#[async_trait]
1407impl<M: RemoteMessage> Rx<M> for ChannelRx<M> {
1408 async fn recv(&mut self) -> Result<M, ChannelError> {
1409 tracing::trace!(
1410 name = "recv",
1411 dest = %self.dest,
1412 "receiving message"
1413 );
1414 self.receiver.recv().await.ok_or(ChannelError::Closed)
1415 }
1416
1417 fn addr(&self) -> ChannelAddr {
1418 self.dest.clone()
1419 }
1420
1421 async fn join(mut self) {
1424 self.stop("joined");
1425 let _ = (&mut self.server).await;
1426 }
1428}
1429
1430impl<M: RemoteMessage> Drop for ChannelRx<M> {
1431 fn drop(&mut self) {
1432 self.stop("dropped");
1433 }
1434}
1435
1436#[allow(clippy::result_large_err)] #[track_caller]
1441pub fn dial<M: RemoteMessage>(addr: ChannelAddr) -> Result<ChannelTx<M>, ChannelError> {
1442 let addr = addr.into_dial_addr();
1443 tracing::debug!(name = "dial", caller = %Location::caller(), %addr, "dialing channel {}", addr);
1444 Ok(net::spawn::<M>(net::link(
1445 addr,
1446 net::SessionId::random(),
1447 0,
1448 net::ProtocolKind::Simplex,
1449 )?))
1450}
1451
1452pub mod unordered {
1454 use super::*;
1455
1456 #[track_caller]
1502 pub fn dial<M: RemoteMessage>(
1503 addr: ChannelAddr,
1504 num_streams: usize,
1505 ) -> Result<ChannelTx<M>, ChannelError> {
1506 assert!(num_streams > 0);
1507 let addr = addr.into_dial_addr();
1508 let session_id = net::SessionId::random();
1509 let links: Vec<net::NetLink> = (1..=num_streams)
1510 .map(|i| {
1511 net::link(
1512 addr.clone(),
1513 session_id,
1514 i as u8,
1515 net::ProtocolKind::Simplex,
1516 )
1517 })
1518 .collect::<Result<_, _>>()?;
1519 Ok(net::spawn_unordered::<M>(links))
1520 }
1521
1522 #[track_caller]
1528 pub fn serve<M: RemoteMessage>(
1529 addr: ChannelAddr,
1530 ) -> Result<(ChannelAddr, ChannelRx<M>), ChannelError> {
1531 super::serve(addr)
1532 }
1533
1534 #[track_caller]
1539 pub fn serve_with_listener<M: RemoteMessage>(
1540 addr: ChannelAddr,
1541 listener: Option<std::net::TcpListener>,
1542 ) -> Result<(ChannelAddr, ChannelRx<M>), ChannelError> {
1543 super::serve_with_listener(addr, listener)
1544 }
1545}
1546
1547#[track_caller]
1550pub fn serve<M: RemoteMessage>(
1551 addr: ChannelAddr,
1552) -> Result<(ChannelAddr, ChannelRx<M>), ChannelError> {
1553 serve_with_listener(addr, None)
1554}
1555
1556#[track_caller]
1560pub fn serve_with_listener<M: RemoteMessage>(
1561 addr: ChannelAddr,
1562 listener: Option<std::net::TcpListener>,
1563) -> Result<(ChannelAddr, ChannelRx<M>), ChannelError> {
1564 let caller = Location::caller();
1565 serve_inner(addr, listener).map(|(addr, rx)| {
1566 tracing::debug!(
1567 name = "serve",
1568 %addr,
1569 %caller,
1570 );
1571 (addr, rx)
1572 })
1573}
1574
1575#[track_caller]
1587pub fn serve_mux<M: RemoteMessage, In: RemoteMessage, Out: RemoteMessage>(
1588 addr: ChannelAddr,
1589 prebound_listener: Option<std::net::TcpListener>,
1590) -> Result<MuxServer<M, In, Out>, ChannelError> {
1591 if !addr.transport().is_net() {
1592 return Err(ChannelError::InvalidAddress(format!(
1593 "serve_mux requires a net transport; got {}",
1594 addr
1595 )));
1596 }
1597 let parts = net::mux::serve::<M, In, Out>(addr, prebound_listener)?;
1598 Ok(MuxServer {
1599 addr: parts.addr,
1600 simplex: parts.simplex,
1601 duplex: parts.duplex,
1602 shutdown: MuxShutdown {
1603 join_handle: parts.join_handle,
1604 cancel: parts.cancel,
1605 },
1606 })
1607}
1608
1609pub struct MuxServer<M: RemoteMessage, In: RemoteMessage, Out: RemoteMessage> {
1627 addr: ChannelAddr,
1628 simplex: ChannelRx<M>,
1629 duplex: net::duplex::DuplexServer<In, Out>,
1630 shutdown: MuxShutdown,
1631}
1632
1633impl<M: RemoteMessage, In: RemoteMessage, Out: RemoteMessage> MuxServer<M, In, Out> {
1634 pub fn addr(&self) -> &ChannelAddr {
1636 &self.addr
1637 }
1638
1639 pub fn simplex_mut(&mut self) -> &mut ChannelRx<M> {
1641 &mut self.simplex
1642 }
1643
1644 pub fn duplex_mut(&mut self) -> &mut net::duplex::DuplexServer<In, Out> {
1646 &mut self.duplex
1647 }
1648
1649 pub fn stop(&self, reason: &str) {
1651 self.shutdown.stop(reason);
1652 }
1653
1654 pub fn split(
1661 self,
1662 ) -> (
1663 ChannelAddr,
1664 ChannelRx<M>,
1665 net::duplex::DuplexServer<In, Out>,
1666 MuxShutdown,
1667 ) {
1668 (self.addr, self.simplex, self.duplex, self.shutdown)
1669 }
1670
1671 pub fn serve<SH, DH, DF>(
1698 self,
1699 simplex_handler: SH,
1700 duplex_handler: DH,
1701 ) -> crate::mailbox::MailboxServerHandle
1702 where
1703 SH: FnOnce(ChannelRx<M>) -> crate::mailbox::MailboxServerHandle,
1704 DH: FnOnce(net::duplex::DuplexServer<In, Out>, tokio::sync::watch::Receiver<bool>) -> DF,
1705 DF: std::future::Future<Output = ()> + Send + 'static,
1706 {
1707 let (stopped_tx, mut stopped_rx) = tokio::sync::watch::channel(false);
1708 let duplex_stop = stopped_rx.clone();
1709 let simplex_handle = simplex_handler(self.simplex);
1710 let duplex_task = tokio::spawn(duplex_handler(self.duplex, duplex_stop));
1711 let shutdown = self.shutdown;
1712 let join_handle = tokio::spawn(async move {
1713 let ok = stopped_rx.wait_for(|stopped| *stopped).await.is_ok();
1719 if !ok {
1720 std::future::pending::<()>().await;
1721 }
1722 const REASON: &str = "MuxServer shutdown";
1723 let _ = duplex_task.await;
1730 let _ = simplex_handle.await;
1738 shutdown.stop(REASON);
1742 let _ = shutdown.await;
1743 Ok::<(), crate::mailbox::MailboxServerError>(())
1744 });
1745 crate::mailbox::MailboxServerHandle::from_parts(join_handle, stopped_tx)
1746 }
1747}
1748
1749pub struct MuxShutdown {
1759 join_handle: tokio::task::JoinHandle<Result<(), net::ServerError>>,
1760 cancel: CancellationToken,
1761}
1762
1763impl MuxShutdown {
1764 pub fn stop(&self, reason: &str) {
1768 tracing::info!(
1769 name = "MuxServerStatus",
1770 status = "Stop::Sent",
1771 reason,
1772 "muxed frontend stop signalled",
1773 );
1774 self.cancel.cancel();
1775 }
1776
1777 pub async fn cancelled(&self) {
1781 self.cancel.cancelled().await;
1782 }
1783}
1784
1785impl std::future::Future for MuxShutdown {
1786 type Output =
1787 <tokio::task::JoinHandle<Result<(), net::ServerError>> as std::future::Future>::Output;
1788
1789 fn poll(
1790 mut self: std::pin::Pin<&mut Self>,
1791 cx: &mut std::task::Context<'_>,
1792 ) -> std::task::Poll<Self::Output> {
1793 std::pin::Pin::new(&mut self.join_handle).poll(cx)
1798 }
1799}
1800
1801impl Drop for MuxShutdown {
1802 fn drop(&mut self) {
1803 self.cancel.cancel();
1804 }
1805}
1806
1807fn serve_inner<M: RemoteMessage>(
1808 addr: ChannelAddr,
1809 listener: Option<std::net::TcpListener>,
1810) -> Result<(ChannelAddr, ChannelRx<M>), ChannelError> {
1811 match addr {
1812 ChannelAddr::Unix(_) => {
1813 assert!(
1814 listener.is_none(),
1815 "pre-opened listener not supported for Unix transport"
1816 );
1817 let (addr, rx) = net::server::serve::<M>(addr, listener)?;
1818 Ok((addr, rx))
1819 }
1820 ChannelAddr::Tcp(_)
1821 | ChannelAddr::Local(_)
1822 | ChannelAddr::Tls(_)
1823 | ChannelAddr::MetaTls(_)
1824 | ChannelAddr::Quic(_)
1825 | ChannelAddr::MetaQuic(_)
1826 | ChannelAddr::Alias { .. } => {
1830 let (addr, rx) = net::server::serve::<M>(addr, listener)?;
1831 Ok((addr, rx))
1832 }
1833 }
1834}
1835
1836pub fn serve_local<M: RemoteMessage>() -> (ChannelAddr, ChannelRx<M>) {
1839 serve::<M>(ChannelAddr::Local(0)).expect("fresh local stream port must bind")
1840}
1841
1842pub fn reserve_local_addr() -> ChannelAddr {
1855 ChannelAddr::Local(local::reserve())
1856}
1857
1858#[cfg(test)]
1859mod tests {
1860 use std::assert_matches;
1861 use std::collections::HashSet;
1862 use std::net::IpAddr;
1863 use std::net::Ipv4Addr;
1864 use std::net::Ipv6Addr;
1865 use std::time::Duration;
1866
1867 use rand::RngExt as _;
1868 use rand::distr::Uniform;
1869 use tokio::task::JoinSet;
1870
1871 use super::net::*;
1872 use super::*;
1873 #[test]
1874 fn test_channel_addr() {
1875 let cases_ok = vec![
1876 (
1877 "tcp<DELIM>[::1]:1234",
1878 ChannelAddr::Tcp(SocketAddr::new(
1879 IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
1880 1234,
1881 )),
1882 ),
1883 (
1884 "tcp<DELIM>127.0.0.1:8080",
1885 ChannelAddr::Tcp(SocketAddr::new(
1886 IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
1887 8080,
1888 )),
1889 ),
1890 (
1891 "quic<DELIM>example.com:443",
1892 ChannelAddr::Quic(TlsAddr::new("example.com", 443)),
1893 ),
1894 (
1895 "metaquic<DELIM>example.com:443",
1896 ChannelAddr::MetaQuic(TlsAddr::new("example.com", 443)),
1897 ),
1898 #[cfg(target_os = "linux")]
1899 ("local<DELIM>123", ChannelAddr::Local(123)),
1900 (
1901 "unix<DELIM>@yolo",
1902 ChannelAddr::Unix(
1903 unix::SocketAddr::from_abstract_name("yolo")
1904 .expect("can't make socket from abstract name"),
1905 ),
1906 ),
1907 (
1908 "unix<DELIM>/cool/socket-path",
1909 ChannelAddr::Unix(
1910 unix::SocketAddr::from_pathname("/cool/socket-path")
1911 .expect("can't make socket from path"),
1912 ),
1913 ),
1914 ];
1915
1916 for (raw, parsed) in cases_ok {
1917 for delim in ["!", ":"] {
1918 let raw = raw.replace("<DELIM>", delim);
1919 assert_eq!(raw.parse::<ChannelAddr>().unwrap(), parsed);
1920 }
1921 }
1922
1923 let cases_err = vec![
1924 ("tcp:abcdef..123124", "invalid socket address syntax"),
1925 ("xxx:foo", "no such channel type: xxx"),
1926 ("127.0.0.1", "no channel type specified"),
1927 ("local:abc", "invalid digit found in string"),
1928 ];
1929
1930 for (raw, error) in cases_err {
1931 let Err(err) = raw.parse::<ChannelAddr>() else {
1932 panic!("expected error parsing: {}", raw)
1933 };
1934 assert_eq!(format!("{}", err), error);
1935 }
1936 }
1937
1938 #[test]
1939 fn test_zmq_style_channel_addr() {
1940 assert_eq!(
1942 ChannelAddr::from_zmq_url("tcp://127.0.0.1:8080").unwrap(),
1943 ChannelAddr::Tcp("127.0.0.1:8080".parse().unwrap())
1944 );
1945
1946 assert_eq!(
1948 ChannelAddr::from_zmq_url("tcp://*:5555").unwrap(),
1949 ChannelAddr::Tcp("[::]:5555".parse().unwrap())
1950 );
1951
1952 assert_eq!(
1954 ChannelAddr::from_zmq_url("inproc://12345").unwrap(),
1955 ChannelAddr::Local(12345)
1956 );
1957
1958 assert_eq!(
1960 ChannelAddr::from_zmq_url("ipc:///tmp/my-socket").unwrap(),
1961 ChannelAddr::Unix(unix::SocketAddr::from_pathname("/tmp/my-socket").unwrap())
1962 );
1963
1964 assert_eq!(
1966 ChannelAddr::from_zmq_url("metatls://example.com:443").unwrap(),
1967 ChannelAddr::MetaTls(TlsAddr::new("example.com", 443))
1968 );
1969
1970 assert_eq!(
1972 ChannelAddr::from_zmq_url("metatls://192.168.1.1:443").unwrap(),
1973 ChannelAddr::MetaTls(TlsAddr::new("192.168.1.1", 443))
1974 );
1975
1976 assert_eq!(
1978 ChannelAddr::from_zmq_url("quic://example.com:443").unwrap(),
1979 ChannelAddr::Quic(TlsAddr::new("example.com", 443))
1980 );
1981
1982 assert_eq!(
1984 ChannelAddr::from_zmq_url("quic://*:8443").unwrap(),
1985 ChannelAddr::Quic(TlsAddr::new("::", 8443))
1986 );
1987
1988 assert_eq!(
1990 ChannelAddr::from_zmq_url("metaquic://example.com:443").unwrap(),
1991 ChannelAddr::MetaQuic(TlsAddr::new("example.com", 443))
1992 );
1993
1994 assert_eq!(
1996 ChannelAddr::from_zmq_url("metaquic://*:8443").unwrap(),
1997 ChannelAddr::MetaQuic(TlsAddr::new("::", 8443))
1998 );
1999
2000 assert_eq!(
2002 ChannelAddr::from_zmq_url("metatls://*:8443").unwrap(),
2003 ChannelAddr::MetaTls(TlsAddr::new("::", 8443))
2004 );
2005
2006 let tcp_hostname_result = ChannelAddr::from_zmq_url("tcp://localhost:8080");
2010 assert!(tcp_hostname_result.is_ok());
2011
2012 assert_eq!(
2014 ChannelAddr::from_zmq_url("tcp://[::1]:1234").unwrap(),
2015 ChannelAddr::Tcp("[::1]:1234".parse().unwrap())
2016 );
2017
2018 assert!(ChannelAddr::from_zmq_url("invalid://scheme").is_err());
2020 assert!(ChannelAddr::from_zmq_url("tcp://invalid-port").is_err());
2021 assert!(ChannelAddr::from_zmq_url("metatls://no-port").is_err());
2022 assert!(ChannelAddr::from_zmq_url("inproc://not-a-number").is_err());
2023
2024 assert_eq!(
2026 ChannelAddr::from_zmq_url("metatls://2a03:83e4:5000:c000:56d7:00cf:75ce:144a:443")
2027 .unwrap(),
2028 ChannelAddr::MetaTls(TlsAddr::new("2a03:83e4:5000:c000:56d7:cf:75ce:144a", 443))
2029 );
2030
2031 assert_eq!(
2033 ChannelAddr::from_zmq_url("metatls://2a03:83e4:5000:c000:56d7:00cf:75ce:144a:443")
2034 .unwrap(),
2035 ChannelAddr::from_zmq_url("metatls://2a03:83e4:5000:c000:56d7:cf:75ce:144a:443")
2036 .unwrap(),
2037 );
2038
2039 assert_eq!(
2041 ChannelAddr::from_zmq_url("metatls://[::1]:443").unwrap(),
2042 ChannelAddr::MetaTls(TlsAddr::new("::1", 443))
2043 );
2044
2045 assert_eq!(
2047 ChannelAddr::from_zmq_url("tls://2a03:83e4:5000:c000:56d7:00cf:75ce:144a:443").unwrap(),
2048 ChannelAddr::Tls(TlsAddr::new("2a03:83e4:5000:c000:56d7:cf:75ce:144a", 443))
2049 );
2050 assert_eq!(
2051 ChannelAddr::from_zmq_url("tls://2a03:83e4:5000:c000:56d7:00cf:75ce:144a:443").unwrap(),
2052 ChannelAddr::from_zmq_url("tls://2a03:83e4:5000:c000:56d7:cf:75ce:144a:443").unwrap(),
2053 );
2054 assert_eq!(
2055 ChannelAddr::from_zmq_url("tls://[::1]:443").unwrap(),
2056 ChannelAddr::Tls(TlsAddr::new("::1", 443))
2057 );
2058 }
2059
2060 #[tokio::test]
2061 async fn test_reserved_local_addr_can_be_served() {
2062 let addr = reserve_local_addr();
2063 assert!(dial::<u64>(addr.clone()).is_err());
2064
2065 let (bound_addr, mut rx) = serve::<u64>(addr.clone()).unwrap();
2066 assert_eq!(bound_addr, addr);
2067
2068 let tx = dial::<u64>(addr.clone()).unwrap();
2069 tx.post(123);
2070 assert_eq!(rx.recv().await.unwrap(), 123);
2071 rx.join().await;
2072
2073 let (rebound_addr, _rx) = serve::<u64>(addr.clone()).unwrap();
2074 assert_eq!(rebound_addr, addr);
2075 }
2076
2077 #[test]
2078 fn test_normalize_host() {
2079 assert_eq!(normalize_host("192.168.1.1"), "192.168.1.1");
2081
2082 assert_eq!(normalize_host("example.com"), "example.com");
2084
2085 assert_eq!(
2087 normalize_host("2a03:83e4:5000:c000:56d7:00cf:75ce:144a"),
2088 "2a03:83e4:5000:c000:56d7:cf:75ce:144a"
2089 );
2090
2091 assert_eq!(normalize_host("[::1]"), "::1");
2093
2094 assert!("[::1]".parse::<IpAddr>().is_err());
2098 }
2099
2100 #[test]
2101 fn test_find_routable_address_skips_loopback() {
2102 let addresses = [
2103 "127.0.1.1".parse::<IpAddr>().unwrap(),
2104 "169.254.1.1".parse::<IpAddr>().unwrap(),
2105 "::1".parse::<IpAddr>().unwrap(),
2106 "fe80::1".parse::<IpAddr>().unwrap(),
2107 "192.0.2.10".parse::<IpAddr>().unwrap(),
2108 ];
2109
2110 assert_eq!(
2111 find_routable_address(&addresses),
2112 Some("192.0.2.10".parse::<IpAddr>().unwrap())
2113 );
2114 }
2115
2116 #[test]
2117 fn test_find_routable_address_rejects_unroutable_addresses() {
2118 let addresses = [
2119 "127.0.1.1".parse::<IpAddr>().unwrap(),
2120 "169.254.1.1".parse::<IpAddr>().unwrap(),
2121 "::1".parse::<IpAddr>().unwrap(),
2122 "fe80::1".parse::<IpAddr>().unwrap(),
2123 ];
2124
2125 assert_eq!(find_routable_address(&addresses), None);
2126 }
2127
2128 #[test]
2129 fn test_zmq_style_alias_channel_addr() {
2130 let alias_addr = ChannelAddr::from_zmq_url("tcp://127.0.0.1:9000@tcp://[::]:8800").unwrap();
2136 match alias_addr {
2137 ChannelAddr::Alias { dial_to, bind_to } => {
2138 assert_eq!(
2139 *dial_to,
2140 ChannelAddr::Tcp("127.0.0.1:9000".parse().unwrap())
2141 );
2142 assert_eq!(*bind_to, ChannelAddr::Tcp("[::]:8800".parse().unwrap()));
2143 }
2144 _ => panic!("Expected Alias"),
2145 }
2146
2147 let non_alias = ChannelAddr::from_zmq_url("metatls://example.com:443@tcp://127.0.0.1:8080");
2150 assert!(
2151 !matches!(non_alias, Ok(ChannelAddr::Alias { .. })),
2152 "non-tcp left side must not produce Alias"
2153 );
2154
2155 assert!(
2157 ChannelAddr::from_zmq_url("tcp://127.0.0.1:8080@metatls://example.com:443").is_err()
2158 );
2159
2160 assert!(ChannelAddr::from_zmq_url("invalid://scheme@tcp://127.0.0.1:8080").is_err());
2162
2163 assert!(ChannelAddr::from_zmq_url("tcp://127.0.0.1:8080@invalid://scheme").is_err());
2165
2166 assert!(ChannelAddr::from_zmq_url("tcp://host@tcp://127.0.0.1:8080").is_err());
2168
2169 assert!(ChannelAddr::from_zmq_url("tcp://127.0.0.1:8080@tcp://example.com").is_err());
2171 }
2172
2173 #[tokio::test]
2174 async fn test_multiple_connections() {
2175 for addr in ChannelTransport::all().map(ChannelAddr::any) {
2176 let (listen_addr, mut rx) = crate::channel::serve::<u64>(addr).unwrap();
2177
2178 let mut sends: JoinSet<()> = JoinSet::new();
2179 for message in 0u64..100u64 {
2180 let addr = listen_addr.clone();
2181 sends.spawn(async move {
2182 let tx = dial::<u64>(addr).unwrap();
2183 tx.post(message);
2184 });
2185 }
2186
2187 let mut received: HashSet<u64> = HashSet::new();
2188 while received.len() < 100 {
2189 received.insert(rx.recv().await.unwrap());
2190 }
2191
2192 for message in 0u64..100u64 {
2193 assert!(received.contains(&message));
2194 }
2195
2196 loop {
2197 match sends.join_next().await {
2198 Some(Ok(())) => (),
2199 Some(Err(err)) => panic!("{}", err),
2200 None => break,
2201 }
2202 }
2203 }
2204 }
2205
2206 #[tokio::test]
2207 async fn test_server_close() {
2208 for addr in ChannelTransport::all().map(ChannelAddr::any) {
2209 if net::is_net_addr(&addr) {
2210 continue;
2213 }
2214
2215 let (listen_addr, rx) = crate::channel::serve::<u64>(addr).unwrap();
2216
2217 let tx = dial::<u64>(listen_addr).unwrap();
2218 tx.post(123);
2219 drop(rx);
2220
2221 let start = tokio::time::Instant::now();
2226
2227 let result = loop {
2228 let result = tx.try_post(123).await;
2229
2230 if result.is_err() || start.elapsed() > Duration::from_secs(10) {
2231 break result;
2232 }
2233 };
2234 assert_matches!(
2235 result,
2236 Err(SendError {
2237 error: ChannelError::Closed,
2238 message: 123,
2239 reason: None
2240 })
2241 );
2242 }
2243 }
2244
2245 fn addrs() -> Vec<ChannelAddr> {
2246 let rng = rand::rng();
2247 let uniform = Uniform::new_inclusive('a', 'z').unwrap();
2248 vec![
2249 "tcp:[::1]:0".parse().unwrap(),
2250 "local:0".parse().unwrap(),
2251 #[cfg(target_os = "linux")]
2252 "unix:".parse().unwrap(),
2253 #[cfg(target_os = "linux")]
2254 format!(
2255 "unix:@{}",
2256 rng.sample_iter(uniform).take(10).collect::<String>()
2257 )
2258 .parse()
2259 .unwrap(),
2260 ]
2261 }
2262
2263 #[test]
2264 fn test_bind_spec_from_str() {
2265 assert_eq!(
2267 BindSpec::from_str("tcp").unwrap(),
2268 BindSpec::Any(ChannelTransport::Tcp(TcpMode::Hostname))
2269 );
2270 assert_eq!(
2271 BindSpec::from_str("metatls(Hostname)").unwrap(),
2272 BindSpec::Any(ChannelTransport::MetaTls(TlsMode::Hostname))
2273 );
2274
2275 assert_eq!(
2277 BindSpec::from_str("tcp:127.0.0.1:8080").unwrap(),
2278 BindSpec::Addr(ChannelAddr::Tcp("127.0.0.1:8080".parse().unwrap()))
2279 );
2280
2281 assert_eq!(
2283 BindSpec::from_str("tcp://127.0.0.1:9000").unwrap(),
2284 BindSpec::Addr(ChannelAddr::Tcp("127.0.0.1:9000".parse().unwrap()))
2285 );
2286 assert_eq!(
2287 BindSpec::from_str("tcp://127.0.0.1:9000@tcp://[::1]:7200").unwrap(),
2288 BindSpec::Addr(
2289 ChannelAddr::from_zmq_url("tcp://127.0.0.1:9000@tcp://[::1]:7200").unwrap()
2290 )
2291 );
2292
2293 assert!(BindSpec::from_str("invalid_spec").is_err());
2295 assert!(BindSpec::from_str("unknown://scheme").is_err());
2296 assert!(BindSpec::from_str("").is_err());
2297 }
2298
2299 #[tokio::test]
2300 #[cfg_attr(not(fbcode_build), ignore)]
2302 async fn test_dial_serve() {
2303 for addr in addrs() {
2304 let (listen_addr, mut rx) = crate::channel::serve::<i32>(addr).unwrap();
2305 let tx = crate::channel::dial(listen_addr).unwrap();
2306 tx.post(123);
2307 assert_eq!(rx.recv().await.unwrap(), 123);
2308 }
2309 }
2310
2311 #[tokio::test]
2312 #[cfg_attr(not(fbcode_build), ignore)]
2314 async fn test_serve_alias_advertises_dial_to() {
2315 let probe = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
2318 let port = probe.local_addr().unwrap().port();
2319 drop(probe);
2320
2321 let alias =
2325 ChannelAddr::from_zmq_url(&format!("tcp://127.0.0.1:{port}@tcp://0.0.0.0:{port}"))
2326 .unwrap();
2327 assert_matches!(alias, ChannelAddr::Alias { .. });
2328
2329 let (listen_addr, mut rx) = crate::channel::serve::<i32>(alias).unwrap();
2330
2331 assert_eq!(
2337 listen_addr,
2338 ChannelAddr::Tcp(format!("127.0.0.1:{port}").parse().unwrap()),
2339 "serving an alias must advertise dial_to, not the alias itself"
2340 );
2341
2342 let tx = crate::channel::dial(listen_addr).unwrap();
2343 tx.post(123);
2344 assert_eq!(rx.recv().await.unwrap(), 123);
2345 }
2346
2347 #[tokio::test]
2348 #[cfg_attr(not(fbcode_build), ignore)]
2350 async fn test_send() {
2351 let config = hyperactor_config::global::lock();
2352
2353 let _guard1 = config.override_key(
2355 crate::config::MESSAGE_DELIVERY_TIMEOUT,
2356 Duration::from_secs(1),
2357 );
2358 let _guard2 = config.override_key(crate::config::MESSAGE_ACK_EVERY_N_MESSAGES, 1);
2359 for addr in addrs() {
2360 let (listen_addr, mut rx) = crate::channel::serve::<i32>(addr).unwrap();
2361 let tx = crate::channel::dial(listen_addr).unwrap();
2362 tx.send(123).await.unwrap();
2363 assert_eq!(rx.recv().await.unwrap(), 123);
2364
2365 drop(rx);
2366 assert_matches!(
2367 tx.send(123).await.unwrap_err(),
2368 SendError {
2369 error: ChannelError::Closed,
2370 message: 123,
2371 ..
2372 }
2373 );
2374 }
2375 }
2376
2377 #[test]
2378 fn test_find_routable_address_skips_link_local_ipv6() {
2379 let link_local_v6: IpAddr = "fe80::1".parse().unwrap();
2380 let routable_v6: IpAddr = "2001:db8::1".parse().unwrap();
2381 let addrs = vec![link_local_v6, routable_v6];
2382 assert_eq!(find_routable_address(&addrs), Some(routable_v6));
2383 }
2384
2385 #[test]
2386 fn test_find_routable_address_skips_link_local_ipv4() {
2387 let link_local_v4: IpAddr = "169.254.1.1".parse().unwrap();
2388 let routable_v4: IpAddr = "192.168.1.1".parse().unwrap();
2389 let addrs = vec![link_local_v4, routable_v4];
2390 assert_eq!(find_routable_address(&addrs), Some(routable_v4));
2391 }
2392
2393 #[test]
2394 fn test_find_routable_address_returns_none_when_all_link_local() {
2395 let link_local_v6: IpAddr = "fe80::1".parse().unwrap();
2396 let link_local_v4: IpAddr = "169.254.1.1".parse().unwrap();
2397 let addrs = vec![link_local_v6, link_local_v4];
2398 assert_eq!(find_routable_address(&addrs), None);
2399 }
2400
2401 #[test]
2402 fn test_find_routable_address_mixed() {
2403 let link_local_v6: IpAddr = "fe80::1".parse().unwrap();
2404 let link_local_v4: IpAddr = "169.254.0.1".parse().unwrap();
2405 let routable_v4: IpAddr = "10.0.0.1".parse().unwrap();
2406 let routable_v6: IpAddr = "2001:db8::2".parse().unwrap();
2407
2408 let addrs = vec![link_local_v6, link_local_v4, routable_v4, routable_v6];
2410 assert_eq!(find_routable_address(&addrs), Some(routable_v4));
2411 }
2412}