samizdatreverse

package
v0.0.217 Latest Latest
Warning

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

Go to latest
Published: Nov 10, 2026 License: GPL-3.0 Imports: 39 Imported by: 0

Documentation

Overview

Package samizdatreverse implements reverse direct mode: the inviter's portal inbound accepts samizdat sessions that a helper's bridge inbound dials out, and the inviter's reverse outbounds open streams down those sessions, so the inviter's traffic exits from the helper.

Index

Constants

This section is empty.

Variables

View Source
var ErrNoSession = errors.New("samizdat-reverse: no session")

ErrNoSession is returned by a reverse outbound's dials while its pairing has no live reversed session on the portal.

Functions

func NewBridge

NewBridge creates a bridge inbound. It dials only once started.

func NewPortal

NewPortal creates a portal inbound. It listens only once Configure gives it a pairing.

func NewReverseOutbound

func NewReverseOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SamizdatReverseOutboundOptions) (adapter.Outbound, error)

NewReverseOutbound creates a reverse outbound. The portal is resolved on each dial rather than at construction: inbounds are not outbound dependencies, and the portal may be configured after this outbound starts.

func RegisterInbounds

func RegisterInbounds(registry *inbound.Registry)

RegisterInbounds registers the portal and bridge inbounds.

func RegisterOutbound

func RegisterOutbound(registry *outbound.Registry)

RegisterOutbound registers the reverse outbound.

Types

type Bridge

type Bridge struct {
	inbound.Adapter
	// contains filtered or unexported fields
}

Bridge is the inbound on a helper's device that dials out to an inviter's portal. On the samizdat session it dials it serves HTTP/2 with the roles reversed: the portal opens CONNECT streams, and the bridge routes each as an inbound connection, so the inviter's traffic exits from the helper. It keeps one session up at a time, redialing with backoff when it ends.

The bridge emits no peerconn events. The app that embeds it counts bridge traffic with its own connection tracker, and peer share's peerconn listener, which may run in the same process, must not count these streams as anonymous pool traffic.

func (*Bridge) Close

func (b *Bridge) Close() error

Close stops the dial loop and ends the live session.

func (*Bridge) ServeHTTP

func (b *Bridge) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP answers a CONNECT from the portal with 200 and routes the stream to its destination.

func (*Bridge) Start

func (b *Bridge) Start(stage adapter.StartStage) error

Start starts the dial loop.

type Outbound

type Outbound struct {
	outbound.Adapter
	// contains filtered or unexported fields
}

Outbound opens HTTP/2 CONNECT streams on the reversed session the portal holds for this outbound's pairing, keyed by the outbound's own tag.

func (*Outbound) Close

func (o *Outbound) Close() error

Close is a no-op: the portal owns the session.

func (*Outbound) DialContext

func (o *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error)

DialContext opens a TCP stream, or a UoT stream for UDP, on the session. UDP to port 443 goes to a black hole instead (see blackHole).

func (*Outbound) ListenPacket

func (o *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error)

ListenPacket opens a UoT packet connection on the session, or a black hole for port 443.

type Portal

type Portal struct {
	inbound.Adapter
	// contains filtered or unexported fields
}

Portal is the inbound on an inviter's device that helpers dial into. Each authenticated samizdat session carries HTTP/2 with the roles reversed: the portal is the HTTP/2 client, and reverse outbounds open CONNECT streams on the session that Session returns for their tag. The portal holds at most one session per pairing; a newer one replaces the older.

func (*Portal) Addr

func (p *Portal) Addr() net.Addr

Addr returns the address the portal listens on, or nil when it is not listening.

func (*Portal) Close

func (p *Portal) Close() error

Close stops the portal and ends every session. Configure fails after it.

func (*Portal) Configure

func (p *Portal) Configure(cfg PortalConfig) error

Configure makes cfg the portal's configuration. A change of pairings alone keeps the listener and sessions, ends the sessions of pairings that are gone and stops their short IDs from authenticating. No pairings at all stops the portal. Configuring the current configuration again changes nothing, unless the listener has stopped serving on its own, in which case the portal is rebuilt.

A change of key or certificate on the same port keeps the listener. New sessions authenticate under cfg's identity, and also under cfg.Previous until its Until passes. An identity the portal stops taking new sessions under keeps the sessions it holds until they end or their pairing is removed, so rotating the key ends no session.

A change of port replaces the listener and every identity, which ends every session. The new port is bound before the old listener stops, so a failed bind returns an error and leaves the old configuration serving. A failed bind while the portal is down leaves it down with cfg wanted, and Rebind or Configure tries again. ListenPort 0 binds the port the portal last served on, if any, and otherwise any free port.

func (*Portal) InterfaceUpdated

func (p *Portal) InterfaceUpdated()

InterfaceUpdated implements adapter.InterfaceUpdateListener. sing-box's network manager calls it on every inbound when the default interface changes. The listener was bound to the interface that was the default when it was created, so the portal rebinds after updates stop arriving for rebindDelay. It returns at once: the caller is the network monitor.

func (*Portal) Rebind

func (p *Portal) Rebind() error

Rebind replaces the portal's listener with a new one on the same port, bound to the current default interface. The samizdat servers and their sessions are kept: only accepting moves to the new listener. If the portal is down, because its listener stopped serving or an earlier bind failed, it is rebuilt from the wanted configuration on the port it last served on. If the bind fails, the portal stays down, keeps that configuration and returns the error, and the next Rebind or Configure tries again. Rebind does nothing when no configuration is wanted or the portal is closed.

func (*Portal) Session

func (p *Portal) Session(reverseTag string) (*http2.ClientConn, bool)

Session returns the HTTP/2 client of the live session for reverseTag, if it can take a new stream. The client is checked outside sessMu, as every call into an HTTP/2 client must be.

func (*Portal) Start

func (p *Portal) Start(stage adapter.StartStage) error

Start does nothing: the portal starts serving on Configure.

type PortalConfig

type PortalConfig struct {
	ListenPort uint16
	PrivateKey []byte // X25519, 32 bytes
	CertPEM    []byte
	KeyPEM     []byte
	Pairings   map[[8]byte]string // accepted short ID -> reverse outbound tag
	// Previous, if set, is an identity the portal also accepts new sessions
	// under until Previous.Until, so that helpers still dialing with the
	// identity the portal held before this one can reconnect while they
	// learn the new one. Its key must differ from PrivateKey.
	Previous *PreviousIdentity
}

PortalConfig is what the portal serves: where it listens, the samizdat server identity, and which helper each accepted short ID belongs to.

type PreviousIdentity added in v0.0.210

type PreviousIdentity struct {
	PrivateKey []byte // X25519, 32 bytes
	CertPEM    []byte
	KeyPEM     []byte
	// Until is when the portal stops accepting new sessions under the
	// identity. Sessions it already holds under it go on.
	Until time.Time
}

PreviousIdentity is a samizdat server identity the portal accepts new sessions under for a while after it has moved to another.

Jump to

Keyboard shortcuts

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