v1alpha1

package
v0.0.38 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 29, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Overview

Package v1alpha1 is the current implementation behind the v1 tunnel interfaces: the backend-agnostic lazy core plus generic providers. Backend engines live in subpackages (v1alpha1/cloudflare). The root libtunnel façade wraps this; callers reaching directly into v1alpha1 use it for the concrete structs and providers. Anything here may change between alpha revisions — depend on the v1 contract, not these internals.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CacheDir added in v0.0.18

func CacheDir() (string, error)

CacheDir is the directory specs are cached to and replayed from: v1.CacheDirEnv when set (used as-is), otherwise os.UserCacheDir() namespaced by the stable v1 contract package path (e.g. .../github.com/cnuss/libtunnel/v1).

func DecodeSpec added in v0.0.17

func DecodeSpec(envelope string) (backend string, spec json.RawMessage, err error)

DecodeSpec splits an envelope (EncodeSpec output / v1.SpecEnv value) into its backend tag and the raw backend spec JSON, for a caller to unmarshal into the matching spec type. A value with no backend tag is not an envelope.

func EncodeSpec added in v0.0.17

func EncodeSpec[T v1.Spec](backend string, spec T) (string, error)

EncodeSpec returns spec as a tagged-envelope JSON string — the value carried by v1.SpecEnv and returned by Spec.Serialize. backend tags which engine minted it so a decoder routes to the right spec type.

func Env

func Env[E any, T interface {
	*E
	v1.Spec
}](backend string, next v1.Provider[T]) v1.Provider[T]

Env wraps a provider with LIBTUNNEL_SPEC handling for the named backend: when the environment carries a spec inherited from a parent process, it wins; otherwise the wrapped provider resolves one and the result is exported back into this process's environment, so spawned children inherit the same tunnel identity with no further plumbing. A spec this process exported itself is never re-adopted — a second in-process tunnel mints its own identity. E is the concrete spec struct (e.g. cloudflare.Spec) — inferred from the wrapped provider's *E spec type.

func EnvBool added in v0.0.26

func EnvBool(name string) (value, fixed bool, err error)

EnvBool reads an env-fixed boolean knob for a backend: fixed reports whether name is set (its value then overrides code), and err carries an unparsable value for the backend to surface at connect — loud beats a silently ignored override.

func EnvDuration added in v0.0.32

func EnvDuration(name string) (value time.Duration, fixed bool, err error)

EnvDuration reads an env-fixed duration knob for a backend, the sibling of EnvBool: fixed reports whether name is set (its value then overrides code), and err carries an unparsable value for the backend to surface at connect. The value uses time.ParseDuration syntax (e.g. "1s", "500ms").

func ExportSpec

func ExportSpec[T v1.Spec](backend string, spec T) error

ExportSpec publishes spec into this process's own environment so re-exec'd or spawned children inherit it. The exported value is remembered and never re-adopted by this process's own SpecFromEnv (see Env). It also sets v1.HostnameEnv to the spec's plain hostname as a convenience mirror.

func Failed added in v0.0.17

func Failed(cause error) v1.Tunnel

Failed returns a tunnel already canceled with cause — for façade entry points (e.g. libtunnel.From) that detect bad input and report it through the Err()/Done() channel instead of a second return value. It has no backend, so every getter resolves to its zero value.

func From added in v0.0.17

func From(spec string, build func(backend string, raw json.RawMessage) (v1.Tunnel, error)) v1.Tunnel

From loads a serialized spec and replays it into a tunnel. spec is resolved, in order: an existing file at the given path; a file in CacheDir named spec; a file in CacheDir named <cacheFileName(spec)> (so a bare hostname replays from cache); otherwise the literal JSON. It decodes the envelope and hands the backend tag plus the raw backend spec to build, which constructs the tunnel for that backend — the one piece From can't own, since v1alpha1 is backend-agnostic and the façade wires the concrete backend. Any failure (unparseable, unknown backend, or a build error) returns a tunnel already canceled with the cause, so callers get it through Err()/Done() rather than a second return value.

func Hosts added in v0.0.18

func Hosts() []string

Hosts lists the cached specs as public URL strings — "https://<hostname>:<port>/", the port backfilled to 443 when the hostname carries none — sorted. It reads the envelopes' hostname field, so it needs no backend knowledge. Best effort: a missing CacheDir or any unreadable / unparseable / hostname-less file is skipped, yielding a shorter (or empty) list rather than an error.

func NewInterceptCtx added in v0.0.36

func NewInterceptCtx[T v1.Spec](backend Engine[T], w http.ResponseWriter, r *http.Request) v1.InterceptCtx

NewInterceptCtx builds the per-request InterceptCtx handed to interceptors. It embeds the request context (so the value satisfies context.Context) and seeds the default handler to proxy the request to the origin — the behavior when no interceptor matches or an interceptor declines by not replacing it.

func Replay added in v0.0.26

func Replay[E any, T interface {
	*E
	v1.Spec
}](backend string, next v1.Provider[T]) v1.Provider[T]

Replay wraps next with v1.FromEnv handling for the named backend: when the environment references a spec, it is loaded and replayed — superseding next, even a code-pinned From spec; env beats code. A reference that cannot be parsed, carries a foreign backend tag, or fails to decode is an error, not a fallthrough. Unset, next resolves as usual. E is the concrete spec struct, inferred like Env's.

func SpecEnviron

func SpecEnviron[T v1.Spec](backend string, spec T) (string, error)

SpecEnviron encodes spec as a "LIBTUNNEL_SPEC=<json>" entry for a child process's exec.Cmd.Env, tagged with the minting backend's name.

func SpecFromEnv

func SpecFromEnv[T v1.Spec](backend string, spec T) (bool, error)

SpecFromEnv decodes LIBTUNNEL_SPEC into the caller-allocated spec. It reports whether a spec was adopted; a present-but-malformed value, or one minted by a different backend, is an error. A value this process exported itself (ExportSpec) reads as absent — the handoff channel carries parent→child inheritance only.

func Static

func Static[T v1.Spec](spec T) v1.Provider[T]

Static returns a provider that yields the given spec verbatim. Useful for replaying known credentials (tests, fixed tunnels).

Types

type Engine

type Engine[T v1.Spec] interface {
	v1.Backend[T]

	// CACerts returns the trust roots for this backend's edge connections.
	CACerts() []*x509.Certificate
	// Proxy is the in-process reverse proxy that fronts the origin, live once
	// the tunnel has connected. NewInterceptCtx seeds an interception's default
	// handler from it (proxy the request to the origin). Nil before connect.
	Proxy() *httputil.ReverseProxy
	// Listener is the loopback listener the engine dials to reach Proxy — the
	// proxy's own accept socket, not the origin. Surfaced to interceptors as
	// InterceptCtx.Target. Nil before connect.
	Listener() net.Listener
	// WithListener mirrors the top-level mutator: the core hands the provided
	// listener down when the tunnel's WithListener fires. It is invoked once,
	// in its own goroutine, and blocks until the edge connection is up
	// (returning any setup failure). Runtime failures after that are reported
	// through t.Cancel. The core closes TunnelReady once WithListener returns
	// nil and the hostname resolves publicly.
	WithListener(t *TunnelImpl[T], l net.Listener) error
	// WithLocalURL is WithListener's counterpart for URL origins: the core
	// hands down the validated origin URL (scheme http/https, host set, path
	// "/") when the tunnel's WithLocalURL fires. Same contract — invoked
	// once, in its own goroutine, blocking until the edge connection is up.
	WithLocalURL(t *TunnelImpl[T], u *url.URL) error
}

Engine is the alpha-internal contract behind v1.Backend: what the tunnel core needs from a transport implementation. It extends the opaque v1.Backend so a backend value flows through the stable surface and is asserted back here.

type LoggerSetter added in v0.0.3

type LoggerSetter interface {
	SetLogger(*slog.Logger)
}

LoggerSetter is the optional provider capability the tunnel core probes to thread its logger into providers that can log (retry warnings, rate limits). Provider wrappers must forward SetLogger to what they wrap, or logging is silently severed for everything beneath them.

type TunnelImpl

type TunnelImpl[T v1.Spec] struct {
	// contains filtered or unexported fields
}

TunnelImpl is the lazy tunnel core behind v1.Tunnel. Every getter resolves through a sync.Once on first use; getters whose input is not yet available block on the tunnel context. The configurable fields are write-once for the same reason — each is guarded by its own sync.Once, fixed by the first mutator call or the first internal use, so a fixed value never mutates under a goroutine that already read it.

func New

func New[T v1.Spec](backend v1.Backend[T]) *TunnelImpl[T]

func (*TunnelImpl[T]) CACerts

func (t *TunnelImpl[T]) CACerts() []*x509.Certificate

CACerts returns the backend's trust roots for its edge connections.

func (*TunnelImpl[T]) Cancel

func (t *TunnelImpl[T]) Cancel(cause error)

Cancel records cause and cancels the tunnel's context. Exposed for Engine implementations in subpackages.

func (*TunnelImpl[T]) Context

func (t *TunnelImpl[T]) Context() context.Context

Context is the tunnel's lifetime context, canceled (with cause) on any fatal tunnel error. Exposed for Engine implementations in subpackages.

func (*TunnelImpl[T]) Domain

func (t *TunnelImpl[T]) Domain() string

Domain is Hostname with the first label removed.

func (*TunnelImpl[T]) Done

func (t *TunnelImpl[T]) Done() <-chan struct{}

Done implements v1.Tunnel: closed when the tunnel fails or shuts down.

func (*TunnelImpl[T]) Err

func (t *TunnelImpl[T]) Err() error

Err implements v1.Tunnel: the cancellation cause, nil while alive.

func (*TunnelImpl[T]) Host

func (t *TunnelImpl[T]) Host() string

Host is the first label of Hostname.

func (*TunnelImpl[T]) Hostname

func (t *TunnelImpl[T]) Hostname() string

Hostname is the public hostname from the spec.

func (*TunnelImpl[T]) HostnameReady

func (t *TunnelImpl[T]) HostnameReady() <-chan struct{}

HostnameReady returns the channel closed once the public hostname resolves on the zone's authoritative nameservers. The poll that closes it is started by WithListener and gated on hostnameProvided, so this is a pure accessor — select on it (and on Done).

Readiness is authoritative-only: pollAuthoritative queries the zone's nameservers directly (the dig equivalent, via package resolver) and fires as soon as one of them serves a non-empty A+AAAA set — a record on any authoritative nameserver, never a recursive resolver's cache. Queries are RD=1 (the quick-tunnel nameservers REFUSE RD=0).

func (*TunnelImpl[T]) Intercept added in v0.0.35

func (t *TunnelImpl[T]) Intercept(ctx v1.InterceptCtx) http.HandlerFunc

Intercept resolves the handler for a request through the interceptor registry. The lowest-Priority (highest-precedence) interceptor whose Match returns true runs (ties by registration order — the registry is kept sorted), given the InterceptCtx (which carries the request and the default origin-proxy handler); it shapes the response by calling ctx.WithHandler and returns the ctx. When nothing matches — or an interceptor returns nil — the ctx's default handler (proxy to the origin) stands. The registry lock is held only across the match scan, not the interceptor. The returned handler is the one the engine serves.

func (*TunnelImpl[T]) Interceptors added in v0.0.37

func (t *TunnelImpl[T]) Interceptors() v1.Interceptors

Interceptors returns a snapshot of the registry in precedence order (ascending Priority, ties in registration order) — the order Intercept consults them. It's a defensive copy: mutating the returned slice does not affect the live registry (Interceptor is a value type; its func fields are immutable references). If Interceptor ever gains a reference-type field, deep-copy it here.

func (*TunnelImpl[T]) Listener

func (t *TunnelImpl[T]) Listener() net.Listener

Listener returns a tunnel-owned listener to serve on. If a listener was provided via WithListener it returns a tunnel-owned view of that one; otherwise it mints a loopback listener (127.0.0.1:0), adopts it as WithListener would — same edge-connect and DNS-readiness path — and the tunnel owns it (closed on shutdown). Idempotent: repeated calls return the same listener.

Closing the returned listener closes the tunnel. A caller-provided listener stays caller-owned, so closing that restarts the origin while the tunnel persists; a minted one has no separate owner, so closing it is terminal.

func (*TunnelImpl[T]) LocalHost

func (t *TunnelImpl[T]) LocalHost() string

LocalHost is the machine's hostname, truncated at the first dot.

func (*TunnelImpl[T]) LocalIP

func (t *TunnelImpl[T]) LocalIP() net.IP

LocalIP is the origin's local IP: the listener's bound IP, or for a URL origin the URL's host, parsed as an IP or resolved. An unspecified address (0.0.0.0 / ::) has no concrete IP to report, so it falls back to the outbound-route IP, discovered with a UDP dial (no packets are sent). Blocks until an origin is provided.

func (*TunnelImpl[T]) LocalPort

func (t *TunnelImpl[T]) LocalPort() int

LocalPort is the origin's local port: the listener's bound port, or for a URL origin the URL's port — 443 for https and 80 for http when the URL has none. Blocks until an origin is provided.

func (*TunnelImpl[T]) LocalURL

func (t *TunnelImpl[T]) LocalURL() *url.URL

LocalURL is the local origin's URL. For a listener origin it is http://<LocalIP>:<LocalPort>/ — always http: the origin's scheme is a backend setting (WithTLS), not derived here, and the public URL carries the real scheme. For a URL origin it is the provided URL itself, scheme intact. Blocks until an origin is provided.

func (*TunnelImpl[T]) Logger

func (t *TunnelImpl[T]) Logger() *slog.Logger

Logger is the tunnel's logger (never nil; silent by default). Exposed for Engine implementations in subpackages. The first read without a prior WithLogger fixes the default — the log field is write-once. The default is silent unless v1.LogEnv names a level; then it is a stderr text logger at that level.

func (*TunnelImpl[T]) Port

func (t *TunnelImpl[T]) Port() int

Port is the port encoded in Hostname, or 443 when absent.

func (*TunnelImpl[T]) Spec

func (t *TunnelImpl[T]) Spec() T

Spec returns the resolved tunnel spec, fetching it from the backend's provider chain on first use.

func (*TunnelImpl[T]) TunnelReady

func (t *TunnelImpl[T]) TunnelReady() <-chan struct{}

TunnelReady is closed when the edge connection is up and the hostname resolves publicly. Waiting on readiness demands a running tunnel, so like URL it is a start trigger: with no origin provided it mints a loopback listener and starts the edge connection before handing back the channel.

func (*TunnelImpl[T]) URL

func (t *TunnelImpl[T]) URL() *url.URL

URL is https://<Hostname>/. It blocks until the hostname resolves on the zone's authoritative nameservers. Returns nil if the tunnel is canceled before that happens, per the v1 contract's zero-value-on-cancel rule.

URL demands public reachability, so like Listener it is a start trigger: with no origin provided it mints a loopback listener and starts the edge connection, instead of waiting on readiness that could never arrive.

func (*TunnelImpl[T]) WithContext added in v0.0.6

func (t *TunnelImpl[T]) WithContext(ctx context.Context) v1.Tunnel

WithContext threads a caller context into the tunnel: once set, URL upgrades from "the hostname resolves" to "the tunnel is reachable end to end" — it waits for TunnelReady, honoring this context, and returns nil if the context is done first. It is also the tunnel's shutdown handle: canceling the context tears the tunnel down (Done fires, Err reports the context's cause), which is the only teardown a WithLocalURL origin has. Write-once: the first call wins, a nil ctx is ignored, and a URL call that already fixed the field (to nil, unset) makes this a no-op.

func (*TunnelImpl[T]) WithInterceptor added in v0.0.35

func (t *TunnelImpl[T]) WithInterceptor(interceptor v1.Interceptor) v1.Tunnel

WithInterceptor registers an interceptor, keeping the registry ordered by ascending Priority (ties in registration order) so Intercept's first-match scan yields the lowest-Priority — highest-precedence — match. A Priority of 0 (unset) is auto-assigned from the top of the range downward, so later- registered unprioritized interceptors win and any explicit Priority outranks them. The stable sort preserves insertion order for equal Priorities. Safe to call concurrently and after the tunnel is live (see v1.Tunnel.WithInterceptor).

func (*TunnelImpl[T]) WithListener

func (t *TunnelImpl[T]) WithListener(l net.Listener) v1.Tunnel

WithListener provides the local origin as a listener and lazily starts the edge connection. The listener is the single source of local-side truth: LocalIP, LocalPort, and LocalURL all derive from its address.

The origin is provided exactly once, shared with WithLocalURL and the start-trigger mint. Providing it again — a second WithListener or WithLocalURL, or either after a start trigger (Listener, URL, TunnelReady) already minted one — cancels the tunnel (Err reports "origin already provided"). As an alternative to bringing your own, call Listener() to have the tunnel mint a loopback listener for you.

func (*TunnelImpl[T]) WithLocalURL added in v0.0.25

func (t *TunnelImpl[T]) WithLocalURL(u *url.URL) v1.Tunnel

WithLocalURL provides the local origin as the URL of an already-running local service and lazily starts the edge connection — the cloudflared `tunnel --url` shape. Only the scheme and host are kept: the scheme (http or https) declares how the origin is dialed, superseding the backend's WithTLS, and path/query/user info are dropped. A nil URL, a scheme other than http/https, or an empty host cancels the tunnel.

The origin is provided exactly once, shared with WithListener and the start-trigger mint (see WithListener).

func (*TunnelImpl[T]) WithLogger

func (t *TunnelImpl[T]) WithLogger(log *slog.Logger) v1.Tunnel

WithLogger sets the logger, once: the first call wins, and a nil logger is ignored. A no-op after the log field is fixed — by an earlier WithLogger, or by the tunnel's first internal Logger read fixing the default (silent, or a stderr logger when v1.LogEnv is set).

Directories

Path Synopsis
Package cloudflare is the Cloudflare backend for libtunnel: a cloudflared quick-tunnel engine driven entirely in-process (no cloudflared binary).
Package cloudflare is the Cloudflare backend for libtunnel: a cloudflared quick-tunnel engine driven entirely in-process (no cloudflared binary).
Package resolver does direct DNS lookups against a specific server — the dig(1) equivalent: build the query with golang.org/x/net/dns/dnsmessage, send it over UDP (retrying over TCP when the answer is truncated), and parse the reply.
Package resolver does direct DNS lookups against a specific server — the dig(1) equivalent: build the query with golang.org/x/net/dns/dnsmessage, send it over UDP (retrying over TCP when the answer is truncated), and parse the reply.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL