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;
35#[cfg_attr(not(feature = "async_tokio"), expect(dead_code))]
36mod map_request_builder;
37mod node;
38#[cfg(feature = "async_tokio")]
39mod serve;
40mod service;
41mod ssh_policy;
42mod tka;
43#[cfg(feature = "async_tokio")]
44mod tokio;
45#[cfg(feature = "identity-federation")]
46pub mod wif;
47
48use std::fmt;
49
50#[cfg(feature = "async_tokio")]
51pub use cert::{
52 CertError, MISSING_CERT_RPC, certified_key_from_pem, get_certificate, is_tailnet_name,
53};
54#[cfg(feature = "acme")]
55pub use cert::{
56 PublishTxt, SetDnsPublisher, issue_cert_pair_via_setdns, issue_certificate_via_setdns,
57};
58#[doc(inline)]
59pub use config::{
60 Config, DEFAULT_CONTROL_SERVER, DEFAULT_PERSISTENT_KEEPALIVE, ExitProxyConfig, ExitProxyScheme,
61 TransportMode, TunConfig, services_hash,
62};
63pub use control_dialer::{ControlDialer, TcpDialer, complete_connection};
64pub use derp::{Map as DerpMap, Region as DerpRegion, convert_derp_map};
65pub use dial_plan::{DialCandidate, DialMode, DialPlan};
66pub use dns::{DnsConfig, ExtraRecord, Resolver as DnsResolver, ResolverTransport};
67pub use node::{
68 ExitNodeSelector, Id as NodeId, Node, NodeCapMap, PeerChange, StableId as StableNodeId,
69 TailnetAddress, UserProfile, is_tailscale_ip, validate_service_name,
70};
71#[cfg(feature = "async_tokio")]
72pub use serve::{
73 FunnelError, FunnelOptions, MISSING_FUNNEL_RELAY, ServeConfig, ServeState, ServeTarget,
74 accept_tls, funnel_access, listen_funnel, listen_tls, tls_acceptor,
75};
76pub use service::{ServiceError, ServiceMode, resolve_service_listen};
77pub use ssh_policy::{
78 SshAccept, SshAction, SshConnIdentity, SshDecision, SshDenyReason, SshPolicy, SshPrincipal,
79 SshRule,
80};
81pub use tka::TkaStatus;
82pub use ts_control_serde::{
83 Endpoint, EndpointType, TkaBootstrapRequest, TkaBootstrapResponse, TkaSyncOfferRequest,
84 TkaSyncOfferResponse, TkaSyncSendRequest, TkaSyncSendResponse, UserId,
85};
86#[cfg(feature = "identity-federation")]
87pub use wif::{WifConfig, WifError, resolve_auth_key};
88
89#[cfg(feature = "async_tokio")]
93pub mod tls {
94 pub use tokio_rustls::{TlsAcceptor, rustls::sign::CertifiedKey, server::TlsStream};
95}
96
97#[cfg(feature = "async_tokio")]
98pub use crate::tokio::{
99 AsyncControlClient, FilterUpdate, IdTokenError, LogoutError, LogoutInternalErrorKind,
100 PeerUpdate, SetDnsError, SetDnsInternalErrorKind, StateUpdate, TkaSyncError,
101 TkaSyncInternalErrorKind, fetch_id_token, logout, set_dns, tka_bootstrap, tka_sync_offer,
102 tka_sync_send,
103};
104
105#[derive(Debug, thiserror::Error, Clone, Eq, PartialEq)]
107pub enum Error {
108 #[error("machine was not authorized by control to join tailnet, authorize at {0}")]
110 MachineNotAuthorized(url::Url),
111
112 #[error("invalid URL: {0}")]
114 InvalidUrl(url::Url),
115
116 #[error("control rejected registration: {0}")]
119 Registration(String),
120
121 #[error("a networking error occurred in {0}")]
127 NetworkError(Operation),
128
129 #[error("{0} error in {1}")]
134 Internal(InternalErrorKind, Operation),
135}
136
137impl Error {
138 fn io_error(err: std::io::Error, op: Operation) -> Self {
139 if crate::is_network_error(&err) {
140 Error::NetworkError(op)
141 } else {
142 Error::Internal(InternalErrorKind::Io, op)
143 }
144 }
145}
146
147#[non_exhaustive]
151#[derive(Debug, Clone, Copy, Eq, PartialEq)]
152pub enum InternalErrorKind {
153 Url,
155 Http,
157 SerDe,
159 Io,
161 MessageFormat,
163 Utf8,
165 NoiseHandshake,
167 Challenge,
169 MachineAuthorization,
172}
173
174impl fmt::Display for InternalErrorKind {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 match self {
177 InternalErrorKind::Url => write!(f, "URL parsing error"),
178 InternalErrorKind::Http => write!(f, "unsuccessful HTTP request or upgrade"),
179 InternalErrorKind::SerDe => write!(f, "serialization/deserialization error"),
180 InternalErrorKind::Io => write!(f, "I/O error"),
181 InternalErrorKind::MessageFormat => write!(f, "message format error"),
182 InternalErrorKind::Utf8 => write!(f, "invalid UTF8"),
183 InternalErrorKind::NoiseHandshake => write!(f, "error in Noise handshake"),
184 InternalErrorKind::Challenge => write!(f, "error with Tailscale challenge packet"),
185 InternalErrorKind::MachineAuthorization => {
186 write!(f, "machine not authorized to register with Tailnet")
187 }
188 }
189 }
190}
191
192#[derive(Debug, Clone, Copy, Eq, PartialEq)]
194pub enum Operation {
195 MapRequest,
197 ConnectToControlServer,
199 Registration,
201}
202
203impl fmt::Display for Operation {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 match self {
206 Operation::MapRequest => write!(f, "net map request"),
207 Operation::ConnectToControlServer => write!(f, "connection to control server"),
208 Operation::Registration => write!(f, "registration"),
209 }
210 }
211}
212
213impl From<ts_http_util::Error> for Error {
214 fn from(error: ts_http_util::Error) -> Self {
215 tracing::error!(%error, "http error");
216
217 if http_error_is_recoverable(error) {
218 Error::NetworkError(Operation::ConnectToControlServer)
219 } else {
220 Error::Internal(InternalErrorKind::Http, Operation::ConnectToControlServer)
221 }
222 }
223}
224
225fn is_network_error(err: &std::io::Error) -> bool {
227 use std::io::ErrorKind::*;
228 matches!(
229 err.kind(),
230 ConnectionRefused
231 | ConnectionReset
232 | HostUnreachable
233 | NetworkUnreachable
234 | ConnectionAborted
235 | NotConnected
236 | TimedOut
237 | AddrNotAvailable
238 | Interrupted
239 | NetworkDown
240 )
241}
242
243fn http_error_is_recoverable(error: ts_http_util::Error) -> bool {
245 match error {
246 ts_http_util::Error::Io => true,
247 ts_http_util::Error::InvalidInput
248 | ts_http_util::Error::Timeout
251 | ts_http_util::Error::InvalidResponse
252 | ts_http_util::Error::BodyTooLarge => false,
255 ts_http_util::Error::ConnectionClosed => false,
257 }
258}