server

package
v0.0.0-...-54f22b9 Latest Latest
Warning

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

Go to latest
Published: Aug 24, 2026 License: MIT Imports: 35 Imported by: 0

Documentation

Overview

Package server provides cache management API handlers.

Package server provides the REST API for Bifrost server.

Index

Constants

View Source
const (
	SectionServer        = "server"
	SectionBackends      = "backends"
	SectionRoutes        = "routes"
	SectionAuth          = "auth"
	SectionRateLimit     = "rate_limit"
	SectionAccessControl = "access_control"
	SectionAccessLog     = "access_log"
	SectionMetrics       = "metrics"
	SectionLogging       = "logging"
	SectionWebUI         = "web_ui"
	SectionAPI           = "api"
	SectionHealthCheck   = "health_check"
	SectionAutoUpdate    = "auto_update"
	SectionCache         = "cache"
	SectionNetwork       = "network"
	SectionSession       = "session"
	SectionMITM          = "mitm"
	SectionMesh          = "mesh"
)

Config section names. These are the canonical identifiers shared by the save/validate responses, the /config/meta endpoint and the Web UI, and they match the `yaml`/`json` tags of the corresponding config.ServerConfig fields. Every top-level field of config.ServerConfig must have a constant here — TestConfigSectionsCoverServerConfig enforces that.

View Source
const (
	EventBackendHealth   = "backend.health"
	EventConnectionNew   = "connection.new"
	EventConnectionClose = "connection.close"
	EventConfigReload    = "config.reload"
	EventStats           = "stats.update"
)

Event types for WebSocket broadcasts

View Source
const EventConfigSaved = "config.saved"

EventConfigSaved is broadcast when config is saved.

View Source
const MaxWebSocketClients = 100

MaxWebSocketClients is the maximum number of concurrent WebSocket connections.

View Source
const WebSocketPingInterval = 20 * time.Second

WebSocketPingInterval is how often the server sends a protocol-level ping to prove the peer is alive. Must be well under WebSocketReadTimeout so a healthy but idle connection is refreshed before the read deadline expires.

View Source
const WebSocketReadTimeout = 60 * time.Second

WebSocketReadTimeout bounds how long a connection may go without ANY traffic (application message, or a pong in reply to our keepalive ping) before it is considered dead. Keep it comfortably above WebSocketPingInterval.

View Source
const WebSocketWriteTimeout = 5 * time.Second

WebSocketWriteTimeout bounds a single broadcast write to one client, so one slow reader cannot stall the hub.

Variables

View Source
var (
	ErrNetworkExists   = errors.New("network already exists")
	ErrNetworkNotFound = errors.New("network not found")
)

Common errors

Functions

func StaticHandler

func StaticHandler() http.Handler

StaticHandler returns a handler for serving the embedded static files.

Types

type API

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

API provides the REST API for Bifrost server.

func New

func New(cfg Config) *API

New creates a new API server.

func (*API) ConnectionTracker

func (a *API) ConnectionTracker() *ConnectionTracker

ConnectionTracker returns the connection tracker for tracking active connections.

func (*API) RequestLog

func (a *API) RequestLog() *RequestLog

RequestLog returns the request log for adding entries.

func (*API) Router

func (a *API) Router() http.Handler

Router returns the HTTP router for the API without the WebSocket endpoint, the PAC endpoints or the static Web UI. The server binary uses RouterWithWebSocket; this variant exists for embedders that mount only the REST surface.

func (*API) RouterWithWebSocket

func (a *API) RouterWithWebSocket(hub *WebSocketHub) http.Handler

RouterWithWebSocket returns a router with WebSocket and static file support.

type BackendHealthEvent

type BackendHealthEvent struct {
	Name    string `json:"name"`
	Healthy bool   `json:"healthy"`
}

BackendHealthEvent represents a backend health change event.

type CacheAPI

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

CacheAPI provides cache management endpoints.

func NewCacheAPI

func NewCacheAPI(manager *cache.Manager) *CacheAPI

NewCacheAPI creates a new cache API handler.

func (*CacheAPI) RegisterRoutes

func (c *CacheAPI) RegisterRoutes(r chi.Router)

RegisterRoutes registers cache API routes on the given router.

type ClientSummary

type ClientSummary struct {
	ClientIP    string    `json:"client_ip"`
	Connections int       `json:"connections"`
	BytesSent   int64     `json:"bytes_sent"`
	BytesRecv   int64     `json:"bytes_recv"`
	FirstSeen   time.Time `json:"first_seen"`
}

ClientSummary summarizes connections from a single client.

type Config

type Config struct {
	Backends         *backend.Manager
	HealthManager    *health.Manager
	CacheManager     *cache.Manager
	Token            string
	GetConfig        func() interface{}               // Returns sanitized config
	GetFullConfig    func() *config.ServerConfig      // Returns full config for editing
	ReloadConfig     func() error                     // Triggers config reload
	SaveConfig       func(*config.ServerConfig) error // Saves config to file
	ConfigPath       string                           // Path to config file
	ProxyHost        string                           // Proxy host for PAC file
	ProxyPort        string                           // HTTP proxy port for PAC file
	SOCKS5Port       string                           // SOCKS5 port for PAC file
	EnableRequestLog bool                             // Enable request logging
	RequestLogSize   int                              // Max requests to keep
	// SessionManager, when non-nil AND Token is set, enables the token-exchange
	// login/cookie flow (see API.sessionManager). The caller (server) owns the
	// manager's lifecycle and must Close it on shutdown. Ignored when Token is
	// empty because the API is unauthenticated in that case and sessions would
	// gate nothing.
	SessionManager *session.Manager

	// Mesh configures the mesh coordinator API. When Mesh.Enabled is false the
	// /api/v1/mesh routes are not mounted at all. Mesh.StatePath, when set,
	// persists coordinator networks and peers across restarts.
	Mesh config.MeshConfig
}

Config holds API configuration.

type ConfigMeta

type ConfigMeta struct {
	Section       string `json:"section"`
	HotReloadable bool   `json:"hot_reloadable"`
	Description   string `json:"description"`
}

ConfigMeta describes which config sections are hot-reloadable.

type ConfigSaveRequest

type ConfigSaveRequest struct {
	Config       config.ServerConfig `json:"config"`
	CreateBackup bool                `json:"create_backup"`
}

ConfigSaveRequest represents a config save request.

type ConfigSaveResponse

type ConfigSaveResponse struct {
	Success    bool   `json:"success"`
	Message    string `json:"message"`
	BackupPath string `json:"backup_path,omitempty"`
	// RequiresRestart is true when at least one changed section cannot be
	// applied by ReloadConfig, i.e. RestartRequiredSections is non-empty.
	RequiresRestart bool     `json:"requires_restart"`
	ChangedSections []string `json:"changed_sections"`
	// HotReloadedSections lists the changed sections that were applied to the
	// running server without a restart. Populated so the Web UI never has to
	// re-derive hot-reloadability client-side (where it can drift).
	HotReloadedSections []string `json:"hot_reloaded_sections"`
	// RestartRequiredSections lists the changed sections that were written to
	// disk but only take effect after a server restart.
	RestartRequiredSections []string          `json:"restart_required_sections"`
	Errors                  []ValidationError `json:"errors,omitempty"`
}

ConfigSaveResponse represents the response after saving config.

type Connection

type Connection struct {
	ID         string    `json:"id"`
	ClientIP   string    `json:"client_ip"`
	ClientPort string    `json:"client_port"`
	Host       string    `json:"host"`
	Backend    string    `json:"backend"`
	Protocol   string    `json:"protocol"` // HTTP, SOCKS5, CONNECT
	StartTime  time.Time `json:"start_time"`
	BytesSent  int64     `json:"bytes_sent"`
	BytesRecv  int64     `json:"bytes_recv"`
}

Connection represents an active proxy connection.

type ConnectionEvent

type ConnectionEvent struct {
	Protocol string `json:"protocol"`
	Host     string `json:"host"`
	Backend  string `json:"backend"`
	ClientIP string `json:"client_ip"`
}

ConnectionEvent represents a connection event.

type ConnectionTracker

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

ConnectionTracker tracks active proxy connections.

func NewConnectionTracker

func NewConnectionTracker() *ConnectionTracker

NewConnectionTracker creates a new connection tracker.

func (*ConnectionTracker) Add

func (t *ConnectionTracker) Add(clientIP, clientPort, host, backend, protocol string) string

Add adds a new connection and returns its ID.

func (*ConnectionTracker) Count

func (t *ConnectionTracker) Count() int

Count returns the number of active connections.

func (*ConnectionTracker) Get

func (t *ConnectionTracker) Get(id string) (Connection, bool)

Get returns a snapshot of the tracked connection and true when it exists.

func (*ConnectionTracker) GetAll

func (t *ConnectionTracker) GetAll() []Connection

GetAll returns all active connections.

func (*ConnectionTracker) GetByClient

func (t *ConnectionTracker) GetByClient(clientIP string) []Connection

GetByClient returns connections for a specific client IP.

func (*ConnectionTracker) GetUniqueClients

func (t *ConnectionTracker) GetUniqueClients() []ClientSummary

GetUniqueClients returns a list of unique client IPs with connection counts.

func (*ConnectionTracker) Remove

func (t *ConnectionTracker) Remove(id string)

Remove removes a connection by ID.

func (*ConnectionTracker) SetDestination

func (t *ConnectionTracker) SetDestination(id, host, backend string) (Connection, bool)

SetDestination sets the destination host and backend for a tracked connection once they are known (after CONNECT / backend selection). It returns a snapshot of the updated connection and true when the connection is still tracked, or a zero Connection and false otherwise.

func (*ConnectionTracker) UpdateBytes

func (t *ConnectionTracker) UpdateBytes(id string, sent, recv int64)

UpdateBytes updates the byte counters for a connection.

type HostCount

type HostCount struct {
	Host  string `json:"host"`
	Count int    `json:"count"`
}

HostCount is a single entry of RequestLogStats.TopHosts.

type MeshAPI

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

MeshAPI provides the REST API for mesh network management.

func NewMeshAPI

func NewMeshAPI() *MeshAPI

NewMeshAPI creates an in-memory mesh API handler with no persistence.

func NewMeshAPIWithConfig

func NewMeshAPIWithConfig(cfg config.MeshConfig) (*MeshAPI, error)

NewMeshAPIWithConfig creates a mesh API handler from the server's mesh config. When cfg.StatePath is set, previously persisted networks and peers are restored and every subsequent mutation is written back. A restore failure is returned so the caller can decide whether to surface it; the returned handler is always usable (it simply starts empty).

func (*MeshAPI) CreateNetwork

func (m *MeshAPI) CreateNetwork(id, name, cidr string) (*MeshNetwork, error)

CreateNetwork creates a mesh network programmatically.

func (*MeshAPI) GetNetwork

func (m *MeshAPI) GetNetwork(id string) (*MeshNetwork, bool)

GetNetwork returns a mesh network by ID.

func (*MeshAPI) RegisterRoutes

func (m *MeshAPI) RegisterRoutes(r chi.Router)

RegisterRoutes registers mesh API routes on a chi router.

type MeshNetwork

type MeshNetwork struct {
	ID      string    `json:"id"`
	Name    string    `json:"name"`
	CIDR    string    `json:"cidr"`
	Created time.Time `json:"created"`
	// contains filtered or unexported fields
}

MeshNetwork represents a single mesh network.

type PACGenerator

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

PACGenerator generates PAC (Proxy Auto-Configuration) files.

func NewPACGenerator

func NewPACGenerator(getConfig func() *config.ServerConfig, proxyHost, proxyPort, socks5Port string) *PACGenerator

NewPACGenerator creates a new PAC generator.

func (*PACGenerator) Generate

func (p *PACGenerator) Generate(requestHost string) string

Generate creates a PAC file based on the current routes configuration.

func (*PACGenerator) HandlePAC

func (p *PACGenerator) HandlePAC(w http.ResponseWriter, r *http.Request)

HandlePAC serves the PAC file.

type RequestLog

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

RequestLog maintains a ring buffer of recent requests.

func NewRequestLog

func NewRequestLog(maxSize int, enabled bool) *RequestLog

NewRequestLog creates a new request log with the given max size.

func (*RequestLog) Add

func (r *RequestLog) Add(entry RequestLogEntry)

Add adds a new entry to the request log.

func (*RequestLog) Clear

func (r *RequestLog) Clear()

Clear removes all entries and resets the running totals reported by Stats. Entry IDs keep increasing so that GetSince cursors held by clients stay valid across a clear.

func (*RequestLog) GetAll

func (r *RequestLog) GetAll() []RequestLogEntry

GetAll returns all entries.

func (*RequestLog) GetRecent

func (r *RequestLog) GetRecent(n int) []RequestLogEntry

GetRecent returns the most recent n entries.

func (*RequestLog) GetSince

func (r *RequestLog) GetSince(sinceID int64) []RequestLogEntry

GetSince returns entries since the given ID.

func (*RequestLog) IsEnabled

func (r *RequestLog) IsEnabled() bool

IsEnabled returns whether request logging is enabled.

func (*RequestLog) SetEnabled

func (r *RequestLog) SetEnabled(enabled bool)

SetEnabled enables or disables request logging.

func (*RequestLog) Stats

func (r *RequestLog) Stats() RequestLogStats

Stats returns statistics about the request log. See RequestLogStats for the scope of each field.

type RequestLogEntry

type RequestLogEntry struct {
	ID         int64     `json:"id"`
	Timestamp  time.Time `json:"timestamp"`
	Method     string    `json:"method"`
	Host       string    `json:"host"`
	Path       string    `json:"path"`
	URL        string    `json:"url"`
	UserAgent  string    `json:"user_agent"`
	ClientIP   string    `json:"client_ip"`
	Username   string    `json:"username,omitempty"`
	Backend    string    `json:"backend"`
	StatusCode int       `json:"status_code"`
	BytesSent  int64     `json:"bytes_sent"`
	BytesRecv  int64     `json:"bytes_recv"`
	Duration   int64     `json:"duration_ms"`
	Error      string    `json:"error,omitempty"`
	Protocol   string    `json:"protocol"` // HTTP, SOCKS5, CONNECT
}

RequestLogEntry represents a single request log entry.

type RequestLogStats

type RequestLogStats struct {
	Enabled          bool           `json:"enabled"`
	Count            int            `json:"count"`
	MaxSize          int            `json:"max_size"`
	TotalRequests    int64          `json:"total_requests"`
	TotalBytesSent   int64          `json:"total_bytes_sent"`
	TotalBytesRecv   int64          `json:"total_bytes_recv"`
	RequestsByMethod map[string]int `json:"requests_by_method"`
	RequestsByStatus map[string]int `json:"requests_by_status"`
	TopHosts         []HostCount    `json:"top_hosts"`
}

RequestLogStats is the payload of GET /api/v1/requests/stats.

Two different scopes are mixed here deliberately, because the ring buffer cannot answer both questions from the same data:

  • TotalRequests, TotalBytesSent and TotalBytesRecv are running totals accumulated on every Add since the process started or since the last Clear. They survive ring buffer eviction.
  • Count, RequestsByMethod, RequestsByStatus and TopHosts describe only the entries still retained in the buffer (at most MaxSize of them), because evicted entries no longer exist.

Every field is always present, and the map/slice fields are never nil, so the Web UI can render them without null checks.

type StatsEvent

type StatsEvent struct {
	ActiveConnections int64 `json:"active_connections"`
	TotalConnections  int64 `json:"total_connections"`
	BytesSent         int64 `json:"bytes_sent"`
	BytesReceived     int64 `json:"bytes_received"`
}

StatsEvent represents a stats update event.

type ValidationError

type ValidationError struct {
	Section string `json:"section"`
	Field   string `json:"field,omitempty"`
	Message string `json:"message"`
}

ValidationError represents a config validation error.

type WebSocketHub

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

WebSocketHub manages WebSocket connections.

func NewWebSocketHub

func NewWebSocketHub() *WebSocketHub

NewWebSocketHub creates a new WebSocket hub with default max clients.

func NewWebSocketHubWithMaxClients

func NewWebSocketHubWithMaxClients(maxClients int) *WebSocketHub

NewWebSocketHubWithMaxClients creates a new WebSocket hub with a custom max clients limit. For low-power devices (OpenWrt routers), use 5-10 to reduce memory usage.

func (*WebSocketHub) Broadcast

func (h *WebSocketHub) Broadcast(eventType string, data interface{})

Broadcast sends a message to all connected clients.

func (*WebSocketHub) Run

func (h *WebSocketHub) Run()

Run starts the hub's main loop. Call Stop() to terminate the loop.

func (*WebSocketHub) ServeHTTP

func (h *WebSocketHub) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP upgrades the request and services the connection until it dies.

⚠ This used to use golang.org/x/net/websocket, which that package's own docs describe as having "limited support for pings, pongs and close frames". Any peer that sent a protocol-level ping — notably the Home Assistant Ingress proxy, and every browser-side keepalive — desynchronised the frame stream and the client aborted with "RSV1 set / reserved bits must be 0". The old code also only understood a literal text message "ping", which no standard client sends. Migrated to github.com/coder/websocket (the successor x/net/websocket itself points at), which handles control frames in the library.

Origin enforcement: WebSockets are exempt from both the same-origin policy and CORS, so without an Origin check any web page loaded in a browser that can reach this server could open a socket and read the live traffic stream — and when no api.token is configured this route has no auth either. Requests whose Origin host matches the request Host are always accepted (the dashboard this server serves); anything else must be named in api.allowed_origins. Requests with no Origin header at all are accepted, because non-browser clients (the CLI, curl, integration tests) do not send one and are not subject to the browser-driven attack this check defends against.

func (*WebSocketHub) SetAllowedOrigins

func (h *WebSocketHub) SetAllowedOrigins(origins []string)

func (*WebSocketHub) SkipsOriginCheck

func (h *WebSocketHub) SkipsOriginCheck() bool

SkipsOriginCheck reports whether origin verification has been disabled via the "*" wildcard, so the server can log that fact at startup.

func (*WebSocketHub) Stop

func (h *WebSocketHub) Stop()

Stop signals the hub to stop and close all connections.

Jump to

Keyboard shortcuts

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