bifrost

package module
v0.4.1 Latest Latest
Warning

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

Go to latest
Published: Jun 19, 2026 License: MIT Imports: 15 Imported by: 0

README

Bifrost

CI Docker Release Go Reference

Bifrost is a self-hosted TCP reverse tunnel for exposing selected private services through a public relay when your private network is behind NAT, DS-Lite, CGNAT, or a firewall. It is a small NAT traversal building block for homelabs, SOHO networks, and product integrations that need outbound-only connectivity.

Run bifrost-server on a reachable VM, run bifrost-client next to the private service, and the client opens the tunnel with an outbound TLS connection. Public callers connect to a listener on the relay side; Bifrost forwards those byte streams through the tunnel to the private target.

Typical use case: Home Assistant, Jellyfin, an admin UI, a development box, or another internal TCP service runs in a homelab or SOHO network that cannot accept inbound traffic. A small public VM runs Bifrost and your normal reverse proxy. The private network only needs outbound access to that VM.

Status: Bifrost is early and pre-1.0. The transport, static admission model, Docker entrypoint, metrics, and config schema are usable for development, demos, pilots, and review, but the public protocol and config schema should still be treated as compatibility-sensitive.

Why Bifrost Exists

Homelab and small office networks often have the same set of practical problems:

  • The ISP uses CGNAT, DS-Lite, dynamic addressing, or blocks inbound ports.
  • The router is managed by somebody else, or port forwarding is not desirable.
  • A service needs a normal public HTTPS URL without moving the service to the cloud.
  • The operator wants to keep using Caddy, Nginx, DNS, ACME, access control, and logs they already understand.
  • Only one or two services should be reachable, not the whole private network.
  • The data path should be self-hosted, inspectable, and independent from a hosted tunnel account.

Bifrost is intentionally narrow: it moves byte streams from a relay listener to a private TCP target over an outbound tunnel. It does not try to become a VPN, identity provider, dashboard product, DNS manager, billing system, or reverse proxy.

What You Get

Problem Bifrost answer
No inbound connectivity to the private network The connector dials out to the public relay over TLS.
Need a public URL for a private service Put Caddy or Nginx on the relay and proxy to a Bifrost listener.
Avoid exposing an entire LAN Each connector owns one configured endpoint and target.
Keep control of the data path The relay, connector, certificates, and reverse proxy are yours.
Reconnects should be predictable Endpoint ownership is explicit: reject, replace, or allow parallel sessions.
Small networks still need guardrails Sessions, streams, bandwidth, idle time, headers, and metrics are bounded.
Existing observability should keep working Optional Prometheus metrics expose sessions, streams, rejects, and bytes.
Need tunnel latency without app probes Embedders can read endpoint-keyed passive mux/session latency state.

Project Shape

Bifrost is the generic tunnel runtime:

  • bifrost-server accepts connector tunnels on a reachable host.
  • bifrost-client runs near the private service and dials the server outbound.
  • bifrostctl validates and prints starter config.
  • The Go library exposes an AcceptProvider interface for embedded products and integrations.

For Caddy-native deployments, see caddy-bifrost. It embeds Bifrost as a Caddy app, adds a reverse_proxy transport, and supports SNI passthrough with listener wrappers.

Passive latency observations are documented in docs/passive-latency.md. They are keyed by endpoint_key and contain only latency milliseconds, observation time, and a controlled state.

How It Works

flowchart LR
  user["Public caller<br/>https://home.example.com"]

  subgraph public_vm["Public VM"]
    proxy["Caddy / Nginx<br/>public HTTPS + routing"]
    socket["Unix socket<br/>/sockets/home-assistant.sock"]
    server["bifrost-server<br/>relay.example.com:8443"]
  end

  subgraph private_host["Private Docker host"]
    client["bifrost-client"]
    app["Home Assistant<br/>homeassistant:8123"]
  end

  user --> proxy --> socket --> server
  client -->|"dials outbound TLS"| server
  server -->|"forwards streams over yamux"| client
  client --> app

The connector sends a token during the tunnel handshake. On the server side, Bifrost matches that token against native clients[] configuration or an embedded AcceptProvider. If the connector is allowed, the server binds the configured listener and applies configured limits.

For HTTP services, the common shape is:

  1. Public Caddy receives https://home.example.com.
  2. Caddy proxies to unix//sockets/home-assistant.sock.
  3. Bifrost owns that Unix socket and forwards the stream through the tunnel.
  4. The local connector dials homeassistant:8123 or another private TCP target.

When To Use It

Use Bifrost when you want:

  • outbound-only connectivity from a private network
  • a relay you operate yourself
  • TLS on the tunnel transport
  • explicit admission decisions for each connector
  • per-session limits for streams, bandwidth, and idle time
  • listener placement controlled by server-side policy
  • a small tunnel layer that leaves HTTP routing and certificates to your proxy

Use a VPN instead when you need IP-level access to many hosts or subnets.

Use a hosted tunnel product when you want somebody else to operate the relay, account model, dashboards, DNS integration, and edge network.

Use only a reverse proxy when the private service is already directly reachable from the public proxy.

Quick Start

This quick start shows the real deployment shape: a public server with an existing Caddy reverse proxy and a local Docker environment that already runs a service such as Home Assistant or Jellyfin.

You need:

  • A public Linux VM with Docker Compose and Caddy or another reverse proxy.
  • A DNS name for the tunnel endpoint, shown below as relay.example.com.
  • A DNS name for the exposed app, shown below as home.example.com.
  • TCP port 8443 open on the VM for the Bifrost tunnel.
  • TCP ports 80 and 443 handled by your public reverse proxy.
  • A local Docker host with outbound access to relay.example.com:8443.

The example values used below are:

Value Meaning
relay.example.com DNS name used by the local connector to reach bifrost-server
home.example.com Public HTTPS hostname served by Caddy
homeassistant:8123 Private Docker service and port reached by bifrost-client
/sockets/home-assistant.sock Unix socket created by bifrost-server and consumed by Caddy
replace-this-with-a-long-random-token Shared connector token in the server and client config

The snippets assume your public Compose project already has a caddy service, your local Compose project already has a homeassistant service, and Bifrost files live under ./bifrost/ beside each Compose file.

For Jellyfin, use jellyfin:8096 as the target address and a matching socket name such as /sockets/jellyfin.sock.

1. Create Tunnel Credentials On The Public Server

On the public server, from the directory containing your public Compose file:

mkdir -p bifrost/certs

Bifrost uses TLS between bifrost-client and bifrost-server. For a first deployment, create a self-signed relay certificate whose SAN matches relay.example.com:

openssl req -x509 -newkey rsa:4096 -sha256 -nodes \
  -days 365 \
  -keyout bifrost/certs/server.key \
  -out bifrost/certs/server.crt \
  -subj "/CN=relay.example.com" \
  -addext "subjectAltName=DNS:relay.example.com"
cp bifrost/certs/server.crt bifrost/certs/ca.crt

The token admits the connector. The relay certificate secures the tunnel transport. Public HTTPS for home.example.com is still handled by Caddy and is separate from this tunnel certificate. For real use, replace the example token with a random value such as the output of openssl rand -hex 32.

If the relay certificate is issued by a public CA such as Let's Encrypt, mount that certificate and key on the relay instead. In that case the connector does not need a ca.crt mount; the Docker image falls back to the system trust store when /certs/ca.crt is absent.

Add the client definition to the server configuration. In Docker-generated config this is supplied through BIFROST_SERVER_CLIENTS_JSON. The listener.path must match the Unix socket your public reverse proxy will use:

[
  {
    "token": "replace-this-with-a-long-random-token",
    "endpoint_key": "home-assistant",
    "listener": {
      "type": "unix",
      "path": "/sockets/home-assistant.sock",
      "mode": "0600"
    },
    "connection_policy": {
      "mode": "replace_existing",
      "max_parallel": 1
    },
    "limits": {
      "max_streams": 100,
      "max_bandwidth_bps": 25000000,
      "stream_idle_timeout_seconds": 300
    },
    "labels": {
      "service": "home-assistant"
    }
  }
]

Keep bifrost/certs/server.key only on the public server. The local connector only needs bifrost/certs/ca.crt and the same token used in the server clients[] config.

2. Add Bifrost To The Public Compose Project

Add the relay service to your public Compose file. If your Compose file already has a volumes: section, merge the bifrost_sockets entry into it.

services:
  bifrost-cloud:
    image: ghcr.io/tunely-eu/bifrost:latest
    command: ["server"]
    restart: unless-stopped
    environment:
      BIFROST_SERVER_CLIENTS_JSON: |
        [
          {
            "token": "replace-this-with-a-long-random-token",
            "endpoint_key": "home-assistant",
            "listener": {
              "type": "unix",
              "path": "/sockets/home-assistant.sock",
              "mode": "0600"
            },
            "connection_policy": {"mode": "replace_existing"},
            "limits": {"max_streams": 100}
          }
        ]
    volumes:
      - ./bifrost/certs/server.crt:/certs/server.crt:ro
      - ./bifrost/certs/server.key:/certs/server.key:ro
      - bifrost_sockets:/sockets
    ports:
      - "8443:8443"

volumes:
  bifrost_sockets:

Mount the same socket volume into your existing Caddy service. Keep your existing Caddy config, data, and site volumes; add the /sockets mount alongside them.

services:
  caddy:
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - bifrost_sockets:/sockets

Add a route to your Caddyfile:

home.example.com {
	reverse_proxy unix//sockets/home-assistant.sock
}

The snippets assume Caddy can read the Unix socket created by bifrost-cloud. If your Caddy container runs as a non-root user, configure socket permissions and container users accordingly.

3. Add Bifrost To The Local Compose Project

On the local Docker host, place the relay CA certificate at ./bifrost/certs/ca.crt. For the self-signed certificate above, this is the public server.crt copied as ca.crt. Do not copy server.key to the local side.

Add the connector service to the Compose file that already runs Home Assistant:

services:
  bifrost-client:
    image: ghcr.io/tunely-eu/bifrost:latest
    command: ["client"]
    restart: unless-stopped
    depends_on:
      - homeassistant
    environment:
      BIFROST_CLIENT_SERVER_URL: "relay.example.com:8443"
      BIFROST_CLIENT_TLS_SERVER_NAME: "relay.example.com"
      BIFROST_CLIENT_TARGET_ADDRESS: "homeassistant:8123"
      BIFROST_CLIENT_TOKEN: "replace-this-with-a-long-random-token"
    volumes:
      - ./bifrost/certs/ca.crt:/certs/ca.crt:ro

For Jellyfin, use /sockets/jellyfin.sock in BIFROST_SERVER_CLIENTS_JSON and the Caddy route, then change the local target:

environment:
  BIFROST_CLIENT_TARGET_ADDRESS: "jellyfin:8096"

If the target service is not in the same Compose project, point BIFROST_CLIENT_TARGET_ADDRESS at a reachable LAN address instead, for example 192.168.1.20:8123.

4. Test The Public Route

From any machine that can reach the public VM:

curl -I https://home.example.com

You should see the same HTTP response headers you would get from the service inside the private network.

If the request fails, check these points first:

  • DNS for relay.example.com and home.example.com points at the public VM.
  • The public VM allows inbound TCP 8443, 80, and 443.
  • The local host can open outbound TCP connections to relay.example.com:8443.
  • For a private or self-signed relay certificate, the local host has ca.crt, not server.key. For a public-CA relay certificate, no client CA mount is needed.
  • BIFROST_CLIENT_TOKEN matches the token in the server clients[] config.
  • The Caddy socket path matches clients[].listener.path.

Configuration Model

The Docker image is the default runtime interface. Use the same image for each runtime role:

  • Cloud relay: command: ["server"]
  • Homelab connector: command: ["client"]
  • Control command: command: ["ctl", "..."]

The Docker entrypoint supports two configuration styles:

  • generated config for the default Docker path
  • explicit YAML or JSON config files passed with --config

The quick start uses generated Docker config on both sides: the relay uses the default container paths, and the connector is configured through Docker environment variables.

Default Container Paths

Mount these paths when using the generated Docker configuration:

  • /certs/server.crt: server TLS certificate
  • /certs/server.key: server TLS private key
  • /certs/ca.crt: optional client CA certificate for private or self-signed relay certificates
  • /sockets: allowed Unix socket directory for server-side listeners

The cloud relay listens on :8443 by default. Generated Docker config requires BIFROST_SERVER_CLIENTS_JSON, a JSON array of client definitions.

The full generated Docker config reference and explicit config schema are documented in Configuration.

Security Model

  • Client-server tunnel transport is always TLS.
  • ALPN must be bifrost/1.
  • The client hello is JSON and size-limited.
  • Header names are validated and normalized before admission decisions.
  • Header values are generic runtime metadata and are redacted in logs.
  • Admission failures, invalid client definitions, invalid listener specs, and missing endpoint_key fail closed.
  • Streams, sessions, buffers, idle time, and bandwidth have bounded defaults.

TLS protects the tunnel between bifrost-client and bifrost-server. Bifrost does not automatically apply TLS to the server-side listener or to the client-side local target; it forwards the bytes it receives.

Application authentication stays with your reverse proxy or target service. Public HTTPS for web services stays with Caddy, Nginx, or another proxy on the public VM. See Security.

Observability

When admin.listen is enabled, Bifrost exposes:

  • /readyz: readiness status
  • /metrics: Prometheus-style text metrics

Useful endpoint metrics include active sessions, started streams, ended streams, rejected streams, and stream bytes. Metric labels intentionally use stable endpoint and reason values only. Tokens, remote addresses, application paths, and SNI names are not exported as metric labels.

Library Accept Providers

Bifrost exposes an AcceptProvider interface for embedded use. The standalone bifrost-server builds a static provider from top-level clients[] config.

Product-specific modules can provide their own AcceptProvider without shell hooks or external helper processes. This is the integration path used by Caddy-native and future control-plane deployments.

The library API and config schema are documented in Configuration.

Local Development

Build and test locally:

make test
make entrypoint-test
make build

The binaries are written to bin/.

Generate starter configs:

go run ./cmd/bifrostctl config example --server
go run ./cmd/bifrostctl config example --client

Validate your own configs:

go run ./cmd/bifrostctl config validate --server --file path/to/server.yaml
go run ./cmd/bifrostctl config validate --client --file path/to/client.yaml

Documentation

Contributing

See CONTRIBUTING.md for development setup, review expectations, and pull request guidelines.

Please report suspected vulnerabilities through the process in SECURITY.md.

License

MIT License. See LICENSE.

Documentation

Overview

Package bifrost provides the embeddable runtime for Bifrost self-hosted TCP tunnels.

A Bifrost deployment has two roles. A server runs on a reachable relay and accepts connector sessions. A client runs near a private TCP service and dials the server with an outbound TLS connection. Once a connector is admitted, the server can open streams to the private target through the tunnel.

The package is intentionally focused on the tunnel data path. It does not perform HTTP routing, DNS management, browser-facing TLS termination, account management, or application authentication. Those concerns belong to the embedding product, reverse proxy, or target service.

Most standalone deployments use the bifrost-server and bifrost-client binaries. Use this package when embedding the tunnel runtime into another Go program, Caddy module, control plane, or test harness.

Index

Constants

View Source
const (
	// TokenHeader is the normalized hello header name that carries the connector
	// token used by static admission providers.
	TokenHeader = acceptor.TokenHeader

	// ALPN is the TLS application protocol negotiated by Bifrost tunnel
	// connections.
	ALPN = protocol.ALPN

	// PolicyRejectIfExists rejects a new connector session when the endpoint key
	// already has an active session.
	PolicyRejectIfExists = acceptor.PolicyRejectIfExists
	// PolicyReplaceExisting closes an existing session and lets the new
	// connector session take ownership of the endpoint key.
	PolicyReplaceExisting = acceptor.PolicyReplaceExisting
	// PolicyAllowParallel allows multiple active connector sessions for the same
	// endpoint key up to ConnectionPolicy.MaxParallel.
	PolicyAllowParallel = acceptor.PolicyAllowParallel
)
View Source
const (
	// DirectionIngressToEndpoint counts bytes flowing from the server-side
	// ingress connection toward the private endpoint.
	DirectionIngressToEndpoint = metrics.DirectionIngressToEndpoint
	// DirectionEndpointToIngress counts bytes flowing from the private endpoint
	// back to the server-side ingress connection.
	DirectionEndpointToIngress = metrics.DirectionEndpointToIngress
)

Variables

This section is empty.

Functions

func Copy

func Copy(ctx context.Context, a net.Conn, b net.Conn, opts CopyOptions)

Copy proxies bytes between a and b until one side closes, ctx is canceled, or the idle timeout is reached. Copy closes both connections before returning.

func RunClient

func RunClient(ctx context.Context, cfg ClientConfig, opts ClientOptions) error

RunClient runs a connector until ctx is canceled. The connector maintains an outbound session to ServerURL and forwards each accepted stream to TargetAddress, unless StreamHandler is set in opts.

func RunServer

func RunServer(ctx context.Context, cfg ServerConfig, opts ServerOptions) error

RunServer constructs and runs a Server until ctx is canceled or the server returns an error.

Types

type AcceptDecision

type AcceptDecision = acceptor.Decision

AcceptDecision is the provider result for a connector session.

type AcceptProvider

type AcceptProvider = acceptor.Provider

AcceptProvider decides whether an inbound connector session should be accepted and, if accepted, which endpoint, limits, and ownership policy it receives.

func NewStaticAcceptProvider

func NewStaticAcceptProvider(clients []StaticClient) (AcceptProvider, error)

NewStaticAcceptProvider builds an AcceptProvider from static token-backed clients. The provider rejects missing, unknown, and duplicate tokens.

type AcceptRequest

type AcceptRequest = acceptor.Request

AcceptRequest describes a connector session after TLS and protocol hello validation. Header names are normalized before the request reaches a provider.

type ClientConfig

type ClientConfig struct {
	// ServerURL is the relay address in host:port form.
	ServerURL string

	// Headers contains additional hello headers sent to the relay. TokenHeader is
	// managed by the CLI configuration path and should not be duplicated here.
	Headers map[string]string

	// TargetAddress is the private TCP target reached for every accepted stream.
	TargetAddress string

	// TLSCAFile optionally points at a CA bundle used to verify the relay
	// certificate. When empty, the system trust store is used.
	TLSCAFile string

	// TLSServerName overrides the TLS server name used to verify the relay
	// certificate.
	TLSServerName string

	// TLSInsecureSkipVerify disables relay certificate verification. It is
	// development-only.
	TLSInsecureSkipVerify bool

	// AdminListen enables the optional admin HTTP listener for readiness and
	// metrics when non-empty.
	AdminListen string
}

ClientConfig configures a Bifrost connector runtime.

type ClientOptions

type ClientOptions struct {
	// Logger receives structured runtime logs. A nil logger uses the internal
	// default.
	Logger *slog.Logger

	// Observer receives lifecycle and byte-count events. It may be nil.
	Observer Observer

	// StreamHandler overrides the default TCP target dialer. Embedders can use it
	// to handle accepted streams in-process instead of dialing TargetAddress.
	StreamHandler func(context.Context, net.Conn)

	// AdminReady is called with the admin listener address when AdminListen is
	// enabled and ready.
	AdminReady func(net.Addr)
}

ClientOptions supplies dependencies and callbacks for an embedded connector.

type ConnectionPolicy

type ConnectionPolicy = acceptor.ConnectionPolicy

ConnectionPolicy controls how multiple sessions for the same endpoint key are handled.

type CopyOptions

type CopyOptions struct {
	// BufferSize sets the copy buffer size. A zero or invalid value uses the
	// runtime default.
	BufferSize int

	// IdleTimeout closes the proxy after both directions have been idle for the
	// configured duration. Zero disables idle timeout handling.
	IdleTimeout time.Duration

	// OnBytes is called with the copy direction and byte count for each successful
	// copy chunk.
	OnBytes func(direction string, n int64)
}

CopyOptions tunes Copy.

type Direction added in v0.3.0

type Direction = metrics.Direction

Direction describes the side of a proxied stream that produced bytes.

type Guardrails

type Guardrails struct {
	// MaxSessions limits active connector sessions on the server.
	MaxSessions int

	// MaxStreamsPerSession is the upper bound for a session's concurrent stream
	// limit.
	MaxStreamsPerSession int

	// MaxBandwidthBPSPerSession is the upper bound for a session's byte-per-second
	// bandwidth limit.
	MaxBandwidthBPSPerSession int64

	// MinStreamIdleTimeout is the shortest idle timeout a provider decision may
	// request.
	MinStreamIdleTimeout time.Duration

	// MaxStreamIdleTimeout is the longest idle timeout a provider decision may
	// request.
	MaxStreamIdleTimeout time.Duration

	// MaxHeaders limits the number of hello headers accepted from a connector.
	MaxHeaders int

	// MaxHeaderBytes limits the combined size of hello header names and values.
	MaxHeaderBytes int
}

Guardrails sets server-wide ceilings for sessions and per-session decisions.

Zero fields use the runtime defaults. Guardrails are enforced after provider defaults are applied, so a custom AcceptProvider cannot grant limits above these ceilings.

type Listener added in v0.3.1

type Listener = listener.Spec

Listener describes a server-side listener specification. Use ListenerSpec to build values without depending on the internal listener package path.

func ListenerSpec

func ListenerSpec(network string, address string) Listener

ListenerSpec returns a listener specification for the supplied network and address. Supported networks are "unix" and "tcp"; unknown networks are passed through as a listener type with address set.

type NoopObserver added in v0.3.0

type NoopObserver = metrics.Noop

NoopObserver is an Observer implementation that ignores every event.

type NoopStreamObserver added in v0.3.0

type NoopStreamObserver = metrics.NoopStream

NoopStreamObserver is a StreamObserver implementation that ignores every event.

type Observer added in v0.3.0

type Observer = metrics.Observer

Observer receives tunnel lifecycle and byte-count events.

func NewMultiObserver added in v0.3.0

func NewMultiObserver(observers ...Observer) Observer

NewMultiObserver returns one Observer that fans out events to every non-nil observer in order. It returns a no-op observer when no observers are supplied.

type PassiveLatencyObservation added in v0.4.0

type PassiveLatencyObservation = latency.Observation

PassiveLatencyObservation is the latest passive latency state for one endpoint. It carries only endpoint key, latency milliseconds, observation time, and controlled state.

type PassiveLatencyObserver added in v0.4.0

type PassiveLatencyObserver = latency.Observer

PassiveLatencyObserver receives endpoint-keyed passive session latency observations derived from Bifrost session/mux control traffic.

type PassiveLatencySnapshotter added in v0.4.0

type PassiveLatencySnapshotter = latency.Snapshotter

PassiveLatencySnapshotter exposes endpoint-keyed passive latency state.

type PassiveLatencyState added in v0.4.0

type PassiveLatencyState = latency.State

PassiveLatencyState is the controlled state for a passive latency observation.

const (
	// PassiveLatencyOK means a fresh passive latency observation is available.
	PassiveLatencyOK PassiveLatencyState = latency.StateOK
	// PassiveLatencyUnknown means no passive observation is available for the
	// endpoint.
	PassiveLatencyUnknown PassiveLatencyState = latency.StateUnknown
	// PassiveLatencyStale means the latest passive observation is older than the
	// store's freshness window.
	PassiveLatencyStale PassiveLatencyState = latency.StateStale
)

type PassiveLatencyStore added in v0.4.0

type PassiveLatencyStore = latency.Store

PassiveLatencyStore records latest passive latency observations by endpoint.

func NewPassiveLatencyStore added in v0.4.0

func NewPassiveLatencyStore(staleAfter time.Duration) *PassiveLatencyStore

NewPassiveLatencyStore returns an endpoint-keyed passive latency store. A non-positive staleAfter keeps observations in ok state until replaced.

type PlanLimits

type PlanLimits = limits.PlanLimits

PlanLimits contains per-session stream, bandwidth, and idle-time limits.

type ProxyStreamOptions added in v0.4.1

type ProxyStreamOptions struct {
	// Observer receives byte-count and end events for this proxied stream in
	// addition to the server's configured endpoint observer.
	Observer StreamObserver
}

ProxyStreamOptions configures one server-side proxied stream.

type Runtime

type Runtime struct {
	// HandshakeTimeout bounds TLS and Bifrost hello negotiation work.
	HandshakeTimeout time.Duration

	// StreamCopyBufferBytes sets the copy buffer size used for proxied streams.
	StreamCopyBufferBytes int

	// TunnelKeepAliveInterval is the yamux keepalive ping interval.
	TunnelKeepAliveInterval time.Duration

	// TunnelKeepAliveTimeout is the maximum wait for a keepalive response before
	// closing the tunnel session.
	TunnelKeepAliveTimeout time.Duration
}

Runtime contains low-level tunnel runtime tuning.

Zero fields use the runtime defaults.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is an embedded Bifrost relay runtime.

func NewServer

func NewServer(cfg ServerConfig, opts ServerOptions) (*Server, error)

NewServer validates the server configuration and returns a relay runtime ready to be started with Server.Run.

func (*Server) OpenStream

func (s *Server) OpenStream(ctx context.Context, endpointKey string) (net.Conn, error)

OpenStream opens a new stream to an active endpoint and returns it as a net.Conn. The endpoint must have an admitted connector session.

func (*Server) PassiveLatencyObservation added in v0.4.0

func (s *Server) PassiveLatencyObservation(endpointKey string, now time.Time) PassiveLatencyObservation

PassiveLatencyObservation returns the latest passive latency state for endpointKey. Unknown is returned when no observation exists.

func (*Server) PassiveLatencySnapshot added in v0.4.0

func (s *Server) PassiveLatencySnapshot(now time.Time) []PassiveLatencyObservation

PassiveLatencySnapshot returns latest passive latency state for every observed endpoint.

func (*Server) ProxyStream

func (s *Server) ProxyStream(ctx context.Context, endpointKey string, ingressConn net.Conn) error

ProxyStream attaches ingressConn to a new stream for endpointKey and copies bytes in both directions until either side closes or ctx is canceled.

func (*Server) ProxyStreamWithOptions added in v0.4.1

func (s *Server) ProxyStreamWithOptions(ctx context.Context, endpointKey string, ingressConn net.Conn, opts ProxyStreamOptions) error

ProxyStreamWithOptions attaches ingressConn to a new stream for endpointKey and copies bytes in both directions until either side closes or ctx is canceled.

func (*Server) Run

func (s *Server) Run(ctx context.Context) error

Run starts the server and blocks until ctx is canceled or the server stops with an error.

type ServerConfig

type ServerConfig struct {
	// Listen is the address used by the server when it creates its own connector
	// listener. It defaults to the internal server default when empty.
	Listen string

	// TLSCertFile is the certificate file presented to connector clients when
	// TLSConfig is nil.
	TLSCertFile string

	// TLSKeyFile is the private key file for TLSCertFile when TLSConfig is nil.
	TLSKeyFile string

	// TLSConfig is an optional externally managed TLS configuration. Embedders
	// should include ALPN in NextProtos or use a TLS policy that negotiates ALPN.
	TLSConfig *tls.Config

	// Clients defines token-backed static connector sessions. It is ignored when
	// ServerOptions.AcceptProvider is set.
	Clients []StaticClient

	// Guardrails sets server-wide ceilings enforced after every admission
	// decision.
	Guardrails Guardrails

	// Runtime tunes handshake, copy buffer, and tunnel keepalive behavior.
	Runtime Runtime

	// AdminListen enables the optional admin HTTP listener for readiness and
	// Prometheus-style metrics when non-empty.
	AdminListen string
}

ServerConfig configures a Bifrost relay runtime.

TLSConfig can be supplied by embedders that already manage certificates. When TLSConfig is nil, TLSCertFile and TLSKeyFile are used. Clients configures the built-in static admission provider unless ServerOptions.AcceptProvider is set.

type ServerOptions

type ServerOptions struct {
	// Logger receives structured runtime logs. A nil logger uses the internal
	// default.
	Logger *slog.Logger

	// AcceptProvider overrides ServerConfig.Clients with custom admission logic.
	AcceptProvider AcceptProvider

	// Observer receives lifecycle and byte-count events. It may be nil.
	Observer Observer

	// PassiveLatencyObserver receives endpoint-keyed passive latency
	// observations. It may be nil; the Server still keeps its own latest
	// observation snapshot.
	PassiveLatencyObserver PassiveLatencyObserver

	// Listener supplies an already-created connector listener. When set, Listen in
	// ServerConfig is informational and the server does not create a listener.
	Listener net.Listener

	// Ready is called with the connector listener address after the server is
	// accepting connector sessions.
	Ready func(net.Addr)

	// AdminReady is called with the admin listener address when AdminListen is
	// enabled and ready.
	AdminReady func(net.Addr)
}

ServerOptions supplies dependencies and callbacks for an embedded server.

type StaticClient

type StaticClient = acceptor.StaticClient

StaticClient describes one token-backed connector accepted by NewStaticAcceptProvider.

type StreamObserver added in v0.3.0

type StreamObserver = metrics.StreamObserver

StreamObserver receives byte-count and end events for one proxied stream.

Directories

Path Synopsis
cmd
bifrost-client command
bifrost-server command
bifrostctl command
internal
acceptor
Package acceptor contains Bifrost connector admission interfaces and the static token-backed provider used by standalone deployments.
Package acceptor contains Bifrost connector admission interfaces and the static token-backed provider used by standalone deployments.
latency
Package latency stores endpoint-keyed passive session latency observations.
Package latency stores endpoint-keyed passive session latency observations.
limits
Package limits defines the per-session limits, server guardrails, and small runtime helpers used by the Bifrost tunnel data path.
Package limits defines the per-session limits, server guardrails, and small runtime helpers used by the Bifrost tunnel data path.
metrics
Package metrics defines Bifrost observer interfaces and the in-memory metrics implementation used by the standalone admin endpoint.
Package metrics defines Bifrost observer interfaces and the in-memory metrics implementation used by the standalone admin endpoint.

Jump to

Keyboard shortcuts

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