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