boxapi

package
v0.1.17 Latest Latest
Warning

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

Go to latest
Published: Jul 12, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package boxapi serves the box-facing port-publishing API: a small HTTP/JSON API on a per-box unix socket that lets the workload inside a box open, close, and list public URLs for ports of ITS OWN box only. The handler is bound to one box's identity at construction — the request body carries only a port and description, never a box or spoke identity — so whichever channel a request arrives on decides which box it acts on. The socket is deliberately a unix socket (not TCP) so the box's own proxy data path, which only dials TCP ports, can never publish the control API itself.

The spoke serves the socket host-side for Docker boxes (in the per-box bind-mount dir, so it appears in-box at /run/llmbox/boxapi.sock); for Firecracker the same handler listens on the per-VM host UDS that the guest guest bridges the in-guest socket to. Either way the listener — and thus the enforcement — lives outside the sandbox.

Index

Constants

View Source
const SocketName = "boxapi.sock"

SocketName is the file name of the box-port API socket: the in-box path is /run/llmbox/boxapi.sock (next to the guest's control.sock), and the Docker host side is the same name inside the box's private socket dir.

Variables

This section is empty.

Functions

func NewHandler

func NewHandler(boxID string, svc PortService) http.Handler

NewHandler returns the box-port HTTP API for one box. boxID is the identity every forwarded request is stamped with; it is fixed at construction so nothing the box sends can change it. A box created without a box ID gets a handler whose every call fails with a clear explanation.

Routes (all POST, JSON in/out; errors are {"error":"..."} with status 400 on a bad request and 502 when the hub rejects or is unreachable):

POST /v1/open_port  {"port":3000,"description":"..."} → {"port":{"port":3000,"url":"https://...","description":"..."}}
POST /v1/close_port {"port":3000}                     → {}
POST /v1/list_ports {}                                → {"ports":[...]}

@arg boxID The identity of the box this handler serves; "" yields a handler that only explains the box cannot publish ports. @arg svc The service forwarding requests to the hub. @return http.Handler The box-port API for that one box.

@testcase TestBoxAPIOpenPort opens a port and sees the bound box ID stamped. @testcase TestBoxAPIClosePort closes a port through the handler. @testcase TestBoxAPIListPorts lists ports through the handler. @testcase TestBoxAPIRejectsBadRequest rejects malformed JSON and out-of-range ports. @testcase TestBoxAPIServiceError maps a service failure to a 502 with the error body. @testcase TestBoxAPINoBoxID explains that a box without a box ID cannot publish ports.

Types

type PortService

type PortService interface {
	// OpenBoxPort publishes a box port and returns its public view.
	OpenBoxPort(ctx context.Context, boxID string, port int, description string) (cluster.BoxPortInfo, error)
	// CloseBoxPort unpublishes a box port.
	CloseBoxPort(ctx context.Context, boxID string, port int) error
	// ListBoxPorts returns the box's published ports.
	ListBoxPorts(ctx context.Context, boxID string) ([]cluster.BoxPortInfo, error)
}

PortService is what the box-port API needs from the spoke: forward a request, stamped with the originating box's identity, to the hub. *cluster.HubCaller satisfies it.

type Server

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

Server serves the box-port API for one box on a unix socket.

func ServeUnix

func ServeUnix(path, boxID string, svc PortService, log *slog.Logger) (*Server, error)

ServeUnix creates (replacing any stale socket file) and serves the box-port API for one box at path, returning immediately; the caller stops it with Close. The socket is made group/other-accessible for the same reason as the guest control socket travelling the other way: across the container boundary the connecting peer runs as a different uid, and connect() needs write permission on the socket — the 0700 parent directory (the box's private socket dir) remains the access gate on the host side.

@arg path The filesystem path of the socket to create. @arg boxID The identity of the box this socket serves; every request is stamped with it. @arg svc The service forwarding requests to the hub. @arg log Logger for serve-loop failures; nil uses slog.Default. @return *Server The serving listener; the caller must Close it. @error error if the socket cannot be created or its mode cannot be set.

@testcase TestBoxAPIOpenPort serves requests over a socket created by ServeUnix. @testcase TestServeUnixReplacesStaleSocket replaces a stale socket file at the same path.

func (*Server) Close

func (s *Server) Close() error

Close stops the server, closing the listener and any in-flight connections. The listener is also closed directly: http.Server.Close only closes listeners Serve has already registered, and the serving goroutine may not have been scheduled yet when Close runs — without the direct close, the socket would keep accepting until that goroutine finally starts. Closing more than once is harmless.

@error error if closing the underlying server fails.

@testcase TestServeUnixCloseStopsServing refuses connections after Close, even when the serve goroutine had not started.

Jump to

Keyboard shortcuts

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