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    /// An error occurred which can not be anticipated or handled by a library user.
46    ///
47    /// This is likely due to a bug in our code or a rare and unexpected error.
48    ///
49    /// [`InternalErrorKind`] is intended to be informational (might be used to improve error reporting
50    /// in logs or to the end-user), rather then inspected during handling.
51    #[error("internal error ({0})")]
52    Internal(InternalErrorKind),
53}
54
55impl From<ts_runtime::SuggestExitNodeError> for Error {
56    fn from(value: ts_runtime::SuggestExitNodeError) -> Self {
57        match value {
58            ts_runtime::SuggestExitNodeError::NoPreferredDerp => Error::NoPreferredDerp,
59        }
60    }
61}
62
63/// Informational detail on the kind of internal error.
64#[non_exhaustive]
65#[derive(Debug, Clone, Copy, Eq, PartialEq)]
66pub enum InternalErrorKind {
67    /// Invalid socket state.
68    InvalidSocketState,
69    /// Response type mismatched to request type.
70    InternalResponseMismatch,
71    /// Channel closed.
72    InternalChannelClosed,
73    /// Handle to invalid TCP listener.
74    BadListenerHandle,
75    /// Handle to invalid socket.
76    BadSocketHandle,
77    /// Bad request.
78    BadRequest,
79    /// Buffer is full, cannot read in packet.
80    BufferFull,
81    /// Actor missing or shutdown.
82    Actor,
83    /// The operation is not supported while running in TUN transport mode, or
84    /// TUN mode was requested but is unavailable (no device, or the `tun`
85    /// feature is disabled in this build).
86    UnsupportedInTunMode,
87    /// The internal OS network monitor was requested (`Config::network_monitor`)
88    /// but this build was compiled without the `network-monitor` feature, so the
89    /// supervisor could not be started. Rebuild with the feature enabled, or leave
90    /// `network_monitor` off and drive `Device::rebind` from your own link monitor.
91    NetworkMonitorUnavailable,
92    /// The requested resource (e.g. a Taildrop file) does not exist.
93    NotFound,
94    /// The resource already exists (e.g. a Taildrop transfer for the same file is in progress).
95    AlreadyExists,
96    /// An underlying I/O error (e.g. a Taildrop filesystem operation failed).
97    Io,
98}
99
100impl fmt::Display for InternalErrorKind {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        match self {
103            InternalErrorKind::InvalidSocketState => write!(f, "invalid socket state"),
104            InternalErrorKind::InternalResponseMismatch => {
105                write!(f, "response type mismatched to request type")
106            }
107            InternalErrorKind::InternalChannelClosed => write!(f, "channel closed"),
108            InternalErrorKind::BadListenerHandle => write!(f, "handle to invalid TCP listener"),
109            InternalErrorKind::BadSocketHandle => write!(f, "handle to invalid socket"),
110            InternalErrorKind::BadRequest => write!(f, "bad request"),
111            InternalErrorKind::BufferFull => write!(f, "buffer full"),
112            InternalErrorKind::Actor => write!(f, "actor missing or shutdown"),
113            InternalErrorKind::UnsupportedInTunMode => {
114                write!(f, "operation unsupported in TUN transport mode")
115            }
116            InternalErrorKind::NetworkMonitorUnavailable => {
117                write!(
118                    f,
119                    "network monitor requested but the `network-monitor` feature is disabled"
120                )
121            }
122            InternalErrorKind::NotFound => write!(f, "resource not found"),
123            InternalErrorKind::AlreadyExists => write!(f, "resource already exists"),
124            InternalErrorKind::Io => write!(f, "I/O error"),
125        }
126    }
127}
128
129impl From<crate::netstack::InternalErrorKind> for InternalErrorKind {
130    fn from(e: crate::netstack::InternalErrorKind) -> Self {
131        match e {
132            crate::netstack::InternalErrorKind::InvalidSocketState => {
133                InternalErrorKind::InvalidSocketState
134            }
135            crate::netstack::InternalErrorKind::InternalResponseMismatch => {
136                InternalErrorKind::InternalResponseMismatch
137            }
138            crate::netstack::InternalErrorKind::InternalChannelClosed => {
139                InternalErrorKind::InternalChannelClosed
140            }
141            crate::netstack::InternalErrorKind::BadListenerHandle => {
142                InternalErrorKind::BadListenerHandle
143            }
144            crate::netstack::InternalErrorKind::BadSocketHandle => {
145                InternalErrorKind::BadSocketHandle
146            }
147            crate::netstack::InternalErrorKind::BufferFull => InternalErrorKind::BufferFull,
148            _ => unreachable!(),
149        }
150    }
151}
152
153impl From<ts_runtime::Error> for Error {
154    fn from(value: ts_runtime::Error) -> Self {
155        match value.kind {
156            ts_runtime::ErrorKind::Timeout => Error::Timeout,
157            ts_runtime::ErrorKind::ActorGone
158            | ts_runtime::ErrorKind::MailboxFull
159            | ts_runtime::ErrorKind::ReplyErr => Error::Internal(InternalErrorKind::Actor),
160            // TUN transport mode: a netstack-only operation, or TUN requested but unavailable
161            // (no device / `tun` feature off).
162            ts_runtime::ErrorKind::UnsupportedInTunMode | ts_runtime::ErrorKind::TunUnavailable => {
163                Error::Internal(InternalErrorKind::UnsupportedInTunMode)
164            }
165            // The network monitor was requested but this build lacks the `network-monitor` feature.
166            ts_runtime::ErrorKind::NetworkMonitorUnavailable => {
167                Error::Internal(InternalErrorKind::NetworkMonitorUnavailable)
168            }
169        }
170    }
171}
172
173impl From<NetstackError> for Error {
174    fn from(value: NetstackError) -> Self {
175        match value {
176            NetstackError::Internal(k) => Error::Internal(k.into()),
177            NetstackError::ConnectionReset => Error::ConnectionReset,
178            NetstackError::BadRequest(_) => Error::Internal(InternalErrorKind::BadRequest),
179        }
180    }
181}