tailscale/error.rs
1use std::fmt;
2
3use crate::netstack::Error as NetstackError;
4
5/// Errors that may occur while interacting with a device.
6#[derive(Debug, thiserror::Error, Clone, Copy, Eq, PartialEq)]
7pub enum Error {
8 /// An operation timed-out.
9 ///
10 /// This error can often be handled by retrying.
11 #[error("operation timed-out")]
12 Timeout,
13
14 /// A connection was reset.
15 ///
16 /// This error can often be handled by retrying.
17 #[error("connection reset")]
18 ConnectionReset,
19
20 /// An error reading or parsing the key file.
21 #[error("an error reading or parsing the key file")]
22 KeyFileRead,
23
24 /// An error writing out the key file.
25 #[error("an error writing out the key file")]
26 KeyFileWrite,
27
28 /// The environment variable `TS_RS_EXPERIMENT` was not set.
29 ///
30 /// The end-user must set `TS_RS_EXPERIMENT=this_is_unstable_software` to acknowledge that tailscale-rs
31 /// is early-days experimental software containing bugs, unvalidated cryptography, and no stability
32 /// or compatibility guarantees.
33 #[error("the environment variable `{}` was not set", crate::ENV_MAGIC_VAR)]
34 UnstableEnvVar,
35
36 /// No exit-node suggestion could be made because this node has no measured preferred DERP
37 /// region yet — the Rust analog of Go's `ErrNoPreferredDERP` ("no preferred DERP, try again
38 /// later"). Returned by [`Device::suggest_exit_node`](crate::Device::suggest_exit_node) before
39 /// the first netcheck has completed; callers should treat it as transient and retry once
40 /// connectivity has been measured. Distinct from a *successful* "no suggestion" (an empty
41 /// candidate set), which is `Ok(None)`.
42 #[error("no preferred DERP, try again later")]
43 NoPreferredDerp,
44
45 /// The targeted peer's node key has expired, so this node refuses to talk to its peerAPI — the
46 /// Rust analog of Go's `errors.New("peer's node key has expired")` (`LocalBackend.pingPeerAPI`,
47 /// `ipn/ipnlocal/local.go`).
48 ///
49 /// An expired peer is deliberately kept in the netmap rather than dropped, precisely so this
50 /// can be reported instead of "no such peer". It is not transient: the peer is unreachable
51 /// until control re-issues its node key, at which point the next netmap clears the flag.
52 #[error("{}", ts_control::PEER_KEY_EXPIRED)]
53 PeerKeyExpired,
54
55 /// An error occurred which can not be anticipated or handled by a library user.
56 ///
57 /// This is likely due to a bug in our code or a rare and unexpected error.
58 ///
59 /// [`InternalErrorKind`] is intended to be informational (might be used to improve error reporting
60 /// in logs or to the end-user), rather then inspected during handling.
61 #[error("internal error ({0})")]
62 Internal(InternalErrorKind),
63}
64
65impl From<ts_runtime::SuggestExitNodeError> for Error {
66 fn from(value: ts_runtime::SuggestExitNodeError) -> Self {
67 match value {
68 ts_runtime::SuggestExitNodeError::NoPreferredDerp => Error::NoPreferredDerp,
69 }
70 }
71}
72
73/// Informational detail on the kind of internal error.
74#[non_exhaustive]
75#[derive(Debug, Clone, Copy, Eq, PartialEq)]
76pub enum InternalErrorKind {
77 /// Invalid socket state.
78 InvalidSocketState,
79 /// Response type mismatched to request type.
80 InternalResponseMismatch,
81 /// Channel closed.
82 InternalChannelClosed,
83 /// Handle to invalid TCP listener.
84 BadListenerHandle,
85 /// Handle to invalid socket.
86 BadSocketHandle,
87 /// Bad request.
88 BadRequest,
89 /// Buffer is full, cannot read in packet.
90 BufferFull,
91 /// Actor missing or shutdown.
92 Actor,
93 /// The operation is not supported while running in TUN transport mode, or
94 /// TUN mode was requested but is unavailable (no device, or the `tun`
95 /// feature is disabled in this build).
96 UnsupportedInTunMode,
97 /// The internal OS network monitor was requested (`Config::network_monitor`)
98 /// but this build was compiled without the `network-monitor` feature, so the
99 /// supervisor could not be started. Rebuild with the feature enabled, or leave
100 /// `network_monitor` off and drive `Device::rebind` from your own link monitor.
101 NetworkMonitorUnavailable,
102 /// The requested resource (e.g. a Taildrop file) does not exist.
103 NotFound,
104 /// The resource already exists (e.g. a Taildrop transfer for the same file is in progress).
105 AlreadyExists,
106 /// An underlying I/O error (e.g. a Taildrop filesystem operation failed).
107 Io,
108}
109
110impl fmt::Display for InternalErrorKind {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 match self {
113 InternalErrorKind::InvalidSocketState => write!(f, "invalid socket state"),
114 InternalErrorKind::InternalResponseMismatch => {
115 write!(f, "response type mismatched to request type")
116 }
117 InternalErrorKind::InternalChannelClosed => write!(f, "channel closed"),
118 InternalErrorKind::BadListenerHandle => write!(f, "handle to invalid TCP listener"),
119 InternalErrorKind::BadSocketHandle => write!(f, "handle to invalid socket"),
120 InternalErrorKind::BadRequest => write!(f, "bad request"),
121 InternalErrorKind::BufferFull => write!(f, "buffer full"),
122 InternalErrorKind::Actor => write!(f, "actor missing or shutdown"),
123 InternalErrorKind::UnsupportedInTunMode => {
124 write!(f, "operation unsupported in TUN transport mode")
125 }
126 InternalErrorKind::NetworkMonitorUnavailable => {
127 write!(
128 f,
129 "network monitor requested but the `network-monitor` feature is disabled"
130 )
131 }
132 InternalErrorKind::NotFound => write!(f, "resource not found"),
133 InternalErrorKind::AlreadyExists => write!(f, "resource already exists"),
134 InternalErrorKind::Io => write!(f, "I/O error"),
135 }
136 }
137}
138
139impl From<crate::netstack::InternalErrorKind> for InternalErrorKind {
140 fn from(e: crate::netstack::InternalErrorKind) -> Self {
141 match e {
142 crate::netstack::InternalErrorKind::InvalidSocketState => {
143 InternalErrorKind::InvalidSocketState
144 }
145 crate::netstack::InternalErrorKind::InternalResponseMismatch => {
146 InternalErrorKind::InternalResponseMismatch
147 }
148 crate::netstack::InternalErrorKind::InternalChannelClosed => {
149 InternalErrorKind::InternalChannelClosed
150 }
151 crate::netstack::InternalErrorKind::BadListenerHandle => {
152 InternalErrorKind::BadListenerHandle
153 }
154 crate::netstack::InternalErrorKind::BadSocketHandle => {
155 InternalErrorKind::BadSocketHandle
156 }
157 crate::netstack::InternalErrorKind::BufferFull => InternalErrorKind::BufferFull,
158 _ => unreachable!(),
159 }
160 }
161}
162
163impl From<ts_runtime::Error> for Error {
164 fn from(value: ts_runtime::Error) -> Self {
165 match value.kind {
166 ts_runtime::ErrorKind::Timeout => Error::Timeout,
167 ts_runtime::ErrorKind::ActorGone
168 | ts_runtime::ErrorKind::MailboxFull
169 | ts_runtime::ErrorKind::ReplyErr => Error::Internal(InternalErrorKind::Actor),
170 // TUN transport mode: a netstack-only operation, or TUN requested but unavailable
171 // (no device / `tun` feature off).
172 ts_runtime::ErrorKind::UnsupportedInTunMode | ts_runtime::ErrorKind::TunUnavailable => {
173 Error::Internal(InternalErrorKind::UnsupportedInTunMode)
174 }
175 // The network monitor was requested but this build lacks the `network-monitor` feature.
176 ts_runtime::ErrorKind::NetworkMonitorUnavailable => {
177 Error::Internal(InternalErrorKind::NetworkMonitorUnavailable)
178 }
179 }
180 }
181}
182
183impl From<NetstackError> for Error {
184 fn from(value: NetstackError) -> Self {
185 match value {
186 NetstackError::Internal(k) => Error::Internal(k.into()),
187 NetstackError::ConnectionReset => Error::ConnectionReset,
188 NetstackError::BadRequest(_) => Error::Internal(InternalErrorKind::BadRequest),
189 }
190 }
191}