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