Skip to main content

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}