pep

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Aug 28, 2026 License: MIT Imports: 42 Imported by: 0

Documentation

Overview

Package pep implements the paired fixed-egress performance-enhancing proxy.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ListenLocal added in v0.1.1

func ListenLocal(ctx context.Context, address string) (net.Listener, error)

ListenLocal binds a local SOCKS5 listener. Serve and any caller which binds ahead of ServeListener share it so both get identical socket options.

func NewAggregateBudget added in v0.1.1

func NewAggregateBudget(totalBytesPerSec, reserveBytesPerSec uint64) *limiter.Budget

NewAggregateBudget builds one byte budget several clients can share, so a process running many clients paces to the configured total instead of offering that total once per client. A zero rate means unpaced.

Types

type Client

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

func NewClient

func NewClient(cfg ClientConfig) (*Client, error)

func (*Client) MemoryStats

func (c *Client) MemoryStats() MemoryStats

func (*Client) Metrics

func (c *Client) Metrics() *metrics.Registry

Metrics exposes aggregate counters for an optional operator endpoint.

func (*Client) Probe

func (c *Client) Probe(ctx context.Context) (ProbeResult, error)

Probe verifies that the configured provider accepts this device identity. It deliberately opens no destination. After mutual TLS it sends a bounded, intentionally invalid lane-join control frame and requires the protocol reset defined for that request. That round trip matters with TLS 1.3: a client can briefly consider its handshake complete before it observes the server rejecting a revoked certificate.

func (*Client) Serve

func (c *Client) Serve(ctx context.Context) error

func (*Client) ServeListener

func (c *Client) ServeListener(ctx context.Context, listener net.Listener) error

ServeListener is primarily useful for tests and service managers which provide an already-bound socket. The listener is closed when the context is cancelled or the method returns.

func (*Client) UpdateCredentials

func (c *Client) UpdateCredentials(updated identity.ClientCredentials) error

UpdateCredentials installs a renewed certificate for future handshakes. Trust domain, account, device, and public key are immutable: changing any of them requires importing a new profile and constructing a new client.

type ClientConfig

type ClientConfig struct {
	ListenAddr   string
	RemoteAddr   string
	LocalAddress string
	// SocketControl is invoked after an outer socket is created and before it
	// is bound or connected. Mobile VPN clients use it to exempt Queqiao's own
	// TCP and UDP sockets from the virtual interface. A failure aborts the dial;
	// silently continuing would route the tunnel through itself.
	SocketControl func(network, address string, conn syscall.RawConn) error
	// SOCKSAuth optionally requires RFC 1929 username/password on the local
	// SOCKS5 listener. It is nil for the loopback-private listener used by the
	// desktop agent and the mobile packet tunnel, and set when the listener is
	// reachable by other applications on the same host, as in Android export
	// mode where loopback is shared across every installed app.
	SOCKSAuth   *socks5.Credentials
	Credentials identity.ClientCredentials
	// Profile names the deployment this client is running in, and carries the
	// policy that differs between deployments. The zero value is the supported
	// access-link profile, so a caller that does not choose gets the behaviour
	// the published measurements describe.
	Profile profile.Profile
	// FlowMetadataSocket is a local capture agent's lookup socket. Empty
	// disables the lookup and is the default: a deployment without an agent
	// behaves exactly as it did before this existed.
	FlowMetadataSocket string
	// FlowMetadataTimeout bounds one lookup. It runs on the accept path, so an
	// agent that has wedged costs a flow a millisecond rather than its
	// handshake.
	FlowMetadataTimeout time.Duration
	ChunkSize           int
	DialTimeout         time.Duration
	HandshakeTimeout    time.Duration
	FlowIdleTimeout     time.Duration
	FlowMaxLifetime     time.Duration
	MaxSessions         int
	// SessionLimit optionally shares admission across several clients in one
	// process. Nil gives this client its own MaxSessions-sized limit.
	SessionLimit *SessionLimit
	// Budget optionally shares the aggregate byte budget across several
	// clients in one process. Nil derives a private budget from
	// AggregateBytesPerSec, which paces this client alone; a multi-provider
	// process must share one budget or it offers the configured total once
	// per provider.
	Budget *limiter.Budget
	// MaxPendingOpens bounds flows that are still establishing their remote
	// transport. Keeping this separate from MaxSessions prevents a failed
	// endpoint and its retries from occupying every healthy-flow slot.
	MaxPendingOpens int
	Transport       TransportKind
	// TCPFallbackLanes is the number of independent TLS/TCP connections used
	// for a classified bulk flow. Values above one never affect QUIC.
	TCPFallbackLanes int
	// EnableQUICPool keeps one persistent QUIC connection for initial and
	// control streams, and is what makes opening a flow cost nothing.
	//
	// Without it every flow dials its own connection, and so pays a handshake
	// and a congestion ramp from the initial window before it carries a byte.
	// Measured live on a 38% erasure path, a small flow cost 0.64 s, 1.11 s and
	// 14.77 s on three attempts unpooled -- the last being a handshake that
	// lost packets -- against 0.302, 0.292 and 0.300 pooled, which is one round
	// trip and nothing else.
	//
	// It was opt-in because bulk on a pooled connection to a Reno peer measured
	// worse than an independent lane. That reason has gone: the peer runs the
	// erasure controller, the scheduler already moves classified bulk off the
	// pooled connection onto lanes of its own, and bulk measured the same
	// either way (0.85-1.15 MB/s pooled against 0.86-1.02 unpooled).
	EnableQUICPool bool
	// WaitForOpenAcknowledgement makes a flow wait for OPEN_OK before telling
	// the application its connection is up. It is off by default, so a flow on
	// a connection that is already established costs no round trips at all.
	//
	// Waiting costs exactly one round trip per flow, and an application opens
	// a flow far more often than it opens a connection. Measured across an
	// emulated 300 ms path, a first flow costs 922 ms -- a QUIC handshake, an
	// authentication exchange and an open -- and every flow after it cost 306
	// ms, which is one round trip of pure waiting on a pool that was already
	// up. That is the cost this removes: request bytes now leave with the open
	// rather than a round trip behind it.
	//
	// What is given up is the ability to answer SOCKS with a precise failure.
	// The flow reader still validates the eventual OPEN_OK and propagates a
	// typed RESET, so an unreachable destination becomes a connection that
	// opens and then closes rather than one that never opens. Set this when a
	// caller needs the distinction more than it needs the round trip.
	WaitForOpenAcknowledgement bool
	// UDPOnStream keeps SOCKS UDP packets on the lane's control stream even
	// where the QUIC connection negotiated datagrams. It is the control for
	// measuring the datagram substrate against the one it replaced, and both
	// endpoints must be set the same way for the comparison to mean anything.
	UDPOnStream                   bool
	Congestion                    CongestionControlKind
	BrutalBytesPerSec             uint64
	AdaptiveMinBytesSec           uint64
	AdaptiveMaxBytesSec           uint64
	AggregateBytesPerSec          uint64
	InteractiveReserveBytesPerSec uint64
	// StreamReceiveWindow and ConnectionReceiveWindow override the QUIC
	// receive windows. Zero selects the defaults, which match TUIC.
	StreamReceiveWindow     uint64
	ConnectionReceiveWindow uint64
	// Maximum windows disable quic-go's otherwise large receive-window growth.
	// Zero keeps the high-throughput defaults. Resource-constrained clients set
	// initial and maximum to the same bounded values.
	MaxStreamReceiveWindow     uint64
	MaxConnectionReceiveWindow uint64
	MaxIncomingStreams         int64
	// MemoryLimits is required by resource-constrained clients. Nil retains
	// the throughput-oriented defaults used by servers and desktop clients.
	MemoryLimits *MemoryLimits
	Metrics      *metrics.Registry
	// FallbackDelay is when AUTO starts connecting its warm-standby TCP
	// candidate. It is not a deadline for QUIC and not a transport-selection
	// race.
	FallbackDelay time.Duration
	// FallbackGrace is how long a ready TCP standby waits for QUIC. Expiry may
	// serve the current flow over TCP, but it is neutral UDP evidence. With a
	// pool, the QUIC attempt continues in the background so later flows can
	// return to the preferred transport.
	FallbackGrace       time.Duration
	UDPFailureThreshold int
	UDPCooldown         time.Duration
	Logger              *slog.Logger
}

type CongestionControlKind

type CongestionControlKind string

CongestionControlKind selects the QUIC sender.

Erasure is the default and the only one that should normally be chosen. Reno leaves the apNet quic-go default untouched and is the safe control. BBR is the original queqiao controller. BBRTUIC is a faithful Go port of TUIC's quinn-congestions BBR model. Adaptive is a conservative rate-estimating controller for unknown paths. Brutal is a fixed-rate mode for controlled experiments where the operator knows the per-lane budget.

const (
	CongestionReno     CongestionControlKind = "reno"
	CongestionBBR      CongestionControlKind = "bbr"
	CongestionBBRTUIC  CongestionControlKind = "bbr-tuic"
	CongestionAdaptive CongestionControlKind = "adaptive"
	CongestionBrutal   CongestionControlKind = "brutal"
	// CongestionErasure is BBR corrected for a path that erases packets for
	// reasons unrelated to congestion. It is the right choice on a long-haul
	// path with a loss floor; on a clean path it reduces to BBR, because the
	// floor it measures is zero and the correction it applies is one.
	CongestionErasure CongestionControlKind = "erasure"
)

type DestinationPolicy

type DestinationPolicy struct {
	AllowPrivate bool
	DialTimeout  time.Duration
}

func (DestinationPolicy) DialContext

func (p DestinationPolicy) DialContext(ctx context.Context, destination string) (net.Conn, error)

func (DestinationPolicy) ResolveUDPAddr

func (p DestinationPolicy) ResolveUDPAddr(ctx context.Context, destination string) ([]*net.UDPAddr, error)

ResolveUDPAddr validates and resolves a destination using exactly the same public-address policy as TCP CONNECT. It deliberately returns a concrete address: the server performs DNS resolution at the US egress and does not let the client influence a later DNS rebinding or private-address hop.

type FlowStats

type FlowStats struct {
	Started   time.Time
	Ended     time.Time
	BytesSent uint64
	BytesRead uint64
	// LaneBytes records payload bytes carried by each outer lane. It is
	// intentionally optional so one-lane callers can ignore it, while
	// benchmarks and operators can verify actual striping rather than merely
	// counting successful lane handshakes.
	LaneBytes map[uint64]LaneStats
}

type LaneStats

type LaneStats struct {
	Kind     TransportKind
	Sent     uint64
	Received uint64
}

type MemoryLimits

type MemoryLimits struct {
	// SendBudgetBytes and ReceiveBudgetBytes are shared by all flows. Send
	// reservations block source reads; receive reservations fail only the flow
	// that cannot retain an out-of-order frame, avoiding a cross-lane deadlock.
	SendBudgetBytes    int64
	ReceiveBudgetBytes int64

	MaxFlowSendBytes       int
	MaxFlowReceiveBytes    uint64
	MaxFlowOutstanding     int
	MaxFlowReceiveFrames   int
	EventQueueFrames       int
	LaneWriteQueueFrames   int
	LaneInteractiveReserve int
	FrameReadBufferBytes   int
	MaxUDPPacketBytes      int
	MaxBulkConnections     int
}

MemoryLimits turns the transport's multiplicative per-flow buffering into fixed endpoint budgets. A nil *MemoryLimits keeps the throughput-oriented server defaults. Mobile clients provide an explicit profile.

type MemoryStats

type MemoryStats struct {
	Send    memlimit.Snapshot `json:"send"`
	Receive memlimit.Snapshot `json:"receive"`
}

MemoryStats exposes exact retained-payload accounting. It deliberately does not claim to be whole-process RSS; platform and transport runtimes also own bounded buffers outside these budgets.

type ProbeResult

type ProbeResult struct {
	Transport TransportKind
	Latency   time.Duration
}

ProbeResult describes one authenticated provider connection attempt. Latency includes name resolution, transport establishment, mutual TLS, certificate authorization, and Queqiao ALPN negotiation.

type Server

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

func NewServer

func NewServer(cfg ServerConfig) (*Server, error)

func (*Server) MaxObservedLanes

func (s *Server) MaxObservedLanes() int

MaxObservedLanes reports the largest number of lanes attached to any flow since this server instance started. It is safe for benchmark instrumentation and does not expose session IDs or destination metadata.

func (*Server) Metrics

func (s *Server) Metrics() *metrics.Registry

Metrics exposes aggregate counters for an optional operator endpoint.

func (*Server) Serve

func (s *Server) Serve(ctx context.Context) error

func (*Server) ServeListener

func (s *Server) ServeListener(ctx context.Context, listener net.Listener) error

ServeListener runs the authenticated server on an already-bound listener. This also supports socket activation and deterministic integration tests.

func (*Server) ServePacketConn

func (s *Server) ServePacketConn(ctx context.Context, packetConn net.PacketConn) error

ServePacketConn runs the QUIC listener on an already-bound UDP socket.

type ServerConfig

type ServerConfig struct {
	// Profile names the deployment this gateway serves; see internal/profile.
	// The zero value is the supported access-link profile.
	Profile           profile.Profile
	ListenAddr        string
	Credentials       identity.ServerCredentials
	Enrollment        *identity.EnrollmentService
	ChunkSize         int
	HandshakeTimeout  time.Duration
	FlowIdleTimeout   time.Duration
	FlowMaxLifetime   time.Duration
	MaxSessions       int
	DestinationPolicy DestinationPolicy
	EnableTCP         bool
	EnableQUIC        bool
	// TCPFallbackLanes is the admission ceiling for one negotiated TCP-only
	// flow. The client chooses the active target; keeping the server ceiling at
	// 16 lets operators compare 8 and 16 without changing the gateway.
	TCPFallbackLanes int
	// TCPCongestion selects the Linux kernel congestion controller inherited by
	// accepted fallback sockets. "system" leaves the host default untouched.
	TCPCongestion                 string
	Congestion                    CongestionControlKind
	BrutalBytesPerSec             uint64
	AdaptiveMinBytesSec           uint64
	AdaptiveMaxBytesSec           uint64
	AggregateBytesPerSec          uint64
	InteractiveReserveBytesPerSec uint64
	// StreamReceiveWindow and ConnectionReceiveWindow override the QUIC
	// receive windows. Zero selects the defaults, which match TUIC.
	StreamReceiveWindow     uint64
	ConnectionReceiveWindow uint64
	Metrics                 *metrics.Registry
	Logger                  *slog.Logger
	// UDPOnStream keeps SOCKS UDP packets on the lane's control stream even
	// where the QUIC connection negotiated datagrams. See the client's field:
	// it is a measurement control, and both endpoints must agree for the
	// comparison to mean anything.
	UDPOnStream bool
	// contains filtered or unexported fields
}

type SessionLimit added in v0.1.1

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

SessionLimit bounds concurrent local SOCKS sessions across one or more clients. Admission is deliberately non-blocking so an overloaded listener can reject promptly instead of accumulating unauthenticated local sockets.

A limit may hold a private reservation as well as a share of a pool common to every client. The reservation is what keeps a quiet provider able to admit new flows while a busy sibling holds most of the common pool: a failover target which cannot accept a session is not a failover target.

func NewSessionLimit added in v0.1.1

func NewSessionLimit(max int) (*SessionLimit, error)

func NewSharedSessionLimits added in v0.1.1

func NewSharedSessionLimits(max, clients int) ([]*SessionLimit, error)

NewSharedSessionLimits divides max across clients so their combined admission never exceeds max while each client keeps a private reservation. Half the budget is reserved in equal shares and half stays common, so an idle provider can still burst into capacity its siblings are not using. When max is too small to reserve a slot per client the whole budget stays common.

func (*SessionLimit) Reserved added in v0.1.1

func (l *SessionLimit) Reserved() int

Reserved reports the slots this limit holds for its own client alone.

type TransportKind

type TransportKind string
const (
	TransportTCP  TransportKind = "tcp"
	TransportQUIC TransportKind = "quic"
	TransportAuto TransportKind = "auto"
)

Jump to

Keyboard shortcuts

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