libtunnel

package module
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: 6 Imported by: 0

README

libtunnel

Go Reference Go Report Card CI CodeQL OpenSSF Scorecard

libtunnel exposes a local origin to the public internet through a tunnel backend — Cloudflare quick tunnels first, driven entirely in-process (no cloudflared binary required).

The API is pure-lazy: every getter resolves on first use, and the edge connection starts on first demand — WithListener provides the origin listener explicitly, WithLocalURL points at an already-running local origin instead (the cloudflared tunnel --url shape), and Listener, URL, and TunnelReady mint a loopback listener if no origin was provided. Configuration is write-once: each With* mutator takes effect at most once and is a no-op after its value is fixed, whether by an earlier call or by the tunnel's first use of the default.

Quick Start

go get github.com/cnuss/libtunnel
package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"net/http"

	"github.com/cnuss/libtunnel"
)

func main() {
	l, _ := net.Listen("tcp", "127.0.0.1:0") // you own the bind

	conn := libtunnel.New(libtunnel.Cloudflare()).
		WithContext(context.Background()). // URL waits for end-to-end readiness
		WithListener(l)                    // lazily starts the edge connection

	go http.Serve(conn.Listener(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "hello from libtunnel")
	}))

	url := conn.URL() // blocks until reachable end to end (see WithContext)
	if url == nil {
		log.Fatal(conn.Err())
	}
	fmt.Println(url) // https://<something>.trycloudflare.com/
}

(The ingress scheme follows the listener: hand over a plain listener and the origin is dialed over http; wrap it with tls.NewListener — or implement TLS() bool on a custom listener — and the ingress switches to https, self-signed certificates welcome.)

Layout

Three packages, stable/alpha versioning:

github.com/cnuss/libtunnel                      — root façade: New, backends,
                                                  providers, handoff helpers.
github.com/cnuss/libtunnel/v1                   — stable Tunnel +
                                                  Provider[T]/Backend[T] contract.
github.com/cnuss/libtunnel/v1alpha1             — lazy tunnel core + generic
                                                  providers. May change between
                                                  alpha revisions.
github.com/cnuss/libtunnel/v1alpha1/cloudflare  — the cloudflared quick-tunnel
                                                  engine + its Spec type.

Application code imports the root (libtunnel.New(...)). Code that needs to declare types against the interfaces imports v1. Direct access to the implementation lives in v1alpha1.

For the file-by-file map, see CONTRIBUTING.md → Where to find things.

API at a glance

// the lazy tunnel handle — every getter resolves on first use; the tunnel
// starts on first demand. Non-generic: the spec type is a construction-time
// detail, so a tunnel reference stores without threading T through caller code.
type Tunnel interface {
    LocalPort() int  // local side, inferred from the origin (listener or URL)
    LocalIP() net.IP
    LocalHost() string
    LocalURL() *url.URL

    Host() string // public side, derived from the spec
    Hostname() string
    Domain() string
    Port() int
    CACerts() []*x509.Certificate

    Listener() net.Listener // start trigger: mints a loopback listener if none provided
    URL() *url.URL // blocks until the hostname resolves (end-to-end w/ WithContext);
                   // start trigger, like Listener

    HostnameReady() <-chan struct{} // hostname resolves on authoritative NS
    TunnelReady() <-chan struct{}   // connection up + hostname resolves;
                                    // start trigger, like Listener
    Done() <-chan struct{}          // tunnel failed or shut down
    Err() error                     // why (nil while alive)

    // write-once mutators: first call wins, no-ops once the value is fixed
    WithLogger(log *slog.Logger) Tunnel      // default: silent
    WithContext(ctx context.Context) Tunnel  // URL waits end-to-end, honors ctx
    WithListener(l net.Listener) Tunnel      // bring your own listener
    WithLocalURL(u *url.URL) Tunnel          // attach to a running local origin
                                             // (http://localhost:1234); mutually
                                             // exclusive with WithListener

    // hook requests in front of the origin proxy; layerable, not write-once
    WithInterceptor(interceptor Interceptor) Tunnel
    Interceptors() Interceptors // snapshot in precedence order (ascending Priority)
}

type Provider[T Spec] interface { Spec(ctx context.Context) (T, error) }
type Backend[T Spec] interface { // opaque; the engine contract is alpha-internal
    Name() string
    Provider() Provider[T] // the backend's credential chain
}
type Spec interface {
    GetHostname() string
    Serialize() string // tagged-envelope JSON; == a LIBTUNNEL_SPEC value
}

// façade
func New[T v1.Spec](backend v1.Backend[T]) v1.Tunnel // T wires the backend, not the result
func Cloudflare() v1.Backend[*cloudflare.Spec]   // in-process cloudflared engine;
                                                 // adopts LIBTUNNEL_SPEC, else mints
                                                 // an anonymous quick tunnel
func From(spec string) v1.Tunnel                 // replay a serialized spec (JSON,
                                                 // file path, or cached hostname)
func Hosts() []string                            // public URLs of cached specs
func Version() string                            // the libtunnel release this build
                                                 // links against (matches the git tag
                                                 // and the container image tag)

// parent→child handoff — no API: minting exports the LIBTUNNEL_SPEC env var,
// construction adopts it

Interceptors

An in-process reverse proxy always fronts the origin. WithInterceptor hooks that path: for every request the tunnel runs the highest-Priority interceptor whose MatchFn returns true; anything unmatched is proxied to the origin unchanged. Ordering is AWS-ALB style: the lowest Priority wins — 1 is the highest precedence, 65535 the lowest. Priority 0 is not a precedence; it's the zero value meaning unset, and those are auto-assigned from the top of the uint16 range downward, so unprioritized interceptors sit at the low-precedence end — a later-added one wins over an earlier one, and any explicit Priority outranks them all. Interceptors layer (call it more than once) and, unlike the write-once With* mutators, may be added after the tunnel is live. tun.Interceptors() returns the registry in precedence order for visibility.

type MatchFn     = func(r *http.Request) bool
type InterceptFn = func(ctx InterceptCtx) InterceptCtx

// WithInterceptor takes an Interceptor — the {Match, Handler} pair — so reusable
// interceptors ship as constructors: tun.WithInterceptor(addHeaders()).
type Interceptor struct {
    Match    MatchFn
    Handler  InterceptFn
    Priority uint16 // ALB-style: lowest wins (1 highest); 0 = unset, auto-assigned from the top down
}

// InterceptCtx is the per-request handle. It embeds the request's
// context.Context and carries the request, the response writer, and the
// handler that will serve it — seeded to proxy the origin.
type InterceptCtx interface {
    context.Context

    Reconnect(ctx context.Context) error // cycle the edge conn(s), block until back up
    Target() net.Listener                // the proxy's loopback socket (not the origin)

    WithHandler(h http.HandlerFunc) InterceptCtx // replace the serving handler
    Handler() http.HandlerFunc                   // the handler currently set

    Writer() http.ResponseWriter
    Request() *http.Request
}

An InterceptFn receives the InterceptCtx and shapes how the request is served: call WithHandler to take it over, or return the ctx unchanged (or nil) to leave the default in place — the request is proxied to the origin, exactly as if nothing had matched. So an interceptor can match broadly, inspect, and opt out per request.

Ship an interceptor as a constructor that returns an Interceptor. The common shape wraps the default handler (ic.Handler(), which proxies to the origin) — plain net/http middleware:

// addHeader sets a response header on every request, then serves the origin.
func addHeader(key, val string) libtunnel.Interceptor {
    return libtunnel.Interceptor{
        Match: func(*http.Request) bool { return true },
        Handler: func(ic libtunnel.InterceptCtx) libtunnel.InterceptCtx {
            next := ic.Handler() // the default: proxy to the origin
            return ic.WithHandler(func(w http.ResponseWriter, r *http.Request) {
                w.Header().Set(key, val)
                next(w, r)
            })
        },
    }
}

tun := libtunnel.New(libtunnel.Cloudflare()).WithListener(l)
tun.WithInterceptor(addHeader("X-Served-By", "libtunnel"))

Reach past the request through the ctx levers — e.g. force an edge reconnect on ?watch=true, then serve normally:

func reconnectOnWatch() libtunnel.Interceptor {
    return libtunnel.Interceptor{
        Match: func(r *http.Request) bool { return r.URL.Query().Get("watch") == "true" },
        Handler: func(ic libtunnel.InterceptCtx) libtunnel.InterceptCtx {
            next := ic.Handler()
            return ic.WithHandler(func(w http.ResponseWriter, r *http.Request) {
                ctx, cancel := context.WithTimeout(ic, 30*time.Second)
                defer cancel()
                if err := ic.Reconnect(ctx); err != nil {
                    return // request can't proceed — stop, no further writes
                }
                next(w, r)
            })
        },
    }
}

Parent→child handoff

LIBTUNNEL_SPEC is a first-class handoff channel with nothing to call: when the Cloudflare credential chain mints a spec it exports it into the process's environment, and at construction it adopts one found there. A spawned child (or a re-exec) therefore connects under the same hostname — no second quick-tunnel resolution, no plumbing. The export also sets LIBTUNNEL_HOSTNAME to the plain hostname, so tooling can read it without parsing the envelope (libtunnel itself adopts LIBTUNNEL_SPEC, not this).

Two guardrails keep the channel safe: a process never re-adopts a spec it exported itself (a second tunnel in the same process mints its own identity instead of inheriting the first one's), and the exported value is tagged with the backend that minted it, so a child running a different backend fails loudly instead of silently unmarshaling a foreign spec.

// parent: forcing the mint exports LIBTUNNEL_SPEC as a side effect (never
// connects itself); Hostname triggers the mint and returns the public name
libtunnel.New(libtunnel.Cloudflare()).Hostname()
cmd := exec.Command(os.Args[0], "child") // inherits the environment

// child: the Cloudflare credential chain finds LIBTUNNEL_SPEC and adopts it
conn := libtunnel.New(libtunnel.Cloudflare()).WithListener(l)

(Full source: examples/subprocess/main.go.)

Replaying a spec

A freshly minted spec is cached to disk as <hostname>.spec.json (the Serialize() envelope, same form as LIBTUNNEL_SPEC) under the cache dir — LIBTUNNEL_CACHE_DIR if set, else a per-user location from os.UserCacheDir(). libtunnel.Hosts() lists the cached tunnels as https://<host>:443/ URLs, and libtunnel.From(spec) replays one — spec is the JSON, a file path, or just a cached hostname — connecting under the same hostname instead of minting. Only minted specs are cached (adopted or From-loaded ones are not), and a bad/unknown spec yields a tunnel already canceled with the cause (off Err()).

// replay the most recently cached tunnel by hostname
conn := libtunnel.From("foo.trycloudflare.com").WithListener(l)

Environment variables

Every code knob with an env-expressible value has an environment mirror, and env beats code: an operator can redirect a deployed binary without a rebuild. (The one exception is noted below.)

Variable Mirrors Behavior
LIBTUNNEL_SPEC Parent→child handoff: a serialized spec adopted at construction (see above). Beats everything, including a code-pinned From spec.
LIBTUNNEL_FROM From() Replay a spec by hostname, file path, or literal JSON — From's resolution. Applies after LIBTUNNEL_SPEC, before the code-pinned spec and minting.
LIBTUNNEL_LOCAL_URL WithLocalURL() Origin override, applied at origin-provide time: supersedes a WithListener listener, a WithLocalURL argument, and the start-trigger mint. Invalid value cancels the tunnel.
LIBTUNNEL_TLS WithTLS() Bool (strconv.ParseBool). Fixed at backend construction; later WithTLS calls are no-ops. Unparsable value fails at connect.
LIBTUNNEL_HTTP2 WithHTTP2() Same rules as LIBTUNNEL_TLS.
LIBTUNNEL_LOG WithLogger() debug|info|warn|error: the default logger becomes a stderr text logger at that level instead of silent. The exception: an explicit WithLogger keeps its handler — env carries a level, not a sink.
LIBTUNNEL_HOSTNAME Export-only mirror of the minted spec's hostname, for tooling; never adopted.
LIBTUNNEL_CACHE_DIR Where minted specs are cached and From/Hosts look.

Backend-scoped variables follow LIBTUNNEL__<BACKEND>_<FIELD> (double underscore namespaces the backend) and live with their backend package. For Cloudflare, each mirrors a spec-field setter on the backend — env beats code, field by field, patched onto whatever spec the chain resolves; a complete credential set (id, hostname, account tag, secret) skips resolution entirely:

Variable Mirrors
LIBTUNNEL__CLOUDFLARE_ID WithID()
LIBTUNNEL__CLOUDFLARE_NAME WithName()
LIBTUNNEL__CLOUDFLARE_HOSTNAME WithHostname()
LIBTUNNEL__CLOUDFLARE_ACCOUNT_TAG WithAccountTag()
LIBTUNNEL__CLOUDFLARE_SECRET WithSecret() (base64)
LIBTUNNEL__CLOUDFLARE_PROVIDER WithProvider() — quick-tunnel provider host, default api.trycloudflare.com (endpoint https://<host>/tunnel synthesized; a value with a scheme is used verbatim)
LIBTUNNEL__CLOUDFLARE_HEADERS WithHeader() — request headers on the mint call, comma-separated K=V (e.g. X-Opaque=true); entries beat code per key. No escaping — values can't contain , or =. Mint-only.

The Cloudflare backend also has a bare activation switch, LIBTUNNEL__CLOUDFLARE=1, used by the binary below to select it without a spec handoff.

Binary

cmd/libtunnel is a standalone launcher configured only by the environment — no flags, no config files — the operator-side face of the variables above. Shaped for docker run -e ...:

go run ./cmd/libtunnel
# or: go build -o libtunnel ./cmd/libtunnel

It needs two things:

  • a backend, activated by LIBTUNNEL_SPEC (a spec handoff) or LIBTUNNEL__CLOUDFLARE=1 (the explicit switch); and
  • an origin, LIBTUNNEL_LOCAL_URL — a standalone binary has no listener to inherit, so it points at an already-running local service.
LIBTUNNEL__CLOUDFLARE=1 \
LIBTUNNEL_LOCAL_URL=http://localhost:8080 \
LIBTUNNEL_LOG=info \
  libtunnel

It prints the public URL to stdout (one line; logs go to stderr via LIBTUNNEL_LOG) and runs until SIGINT/SIGTERM. Every other knob — LIBTUNNEL_TLS, LIBTUNNEL_FROM, the LIBTUNNEL__CLOUDFLARE_* fields — flows straight through the library. Minting also exports LIBTUNNEL_SPEC/LIBTUNNEL_HOSTNAME, so a child it later spawns inherits the same tunnel identity. libtunnel version prints the build id and exits — the only argument it accepts, since configuration is environment-only.

Each release attaches static, stripped binaries for linux/darwin/windows × amd64/arm64, a SHA256SUMS manifest, and a cosign .sigstore bundle per file. To build locally instead: make dist (cross-compiles the matrix into dist/) or make binary (host only). CGO is off, so the binary is dependency-free and runs on a scratch/distroless base — Dockerfile targets distroless/static:nonroot. Each release publishes a multi-arch (amd64/arm64), cosign-signed image to the GitHub Container Registry:

docker run --rm \
  -e LIBTUNNEL__CLOUDFLARE=1 \
  -e LIBTUNNEL_LOCAL_URL=http://host.docker.internal:8080 \
  ghcr.io/cnuss/libtunnel:latest

Or build it yourself: docker build -t libtunnel .

The image is tagged with the release version (v0.0.29), the bare semver (0.0.29, 0.0), and latest. libtunnel.Version() returns the v-prefixed tag, so a consumer can pin the image to the exact library version it links against without any string munging:

image := "ghcr.io/cnuss/libtunnel:" + libtunnel.Version()

Examples

Self-contained programs in ./examples:

Example Demonstrates
serve Real quick tunnel: serve locally, request the public URL.
serve-tls Same as serve, but a TLS listener (tls.Listen) — ingress flips to https.
subprocess Parent mints a spec; child adopts it via LIBTUNNEL_SPEC and serves.

Run one locally:

make run serve
make run serve-tls
make run subprocess

Testing

make test   # library unit + fuzz tests (fast, in-package)
make e2e    # live tier: real tunnels through the real edge (gated)

make e2e runs go test -count=1 -v ./e2e. The -count=1 defeats the test cache, since the harness builds the example binaries at runtime and the cache key wouldn't otherwise pick up example source changes. The e2e tier is live tunnels only — everything mints from api.trycloudflare.com (rate-limited), so the whole tier is skipped unless you opt in (offline subprocess handoff coverage lives in the unit tier and always runs):

LIBTUNNEL_E2E_LIVE=1 make e2e

Contributing

See CONTRIBUTING.md for the local dev loop, release process, and what makes a good example.

License

MIT

Documentation

Overview

Package libtunnel exposes a local origin to the public internet through a tunnel backend (Cloudflare quick tunnels first), behind a thin, stable façade over stable/alpha versioned packages.

The package is split into these pieces:

  • libtunnel (this package) — thin façade exposing New and the backend constructors. Stable surface for application code.
  • github.com/cnuss/libtunnel/v1 — the stable Tunnel/Provider/Backend interfaces and spec types. Application code that wants to declare types against the contract imports this.
  • github.com/cnuss/libtunnel/v1alpha1 — the current implementation: the lazy tunnel core plus generic providers, with backend engines in subpackages (v1alpha1/cloudflare). Internals may change between alpha revisions.

Everything is lazy: New returns immediately, and the edge connection starts on first demand — WithListener provides the origin listener explicitly, WithLocalURL points at an already-running local origin instead (the cloudflared `tunnel --url` shape), and Listener, URL, and TunnelReady mint a loopback listener if no origin was provided.

l, _ := net.Listen("tcp", "127.0.0.1:0")
conn := libtunnel.New(libtunnel.Cloudflare()).WithListener(l)
go server.Serve(conn.Listener())
select {
case <-conn.TunnelReady():
	fmt.Println(conn.URL()) // public https://<hostname>/
case <-conn.Done():
	log.Fatal(conn.Err()) // TunnelReady never closes on failure
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CacheDir added in v0.0.18

func CacheDir() string

CacheDir is where minted specs are cached and where From and Hosts look: LIBTUNNEL_CACHE_DIR if set, else a per-user location under os.UserCacheDir(). Empty if no per-user cache directory can be determined.

func Cloudflare

func Cloudflare() *cloudflare.Backend

Cloudflare returns the Cloudflare backend: an in-process cloudflared quick-tunnel engine (no cloudflared binary required). Its credential chain resolves env first: adopt a spec from the LIBTUNNEL_SPEC environment variable when a parent process handed one off, replay the spec LIBTUNNEL_FROM references (hostname, file path, or literal JSON — From's resolution), and mint an anonymous *.trycloudflare.com quick tunnel otherwise. A resolved spec is exported back into the environment so spawned children inherit the same tunnel identity; a spec this process exported itself is never re-adopted — a second in-process tunnel mints its own identity.

Individual spec fields can be overridden with the backend's setters — WithID, WithName, WithHostname, WithAccountTag, WithSecret — or their LIBTUNNEL__CLOUDFLARE_* environment mirrors (env beats code, field by field); a complete credential set skips resolution entirely. WithProvider (LIBTUNNEL__CLOUDFLARE_PROVIDER) points the mint at a different quick-tunnel provider host. Chain the backend-specific setters before WithTLS / WithHTTP2, which return the CloudflareV1 interface.

func Hosts added in v0.0.18

func Hosts() []string

Hosts lists the public URLs of the specs cached on disk — "https://<hostname>:443/" each, sorted — from LIBTUNNEL_CACHE_DIR if set, else a per-user location under os.UserCacheDir(). A mint caches its spec there, so this enumerates the tunnels From can replay. Best effort: an unreadable cache yields a shorter or empty list, never an error.

func Version added in v0.0.30

func Version() string

Version reports the libtunnel release this build links against — e.g. "v0.0.29". It matches the git tag and the published container image tag, so a consumer can pin the image to the exact library version it compiles against:

image := "ghcr.io/cnuss/libtunnel:" + libtunnel.Version()

Resolution, in order: the release stamp (set only in libtunnel's own release binaries); the module version recorded in the importer's build info (the common consumer case — the version required in their go.mod); the main-module version; and finally the short VCS revision of a local build (with a -dirty suffix for an uncommitted tree). A build carrying no version information at all returns "unknown".

Image tag convention: each release publishes ghcr.io/cnuss/libtunnel tagged with both the v-prefixed release tag (matching Version exactly) and the bare semver ("0.0.29", "0.0"), plus "latest" — so concatenating Version onto the image name resolves to the matching image with no trimming.

Types

type CloudflareV1 added in v0.0.7

type CloudflareV1 = v1.Backend[*cloudflare.Spec]

CloudflareV1 is the Cloudflare backend's contract type: an alias for v1.Backend[*cloudflare.Spec], re-exported so callers can declare fields and parameters without importing v1 or the cloudflare package. Cloudflare() returns the concrete *cloudflare.Backend (which satisfies this), so the backend-specific setters (WithID and friends) stay reachable.

type InterceptCtx added in v0.0.36

type InterceptCtx = v1.InterceptCtx // per-request handle: request, levers, handler

The interceptor types (see TunnelV1.WithInterceptor) are re-exported from v1 so callers can name them without importing v1.

type InterceptFn added in v0.0.35

type InterceptFn = v1.InterceptFn // shapes how a matched request is served

The interceptor types (see TunnelV1.WithInterceptor) are re-exported from v1 so callers can name them without importing v1.

type Interceptor added in v0.0.35

type Interceptor = v1.Interceptor // a {Match, Handler} pair

The interceptor types (see TunnelV1.WithInterceptor) are re-exported from v1 so callers can name them without importing v1.

type Interceptors added in v0.0.35

type Interceptors = v1.Interceptors // ordered registry; first match wins

The interceptor types (see TunnelV1.WithInterceptor) are re-exported from v1 so callers can name them without importing v1.

type MatchFn added in v0.0.35

type MatchFn = v1.MatchFn // request predicate: does this interceptor apply

The interceptor types (see TunnelV1.WithInterceptor) are re-exported from v1 so callers can name them without importing v1.

type TunnelV1 added in v0.0.7

type TunnelV1 = v1.Tunnel

TunnelV1 is the tunnel handle returned by New: a non-generic alias for v1.Tunnel, re-exported so callers can name the type without importing v1. Storable as a plain field — the backend spec type does not appear in it.

func From added in v0.0.17

func From(spec string) TunnelV1

From returns an unstarted tunnel that replays a previously serialized spec instead of minting or adopting one — the credentials are pinned, so it connects under the same hostname. spec is resolved in order: an existing file at that path; a file of that name in the cache dir; a cached spec for that hostname (so From("foo.trycloudflare.com") replays the cached mint); finally the serialized JSON itself. The cache dir is LIBTUNNEL_CACHE_DIR when set, else a per-user location under os.UserCacheDir() — where a mint writes its spec.

Like New, it returns immediately and WithListener (or Listener) starts the connection. A spec that can't be parsed, or whose backend tag is unknown, yields a tunnel already canceled with that cause — surfaced through Err()/Done(), per the façade's no-error contract.

The LIBTUNNEL_FROM environment variable is From's operator-side mirror: it takes the same spec reference and replays it through any backend's credential chain — including over a code-pinned From spec, after LIBTUNNEL_SPEC (env beats code; SPEC beats FROM).

func New

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

New returns an unstarted tunnel on the given backend, which also supplies the credential chain. T is the backend's spec type, inferred from the backend and used only to wire the credential chain — it does not appear in the returned type, so the tunnel reference is non-generic and storable without threading the spec type through caller code:

libtunnel.New(libtunnel.Cloudflare())

Configure the result with With* methods; WithListener starts the connection.

Directories

Path Synopsis
cmd
libtunnel command
Command libtunnel is the env-only tunnel launcher: a standalone binary with no flags and no config files — every knob comes from the environment, the same variables the library reads (see package v1).
Command libtunnel is the env-only tunnel launcher: a standalone binary with no flags and no config files — every knob comes from the environment, the same variables the library reads (see package v1).
Package e2e is the live tier: real quick tunnels through the real Cloudflare edge, gated behind LIBTUNNEL_E2E_LIVE=1.
Package e2e is the live tier: real quick tunnels through the real Cloudflare edge, gated behind LIBTUNNEL_E2E_LIVE=1.
examples
serve command
Command serve exposes a local HTTP server to the public internet through a Cloudflare quick tunnel, waits until the tunnel is reachable end to end, then requests its own public URL to prove the round trip.
Command serve exposes a local HTTP server to the public internet through a Cloudflare quick tunnel, waits until the tunnel is reachable end to end, then requests its own public URL to prove the round trip.
serve-tls command
Command serve-tls is serve's TLS twin: it hands WithListener a TLS listener (tls.Listen over a self-signed, in-process cert) instead of a plain one, so the tunnel dials the origin over https.
Command serve-tls is serve's TLS twin: it hands WithListener a TLS listener (tls.Listen over a self-signed, in-process cert) instead of a plain one, so the tunnel dials the origin over https.
subprocess command
Command subprocess is the canonical parent→child spec handoff: the parent process mints a tunnel spec and passes it to a spawned child through the LIBTUNNEL_SPEC environment variable; the child provides the listener, connects the tunnel, and serves; the parent then requests the child's public URL.
Command subprocess is the canonical parent→child spec handoff: the parent process mints a tunnel spec and passes it to a spawned child through the LIBTUNNEL_SPEC environment variable; the child provides the listener, connects the tunnel, and serves; the parent then requests the child's public URL.
Package v1 is the stable public surface for libtunnel.
Package v1 is the stable public surface for libtunnel.
Package v1alpha1 is the current implementation behind the v1 tunnel interfaces: the backend-agnostic lazy core plus generic providers.
Package v1alpha1 is the current implementation behind the v1 tunnel interfaces: the backend-agnostic lazy core plus generic providers.
cloudflare
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).
resolver
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