1use std::fmt;
4use std::io;
5use std::net::SocketAddr;
6use std::path::PathBuf;
7use std::time::Duration;
8
9use crate::addr::AddrError;
10use crate::driver::DriverKind;
11use crate::handoff::HandoffError;
12use crate::inherit::InheritError;
13use crate::proxy::ProxyError;
14use crate::tls::TlsError;
15
16#[non_exhaustive]
34#[derive(Debug)]
35pub enum NetError {
36 Bind {
38 addr: SocketAddr,
40 source: io::Error,
42 },
43 AddrInUse {
45 addr: SocketAddr,
47 },
48 AddrNotAvailable {
50 addr: SocketAddr,
52 },
53 PermissionDenied {
55 what: &'static str,
57 },
58 NoAddresses,
60 ConnectTimeout {
62 addr: SocketAddr,
64 after: Duration,
66 },
67 ConnectionRefused {
69 addr: SocketAddr,
71 },
72 HostUnreachable {
74 addr: SocketAddr,
76 },
77 NetworkUnreachable {
79 addr: SocketAddr,
81 },
82 TimedOut {
84 kind: TimeoutKind,
86 },
87 ConnectionReset,
89 BrokenPipe,
91 Closed,
93 Killed {
95 reason: KillReason,
97 },
98 EgressDenied {
100 code: u16,
102 },
103 Refused {
105 cause: RefusalCause,
107 },
108 Limit {
110 which: LimitKind,
112 },
113 SlowPeer {
115 direction: Direction,
117 },
118 InvalidConfig {
120 field: &'static str,
122 reason: ConfigReason,
124 },
125 Unsupported {
127 feature: &'static str,
129 platform: &'static str,
131 },
132 PathTooLong {
134 max: usize,
136 },
137 StaleSocketInUse {
139 path: PathBuf,
141 },
142 PipeNameInUse {
144 name: String,
146 },
147 NotFound,
149 FileTruncated {
151 sent: u64,
153 },
154 Address(AddrError),
156 Proxy(ProxyError),
158 Tls(TlsError),
160 Inherit(InheritError),
162 Handoff(HandoffError),
164 DriverSetup {
166 driver: DriverKind,
168 source: io::Error,
170 },
171 DoorClosed,
173 ShuttingDown,
175 ForkedChild,
177 Os {
179 op: &'static str,
181 source: io::Error,
183 },
184}
185
186#[non_exhaustive]
195#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
196pub enum ErrorClass {
197 Config,
199 Resource,
201 Peer,
203 Timeout,
205 Policy,
207 Platform,
209 Bug,
211}
212
213impl NetError {
214 #[must_use]
221 pub fn class(&self) -> ErrorClass {
222 match self {
223 NetError::InvalidConfig { .. }
224 | NetError::Address(_)
225 | NetError::AddrInUse { .. }
226 | NetError::AddrNotAvailable { .. }
227 | NetError::PermissionDenied { .. }
228 | NetError::NoAddresses
229 | NetError::PathTooLong { .. }
230 | NetError::StaleSocketInUse { .. }
231 | NetError::PipeNameInUse { .. }
232 | NetError::NotFound
233 | NetError::Bind { .. } => ErrorClass::Config,
234 NetError::Limit { which } => match which {
235 LimitKind::BufferBudget | LimitKind::Descriptors | LimitKind::CompletionQueue => {
236 ErrorClass::Resource
237 }
238 _ => ErrorClass::Policy,
239 },
240 NetError::ConnectTimeout { .. } | NetError::TimedOut { .. } => ErrorClass::Timeout,
241 NetError::ConnectionRefused { .. }
242 | NetError::HostUnreachable { .. }
243 | NetError::NetworkUnreachable { .. }
244 | NetError::ConnectionReset
245 | NetError::BrokenPipe
246 | NetError::Closed
247 | NetError::FileTruncated { .. }
248 | NetError::Proxy(_)
249 | NetError::Tls(_)
250 | NetError::SlowPeer { .. } => ErrorClass::Peer,
251 NetError::Killed { .. }
252 | NetError::EgressDenied { .. }
253 | NetError::Refused { .. }
254 | NetError::DoorClosed
255 | NetError::ShuttingDown => ErrorClass::Policy,
256 NetError::Unsupported { .. } | NetError::DriverSetup { .. } => ErrorClass::Platform,
257 NetError::ForkedChild => ErrorClass::Bug,
258 NetError::Inherit(_) | NetError::Handoff(_) => ErrorClass::Config,
259 NetError::Os { source, .. } => class_of_io(source),
260 }
261 }
262
263 #[must_use]
270 pub fn is_retryable(&self) -> bool {
271 matches!(
272 self,
273 NetError::ConnectTimeout { .. }
274 | NetError::TimedOut {
275 kind: TimeoutKind::Connect
276 }
277 ) || self.class() == ErrorClass::Resource
278 }
279
280 #[must_use]
287 pub fn os_error_code(&self) -> Option<i32> {
288 match self {
289 NetError::Bind { source, .. }
290 | NetError::DriverSetup { source, .. }
291 | NetError::Os { source, .. } => source.raw_os_error(),
292 _ => None,
293 }
294 }
295
296 pub(crate) fn from_os(op: &'static str, source: io::Error) -> NetError {
298 match source.kind() {
299 io::ErrorKind::ConnectionReset | io::ErrorKind::ConnectionAborted => {
300 NetError::ConnectionReset
301 }
302 io::ErrorKind::BrokenPipe => NetError::BrokenPipe,
303 io::ErrorKind::NotConnected => NetError::Closed,
304 _ => NetError::Os { op, source },
305 }
306 }
307}
308
309fn class_of_io(e: &io::Error) -> ErrorClass {
310 match e.kind() {
311 io::ErrorKind::TimedOut | io::ErrorKind::WouldBlock => ErrorClass::Timeout,
312 io::ErrorKind::OutOfMemory => ErrorClass::Resource,
313 io::ErrorKind::PermissionDenied | io::ErrorKind::InvalidInput => ErrorClass::Config,
314 io::ErrorKind::Unsupported => ErrorClass::Platform,
315 _ => ErrorClass::Peer,
316 }
317}
318
319impl fmt::Display for NetError {
320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321 match self {
322 NetError::Bind { addr, source } => write!(f, "could not bind {addr}: {source}"),
323 NetError::AddrInUse { addr } => write!(
324 f,
325 "another socket is listening on {addr}; stop it or choose another port (on restart, use listener hand-off)"
326 ),
327 NetError::AddrNotAvailable { addr } => write!(
328 f,
329 "{addr} is not an address of this host (or IPv6 is disabled); bind an address this host owns"
330 ),
331 NetError::PermissionDenied { what } => write!(
332 f,
333 "permission denied: {what}; ports below 1024 need privileges: bind before dropping them, or use a high port behind a reverse proxy"
334 ),
335 NetError::NoAddresses => {
336 f.write_str("name resolution returned no address; check the host name")
337 }
338 NetError::ConnectTimeout { addr, after } => write!(
339 f,
340 "no answer from {addr} within {after:?}; check reachability or raise the connect timeout"
341 ),
342 NetError::ConnectionRefused { addr } => {
343 write!(f, "{addr} refused the connection; nothing listens there")
344 }
345 NetError::HostUnreachable { addr } => write!(f, "no route to host {addr}"),
346 NetError::NetworkUnreachable { addr } => write!(f, "no route to the network of {addr}"),
347 NetError::TimedOut { kind } => write!(
348 f,
349 "the {} deadline passed; the peer is too slow, or the limit is too low for this workload",
350 kind.as_str()
351 ),
352 NetError::ConnectionReset => f.write_str("the peer reset the connection"),
353 NetError::BrokenPipe => f.write_str("write after the peer closed the connection"),
354 NetError::Closed => f.write_str("the connection is already closed"),
355 NetError::Killed { reason } => {
356 write!(f, "the connection was killed (code {})", reason.0)
357 }
358 NetError::EgressDenied { code } => {
359 write!(
360 f,
361 "the gate denied a write (code {code}); the connection was reset"
362 )
363 }
364 NetError::Refused { cause } => {
365 write!(f, "the connection was refused: {}", cause.as_str())
366 }
367 NetError::Limit { which } => {
368 write!(
369 f,
370 "the {} limit was reached; raise it or shed load",
371 which.as_str()
372 )
373 }
374 NetError::SlowPeer { direction } => write!(
375 f,
376 "the peer did not reach the minimum transfer rate ({})",
377 direction.as_str()
378 ),
379 NetError::InvalidConfig { field, reason } => {
380 write!(f, "invalid configuration for `{field}`: {reason}")
381 }
382 NetError::Unsupported { feature, platform } => {
383 write!(f, "{feature} is not supported on {platform}")
384 }
385 NetError::PathTooLong { max } => write!(
386 f,
387 "the socket path is longer than the platform allows ({max} bytes including the terminator); use a shorter path"
388 ),
389 NetError::StaleSocketInUse { path } => write!(
390 f,
391 "{} is in use by a live listener or is not a socket; stop that listener or choose another path",
392 path.display()
393 ),
394 NetError::PipeNameInUse { name } => write!(
395 f,
396 "another process created the pipe {name} first (possible squatting); choose another name or find that process"
397 ),
398 NetError::NotFound => {
399 f.write_str("no local endpoint with that name; start the server first")
400 }
401 NetError::FileTruncated { sent } => write!(
402 f,
403 "the file ended after {sent} bytes while it was being sent; the connection was reset"
404 ),
405 NetError::Address(e) => write!(f, "{e}"),
406 NetError::Proxy(e) => write!(f, "PROXY protocol: {e}"),
407 NetError::Tls(e) => write!(f, "TLS: {e}"),
408 NetError::Inherit(e) => write!(f, "inherited listeners: {e}"),
409 NetError::Handoff(e) => write!(f, "listener hand-off: {e}"),
410 NetError::DriverSetup { driver, source } => write!(
411 f,
412 "the {driver:?} driver could not start: {source}; use DriverKind::Auto or the blocking driver"
413 ),
414 NetError::DoorClosed => f.write_str("the door is closed; call open_door first"),
415 NetError::ShuttingDown => f.write_str("the server is draining or stopped"),
416 NetError::ForkedChild => f.write_str(
417 "a server was used in a child process after fork; create servers after forking",
418 ),
419 NetError::Os { op, source } => write!(f, "{op} failed: {source}"),
420 }
421 }
422}
423
424impl std::error::Error for NetError {
425 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
426 match self {
427 NetError::Bind { source, .. }
428 | NetError::DriverSetup { source, .. }
429 | NetError::Os { source, .. } => Some(source),
430 NetError::Address(e) => Some(e),
431 NetError::Proxy(e) => Some(e),
432 NetError::Tls(e) => Some(e),
433 NetError::Inherit(e) => Some(e),
434 NetError::Handoff(e) => Some(e),
435 _ => None,
436 }
437 }
438}
439
440impl From<io::Error> for NetError {
441 fn from(e: io::Error) -> NetError {
442 match e.kind() {
443 io::ErrorKind::TimedOut | io::ErrorKind::WouldBlock => NetError::TimedOut {
444 kind: TimeoutKind::Read,
445 },
446 _ => NetError::from_os("io", e),
447 }
448 }
449}
450
451impl From<AddrError> for NetError {
452 fn from(e: AddrError) -> NetError {
453 NetError::Address(e)
454 }
455}
456
457impl From<NetError> for io::Error {
458 fn from(e: NetError) -> io::Error {
459 let kind = match &e {
460 NetError::TimedOut { .. }
461 | NetError::ConnectTimeout { .. }
462 | NetError::SlowPeer { .. } => io::ErrorKind::TimedOut,
463 NetError::ConnectionReset | NetError::Killed { .. } | NetError::EgressDenied { .. } => {
464 io::ErrorKind::ConnectionReset
465 }
466 NetError::BrokenPipe => io::ErrorKind::BrokenPipe,
467 NetError::Closed => io::ErrorKind::NotConnected,
468 NetError::ConnectionRefused { .. } => io::ErrorKind::ConnectionRefused,
469 NetError::AddrInUse { .. } => io::ErrorKind::AddrInUse,
470 NetError::AddrNotAvailable { .. } => io::ErrorKind::AddrNotAvailable,
471 NetError::PermissionDenied { .. } => io::ErrorKind::PermissionDenied,
472 NetError::NotFound => io::ErrorKind::NotFound,
473 NetError::Unsupported { .. } => io::ErrorKind::Unsupported,
474 NetError::InvalidConfig { .. } | NetError::Address(_) => io::ErrorKind::InvalidInput,
475 _ => io::ErrorKind::Other,
476 };
477 io::Error::new(kind, e)
478 }
479}
480
481#[non_exhaustive]
493#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
494pub enum CloseReason {
495 PeerClosed,
497 PeerReset,
499 Idle,
501 HandshakeTimeout,
503 ReadTimeout,
505 WriteTimeout,
507 ExchangeDeadline,
509 ParkDeadline,
511 SlowPeer {
513 direction: Direction,
515 },
516 Refused(RefusalCause),
518 ProxyUntrusted,
520 ProxyMissing,
522 ProxyInvalid,
524 TlsFailed,
526 HelloRefused,
528 Killed(KillReason),
530 EgressDenied(u16),
532 Drained,
534 DrainDeadline,
536 Shed,
538 Shutdown,
540 SessionClosed,
542 SessionError,
544 SessionPanicked,
546 Limit(LimitKind),
548 Io(OsErrorClass),
550}
551
552impl CloseReason {
553 pub(crate) const BY_INDEX: [CloseReason; CloseReason::COUNT] = [
556 CloseReason::PeerClosed,
557 CloseReason::PeerReset,
558 CloseReason::Idle,
559 CloseReason::HandshakeTimeout,
560 CloseReason::ReadTimeout,
561 CloseReason::WriteTimeout,
562 CloseReason::ExchangeDeadline,
563 CloseReason::ParkDeadline,
564 CloseReason::SlowPeer {
565 direction: Direction::In,
566 },
567 CloseReason::Refused(RefusalCause::GlobalCap),
568 CloseReason::ProxyUntrusted,
569 CloseReason::ProxyMissing,
570 CloseReason::ProxyInvalid,
571 CloseReason::TlsFailed,
572 CloseReason::HelloRefused,
573 CloseReason::Killed(KillReason(0)),
574 CloseReason::EgressDenied(0),
575 CloseReason::Drained,
576 CloseReason::DrainDeadline,
577 CloseReason::Shed,
578 CloseReason::Shutdown,
579 CloseReason::SessionClosed,
580 CloseReason::SessionError,
581 CloseReason::SessionPanicked,
582 CloseReason::Limit(LimitKind::Connections),
583 CloseReason::Io(OsErrorClass::Other(0)),
584 ];
585
586 #[must_use]
592 pub fn as_str(&self) -> &'static str {
593 match self {
594 CloseReason::PeerClosed => "peer_closed",
595 CloseReason::PeerReset => "peer_reset",
596 CloseReason::Idle => "idle",
597 CloseReason::HandshakeTimeout => "handshake_timeout",
598 CloseReason::ReadTimeout => "read_timeout",
599 CloseReason::WriteTimeout => "write_timeout",
600 CloseReason::ExchangeDeadline => "exchange_deadline",
601 CloseReason::ParkDeadline => "park_deadline",
602 CloseReason::SlowPeer {
603 direction: Direction::In,
604 } => "slow_peer_in",
605 CloseReason::SlowPeer {
606 direction: Direction::Out,
607 } => "slow_peer_out",
608 CloseReason::Refused(_) => "refused",
609 CloseReason::ProxyUntrusted => "proxy_untrusted",
610 CloseReason::ProxyMissing => "proxy_missing",
611 CloseReason::ProxyInvalid => "proxy_invalid",
612 CloseReason::TlsFailed => "tls_failed",
613 CloseReason::HelloRefused => "hello_refused",
614 CloseReason::Killed(_) => "killed",
615 CloseReason::EgressDenied(_) => "egress_denied",
616 CloseReason::Drained => "drained",
617 CloseReason::DrainDeadline => "drain_deadline",
618 CloseReason::Shed => "shed",
619 CloseReason::Shutdown => "shutdown",
620 CloseReason::SessionClosed => "session_closed",
621 CloseReason::SessionError => "session_error",
622 CloseReason::SessionPanicked => "session_panicked",
623 CloseReason::Limit(_) => "limit",
624 CloseReason::Io(_) => "io",
625 }
626 }
627
628 #[must_use]
634 pub fn is_abortive(&self) -> bool {
635 !matches!(
636 self,
637 CloseReason::PeerClosed
638 | CloseReason::Idle
639 | CloseReason::Drained
640 | CloseReason::SessionClosed
641 )
642 }
643
644 pub(crate) fn index(&self) -> usize {
646 match self {
647 CloseReason::PeerClosed => 0,
648 CloseReason::PeerReset => 1,
649 CloseReason::Idle => 2,
650 CloseReason::HandshakeTimeout => 3,
651 CloseReason::ReadTimeout => 4,
652 CloseReason::WriteTimeout => 5,
653 CloseReason::ExchangeDeadline => 6,
654 CloseReason::ParkDeadline => 7,
655 CloseReason::SlowPeer { .. } => 8,
656 CloseReason::Refused(_) => 9,
657 CloseReason::ProxyUntrusted => 10,
658 CloseReason::ProxyMissing => 11,
659 CloseReason::ProxyInvalid => 12,
660 CloseReason::TlsFailed => 13,
661 CloseReason::HelloRefused => 14,
662 CloseReason::Killed(_) => 15,
663 CloseReason::EgressDenied(_) => 16,
664 CloseReason::Drained => 17,
665 CloseReason::DrainDeadline => 18,
666 CloseReason::Shed => 19,
667 CloseReason::Shutdown => 20,
668 CloseReason::SessionClosed => 21,
669 CloseReason::SessionError => 22,
670 CloseReason::SessionPanicked => 23,
671 CloseReason::Limit(_) => 24,
672 CloseReason::Io(_) => 25,
673 }
674 }
675
676 pub(crate) const COUNT: usize = 26;
678
679 pub(crate) fn for_timeout(kind: TimeoutKind) -> CloseReason {
681 match kind {
682 TimeoutKind::Handshake => CloseReason::HandshakeTimeout,
683 TimeoutKind::Idle => CloseReason::Idle,
684 TimeoutKind::Read | TimeoutKind::Connect => CloseReason::ReadTimeout,
685 TimeoutKind::Write => CloseReason::WriteTimeout,
686 TimeoutKind::Exchange => CloseReason::ExchangeDeadline,
687 TimeoutKind::Park => CloseReason::ParkDeadline,
688 TimeoutKind::Drain => CloseReason::DrainDeadline,
689 }
690 }
691}
692
693impl fmt::Display for CloseReason {
694 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
695 f.write_str(self.as_str())
696 }
697}
698
699#[non_exhaustive]
705#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
706pub enum RefusalCause {
707 GlobalCap,
709 ListenerCap,
711 SourceCap,
713 LaneReserve,
715 Gate(u16),
717 Session,
719 Busy,
721 DoorClosed,
723 Shed,
725 Lockdown,
727 Credentials,
730}
731
732impl RefusalCause {
733 pub(crate) const BY_INDEX: [RefusalCause; RefusalCause::COUNT] = [
735 RefusalCause::GlobalCap,
736 RefusalCause::ListenerCap,
737 RefusalCause::SourceCap,
738 RefusalCause::LaneReserve,
739 RefusalCause::Gate(0),
740 RefusalCause::Session,
741 RefusalCause::Busy,
742 RefusalCause::DoorClosed,
743 RefusalCause::Shed,
744 RefusalCause::Lockdown,
745 RefusalCause::Credentials,
746 ];
747
748 #[must_use]
750 pub fn as_str(&self) -> &'static str {
751 match self {
752 RefusalCause::GlobalCap => "global_cap",
753 RefusalCause::ListenerCap => "listener_cap",
754 RefusalCause::SourceCap => "source_cap",
755 RefusalCause::LaneReserve => "lane_reserve",
756 RefusalCause::Gate(_) => "gate",
757 RefusalCause::Session => "session",
758 RefusalCause::Busy => "busy",
759 RefusalCause::DoorClosed => "door_closed",
760 RefusalCause::Shed => "shed",
761 RefusalCause::Lockdown => "lockdown",
762 RefusalCause::Credentials => "credentials",
763 }
764 }
765
766 pub(crate) fn index(&self) -> usize {
767 match self {
768 RefusalCause::GlobalCap => 0,
769 RefusalCause::ListenerCap => 1,
770 RefusalCause::SourceCap => 2,
771 RefusalCause::LaneReserve => 3,
772 RefusalCause::Gate(_) => 4,
773 RefusalCause::Session => 5,
774 RefusalCause::Busy => 6,
775 RefusalCause::DoorClosed => 7,
776 RefusalCause::Shed => 8,
777 RefusalCause::Lockdown => 9,
778 RefusalCause::Credentials => 10,
779 }
780 }
781
782 pub(crate) const COUNT: usize = 11;
783}
784
785#[non_exhaustive]
791#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
792pub enum LimitKind {
793 Connections,
795 PerListener,
797 PerSource,
799 BufferBudget,
801 OutputBudget,
803 CompletionQueue,
805 ProxyHeader,
807 AcceptBatch,
809 ReadBuffer,
811 Amplification,
813 SourceBudget,
815 Descriptors,
817}
818
819impl LimitKind {
820 #[must_use]
822 pub fn as_str(&self) -> &'static str {
823 match self {
824 LimitKind::Connections => "connections",
825 LimitKind::PerListener => "per_listener",
826 LimitKind::PerSource => "per_source",
827 LimitKind::BufferBudget => "buffer_budget",
828 LimitKind::OutputBudget => "output_budget",
829 LimitKind::CompletionQueue => "completion_queue",
830 LimitKind::ProxyHeader => "proxy_header",
831 LimitKind::AcceptBatch => "accept_batch",
832 LimitKind::ReadBuffer => "read_buffer",
833 LimitKind::Amplification => "amplification",
834 LimitKind::SourceBudget => "source_budget",
835 LimitKind::Descriptors => "descriptors",
836 }
837 }
838}
839
840#[non_exhaustive]
846#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
847pub enum TimeoutKind {
848 Handshake,
850 Idle,
852 Read,
854 Write,
856 Exchange,
858 Park,
860 Connect,
862 Drain,
864}
865
866impl TimeoutKind {
867 #[must_use]
869 pub fn as_str(&self) -> &'static str {
870 match self {
871 TimeoutKind::Handshake => "handshake",
872 TimeoutKind::Idle => "idle",
873 TimeoutKind::Read => "read",
874 TimeoutKind::Write => "write",
875 TimeoutKind::Exchange => "exchange",
876 TimeoutKind::Park => "park",
877 TimeoutKind::Connect => "connect",
878 TimeoutKind::Drain => "drain",
879 }
880 }
881}
882
883#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
890pub struct KillReason(pub u16);
891
892#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
898pub enum Direction {
899 In,
901 Out,
903}
904
905impl Direction {
906 #[must_use]
908 pub fn as_str(&self) -> &'static str {
909 match self {
910 Direction::In => "in",
911 Direction::Out => "out",
912 }
913 }
914}
915
916#[non_exhaustive]
924#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
925pub enum OsErrorClass {
926 Reset,
928 Aborted,
930 TimedOut,
932 Refused,
934 Unreachable,
936 FileTruncated,
938 Other(i32),
940}
941
942impl OsErrorClass {
943 #[must_use]
945 pub fn from_io(e: &io::Error) -> OsErrorClass {
946 match e.kind() {
947 io::ErrorKind::ConnectionReset | io::ErrorKind::BrokenPipe => OsErrorClass::Reset,
948 io::ErrorKind::ConnectionAborted => OsErrorClass::Aborted,
949 io::ErrorKind::TimedOut => OsErrorClass::TimedOut,
950 io::ErrorKind::ConnectionRefused => OsErrorClass::Refused,
951 io::ErrorKind::HostUnreachable | io::ErrorKind::NetworkUnreachable => {
952 OsErrorClass::Unreachable
953 }
954 _ => OsErrorClass::Other(e.raw_os_error().unwrap_or(0)),
955 }
956 }
957}
958
959#[non_exhaustive]
966#[derive(Clone, Copy, Debug, PartialEq, Eq)]
967pub enum ConfigReason {
968 Zero,
970 TooLarge {
972 max: u64,
974 },
975 TooSmall {
977 min: u64,
979 },
980 AboveGlobal,
982 BelowTick,
984 WindowBelowCheck,
986 BudgetBelowWorkers {
988 need: u64,
990 },
991 DescriptorLimit {
993 have: u64,
995 need: u64,
997 },
998 Missing,
1000 NotListening,
1002 Inconsistent,
1004 Malformed,
1006}
1007
1008impl fmt::Display for ConfigReason {
1009 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1010 match self {
1011 ConfigReason::Zero => f.write_str("zero is not allowed here"),
1012 ConfigReason::TooLarge { max } => write!(f, "above the maximum of {max}"),
1013 ConfigReason::TooSmall { min } => write!(f, "below the minimum of {min}"),
1014 ConfigReason::AboveGlobal => f.write_str("above the global connection cap"),
1015 ConfigReason::BelowTick => f.write_str("shorter than one timer tick"),
1016 ConfigReason::WindowBelowCheck => {
1017 f.write_str("the minimum-rate window is shorter than its check interval")
1018 }
1019 ConfigReason::BudgetBelowWorkers { need } => write!(
1020 f,
1021 "the buffer budget must be at least {need} bytes (workers x largest class x 2)"
1022 ),
1023 ConfigReason::DescriptorLimit { have, need } => write!(
1024 f,
1025 "needs {need} descriptors but the process limit is {have}; raise the limit or lower the caps"
1026 ),
1027 ConfigReason::Missing => f.write_str("a required part is not set"),
1028 ConfigReason::NotListening => {
1029 f.write_str("the socket is not a listening stream socket")
1030 }
1031 ConfigReason::Inconsistent => f.write_str("values contradict each other"),
1032 ConfigReason::Malformed => f.write_str("contains a byte or character it cannot"),
1033 }
1034 }
1035}
1036
1037#[cfg(test)]
1038mod tests {
1039 use super::*;
1040
1041 #[test]
1042 fn test_by_index_tables_match_index() {
1043 for (i, r) in CloseReason::BY_INDEX.iter().enumerate() {
1044 assert_eq!(r.index(), i, "{r:?}");
1045 }
1046 for (i, c) in RefusalCause::BY_INDEX.iter().enumerate() {
1047 assert_eq!(c.index(), i, "{c:?}");
1048 }
1049 }
1050
1051 #[test]
1052 fn test_close_reason_index_covers_every_variant_once() {
1053 let all = [
1054 CloseReason::PeerClosed,
1055 CloseReason::PeerReset,
1056 CloseReason::Idle,
1057 CloseReason::HandshakeTimeout,
1058 CloseReason::ReadTimeout,
1059 CloseReason::WriteTimeout,
1060 CloseReason::ExchangeDeadline,
1061 CloseReason::ParkDeadline,
1062 CloseReason::SlowPeer {
1063 direction: Direction::Out,
1064 },
1065 CloseReason::Refused(RefusalCause::Busy),
1066 CloseReason::ProxyUntrusted,
1067 CloseReason::ProxyMissing,
1068 CloseReason::ProxyInvalid,
1069 CloseReason::TlsFailed,
1070 CloseReason::HelloRefused,
1071 CloseReason::Killed(KillReason(1)),
1072 CloseReason::EgressDenied(1),
1073 CloseReason::Drained,
1074 CloseReason::DrainDeadline,
1075 CloseReason::Shed,
1076 CloseReason::Shutdown,
1077 CloseReason::SessionClosed,
1078 CloseReason::SessionError,
1079 CloseReason::SessionPanicked,
1080 CloseReason::Limit(LimitKind::ReadBuffer),
1081 CloseReason::Io(OsErrorClass::Reset),
1082 ];
1083 let mut seen = [false; CloseReason::COUNT];
1084 for r in all {
1085 assert!(!seen[r.index()], "duplicate index for {r:?}");
1086 seen[r.index()] = true;
1087 }
1088 assert!(seen.iter().all(|s| *s));
1089 }
1090
1091 #[test]
1092 fn test_close_reason_size_is_eight_bytes() {
1093 assert!(std::mem::size_of::<CloseReason>() <= 8);
1094 }
1095
1096 #[test]
1097 fn test_close_style_graceful_reasons_are_not_abortive() {
1098 for r in [
1099 CloseReason::PeerClosed,
1100 CloseReason::Idle,
1101 CloseReason::Drained,
1102 CloseReason::SessionClosed,
1103 ] {
1104 assert!(!r.is_abortive());
1105 }
1106 assert!(CloseReason::DrainDeadline.is_abortive());
1107 assert!(CloseReason::Killed(KillReason(0)).is_abortive());
1108 }
1109
1110 #[test]
1111 fn test_from_io_timed_out_maps_to_read_timeout() {
1112 let e: NetError = io::Error::from(io::ErrorKind::TimedOut).into();
1113 assert!(matches!(
1114 e,
1115 NetError::TimedOut {
1116 kind: TimeoutKind::Read
1117 }
1118 ));
1119 }
1120
1121 #[test]
1122 fn test_from_io_reset_maps_to_connection_reset() {
1123 let e: NetError = io::Error::from(io::ErrorKind::ConnectionReset).into();
1124 assert!(matches!(e, NetError::ConnectionReset));
1125 assert_eq!(e.class(), ErrorClass::Peer);
1126 }
1127
1128 #[test]
1129 fn test_into_io_keeps_kind_and_source() {
1130 let io_err: io::Error = NetError::BrokenPipe.into();
1131 assert_eq!(io_err.kind(), io::ErrorKind::BrokenPipe);
1132 assert!(io_err.get_ref().is_some());
1133 }
1134
1135 #[test]
1136 fn test_retryable_connect_timeout_true_reset_false() {
1137 let addr: SocketAddr = "127.0.0.1:1".parse().unwrap();
1138 assert!(
1139 NetError::ConnectTimeout {
1140 addr,
1141 after: Duration::from_secs(1)
1142 }
1143 .is_retryable()
1144 );
1145 assert!(!NetError::ConnectionReset.is_retryable());
1146 assert!(
1147 NetError::Limit {
1148 which: LimitKind::BufferBudget
1149 }
1150 .is_retryable()
1151 );
1152 }
1153
1154 #[test]
1155 fn test_display_says_what_to_do() {
1156 let addr: SocketAddr = "0.0.0.0:443".parse().unwrap();
1157 let text = NetError::AddrInUse { addr }.to_string();
1158 assert!(text.contains("0.0.0.0:443"));
1159 assert!(text.contains("hand-off"));
1160 }
1161
1162 #[test]
1163 fn test_os_error_code_present_only_for_os_errors() {
1164 let e = NetError::Os {
1165 op: "x",
1166 source: io::Error::from_raw_os_error(5),
1167 };
1168 assert_eq!(e.os_error_code(), Some(5));
1169 assert_eq!(NetError::NotFound.os_error_code(), None);
1170 }
1171
1172 #[test]
1173 fn test_timeout_kind_maps_to_close_reason() {
1174 assert_eq!(
1175 CloseReason::for_timeout(TimeoutKind::Idle),
1176 CloseReason::Idle
1177 );
1178 assert_eq!(
1179 CloseReason::for_timeout(TimeoutKind::Write),
1180 CloseReason::WriteTimeout
1181 );
1182 }
1183}