Documentation
¶
Overview ¶
Package tunnel is the link between fuse-proxy and the sidecar inside a guest.
the guest dials OUT to the proxy and holds one quic connection open. every tcp connection a client makes to the proxy becomes one quic stream that the proxy opens toward the guest, and the sidecar connects that stream to a port on the guest's loopback. nothing ever dials in to the guest, so where the guest is running never appears in anything a client holds.
quic is here for one property: a connection is identified by a connection id, not by the address it comes from. when the guest's packets start arriving from somewhere else (it was resumed on another host, the host's nat rebound, the link dropped for a while), the proxy validates the new path and carries on, and whatever was lost in between is retransmitted. every open stream survives that. the alternative was a tcp tunnel plus a hand-written protocol for resuming streams across reconnects, which is a reimplementation of the part of quic that already works.
Index ¶
Constants ¶
const ServerName = "fuse-proxy"
ServerName is the name the proxy's certificate carries, and the name a guest verifies it against.
it is a fixed internal name rather than the proxy's address on purpose. the proxy is reached by whatever ip or dns name the deployment gives it, and that is not what a guest is checking: the question is "is this the certificate the orchestrator told me about", which the root pool below already answers. a constant keeps that from turning into a hostname the operator has to keep in step with the certificate.
Variables ¶
var ErrNoTunnel = errors.New("tunnel: guest is not connected")
ErrNoTunnel means the owner's guest is not connected right now.
Functions ¶
func CertPEM ¶
func CertPEM(cert tls.Certificate) (string, error)
CertPEM encodes cert for a Config to carry.
func LoadOrCreateCert ¶
func LoadOrCreateCert(certPath, keyPath string) (tls.Certificate, error)
LoadOrCreateCert returns the proxy's certificate, creating and saving a self-signed one on first use.
it has to be stable across restarts: every guest pins this exact certificate, so a proxy that minted a new one each start would lock out every running guest until the orchestrator rewrote its tunnel.json. it is therefore long-lived, and carries a fixed internal name rather than the deployment's own, which may change.
func Pipe ¶
Pipe copies both ways until both directions are done, passing a half close through so a peer that sends then waits for a reply still gets one.
func Run ¶
Run is the sidecar: it keeps one connection to the proxy up for as long as ctx lives, and connects every stream the proxy opens to a loopback port.
it returns only when ctx is done. everything else is retried, because the process this runs in is the thing that has to outlive fused restarts, network outages and the guest being moved.
Types ¶
type Authenticator ¶
Authenticator reports whether token is the current credential for owner.
type Config ¶
type Config struct {
// ProxyAddr is the proxy's quic listener, host:port.
ProxyAddr string `json:"proxy_addr"`
// ServerCertPEM is the proxy's certificate, and the only one this guest
// will accept. the proxy's cert is self-signed, so this IS the trust
// anchor: it is used as the sole root, which makes trusting it exactly
// equivalent to pinning it, with ordinary chain and name verification
// doing the work rather than a bypass.
ServerCertPEM string `json:"server_cert_pem"`
// Owner names the guest to the proxy; Token proves it.
Owner string `json:"owner"`
Token string `json:"token"`
// Ports are the only loopback ports the sidecar will connect a stream to.
// the proxy is trusted to route, not to reach sshd.
Ports []int `json:"ports"`
}
Config is what the sidecar needs to find the proxy and prove who it is. it is written into the guest as /fuse/tunnel.json by the orchestrator.
type Metrics ¶ added in v0.36.0
type Metrics interface {
// Handshake reports how a guest's hello ended: "accepted",
// "unauthorized", "duplicate" (a live guest already holds the owner) or
// "bad_hello". no owner is passed, because before it is authenticated the
// owner is whatever the dialer chose to write.
Handshake(result string)
// GuestConnected reports an accepted guest. replaced is true when it took
// the place of an established guest that failed its liveness probe.
GuestConnected(owner string, replaced bool)
// GuestsConnected is the number of registered guests after any change.
GuestsConnected(n int)
// Probe reports a liveness probe of an established guest, run when a
// second guest claims its identity (the clone-identity check).
Probe(alive bool)
// PathMigrated reports that a registered guest's packets now arrive from
// a new address on the same connection: the guest moved and every stream
// it carried survived, with no new handshake.
PathMigrated(owner string)
}
Metrics receives what the server observes. it is an interface so this package, which the guest sidecar also links, carries no metrics library.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the proxy's end: it accepts guests and opens streams toward them.
func Listen ¶
func Listen(conn net.PacketConn, cert tls.Certificate, auth Authenticator, logger *slog.Logger) (*Server, error)
Listen starts accepting guests on conn.
func (*Server) Drop ¶
Drop disconnects owner's guest, if any. used when the owner is deleted or its token is replaced, so a revoked credential stops carrying traffic now rather than whenever the connection next ends.
func (*Server) Open ¶
Open connects to port on owner's guest, waiting until ctx is done for the guest to be there and to answer.
the waiting is the point. between a migrate repointing a route and the new guest's sidecar dialing in there is a moment with no guest at all, and a paused guest is registered but silent. a client that connects in either window is held rather than refused, and finds a working connection once the guest is back.
func (*Server) SetMetrics ¶ added in v0.36.0
SetMetrics installs m. call it before Serve.