rtunnels

package
v1.7.7 Latest Latest
Warning

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

Go to latest
Published: Sep 3, 2026 License: GPL-3.0 Imports: 16 Imported by: 0

README

server/core/rtunnels

Overview

Reverse tunnel coordination within the server core. Handles tunnel registration, server-owned reverse-port-forward authorization, bounded outbound connection brokering, and multiplexing.

Reverse port forward destinations are immutable operator inputs stored in a Registry. Implant messages carry an opaque AuthorizationID; only Broker may turn that ID into an outbound TCP connection. Legacy implant addresses are accepted solely as canonical registry lookup keys and are never dialed directly.

Go Files

  • authorization.go – Owns authorization lifecycle and server-authoritative metadata.
  • broker.go – Opens bounded, revocation-aware connections from stored dial plans.
  • rtunnels.go – Manages active reverse tunnel state and lifecycle cleanup.

Documentation

Overview

Package rtunnels owns reverse-port-forward authorization and relay state.

Index

Constants

View Source
const DefaultDialTimeout = 10 * time.Second

DefaultDialTimeout bounds an authorized reverse-port-forward connection attempt.

Variables

View Source
var (
	ErrInvalidSessionID             = errors.New("invalid reverse port forward session ID")
	ErrInvalidForwardAddress        = errors.New("invalid reverse port forward address")
	ErrUnknownAuthorization         = errors.New("unknown reverse port forward authorization")
	ErrAuthorizationSession         = errors.New("reverse port forward authorization belongs to another session")
	ErrAuthorizationRevoked         = errors.New("reverse port forward authorization is revoked")
	ErrAuthorizationActive          = errors.New("reverse port forward authorization is already active")
	ErrAuthorizationIDRequired      = errors.New("reverse port forward authorization ID is required")
	ErrAuthorizationConnectionLimit = errors.New("reverse port forward authorization connection limit reached")
	ErrSessionConnectionLimit       = errors.New("reverse port forward session connection limit reached")
	ErrGlobalConnectionLimit        = errors.New("reverse port forward global connection limit reached")
	ErrAuthorizationReservation     = errors.New("invalid reverse port forward connection reservation")
	ErrDuplicateListenerID          = errors.New("reverse port forward listener ID is already registered")
	ErrAuthorizationIDGeneration    = errors.New("failed to generate a unique reverse port forward authorization ID")
)

ErrInvalidSessionID and the related errors report rejected authorizations.

View Source
var (
	DefaultRegistry = NewRegistry()
	DefaultBroker   = NewBroker(DefaultRegistry, nil, DefaultDialTimeout)
)

DefaultRegistry and DefaultBroker are the production reverse port forward authorization components. Tests should prefer instance-owned registries.

View Source
var (
	ErrReverseTunnelFrameTooLarge = errors.New("reverse tunnel frame exceeds the size limit")
	ErrReverseTunnelWindow        = errors.New("reverse tunnel sequence exceeds the pending window")
	ErrReverseTunnelPendingBytes  = errors.New("reverse tunnel pending data exceeds the byte limit")
	ErrReverseTunnelClosed        = errors.New("reverse tunnel is closed")
	ErrReverseTunnelIngressLimit  = errors.New("reverse tunnel inbound concurrency limit reached")
	ErrReverseTunnelAuthBudget    = errors.New("reverse tunnel authorization pending-data budget reached")
	ErrReverseTunnelSessionBudget = errors.New("reverse tunnel session pending-data budget reached")
	ErrReverseTunnelGlobalBudget  = errors.New("reverse tunnel global pending-data budget reached")
	ErrReverseTunnelTerminal      = errors.New("reverse tunnel terminal sequence is invalid")
)

ErrReverseTunnelFrameTooLarge and the related errors report rejected relay operations.

View Source
var ErrDuplicateTunnelID = errors.New("reverse tunnel ID is already registered")

ErrDuplicateTunnelID rejects reuse of an active reverse tunnel ID.

View Source
var ErrNilDialConnection = errors.New("reverse port forward dialer returned a nil connection")

ErrNilDialConnection rejects dialers that return success without a connection.

Functions

func CloseAuthorization added in v1.7.7

func CloseAuthorization(sessionID string, authorizationID AuthorizationID) int

CloseAuthorization atomically detaches and closes active relays created by a specific authorization. It never closes another session's tunnel.

func CloseLocalIfActive added in v1.7.7

func CloseLocalIfActive(tunnel *RTunnel) (bool, error)

CloseLocalIfActive selects local-close semantics only while this exact generation is still published. The terminal state is established before the registry entry disappears, so retained writers cannot enqueue after detach.

func CloseRemoteIfActive added in v1.7.7

func CloseRemoteIfActive(tunnel *RTunnel) bool

CloseRemoteIfActive closes an exact peer-terminated generation without an echo and removes it only after its closed state is visible to retained work.

func CloseSession added in v1.7.7

func CloseSession(sessionID string) int

CloseSession atomically detaches and closes only tunnels owned by sessionID. It is safe to call repeatedly during disconnect cleanup.

func RemoveRTunnelIf added in v1.7.7

func RemoveRTunnelIf(id uint64, expected *RTunnel) bool

RemoveRTunnelIf removes ID only when it still points to expected. This keeps stale cleanup from deleting a different tunnel that reused the same ID.

func TryAddRTunnel added in v1.7.7

func TryAddRTunnel(tun *RTunnel) bool

TryAddRTunnel atomically rejects duplicate tunnel IDs instead of replacing and orphaning an existing tunnel.

Types

type Authorization added in v1.7.7

type Authorization struct {
	AuthorizationID         AuthorizationID
	SessionID               string
	BindAddress             string
	Address                 string
	KeepAlive               int32
	State                   AuthorizationState
	ImplantListenerID       uint32
	HasListenerID           bool
	RequiresAuthorizationID bool
}

Authorization is an immutable snapshot of a server-owned reverse port forward authorization. Mutating a snapshot cannot affect the registry.

type AuthorizationID added in v1.7.7

type AuthorizationID string

AuthorizationID is an opaque, teamserver-generated capability identifying a reverse port forward authorization. Implants must not be able to choose it.

func (AuthorizationID) String added in v1.7.7

func (id AuthorizationID) String() string

type AuthorizationState added in v1.7.7

type AuthorizationState uint8

AuthorizationState describes the server-owned lifecycle of a reverse port forward listener authorization.

const (
	AuthorizationStarting AuthorizationState = iota + 1
	AuthorizationActive
	AuthorizationRevoked
)

AuthorizationStarting and the related states describe authorization lifecycle.

func (AuthorizationState) String added in v1.7.7

func (state AuthorizationState) String() string

type Broker added in v1.7.7

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

Broker resolves server-owned authorizations and opens their immutable dial plans. A caller can provide an implant address only for legacy lookup; that value is never passed to the dialer.

func NewBroker added in v1.7.7

func NewBroker(registry *Registry, dialer ContextDialer, dialTimeout time.Duration) *Broker

NewBroker creates an outbound reverse port forward broker. Nil dependencies use production-safe defaults, and non-positive timeouts use DefaultDialTimeout.

func (*Broker) Open added in v1.7.7

func (broker *Broker) Open(ctx context.Context, sessionID string, id AuthorizationID, legacyAddress string) (net.Conn, AuthorizationID, error)

Open resolves an authorization, dials only its stored operator destination, applies keepalive policy, and tracks the connection until it is closed or its authorization is revoked. Authorization revocation cancels pending dials and closes committed connections. The destination-bearing plan remains private; callers receive only the resolved capability for tunnel association.

type ContextDialer added in v1.7.7

type ContextDialer interface {
	DialContext(ctx context.Context, network string, address string) (net.Conn, error)
}

ContextDialer is the only dependency allowed to create outbound reverse port forward connections. It is injectable so authorization-to-dial behavior can be tested without opening a real socket.

type DialContextFunc added in v1.7.7

type DialContextFunc func(ctx context.Context, network string, address string) (net.Conn, error)

DialContextFunc adapts a function into a ContextDialer for focused tests.

func (DialContextFunc) DialContext added in v1.7.7

func (dial DialContextFunc) DialContext(ctx context.Context, network string, address string) (net.Conn, error)

DialContext invokes the adapted dial function.

type RTunnel

type RTunnel struct {
	ID        uint64
	SessionID string
	// Reader       io.ReadCloser
	Readers []io.ReadCloser

	Writer io.WriteCloser
	// contains filtered or unexported fields
}

RTunnel - Duplex byte read/write

func GetRTunnel

func GetRTunnel(ID uint64) *RTunnel

Tunnel - Add tunnel to mapping

func NewAuthorizedRTunnel added in v1.7.7

func NewAuthorizedRTunnel(id uint64, sessionID string, authorizationID AuthorizationID, writer io.WriteCloser, readers ...io.ReadCloser) *RTunnel

NewAuthorizedRTunnel associates a reverse tunnel with the authorization that created its outbound connection. NewRTunnel remains available for callers that do not use reverse port forward authorization.

func NewRTunnel

func NewRTunnel(id uint64, sID string, writer io.WriteCloser, readers ...io.ReadCloser) *RTunnel

func (*RTunnel) AuthorizationID added in v1.7.7

func (c *RTunnel) AuthorizationID() AuthorizationID

AuthorizationID returns the server-owned authorization associated with the tunnel, or an empty ID for non-reverse tunnels.

func (*RTunnel) Close

func (c *RTunnel) Close()

Close - close RTunnel reader and writer

func (*RTunnel) Done added in v1.7.7

func (c *RTunnel) Done() <-chan struct{}

Done is closed when the relay is revoked or otherwise closed. Writers use it to abandon a blocked implant send without leaking a goroutine.

func (*RTunnel) IncReadSequence

func (c *RTunnel) IncReadSequence()

func (*RTunnel) IncWriteSequence

func (c *RTunnel) IncWriteSequence()

func (*RTunnel) MarkPeerClose added in v1.7.7

func (c *RTunnel) MarkPeerClose(sequence uint64) (bool, error)

MarkPeerClose records the exclusive last sequence expected from the implant. Legacy zero-sequence closes after data retain their immediate-close behavior.

func (*RTunnel) PeerClosePending added in v1.7.7

func (c *RTunnel) PeerClosePending() bool

PeerClosePending reports whether the peer supplied a terminal sequence.

func (*RTunnel) PeerCloseReady added in v1.7.7

func (c *RTunnel) PeerCloseReady() bool

PeerCloseReady reports whether all frames preceding the peer terminal arrived.

func (*RTunnel) PeerTeardownPending added in v1.7.7

func (c *RTunnel) PeerTeardownPending() bool

PeerTeardownPending reports peer-owned teardown without pretending that a sequenced terminal has been installed. Inbound ordering consults only PeerClosePending; notifier failure suppression uses this combined state.

func (*RTunnel) ProcessInbound added in v1.7.7

func (c *RTunnel) ProcessInbound(sequence uint64, data []byte, write func([]byte) error) (int, error)

ProcessInbound serializes one relay's inbound stream, bounds out-of-order buffering, and drains contiguous frames through write. Pending data is owned by this tunnel instance, so stale cleanup for a reused numeric ID cannot delete another generation's cache.

func (*RTunnel) QueueOutbound added in v1.7.7

func (c *RTunnel) QueueOutbound(send func(uint64) error) error

QueueOutbound serializes server-to-implant frames with terminal close. The sequence advances only after the transport accepted the frame.

func (*RTunnel) ReadSequence

func (c *RTunnel) ReadSequence() uint64

func (*RTunnel) SetPeerCloseNotifier added in v1.7.7

func (c *RTunnel) SetPeerCloseNotifier(notifier func(uint64) error)

SetPeerCloseNotifier installs the bounded transport callback used when the server is the side that first closes this relay. It must be set before the tunnel is published.

func (*RTunnel) StartPeerCloseDeadline added in v1.7.7

func (c *RTunnel) StartPeerCloseDeadline(timeout time.Duration, expired func())

StartPeerCloseDeadline runs expired if the terminal sequence remains incomplete.

func (*RTunnel) WriteSequence

func (c *RTunnel) WriteSequence() uint64

type Registry added in v1.7.7

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

Registry owns all reverse port forward authorizations. Its indexes contain only server-generated IDs and destinations derived from operator requests.

func NewRegistry added in v1.7.7

func NewRegistry() *Registry

NewRegistry creates an empty reverse port forward authorization registry.

func (*Registry) Activate added in v1.7.7

func (registry *Registry) Activate(sessionID string, id AuthorizationID, listenerID uint32) error

Activate binds an implant listener ID to a Starting authorization. A duplicate implant-selected listener ID never replaces or orphans the existing record: the rejected candidate is revoked atomically.

func (*Registry) ActivateProtocol added in v1.7.7

func (registry *Registry) ActivateProtocol(sessionID string, id AuthorizationID, listenerID uint32, requiresAuthorizationID bool) error

ActivateProtocol binds an implant listener ID and records whether the implant proved support for authorization IDs by echoing the issued ID in its start response. Legacy address lookup is disabled for capable listeners, so a new-implant protocol regression fails closed while older implants retain exact-address compatibility.

func (*Registry) Begin added in v1.7.7

func (registry *Registry) Begin(sessionID string, address string, keepAlive int32) (AuthorizationID, error)

Begin creates a Starting authorization for an operator-selected destination. Prefer BeginSpec when the operator's bind address is available.

func (*Registry) BeginSpec added in v1.7.7

func (registry *Registry) BeginSpec(sessionID string, bindAddress string, address string, keepAlive int32) (AuthorizationID, error)

BeginSpec creates a Starting authorization before the start request is sent to the implant. The destination and bind address are immutable thereafter.

func (*Registry) List added in v1.7.7

func (registry *Registry) List(sessionID string) []Authorization

List returns server-authoritative Starting and Active listeners for a session.

func (*Registry) Lookup added in v1.7.7

func (registry *Registry) Lookup(sessionID string, id AuthorizationID) (Authorization, bool)

Lookup returns an immutable snapshot for an authorization.

func (*Registry) LookupListener added in v1.7.7

func (registry *Registry) LookupListener(sessionID string, listenerID uint32) (Authorization, bool)

LookupListener returns an immutable snapshot for an implant listener ID.

func (*Registry) Revoke added in v1.7.7

func (registry *Registry) Revoke(sessionID string, id AuthorizationID) bool

Revoke prevents new acquisitions and closes pending or active broker-owned connections for an authorization. It is idempotent.

func (*Registry) RevokeListener added in v1.7.7

func (registry *Registry) RevokeListener(sessionID string, listenerID uint32) bool

RevokeListener revokes by the implant's listener ID. It does not depend on a successful implant stop response and is idempotent.

func (*Registry) RevokeSession added in v1.7.7

func (registry *Registry) RevokeSession(sessionID string) int

RevokeSession revokes every authorization for a disconnected session, closes its broker-owned connections, and removes authorization tombstones. It is idempotent and never affects another session.

Jump to

Keyboard shortcuts

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