truffle_core/network/mod.rs
1//! Layer 3: Network — Peer discovery, addressing, encrypted tunnels.
2//!
3//! This module defines the [`NetworkProvider`] trait, the public API for Layer 3.
4//! The trait is generic — no Tailscale-specific types leak through.
5//!
6//! The [`tailscale`] submodule contains the [`TailscaleProvider`](tailscale::TailscaleProvider) implementation
7//! that wraps the Go sidecar (tsnet) and bridge.
8
9pub mod tailscale;
10
11use std::net::{IpAddr, Ipv4Addr, SocketAddr};
12use std::time::Duration;
13
14use serde::{Deserialize, Serialize};
15use tokio::net::TcpStream;
16use tokio::sync::broadcast;
17
18// ---------------------------------------------------------------------------
19// NetworkProvider trait — the public API of Layer 3
20// ---------------------------------------------------------------------------
21
22/// Provides network-level peer discovery and raw connectivity.
23///
24/// The primary implementation is [`TailscaleProvider`](tailscale::TailscaleProvider)
25/// which uses tsnet via a Go sidecar. The trait is designed to be swappable —
26/// future providers could use mDNS (LAN), STUN/TURN (internet), or Bluetooth.
27///
28/// # Layer rules
29///
30/// - Layer 3 does NOT know about WebSocket, QUIC, or any Layer 4 protocol
31/// - Layer 3 does NOT know about envelopes, namespaces, or messages
32/// - Layer 3 provides raw `TcpStream` — not framed connections
33/// - `peer_events()` is the ONLY source of peer events — no polling, no announce
34///
35/// All async methods return `Send` futures so that `Node<N>` can be used
36/// inside `tokio::spawn` tasks (required by the file transfer subsystem
37/// and any other code that needs to spawn tasks with node access).
38pub trait NetworkProvider: Send + Sync {
39 /// Start the network provider.
40 ///
41 /// This spawns child processes, binds ports, and performs authentication.
42 /// If authentication is required (e.g., Tailscale browser auth), the provider
43 /// emits `NetworkPeerEvent::AuthRequired { url }` via `peer_events()` and
44 /// **keeps waiting** until auth completes or the timeout is reached.
45 ///
46 /// Callers should subscribe to `peer_events()` BEFORE calling `start()` to
47 /// receive auth URLs and display them to the user.
48 ///
49 /// Returns `Ok(())` when the provider is fully online, or `Err` if auth
50 /// times out or a fatal error occurs.
51 fn start(&mut self) -> impl std::future::Future<Output = Result<(), NetworkError>> + Send;
52
53 /// Stop the network provider and clean up all resources.
54 fn stop(&self) -> impl std::future::Future<Output = Result<(), NetworkError>> + Send;
55
56 /// Local node's identity (stable ID, hostname, display name).
57 ///
58 /// Returns a clone of the current cached identity. The identity is
59 /// populated after [`start()`](Self::start) completes and may be
60 /// updated when the sidecar reports `tsnet:status`.
61 fn local_identity(&self) -> NodeIdentity;
62
63 /// Local node's network address.
64 ///
65 /// Returns a clone of the current cached address. The address is
66 /// populated after [`start()`](Self::start) completes and may be
67 /// updated when the sidecar reports `tsnet:status`.
68 fn local_addr(&self) -> PeerAddr;
69
70 // ── Discovery (event-driven, NOT polling) ──
71
72 /// Subscribe to peer events. Fires immediately when peers join/leave/update.
73 ///
74 /// Uses `WatchIPNBus` for real-time notifications instead of polling.
75 fn peer_events(&self) -> broadcast::Receiver<NetworkPeerEvent>;
76
77 /// Snapshot of all currently known peers.
78 fn peers(&self) -> impl std::future::Future<Output = Vec<NetworkPeer>> + Send;
79
80 // ── Connectivity primitives for Layer 4 ──
81
82 /// Dial a TCP connection to a peer via the encrypted Tailscale tunnel.
83 ///
84 /// Returns a plain `TcpStream` — all bridge internals (pending_dials,
85 /// session token, binary headers) are hidden inside the provider.
86 fn dial_tcp(
87 &self,
88 addr: &str,
89 port: u16,
90 ) -> impl std::future::Future<Output = Result<TcpStream, NetworkError>> + Send;
91
92 /// Dial a TCP connection with explicit options (e.g. a TLS override).
93 ///
94 /// The default implementation ignores the options and delegates to
95 /// [`dial_tcp`](Self::dial_tcp), so providers that don't support the
96 /// options keep working unchanged.
97 fn dial_tcp_opts(
98 &self,
99 addr: &str,
100 port: u16,
101 opts: DialOpts,
102 ) -> impl std::future::Future<Output = Result<TcpStream, NetworkError>> + Send {
103 let _ = opts;
104 self.dial_tcp(addr, port)
105 }
106
107 /// Listen for incoming TCP connections on a port via the Tailscale tunnel.
108 ///
109 /// The returned receiver yields `TcpStream`s for each accepted connection.
110 fn listen_tcp(
111 &self,
112 port: u16,
113 ) -> impl std::future::Future<Output = Result<NetworkTcpListener, NetworkError>> + Send;
114
115 /// As [`listen_tcp`](Self::listen_tcp), with options (RFC 023 §7.1).
116 ///
117 /// Providers without TLS listener support reject `tls: true` rather than
118 /// silently serving plaintext.
119 fn listen_tcp_opts(
120 &self,
121 port: u16,
122 opts: ListenOpts,
123 ) -> impl std::future::Future<Output = Result<NetworkTcpListener, NetworkError>> + Send {
124 async move {
125 if opts.tls {
126 return Err(NetworkError::Unsupported(
127 "TLS listeners are not supported by this provider".into(),
128 ));
129 }
130 self.listen_tcp(port).await
131 }
132 }
133
134 /// Stop listening on a previously opened port.
135 fn unlisten_tcp(
136 &self,
137 port: u16,
138 ) -> impl std::future::Future<Output = Result<(), NetworkError>> + Send;
139
140 /// Bind a UDP socket on a port via the network tunnel.
141 ///
142 /// Returns a [`NetworkUdpSocket`] that transparently relays datagrams
143 /// through the network provider. The socket supports `send_to` / `recv_from`
144 /// with full remote address information.
145 ///
146 /// Not all providers support UDP. Returns [`NetworkError::Internal`] if
147 /// the provider has not implemented UDP transport yet.
148 fn bind_udp(
149 &self,
150 port: u16,
151 ) -> impl std::future::Future<Output = Result<NetworkUdpSocket, NetworkError>> + Send;
152
153 // ── Diagnostics ──
154
155 /// Ping a peer via the network layer (Tailscale TSMP).
156 fn ping(
157 &self,
158 addr: &str,
159 ) -> impl std::future::Future<Output = Result<PingResult, NetworkError>> + Send;
160
161 /// Node health info (key expiry, connection quality, warnings).
162 fn health(&self) -> impl std::future::Future<Output = HealthInfo> + Send;
163
164 // ── Reverse proxy (optional, requires sidecar) ──
165
166 /// Start a reverse proxy. Only supported by providers with sidecar integration.
167 fn proxy_add(
168 &self,
169 _config: ProxyAddParams,
170 ) -> impl std::future::Future<Output = Result<ProxyAddResult, NetworkError>> + Send {
171 std::future::ready(Err(NetworkError::Unsupported(
172 "proxy_add not supported by this provider".into(),
173 )))
174 }
175
176 /// Stop a reverse proxy.
177 fn proxy_remove(
178 &self,
179 _id: &str,
180 ) -> impl std::future::Future<Output = Result<(), NetworkError>> + Send {
181 std::future::ready(Err(NetworkError::Unsupported(
182 "proxy_remove not supported by this provider".into(),
183 )))
184 }
185
186 /// List active reverse proxies.
187 fn proxy_list(
188 &self,
189 ) -> impl std::future::Future<Output = Result<Vec<ProxyListEntry>, NetworkError>> + Send {
190 std::future::ready(Err(NetworkError::Unsupported(
191 "proxy_list not supported by this provider".into(),
192 )))
193 }
194
195 /// Subscribe to runtime proxy-engine errors (RFC 023 G5). `None` for
196 /// providers without a proxy engine — the caller then skips spawning a
197 /// forwarding task.
198 fn proxy_runtime_errors(&self) -> Option<broadcast::Receiver<ProxyRuntimeError>> {
199 None
200 }
201}
202
203// ---------------------------------------------------------------------------
204// Public types
205// ---------------------------------------------------------------------------
206
207/// Options for [`NetworkProvider::dial_tcp_opts`].
208#[derive(Debug, Clone, Copy, Default)]
209pub struct DialOpts {
210 /// Override TLS wrapping of the dial. `None` = no wrap on current
211 /// sidecars (RFC 023 D4 removed the legacy wrap-iff-port-443 rule);
212 /// `Some(true)` / `Some(false)` force it on / off (RFC 021 §6.4).
213 pub tls: Option<bool>,
214}
215
216/// Options for [`NetworkProvider::listen_tcp_opts`].
217#[derive(Debug, Clone, Copy, Default)]
218pub struct ListenOpts {
219 /// Terminate TLS at the provider using its platform certificates
220 /// (Tailscale: tsnet `ListenTLS` with automatic MagicDNS / Let's
221 /// Encrypt certs, RFC 023 §7.1 — requires MagicDNS + HTTPS enabled on
222 /// the tailnet). `false` = plain TCP listener.
223 pub tls: bool,
224}
225
226/// A peer as seen by the network layer (Layer 3).
227///
228/// Contains only information available from the network provider itself
229/// (e.g., Tailscale status). No transport or session state.
230#[derive(Debug, Clone)]
231pub struct NetworkPeer {
232 /// Stable node ID from the network provider.
233 pub id: String,
234 /// Hostname on the network (e.g., "truffle-cli-abc123").
235 pub hostname: String,
236 /// Network IP address (e.g., 100.x.x.x for Tailscale).
237 pub ip: IpAddr,
238 /// Whether the peer is currently online.
239 pub online: bool,
240 /// Direct endpoint address, if connected directly.
241 pub cur_addr: Option<String>,
242 /// DERP relay name if connection is relayed.
243 pub relay: Option<String>,
244 /// Operating system of the peer.
245 pub os: Option<String>,
246 /// Last time the peer was seen online (RFC 3339 string).
247 pub last_seen: Option<String>,
248 /// Key expiry timestamp (RFC 3339 string).
249 pub key_expiry: Option<String>,
250 /// DNS name on the tailnet (e.g., "truffle-cli-abc123.tailnet.ts.net").
251 pub dns_name: Option<String>,
252}
253
254/// Events emitted when network peers change state.
255#[derive(Debug, Clone)]
256pub enum NetworkPeerEvent {
257 /// A new peer appeared on the network.
258 Joined(NetworkPeer),
259 /// A peer left the network (by stable node ID).
260 Left(String),
261 /// A peer's metadata changed (IP, relay, online status, etc.).
262 Updated(NetworkPeer),
263 /// Authentication is required. The URL should be shown to the user.
264 /// Emitted during `start()` — the provider keeps waiting for auth to complete.
265 /// May be emitted multiple times if the URL expires and is refreshed.
266 AuthRequired {
267 /// URL the user should open in a browser.
268 url: String,
269 },
270}
271
272/// Network address of a peer.
273#[derive(Debug, Clone, Default)]
274pub struct PeerAddr {
275 /// IP address (100.x.x.x for Tailscale).
276 pub ip: Option<IpAddr>,
277 /// Hostname on the network.
278 pub hostname: String,
279 /// DNS name on the tailnet.
280 pub dns_name: Option<String>,
281}
282
283/// Identity of the local node on the network.
284///
285/// Carries the RFC 017 identity triple: `app_id` (namespace), `device_id`
286/// (stable per-device ULID), and `device_name` (human-readable). The
287/// Tailscale hostname and stable ID are kept alongside as escape hatches
288/// and for internal filtering.
289#[derive(Debug, Clone, Default)]
290pub struct NodeIdentity {
291 /// Application namespace identifier (RFC 017 §5.1).
292 pub app_id: String,
293 /// Stable per-device ULID (RFC 017 §5.4).
294 pub device_id: String,
295 /// Human-readable device name, original (unsanitised) string.
296 pub device_name: String,
297 /// Tailscale hostname — `truffle-{app_id}-{slug(device_name)}`.
298 pub tailscale_hostname: String,
299 /// Tailscale stable node ID (populated after the sidecar reaches Running).
300 pub tailscale_id: String,
301 /// DNS name on the tailnet.
302 pub dns_name: Option<String>,
303 /// Tailscale IP address.
304 pub ip: Option<IpAddr>,
305}
306
307/// Result of a network-level ping.
308#[derive(Debug, Clone)]
309pub struct PingResult {
310 /// Round-trip latency.
311 pub latency: Duration,
312 /// Connection type description (e.g., "direct" or "relay:sfo").
313 pub connection: String,
314 /// Direct peer endpoint address, if available.
315 pub peer_addr: Option<String>,
316}
317
318/// Health information from the network provider.
319#[derive(Debug, Clone, Default)]
320pub struct HealthInfo {
321 /// Current backend state (e.g., "Running", "NeedsLogin").
322 pub state: String,
323 /// Key expiry timestamp (RFC 3339), if applicable.
324 pub key_expiry: Option<String>,
325 /// Active health warnings.
326 pub warnings: Vec<String>,
327 /// Whether the network is fully operational.
328 pub healthy: bool,
329}
330
331/// A listener for incoming TCP connections via the network provider.
332///
333/// Wraps a channel that receives `TcpStream`s from the bridge. The bridge
334/// internals (binary headers, session tokens) are completely hidden.
335pub struct NetworkTcpListener {
336 /// Port this listener is bound to.
337 pub port: u16,
338 /// Receiver for incoming connections.
339 pub incoming: tokio::sync::mpsc::Receiver<IncomingConnection>,
340}
341
342/// An incoming TCP connection with metadata.
343#[derive(Debug)]
344pub struct IncomingConnection {
345 /// The raw TCP stream (bridge headers already consumed).
346 pub stream: TcpStream,
347 /// Remote address of the connecting peer.
348 pub remote_addr: String,
349 /// Remote DNS name or peer identity JSON.
350 pub remote_identity: String,
351 /// Port the connection arrived on.
352 pub port: u16,
353}
354
355/// A remote peer's Tailscale-authenticated identity, parsed from the WhoIs
356/// JSON the sidecar attaches to inbound bridge connections (the
357/// [`remote_identity`](IncomingConnection::remote_identity) field of
358/// [`IncomingConnection`]).
359///
360/// Produced by Layer 3 — the Go sidecar's `resolvePeerIdentity` writes this
361/// JSON into the bridge header — and consumed by Layer 4+ transports and the
362/// bindings. Every field is optional: the sidecar omits empty ones, WhoIs may
363/// return no Node, and legacy sidecars send a bare DNS name that does not
364/// parse as this struct at all.
365#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)]
366#[serde(rename_all = "camelCase")]
367pub struct TailscalePeerIdentity {
368 /// Tailnet DNS name (e.g., "kitchen.tailnet.ts.net"), trailing dot stripped.
369 pub dns_name: Option<String>,
370 /// Tailscale login (owner) name, e.g., "alice@example.com".
371 pub login_name: Option<String>,
372 /// Human-readable display name from the identity provider.
373 pub display_name: Option<String>,
374 /// URL of the peer owner's profile picture.
375 pub profile_pic_url: Option<String>,
376 /// Stable Tailscale node ID (WhoIs `Node.StableID`).
377 pub node_id: Option<String>,
378}
379
380// ---------------------------------------------------------------------------
381// NetworkUdpSocket — address-framed UDP relay wrapper
382// ---------------------------------------------------------------------------
383
384/// A UDP socket that relays datagrams through the network provider.
385///
386/// Under the hood, the Rust side talks to a local relay socket. Each outbound
387/// datagram is prefixed with a 6-byte address header (`[4-byte IPv4][2-byte port BE]`)
388/// so the relay (Go sidecar) knows where to forward the packet on the tsnet
389/// network. Inbound datagrams arrive with the same header prepended by the relay.
390///
391/// This struct hides the framing — callers use `send_to` / `recv_from` with
392/// normal `SocketAddr` values.
393pub struct NetworkUdpSocket {
394 /// The underlying tokio UDP socket connected to the local relay.
395 inner: tokio::net::UdpSocket,
396 /// The tsnet-bound port (the logical port on the Tailscale network).
397 tsnet_port: u16,
398}
399
400/// Address header size: 4 bytes IPv4 + 2 bytes port (big-endian).
401const UDP_ADDR_HEADER_SIZE: usize = 6;
402
403impl NetworkUdpSocket {
404 /// Create a new `NetworkUdpSocket` from a tokio UdpSocket and the tsnet port.
405 pub(crate) fn new(inner: tokio::net::UdpSocket, tsnet_port: u16) -> Self {
406 Self { inner, tsnet_port }
407 }
408
409 /// Send a datagram to the specified address via the relay.
410 ///
411 /// The relay will forward the datagram to the target on the tsnet network.
412 pub async fn send_to(&self, data: &[u8], addr: SocketAddr) -> Result<usize, NetworkError> {
413 let ip = match addr.ip() {
414 IpAddr::V4(v4) => v4,
415 IpAddr::V6(_) => {
416 return Err(NetworkError::Internal(
417 "NetworkUdpSocket: IPv6 not supported in relay framing".into(),
418 ));
419 }
420 };
421 let port = addr.port();
422
423 // Build framed packet: [4-byte IPv4][2-byte port BE][payload]
424 let mut framed = Vec::with_capacity(UDP_ADDR_HEADER_SIZE + data.len());
425 framed.extend_from_slice(&ip.octets());
426 framed.extend_from_slice(&port.to_be_bytes());
427 framed.extend_from_slice(data);
428
429 tracing::debug!(
430 target_addr = %addr,
431 payload_len = data.len(),
432 framed_len = framed.len(),
433 relay_addr = ?self.inner.peer_addr().ok(),
434 "NetworkUdpSocket: sending framed datagram to relay"
435 );
436
437 let n = self.inner.send(&framed).await.map_err(NetworkError::Io)?;
438
439 tracing::debug!(
440 bytes_sent = n,
441 "NetworkUdpSocket: framed datagram sent to relay"
442 );
443
444 // Return the number of payload bytes sent (subtract header)
445 Ok(n.saturating_sub(UDP_ADDR_HEADER_SIZE))
446 }
447
448 /// Receive a datagram from the relay, returning the payload and sender address.
449 ///
450 /// The relay prepends a 6-byte address header to each inbound datagram.
451 pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, SocketAddr), NetworkError> {
452 tracing::debug!("NetworkUdpSocket: waiting for inbound datagram from relay...");
453
454 // Read into a temporary buffer that includes space for the header
455 let mut tmp = vec![0u8; UDP_ADDR_HEADER_SIZE + buf.len()];
456 let n = self.inner.recv(&mut tmp).await.map_err(NetworkError::Io)?;
457
458 if n < UDP_ADDR_HEADER_SIZE {
459 return Err(NetworkError::Internal(
460 "NetworkUdpSocket: received packet too short for address header".into(),
461 ));
462 }
463
464 // Parse address header
465 let ip = Ipv4Addr::new(tmp[0], tmp[1], tmp[2], tmp[3]);
466 let port = u16::from_be_bytes([tmp[4], tmp[5]]);
467 let addr = SocketAddr::new(IpAddr::V4(ip), port);
468
469 // Copy payload to caller's buffer
470 let payload_len = n - UDP_ADDR_HEADER_SIZE;
471 buf[..payload_len].copy_from_slice(&tmp[UDP_ADDR_HEADER_SIZE..n]);
472
473 tracing::debug!(
474 raw_bytes = n,
475 payload_len = payload_len,
476 sender_addr = %addr,
477 "NetworkUdpSocket: received inbound datagram from relay"
478 );
479
480 Ok((payload_len, addr))
481 }
482
483 /// Return the local address of the underlying relay socket.
484 pub fn local_addr(&self) -> Result<SocketAddr, NetworkError> {
485 self.inner.local_addr().map_err(NetworkError::Io)
486 }
487
488 /// Return the tsnet-bound port (the logical port on the Tailscale network).
489 pub fn tsnet_port(&self) -> u16 {
490 self.tsnet_port
491 }
492
493 /// Get a reference to the inner tokio UdpSocket.
494 ///
495 /// This is the socket connected to the local relay. Direct reads/writes
496 /// bypass the address framing — prefer `send_to` / `recv_from` instead.
497 pub fn inner(&self) -> &tokio::net::UdpSocket {
498 &self.inner
499 }
500}
501
502impl std::fmt::Debug for NetworkUdpSocket {
503 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
504 f.debug_struct("NetworkUdpSocket")
505 .field("tsnet_port", &self.tsnet_port)
506 .field("local_addr", &self.inner.local_addr().ok())
507 .finish()
508 }
509}
510
511// ---------------------------------------------------------------------------
512// Reverse proxy types (used by NetworkProvider trait methods)
513// ---------------------------------------------------------------------------
514
515/// Parameters for starting a reverse proxy via the network provider.
516#[derive(Debug, Clone)]
517pub struct ProxyAddParams {
518 /// Unique identifier for this proxy.
519 pub id: String,
520 /// Human-readable name.
521 pub name: String,
522 /// Port on which the proxy listens on the tailnet.
523 pub listen_port: u16,
524 /// Target host to forward to (e.g., "localhost").
525 pub target_host: String,
526 /// Target port to forward to.
527 pub target_port: u16,
528 /// Target scheme ("http" or "https").
529 pub target_scheme: String,
530 /// Terminate TLS on the tailnet listener (RFC 023; `true` = the v1
531 /// always-TLS behavior, `false` = plain HTTP listener).
532 pub tls: bool,
533 /// Permit non-loopback targets (RFC 023 §9.3; default deny).
534 pub allow_non_loopback: bool,
535 /// loginName allow globs; empty = whole tailnet (RFC 023 §9.7).
536 pub allow: Vec<String>,
537 /// Path-prefix routes; empty = the single-target v1 shape.
538 pub routes: Vec<ProxyRoute>,
539}
540
541/// One path-prefix route of a v2 proxy (RFC 023 §7). Wire-shaped: exactly
542/// one of `target_url` / `dir` must be set. Longest prefix wins (D11).
543#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
544#[serde(rename_all = "camelCase")]
545pub struct ProxyRoute {
546 /// Path prefix to match (must start with `/`).
547 pub prefix: String,
548 /// Proxy target URL (e.g. `http://localhost:8000`).
549 #[serde(default, skip_serializing_if = "Option::is_none")]
550 pub target_url: Option<String>,
551 /// Static directory to serve (absolute path on the serving machine).
552 #[serde(default, skip_serializing_if = "Option::is_none")]
553 pub dir: Option<String>,
554 /// SPA fallback rewritten on static misses (e.g. `/index.html`);
555 /// only meaningful with `dir`.
556 #[serde(default, skip_serializing_if = "Option::is_none")]
557 pub fallback: Option<String>,
558 /// Strip the matched prefix before proxying (default false — D11;
559 /// only meaningful with `target_url`).
560 #[serde(default)]
561 pub strip_prefix: bool,
562 /// Per-route loginName globs; overrides the config-level `allow`.
563 #[serde(default, skip_serializing_if = "Vec::is_empty")]
564 pub allow: Vec<String>,
565}
566
567/// A runtime error from the provider's proxy engine after a successful add
568/// (RFC 023 G5 fix — e.g. sidecar `SERVE_ERROR` / `CONNECTION_REFUSED`).
569#[derive(Debug, Clone)]
570pub struct ProxyRuntimeError {
571 /// Proxy id the error belongs to.
572 pub id: String,
573 /// Machine-readable error code.
574 pub code: String,
575 /// Human-readable detail.
576 pub message: String,
577}
578
579/// Result of successfully starting a reverse proxy.
580#[derive(Debug, Clone)]
581pub struct ProxyAddResult {
582 /// Proxy ID (echoed back from sidecar).
583 pub id: String,
584 /// Actual listen port (may differ from requested if 0 was passed).
585 pub listen_port: u16,
586 /// Fully qualified URL (e.g., "<https://hostname.ts.net:3001>").
587 pub url: String,
588}
589
590/// Entry in the proxy list from the network provider.
591#[derive(Debug, Clone)]
592pub struct ProxyListEntry {
593 pub id: String,
594 pub name: String,
595 pub listen_port: u16,
596 pub target_host: String,
597 pub target_port: u16,
598 pub target_scheme: String,
599 pub url: String,
600}
601
602// ---------------------------------------------------------------------------
603// Errors
604// ---------------------------------------------------------------------------
605
606/// Errors from Layer 3 network operations.
607#[derive(Debug, thiserror::Error)]
608pub enum NetworkError {
609 /// The network provider is not running.
610 #[error("network provider not running")]
611 NotRunning,
612
613 /// The network provider is already running.
614 #[error("network provider already running")]
615 AlreadyRunning,
616
617 /// Failed to start the network provider.
618 #[error("start failed: {0}")]
619 StartFailed(String),
620
621 /// Failed to stop the network provider.
622 #[error("stop failed: {0}")]
623 StopFailed(String),
624
625 /// Authentication is required.
626 #[error("authentication required: {url}")]
627 AuthRequired { url: String },
628
629 /// A dial operation failed.
630 #[error("dial failed: {0}")]
631 DialFailed(String),
632
633 /// A dial operation timed out.
634 #[error("dial timed out after {0:?}")]
635 DialTimeout(Duration),
636
637 /// A listen operation failed.
638 #[error("listen failed: {0}")]
639 ListenFailed(String),
640
641 /// A ping operation failed.
642 #[error("ping failed: {0}")]
643 PingFailed(String),
644
645 /// The sidecar process crashed or is unavailable.
646 #[error("sidecar error: {0}")]
647 SidecarError(String),
648
649 /// Bridge communication error.
650 #[error("bridge error: {0}")]
651 BridgeError(String),
652
653 /// I/O error.
654 #[error("io error: {0}")]
655 Io(#[from] std::io::Error),
656
657 /// Serialization error.
658 #[error("serialization error: {0}")]
659 Serialize(#[from] serde_json::Error),
660
661 /// Generic internal error.
662 #[error("internal error: {0}")]
663 Internal(String),
664
665 /// The operation is not supported by this provider.
666 #[error("unsupported: {0}")]
667 Unsupported(String),
668
669 /// A reverse-proxy operation failed.
670 #[error("proxy error: {0}")]
671 ProxyError(String),
672}