Documentation
¶
Overview ¶
Package sharedport implements the server half of HTCondor's shared-port connect protocol, routing accepted connections to in-process registrations rather than to other processes.
HTCondor's condor_shared_port multiplexes one TCP port across many daemons: a client dials the port, names a "sock id" with a SHARED_PORT_CONNECT request, and the server hands the connected file descriptor to whichever daemon owns that id (SCM_RIGHTS over a Unix socket). This package speaks the same client-facing protocol but stops short of the fd pass: an id is registered by a caller in this process, and a connection for it is delivered as a net.Conn on a Route that implements net.Listener.
That is enough to serve the case this exists for. A process behind a firewall cannot accept CCB's connection reversal, because the private daemon is told to dial back to an address nothing routes to. It can, however, advertise "<host:port?sock=NAME>" for a port it does own, since every HTCondor connect path honors a shared-port id on any sinful -- including the reverse-connect address a CCB broker relays to the target (see Sock::special_connect in src/condor_io/sock.cpp). One inbound port then serves any number of concurrent reverse connections, each addressed by an unguessable id, with no condor_shared_port daemon and no fd passing.
The protocol on the wire, per SharedPortClient::sendSharedPortID in src/condor_io/shared_port_client.cpp, is a single CEDAR message:
int SHARED_PORT_CONNECT (75) string shared port id string client name (debugging only) int deadline, in seconds remaining, or -1 for none int more_args, followed by that many strings to ignore
There is no reply. The client then resets its stream and speaks whatever protocol the target daemon expects, so the connection handed to a Route is positioned at the first byte after the request and must be wrapped in a fresh stream.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrDuplicateID = errors.New("sharedport: shared port id already registered")
ErrDuplicateID is returned by Register when the id is already registered.
var ErrServerClosed = errors.New("sharedport: server closed")
ErrServerClosed is returned by Serve after Close, and by Register once the server is closed.
Functions ¶
This section is empty.
Types ¶
type Options ¶
type Options struct {
// AdvertisedAddr is the "host:port" a peer dials to reach this server. It
// is the only thing a Route's sinful can be built from, and it is not
// always the bound address: behind NAT, a container port mapping, or a
// Kubernetes Service, what peers dial is not what this process bound.
//
// It may be left empty only when the listener is bound to a specific
// address (not the wildcard), in which case the bound address is used.
// Serve fails rather than guessing otherwise -- an unroutable advertised
// address fails later, at the far end, as a connection that never arrives.
AdvertisedAddr string
// HandshakeTimeout bounds one SHARED_PORT_CONNECT exchange (default 20s).
HandshakeTimeout time.Duration
// DeliveryTimeout bounds how long a routed connection waits to be accepted
// by its Route (default 30s).
DeliveryTimeout time.Duration
// MaxPending bounds concurrent in-flight handshakes (default 256).
// Connections beyond it are closed immediately.
MaxPending int
// Logger receives protocol diagnostics. Defaults to slog.Default().
Logger *slog.Logger
}
Options configures a Server.
type Route ¶
type Route struct {
// contains filtered or unexported fields
}
Route is one registered shared-port id. It implements net.Listener, so a caller that would otherwise open its own inbound socket -- notably a CCB reverse-connect dial -- can take one of these instead and be reached through the shared port rather than through a port of its own.
Close unregisters the id and fails any pending Accept. Connections already returned by Accept are unaffected; they belong to the caller.
func (*Route) Accept ¶
Accept returns the next connection routed to this id. The connection is positioned immediately after the peer's SHARED_PORT_CONNECT request, so it must be wrapped in a fresh CEDAR stream -- the shared-port exchange is not part of whatever protocol follows, and the peer has already reset its own stream for the same reason.
func (*Route) Close ¶
Close unregisters this id and unblocks Accept. Undelivered connections still queued on the Route are closed; nothing else will ever accept them.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server routes connections arriving on one TCP port to in-process registrations keyed by shared-port id.
func Listen ¶
Listen binds addr, starts routing on it in the background, and returns the running Server. Close stops it.
func (*Server) AdvertisedAddr ¶
AdvertisedAddr is the "host:port" peers dial to reach this server, as resolved by Listen or Serve. Empty before either has run.
func (*Server) Close ¶
Close stops the server, unblocks Serve, and unregisters every Route. It waits for in-flight handshakes to finish. Connections already delivered to a Route are left alone: they belong to whoever accepted them.
func (*Server) Register ¶
Register claims a shared-port id and returns the Route that receives connections addressed to it. An empty id generates a random one, which is what a per-dial registration wants: the id travels to the peer inside the advertised sinful and is the only thing distinguishing one caller's inbound connection from another's, so it should not be guessable.
The caller must Close the Route when done, which unregisters the id.
type Stats ¶
type Stats struct {
// Accepted is connections accepted on the listener.
Accepted uint64
// Routed is connections successfully delivered to a Route.
Routed uint64
// UnknownID is requests naming an id nobody has registered. Expected in
// small numbers (a dial that races a Route's Close); a steady stream means
// a stale advertised address or a port scan.
UnknownID uint64
// BadRequest is connections that failed the handshake: not a
// SHARED_PORT_CONNECT, malformed, or too slow.
BadRequest uint64
// Undelivered is connections for a registered id that no one accepted
// within DeliveryTimeout.
Undelivered uint64
// Overloaded is connections closed without a handshake because MaxPending
// in-flight handshakes were already running.
Overloaded uint64
}
Stats counts what the router has done, for logging and for tests that need to prove a connection actually took this path.