1#![doc = include_str!("../README.md")]
2
3extern crate alloc;
4
5const PKG_VERSION: &str = if let Some(version) = option_env!("CARGO_PKG_VERSION") {
9 version
10} else {
11 ""
12};
13
14pub(crate) const MAX_CONTROL_RESPONSE: usize = 1024 * 1024;
24
25#[cfg(feature = "acme")]
27pub mod acme;
28#[cfg(feature = "async_tokio")]
29mod cert;
30mod config;
31mod control_dialer;
32mod derp;
33mod dial_plan;
34mod dns;
35mod hostinfo;
36#[cfg_attr(not(feature = "async_tokio"), expect(dead_code))]
37mod map_request_builder;
38mod node;
39#[cfg(feature = "async_tokio")]
40mod serve;
41mod service;
42mod ssh_policy;
43mod tka;
44#[cfg(feature = "async_tokio")]
45mod tokio;
46#[cfg(feature = "identity-federation")]
47pub mod wif;
48
49use std::fmt;
50
51#[cfg(feature = "async_tokio")]
52pub use cert::{
53 CertError, MISSING_CERT_RPC, certified_key_from_pem, get_certificate, is_tailnet_name,
54};
55#[cfg(feature = "acme")]
56pub use cert::{
57 PublishTxt, SetDnsPublisher, issue_cert_pair_via_setdns, issue_certificate_via_setdns,
58};
59#[doc(inline)]
60pub use config::{
61 Config, DEFAULT_CONTROL_SERVER, DEFAULT_PERSISTENT_KEEPALIVE, ExitProxyConfig, ExitProxyScheme,
62 LocalApi, TransportMode, TunConfig, services_hash,
63};
64pub use control_dialer::{ControlDialer, TcpDialer, complete_connection};
65pub use derp::{Map as DerpMap, Region as DerpRegion, convert_derp_map};
66pub use dial_plan::{DialCandidate, DialMode, DialPlan};
67pub use dns::{DnsConfig, ExtraRecord, Resolver as DnsResolver, ResolverTransport};
68pub use node::{
69 ExitNodeSelector, Id as NodeId, Node, NodeCapMap, PeerChange, StableId as StableNodeId,
70 TailnetAddress, UserProfile, is_tailscale_ip, validate_service_name,
71};
72#[cfg(feature = "async_tokio")]
73pub use serve::{
74 FunnelError, FunnelOptions, MISSING_FUNNEL_RELAY, ServeConfig, ServeState, ServeTarget,
75 accept_tls, funnel_access, listen_funnel, listen_tls, tls_acceptor,
76};
77pub use service::{ServiceError, ServiceMode, resolve_service_listen};
78pub use ssh_policy::{
79 SshAccept, SshAction, SshConnIdentity, SshDecision, SshDenyReason, SshPolicy, SshPrincipal,
80 SshRule,
81};
82pub use tka::TkaStatus;
83pub use ts_control_serde::{
84 Endpoint, EndpointType, NODE_ATTR_SUGGEST_EXIT_NODE, TkaBootstrapRequest, TkaBootstrapResponse,
85 TkaDisableRequest, TkaDisableResponse, TkaInitBeginRequest, TkaInitBeginResponse,
86 TkaInitFinishRequest, TkaInitFinishResponse, TkaSignInfo, TkaSubmitSignatureRequest,
87 TkaSubmitSignatureResponse, TkaSyncOfferRequest, TkaSyncOfferResponse, TkaSyncSendRequest,
88 TkaSyncSendResponse, UserId,
89};
90#[cfg(feature = "identity-federation")]
91pub use wif::{WifConfig, WifError, resolve_auth_key};
92
93#[cfg(feature = "async_tokio")]
97pub mod tls {
98 pub use tokio_rustls::{TlsAcceptor, rustls::sign::CertifiedKey, server::TlsStream};
99}
100
101#[cfg(feature = "async_tokio")]
102pub use crate::tokio::{
103 AsyncControlClient, FilterUpdate, IdTokenError, LogoutError, LogoutInternalErrorKind,
104 NETMAP_CACHE_FILE, NODE_ATTR_CACHE_NETWORK_MAPS, NODE_ATTR_DISABLE_CACHE_NETWORK_MAPS,
105 NetmapCache, PeerUpdate, SetDnsError, SetDnsInternalErrorKind, StateUpdate, TkaSyncError,
106 TkaSyncInternalErrorKind, fetch_id_token, logout, netmap_caching_enabled, set_dns,
107 tka_bootstrap, tka_disable, tka_init_begin, tka_init_finish, tka_submit_signature,
108 tka_sync_offer, tka_sync_send,
109};
110
111#[derive(Debug, thiserror::Error, Clone, Eq, PartialEq)]
113pub enum Error {
114 #[error("machine was not authorized by control to join tailnet, authorize at {0}")]
116 MachineNotAuthorized(url::Url),
117
118 #[error("machine awaiting admin approval to join tailnet (no interactive auth URL)")]
126 NeedsMachineAuth,
127
128 #[error("invalid URL: {0}")]
130 InvalidUrl(url::Url),
131
132 #[error("control rejected registration: {0}")]
135 Registration(String),
136
137 #[error("control rate limited the request; retry after {0:?}")]
141 RateLimited(core::time::Duration),
142
143 #[error("a networking error occurred in {0}")]
149 NetworkError(Operation),
150
151 #[error("{0} error in {1}")]
156 Internal(InternalErrorKind, Operation),
157}
158
159impl Error {
160 fn io_error(err: std::io::Error, op: Operation) -> Self {
161 if crate::is_network_error(&err) {
162 Error::NetworkError(op)
163 } else {
164 Error::Internal(InternalErrorKind::Io, op)
165 }
166 }
167}
168
169#[non_exhaustive]
173#[derive(Debug, Clone, Copy, Eq, PartialEq)]
174pub enum InternalErrorKind {
175 Url,
177 Http,
179 SerDe,
181 Io,
183 MessageFormat,
185 Utf8,
187 NoiseHandshake,
189 Challenge,
191 MachineAuthorization,
194}
195
196impl fmt::Display for InternalErrorKind {
197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198 match self {
199 InternalErrorKind::Url => write!(f, "URL parsing error"),
200 InternalErrorKind::Http => write!(f, "unsuccessful HTTP request or upgrade"),
201 InternalErrorKind::SerDe => write!(f, "serialization/deserialization error"),
202 InternalErrorKind::Io => write!(f, "I/O error"),
203 InternalErrorKind::MessageFormat => write!(f, "message format error"),
204 InternalErrorKind::Utf8 => write!(f, "invalid UTF8"),
205 InternalErrorKind::NoiseHandshake => write!(f, "error in Noise handshake"),
206 InternalErrorKind::Challenge => write!(f, "error with Tailscale challenge packet"),
207 InternalErrorKind::MachineAuthorization => {
208 write!(f, "machine not authorized to register with Tailnet")
209 }
210 }
211 }
212}
213
214#[derive(Debug, Clone, Copy, Eq, PartialEq)]
216pub enum Operation {
217 MapRequest,
219 ConnectToControlServer,
221 Registration,
223}
224
225impl fmt::Display for Operation {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 match self {
228 Operation::MapRequest => write!(f, "net map request"),
229 Operation::ConnectToControlServer => write!(f, "connection to control server"),
230 Operation::Registration => write!(f, "registration"),
231 }
232 }
233}
234
235impl From<ts_http_util::Error> for Error {
236 fn from(error: ts_http_util::Error) -> Self {
237 tracing::error!(%error, "http error");
238
239 if http_error_is_recoverable(error) {
240 Error::NetworkError(Operation::ConnectToControlServer)
241 } else {
242 Error::Internal(InternalErrorKind::Http, Operation::ConnectToControlServer)
243 }
244 }
245}
246
247fn is_network_error(err: &std::io::Error) -> bool {
249 use std::io::ErrorKind::*;
250 matches!(
251 err.kind(),
252 ConnectionRefused
253 | ConnectionReset
254 | HostUnreachable
255 | NetworkUnreachable
256 | ConnectionAborted
257 | NotConnected
258 | TimedOut
259 | AddrNotAvailable
260 | Interrupted
261 | NetworkDown
262 )
263}
264
265fn http_error_is_recoverable(error: ts_http_util::Error) -> bool {
267 match error {
268 ts_http_util::Error::Io => true,
269 ts_http_util::Error::InvalidInput
270 | ts_http_util::Error::Timeout
273 | ts_http_util::Error::InvalidResponse
274 | ts_http_util::Error::BodyTooLarge => false,
277 ts_http_util::Error::ConnectionClosed => false,
279 }
280}