netx

package module
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: Feb 22, 2026 License: BSD-3-Clause Imports: 22 Imported by: 9

README

netX

netX or network extended is a collection of small, focused extensions to Go's "net" standard library.

It provides composable building blocks that integrate well with standard net.Conn and net.Listener types without introducing heavy abstractions, only introducing new interfaces and types where necessary to expose real functionality.

Contents

Highlights

  • Buffered connections: NewBufConn adds buffered read/write with explicit Flush.
  • Framed connections: NewFramedConn adds a simple 4-byte length-prefixed frame protocol.
  • Mux / MuxClient: NewMux wraps a net.Listener as a net.Conn; NewMuxClient wraps a Dialer as a net.Conn — both transparently accept/redial on EOF.
  • Demux / DemuxClient: session multiplexer over a single net.Conn using fixed-length ID prefixes. NewDemux returns a net.Listener of virtual sessions; NewDemuxClient returns a Dialer.
  • Poll connections: NewPollConn turns a request-response net.Conn into a persistent bidirectional stream via periodic polling.
  • Tagged connections: TaggedConn interface extends net.Conn with opaque tags that carry context (e.g., DNS query) from read path to write path. TaggedPipe provides an in-memory pair.
  • Connection router/server: Server[ID] accepts on a listener and routes new conns to handlers you register at runtime.
  • Tunneling: Tun and TunMaster[ID] wire two connections together for bidirectional relay (useful to bridge UDP over a framed TCP stream, add TLS, etc.).
  • Driver/wrapper system: pluggable Driver registry and typed Wrapper pipeline for composing connection transformations. Supports type-safe chains across net.Listener, Dialer, net.Conn, and TaggedConn.
  • DNS tunneling: proto/dnst encodes data into DNS TXT queries/responses; combine with Mux, TaggedDemux, DemuxClient, and PollConn for a full tunnel.
  • ICMP support: icmp transport for listener and dialer, tunneling traffic over ICMP Echo Request/Reply.
  • Chainable tunnel CLI and URI builder: compose transports and wrappers with URI in code or via the netx tun command.

Installation

go get github.com/pedramktb/go-netx@latest

Import as:

import netx "github.com/pedramktb/go-netx"

Protocol implementations and drivers live in separate modules:

go get github.com/pedramktb/go-netx/proto/aesgcm@latest   # AES-GCM conn
go get github.com/pedramktb/go-netx/proto/dnst@latest      # DNS tunnel conn
go get github.com/pedramktb/go-netx/proto/ssh@latest        # SSH conn
go get github.com/pedramktb/go-netx/drivers/tls@latest      # TLS driver (register via blank import)
# ... etc.

Library guide

Buffered connections
c, _ := net.Dial("tcp", addr)
bc := netx.NewBufConn(c, netx.WithBufSize(8<<10))

_, _ = bc.Write([]byte("hello"))
_ = bc.Flush() // ensure data is written now

Notes:

  • NewBufConn returns a BufConn that implements net.Conn plus Flush() error.
  • Options: WithBufSize(uint16) sets both reader and writer size; WithBufReaderSize(uint16) and WithBufWriterSize(uint16) set them independently. Default: 4096.
  • Close() will attempt to Flush() and close, returning a joined error if any.
Framed connections
rawClient, rawServer := net.Pipe()
defer rawClient.Close(); defer rawServer.Close()

client := netx.NewFramedConn(rawClient)                           // default max frame size 4096
server := netx.NewFramedConn(rawServer, netx.WithMaxFrameSize(64<<10))

msg := []byte("hello frame")
_, _ = client.Write(msg) // sends a 4-byte big-endian length header then payload

buf := make([]byte, len(msg))
_, _ = io.ReadFull(server, buf) // reads exactly one frame (may deliver across multiple Read calls)

Notes:

  • Each Write(p) sends one frame. Empty frames are allowed and read as n=0, err=nil.
  • If an incoming frame exceeds maxFrameSize, Read returns ErrFrameTooLarge.
  • If the underlying conn also supports Flush (e.g., BufConn), Write flushes to coalesce header+payload.
Mux and MuxClient

NewMux adapts a net.Listener into a single net.Conn. Reads accept connections from the listener; when the current connection reaches EOF, the next one is accepted transparently. Writes go to the most recently accepted connection.

NewMuxClient does the inverse: it wraps a Dialer function as a net.Conn, dialing lazily on first use and redialing on EOF.

// Server side: collapse accepted connections into one conn
ln, _ := net.Listen("tcp", ":9000")
conn := netx.NewMux(ln) // conn implements net.Conn

// Client side: auto-reconnecting conn from a dialer
clientConn := netx.NewMuxClient(func() (net.Conn, error) {
	return net.Dial("tcp", "server:9000")
}, netx.WithMuxClientRemoteAddr(remoteAddr))

Notes:

  • Deadlines set on the mux propagate to newly accepted/dialed connections.
  • Closing the mux closes both the current connection and the underlying listener/dialer.
Demux and DemuxClient

NewDemux is a session multiplexer: it reads from a single net.Conn, extracts a fixed-length session ID prefix from each packet, and routes payloads to virtual per-session connections exposed via a net.Listener.

NewDemuxClient creates a Dialer that produces connections which automatically prepend/strip a session ID on every write/read.

// Server: multiplex sessions over a single conn
sessListener := netx.NewDemux(conn, 4, // 4-byte session ID
	netx.WithDemuxSessQueueSize(16),
	netx.WithDemuxAccQueueSize(8),
)
defer sessListener.Close()

for {
	sess, _ := sessListener.Accept() // each session is a net.Conn
	go handleSession(sess)
}
// Client: wrap a conn with a session ID
dial := netx.NewDemuxClient(conn, []byte{0x01, 0x02, 0x03, 0x04})
sessConn, _ := dial() // net.Conn with ID prepended on writes, stripped on reads

Options:

Option Default Purpose
WithDemuxAccQueueSize(uint16) 0 (unbuffered) Accept queue capacity
WithDemuxSessQueueSize(uint16) 8 Per-session read queue depth
WithDemuxBufSize(uint16) 4096 Underlying read buffer size
WithDemuxClientBufSize(uint16) 4096 Client read/write buffer size
Poll connections

NewPollConn converts a request-response style net.Conn into a persistent bidirectional stream. It sends user data (or empty polls on idle) and reads back responses in a continuous loop.

pollConn := netx.NewPollConn(reqRespConn,
	netx.WithPollInterval(50*time.Millisecond),
	netx.WithPollBufSize(4096),
	netx.WithPollSendQueueSize(32),
	netx.WithPollRecvQueueSize(32),
)
defer pollConn.Close()

_, _ = pollConn.Write(data) // queued, sent on next cycle
_, _ = pollConn.Read(buf)   // blocks until a response arrives

This is essential for protocols where the client must poll to receive data (e.g., DNS tunneling where the server can only respond to queries).

Tagged connections

TaggedConn extends net.Conn semantics with an opaque any tag that carries context from the read path to the write path. This is critical for protocols where responses must correspond to specific requests (e.g., DNS queries).

type TaggedConn interface {
	ReadTagged([]byte, *any) (int, error)   // read payload + capture tag
	WriteTagged([]byte, any) (int, error)   // write payload + attach tag
	Close() error
	LocalAddr() net.Addr
	RemoteAddr() net.Addr
	SetDeadline(t time.Time) error
	SetReadDeadline(t time.Time) error
	SetWriteDeadline(t time.Time) error
}

TaggedPipe() creates an in-memory bidirectional TaggedConn pair (analogous to net.Pipe()), useful for testing.

NewTaggedDemux is the tag-aware variant of NewDemux: it routes {payload, tag} pairs to sessions, and sessions consume tags on write so the response is constructed with the original request context.

Runtime-routable server

Register handlers keyed by an ID (any comparable type). Each handler decides if it matches an incoming connection and returns an io.Closer to track (often the conn itself or a wrapped version).

var s netx.Server[string]

// Route A: TLS connections
s.SetRoute("tls", func(ctx context.Context, conn net.Conn, closed func()) (bool, io.Closer) {
	if _, ok := conn.(interface{ ConnectionState() tls.ConnectionState }); !ok {
		return false, nil
	}
	// handle TLS conn; call closed() when the connection is fully done
	go func() { /* ... */ ; closed() }()
	return true, conn
})

// Route B: plain connections (fallback)
s.SetRoute("plain", func(ctx context.Context, conn net.Conn, closed func()) (bool, io.Closer) {
	if _, ok := conn.(interface{ ConnectionState() tls.ConnectionState }); ok {
		return false, nil
	}
	go func() { /* ... */ ; closed() }()
	return true, conn
})

ln, _ := net.Listen("tcp", ":8080")
go s.Serve(context.Background(), ln)

// Hot-swap or remove routes at runtime
s.SetRoute("plain", newHandler)
s.RemoveRoute("tls")

// Graceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
_ = s.Shutdown(ctx) // waits for tracked connections or force-closes on deadline

Handler contract:

  • Return (matched=false, _ ) quickly if the connection is not yours; the server will try the next route.
  • If you take ownership, return (true, closer). Use closed() exactly once when you are logically done so the server stops tracking it.
  • If you return nil for the closer, the server will track the original conn.
  • Close() immediately stops accepting and closes tracked connections. Shutdown(ctx) stops accepting and waits for tracked connections until ctx is done, after which remaining connections are force-closed.
Tunneling

Tun relays bytes bidirectionally between two endpoints. TunMaster[ID] builds on Server[ID] to create tunnels from accepted conns.

Bridge UDP over a framed TCP stream:

// Server side: accept a TCP stream, frame it, and relay to a UDP socket
var tm netx.TunMaster[string]
tm.SetRoute("udp-over-tcp", func(ctx context.Context, conn net.Conn) (bool, context.Context, netx.Tun) {
	framed := netx.NewFramedConn(conn)
	udpConn, _ := net.DialUDP("udp", nil, serverUDPAddr)
	return true, ctx, netx.Tun{Conn: framed, Peer: udpConn, BufferSize: 64 << 10}
})

ln, _ := net.Listen("tcp", ":9000")
go tm.Serve(context.Background(), ln)

Notes:

  • Tun.Relay(ctx) runs two half-duplex copies until either side closes; Close() shuts both sides.
  • BufferSize controls the copy buffer (default 32KiB).
  • TunMaster.SetRoute starts Relay in a goroutine and calls the server's closed() when finished; it also logs tunnel start/close using the configured Logger.
Driver and wrapper system

netX uses a pluggable driver registry and a typed wrapper pipeline for composing connection transformations.

Drivers are registered globally and instantiate Wrapper values from parameters:

// Register a custom driver (typically in an init function)
netx.Register("myproto", func(params map[string]string, listener bool) (netx.Wrapper, error) {
	return netx.Wrapper{
		Name:       "myproto",
		Params:     params,
		Listener:   listener,
		ConnToConn: func(c net.Conn) (net.Conn, error) { return myWrap(c), nil },
	}, nil
})

Wrappers form a typed pipeline that chains transformations. Each wrapper declares which pipe type it accepts and produces (net.Listener, Dialer, net.Conn, or TaggedConn):

// Apply wrappers manually
var wrappers netx.Wrappers
_ = wrappers.UnmarshalText([]byte("tls{cert=...,key=...}+framed"), true) // true = listener side

wrapped, _ := wrappers.Apply(rawListener) // net.Listener → net.Listener

Schemes combine a transport with wrappers:

var s netx.ListenerScheme
_ = s.UnmarshalText([]byte("tcp+tls{cert=...,key=...}+framed"))
ln, _ := s.Listen(ctx, ":9000")

Built-in drivers available via blank import of drivers/* packages: aesgcm, dnst, dtls, dtlspsk, ssh, tls, tlspsk, utls. Core drivers (buffered, framed, mux, demux) are registered automatically.

Programmatic URIs

The chainable URI system composes a transport, wrappers, and address into a single string. URIs follow the format <transport>+<wrapper1>{params}+<wrapper2>://<address>.

ctx := context.Background()

var listenURI netx.ListenerURI
_ = listenURI.UnmarshalText([]byte("tcp+tls{cert=...hex...,key=...hex...}://:9000"))
ln, _ := listenURI.Listen(ctx)

var dialURI netx.DialerURI
_ = dialURI.UnmarshalText([]byte("udp+aesgcm{key=...hex...}://127.0.0.1:5555"))
peerConn, _ := dialURI.Dial(ctx)

serverConn, _ := ln.Accept()

tun := netx.Tun{Conn: serverConn, Peer: peerConn}
go tun.Relay(ctx)

ListenerURI.Listen and DialerURI.Dial instantiate the transport, apply each wrapper in order, and enforce type-safe pipeline validation.

Logging

You can plug any logger that implements the simple Logger interface:

type Logger interface {
	DebugContext(ctx context.Context, msg string, args ...any)
	InfoContext(ctx context.Context, msg string, args ...any)
	WarnContext(ctx context.Context, msg string, args ...any)
	ErrorContext(ctx context.Context, msg string, args ...any)
}

If Logger is nil, the server/tunnel use slog.Default().

Design notes and guarantees
  • All wrappers implement net.Conn (or TaggedConn) where applicable to remain drop-in.
  • The wrapper pipeline validates type compatibility at parse time — mismatched chains fail early.
  • Server routes use copy-on-write updates; SetRoute/RemoveRoute are safe to call concurrently.
  • Unhandled connections are dropped immediately after all routes decline.
  • Shutdown(ctx) will close listeners, then wait for tracked connections until ctx is done, after which remaining connections are force-closed.
  • Mux and MuxClient transparently handle connection cycling (accept/redial on EOF).
  • Demux sessions are fully independent net.Conn values with their own read queues; backpressure is per-session.

CLI

The CLI is available at cli/cmd with a tun subcommand to relay between chainable endpoints.

Quick start
  1. Install the CLI.

    go install github.com/pedramktb/go-netx/cli/cmd@latest
    
  2. Compose listener and dialer URIs. Quote them so shells do not mangle the +, {, or , characters.

    netx tun \
        --from "tcp+tls{cert=$(cat server.crt | xxd -p),key=$(cat server.key | xxd -p)}://:9000" \
        --to   "udp+aesgcm{key=00112233445566778899aabbccddeeff}://127.0.0.1:5555"
    
  3. Watch the logs. Adjust verbosity with --log debug, or hit Ctrl+C for a graceful shutdown.

Install and upgrade
go install github.com/pedramktb/go-netx/cli/cmd@latest
Build from source
task build
Example commands
# Show help
netx tun -h

# Example: TCP TLS server to TCP TLS+buffered+framed+aesgcm client
netx tun \
	--from tcp+tls{cert=server.crt,key=server.key}://:9000 \
	--to tcp+tls{cert=client.crt}+buffered{size=8192}+framed{maxsize=4096}+aesgcm{key=00112233445566778899aabbccddeeff}://example.com:9443

# Example: UDP DTLS server to UDP aesgcm client
netx tun \
	--from udp+dtls{cert=server.crt,key=server.key}://:4444 \
	--to udp+aesgcm{key=00112233445566778899aabbccddeeff}://10.0.0.10:5555

# Example: DNS tunnel server
netx tun \
	--from udp+dnst{domain=t.example.com}+demux{id=0000,sessqueuesize=16}://:53 \
	--to tcp://internal-service:8080

Options:

  • --from <chain>://listenAddr - Incoming side chain URI (required)
  • --to <chain>://connectAddr - Peer side chain URI (required)
  • --log <level> - Log level: debug|info|warn|error (default: info)
  • -h - Show help
Chain syntax reference

Chains use the form <transport>+<wrapper1>+<wrapper2>+...://host:port where <transport> is a base transport, optionally followed by +-separated wrappers with parameters in braces.

Supported base transports:

  • tcp - TCP listener or dialer
  • udp - UDP listener or dialer
  • icmp - ICMP listener or dialer (tunnels over Echo Request/Reply)

Supported wrappers:

  • buffered - Buffered read/write for better performance

    • Params: size (optional, default: 4096)
  • framed - Length-prefixed frames for packet semantics over streams

    • Params: maxsize (optional, default: 4096)
  • mux - Collapse a listener into a single net.Conn (server) or auto-reconnecting dialer into a net.Conn (client)

    • No params
  • demux - Session multiplexer over a single conn

    • Params: id (hex, required for client), bufsize (optional), accqueuesize (optional), sessqueuesize (optional)
  • dnst - DNS tunnel encoding (Base32 in TXT queries/responses)

    • Params: domain (required)
  • poll - Convert request-response conn into persistent bidirectional stream

    • Params: interval (optional), bufsize (optional), sendqueuesize (optional), recvqueuesize (optional)
  • aesgcm - AES-GCM encryption with passive IV exchange

    • Params: key, maxpacket (optional, default: 32768)
  • tls - Transport Layer Security

    • Server params: cert, key
    • Client params: cert (optional, for SPKI pinning), servername (required if cert not provided)
  • utls - TLS with client fingerprint camouflage via uTLS

    • Client-side only
    • Params: cert (optional, for SPKI pinning), servername (required if cert not provided), hello (optional: chrome, firefox, ios, android, safari, edge, randomized; default: chrome)
  • dtls - Datagram Transport Layer Security

    • Server params: cert, key
    • Client params: cert (optional, for SPKI pinning), servername (required if cert not provided)
  • tlspsk - TLS with pre-shared key (cipher: TLS_DHE_PSK_WITH_AES_256_CBC_SHA)

    • Params: key
  • dtlspsk - DTLS with pre-shared key (cipher: TLS_PSK_WITH_AES_128_GCM_SHA256)

    • Params: key
  • ssh - SSH tunneling via "direct-tcpip" channels

    • Server params: key, pass (optional), pubkey (optional, required if no pass)
    • Client params: pubkey, pass (optional), key (optional, required if no pass)

Notes:

  • All passwords, keys and certificates must be provided as hex-encoded strings.
  • When using cert for client-side tls/utls/dtls, default validation is disabled and a manual SPKI (SubjectPublicKeyInfo) hash comparison is performed against the provided certificate. This is certificate pinning and will fail if the server presents a different key.
  • SSH server must accept "direct-tcpip" channels (most do by default).
  • See docs/mux-tag-poll.md for the full architecture and data-flow diagrams of the mux/demux/poll/tagged system.

Documentation

Overview

PollConn provides persistent, bidirectional connection semantics over a request-response net.Conn.

Many transport protocols (such as DNS tunneling) operate in a strict request-response model: the client sends a request and receives exactly one response. PollConn abstracts this into a standard streaming net.Conn by managing the request-response cycle internally.

When the user writes data, it is queued and sent as the payload of the next request. When idle, PollConn automatically polls the server at a configurable interval to retrieve any server-initiated data. Response data is buffered and made available through Read.

Important: The underlying connection must support sending empty (zero-length) writes that still trigger a round-trip with the server. This is typically achieved by wrapping the connection with a framing layer (e.g., DemuxClient) that adds a header to every write.

Typical usage with DNS tunneling:

dnstConn := dnst.NewDNSTClientConn(transport, domain)
demuxClient, _ := netx.NewDemuxClient(dnstConn, sessionID)()
persistent := netx.NewPollConn(demuxClient)
// Use persistent as a regular net.Conn

Index

Constants

View Source
const (
	IPv4 ipV = 4
	IPv6 ipV = 6
)
View Source
const (
	TransportICMP = "icmp" // ip:1
	TransportTCP  = "tcp"  // ip:6
	TransportUDP  = "udp"  // ip:17
)

Variables

View Source
var (
	ErrClosedListener      = errors.New("icmp: listener closed")
	ErrListenQueueExceeded = errors.New("icmp: listen queue exceeded")
	ErrInvalidBatchConfig  = errors.New("icmp: invalid batch config")
)

Typed errors.

View Source
var ErrFrameTooLarge = errors.New("framedConn: frame too large")
View Source
var (
	ErrServerClosed = errors.New("server is shutting down")
)

Functions

func ConnWrapListener added in v1.3.0

func ConnWrapListener(ln net.Listener, wrapConn func(net.Conn) (net.Conn, error)) (net.Listener, error)

ConnWrapListener adapts a ConnToConn wrapper to a ListenerToListener wrapper.

func Dial added in v1.1.0

func Dial(ctx context.Context, network, addr string, opts ...DialOption) (net.Conn, error)

func Listen added in v1.1.0

func Listen(ctx context.Context, network, addr string, opts ...ListenOption) (net.Listener, error)

func NewDemux added in v1.3.0

func NewDemux(c net.Conn, idMask int, opts ...DemuxOption) net.Listener

NewDemux creates a new Demux. Demux implements a simple connection multiplexer that allows multiple virtual connections (sessions) to be multiplexed over a single underlying net.Conn. idMask: The length of the session ID prefix in bytes.

func NewFramedConn

func NewFramedConn(c net.Conn, opts ...FramedConnOption) net.Conn

NewFramedConn wraps a net.Conn with a simple length-prefixed framing protocol. Each frame is prefixed with a 4-byte big-endian unsigned integer indicating the length of the frame. If the frame size exceeds maxFrameSize, Read will return ErrFrameTooLarge. The default maxFrameSize is 4KB.

func NewICMPClientConn added in v1.2.0

func NewICMPClientConn(conn net.Conn, version ipV) (net.Conn, error)

func NewICMPServerConn added in v1.2.0

func NewICMPServerConn(conn net.Conn, version ipV) (net.Conn, error)

func NewMux added in v1.3.0

func NewMux(ln net.Listener) net.Conn

NewMux wraps a net.Listener as a net.Conn. Reads accept connections from the listener on demand and transition to the next connection transparently when the current one reaches EOF. Writes are sent to the most recently accepted connection. Closing the returned conn closes both the current connection (if any) and the listener.

func NewMuxClient added in v1.3.0

func NewMuxClient(dial Dialer, opts ...MuxClientOption) net.Conn

NewMuxClient wraps a dial function as a net.Conn. A new connection is obtained by calling dial on the first Read/Write and whenever the current connection reaches EOF or encounters an error. Closing the returned conn closes the current underlying connection (if any) and prevents further dialling.

func NewPollConn added in v1.3.0

func NewPollConn(conn net.Conn, opts ...PollConnOption) net.Conn

NewPollConn wraps a request-response net.Conn to provide persistent bidirectional connection semantics. The underlying connection operates in lock-step: each write (request) is followed by a read (response). PollConn manages this cycle automatically.

func NewTaggedDemux added in v1.3.0

func NewTaggedDemux(c TaggedConn, idMask int, opts ...DemuxOption) net.Listener

NewDemuxTagged creates a new Demux with a TaggedConn. Demux implements a simple connection multiplexer that allows multiple virtual connections (sessions) to be multiplexed over a single underlying TaggedConn. idMask: The length of the session ID prefix in bytes.

func Register added in v1.3.0

func Register(name string, d Driver)

func TaggedPipe added in v1.3.0

func TaggedPipe() (TaggedConn, TaggedConn)

Types

type BufConn

type BufConn interface {
	net.Conn
	Flush() error
}

func NewBufConn

func NewBufConn(c net.Conn, opts ...BufConnOption) BufConn

NewBufConn wraps a net.Conn with buffered reader and writer. By default, the buffer size is 4KB. Use WithBufWriterSize and WithBufReaderSize to customize the sizes.

type BufConnOption added in v1.1.0

type BufConnOption func(*bufConn)

func WithBufReaderSize

func WithBufReaderSize(size uint16) BufConnOption

func WithBufSize added in v1.1.0

func WithBufSize(size uint16) BufConnOption

func WithBufWriterSize

func WithBufWriterSize(size uint16) BufConnOption

type ClientWrappers added in v1.3.0

type ClientWrappers struct {
	Wrappers
}

func (*ClientWrappers) UnmarshalText added in v1.3.0

func (ls *ClientWrappers) UnmarshalText(text []byte) error

type DemuxClientOption added in v1.3.0

type DemuxClientOption func(*demuxClient)

func WithDemuxClientBufSize added in v1.3.0

func WithDemuxClientBufSize(size uint16) DemuxClientOption

WithDemuxClientBufSize sets the size of the write buffer for the demux client. This controls how much data is written to the underlying connection at once. Default is 4096.

type DemuxOption added in v1.3.0

type DemuxOption func(*demuxCore)

func WithDemuxAccQueueSize added in v1.3.0

func WithDemuxAccQueueSize(size uint16) DemuxOption

WithAccQueueSize sets the size of the accept queue for new sessions. Default is 0.

func WithDemuxBufSize added in v1.3.0

func WithDemuxBufSize(size uint16) DemuxOption

WithSessionBufSize sets the size of the read and write buffers for each session. This controls how much data is read or written from the underlying connection at once. Default is 4096.

func WithDemuxSessQueueSize added in v1.3.0

func WithDemuxSessQueueSize(size uint16) DemuxOption

WithSessQueueSize sets the size of the read and write queues of the sessions. Default is 8.

type DialOption added in v1.1.0

type DialOption func(*dialCfg)

func WithDialConfig added in v1.1.0

func WithDialConfig(cfg net.Dialer) DialOption

type Dialer added in v1.3.0

type Dialer = func() (net.Conn, error)

Dialer is the function signature accepted by NewMuxClient. It should return a new net.Conn each time it is called.

func ConnWrapDialer added in v1.3.0

func ConnWrapDialer(dial Dialer, wrapConn func(net.Conn) (net.Conn, error)) (Dialer, error)

ConnWrapDialer adapts a ConnToConn wrapper to a DialerToDialer wrapper.

func NewDemuxClient added in v1.3.0

func NewDemuxClient(c net.Conn, id []byte, opts ...DemuxClientOption) Dialer

type DialerScheme added in v1.3.0

type DialerScheme struct {
	Scheme
}

func (DialerScheme) Dial added in v1.3.0

func (c DialerScheme) Dial(ctx context.Context, addr string, opts ...DialOption) (net.Conn, error)

func (*DialerScheme) UnmarshalText added in v1.3.0

func (c *DialerScheme) UnmarshalText(text []byte) error

type DialerTransport added in v1.3.0

type DialerTransport struct {
	Transport
}

func (*DialerTransport) Dial added in v1.3.0

func (t *DialerTransport) Dial(ctx context.Context, addr string, opts ...DialOption) (net.Conn, error)

func (*DialerTransport) UnmarshalText added in v1.3.0

func (t *DialerTransport) UnmarshalText(text []byte) error

type DialerURI added in v1.3.0

type DialerURI struct {
	URI
}

func (DialerURI) Dial added in v1.3.0

func (u DialerURI) Dial(ctx context.Context, opts ...DialOption) (net.Conn, error)

func (*DialerURI) UnmarshalText added in v1.3.0

func (u *DialerURI) UnmarshalText(text []byte) error

type DialerWrapper added in v1.3.0

type DialerWrapper struct {
	Wrapper
}

func (*DialerWrapper) UnmarshalText added in v1.3.0

func (ls *DialerWrapper) UnmarshalText(text []byte) error

type Driver added in v1.3.0

type Driver func(params map[string]string, listener bool) (Wrapper, error)

func GetDriver added in v1.3.0

func GetDriver(name string) (Driver, error)

type FramedConnOption added in v1.1.0

type FramedConnOption func(*framedConn)

func WithMaxFrameSize

func WithMaxFrameSize(size uint16) FramedConnOption

type Handler

type Handler func(ctx context.Context, conn net.Conn, closed func()) (matched bool, wrappedConn io.Closer)

Handler is a function that takes a base context, a net.Conn representing the incoming connection, and a closed function that should be called when the user is done with the connection. It returns a boolean indicating whether the connection matches the handler and a wrappedConn server can continue using for closing them.

You should implement Handler in a way that it returns true for connections that should be handled by this handler. If the connection does not match, return false and nil wrappedConn. Do not call the closed function. Matching should be handled early, as the server will simply try handlers to find a match. The wrappedConn can be the same as the input conn, or a wrapped version of it (e.g. with TLS, obfuscation, etc).

type ListenOption added in v1.1.0

type ListenOption func(*listenCfg)

func WithListenConfig added in v1.1.0

func WithListenConfig(cfg net.ListenConfig) ListenOption

func WithPacketListenConfig added in v1.1.0

func WithPacketListenConfig(cfg pudp.ListenConfig) ListenOption

type ListenerScheme added in v1.3.0

type ListenerScheme struct {
	Scheme
}

func (ListenerScheme) Listen added in v1.3.0

func (s ListenerScheme) Listen(ctx context.Context, addr string, opts ...ListenOption) (net.Listener, error)

func (*ListenerScheme) UnmarshalText added in v1.3.0

func (s *ListenerScheme) UnmarshalText(text []byte) error

type ListenerTransport added in v1.3.0

type ListenerTransport struct {
	Transport
}

func (*ListenerTransport) Listen added in v1.3.0

func (t *ListenerTransport) Listen(ctx context.Context, addr string, opts ...ListenOption) (net.Listener, error)

func (*ListenerTransport) UnmarshalText added in v1.3.0

func (t *ListenerTransport) UnmarshalText(text []byte) error

type ListenerURI added in v1.3.0

type ListenerURI struct {
	URI
}

func (ListenerURI) Listen added in v1.3.0

func (u ListenerURI) Listen(ctx context.Context, opts ...ListenOption) (net.Listener, error)

func (*ListenerURI) UnmarshalText added in v1.3.0

func (u *ListenerURI) UnmarshalText(text []byte) error

type ListenerWrapper added in v1.3.0

type ListenerWrapper struct {
	Wrapper
}

func (*ListenerWrapper) UnmarshalText added in v1.3.0

func (ls *ListenerWrapper) UnmarshalText(text []byte) error

type Logger

type Logger interface {
	DebugContext(ctx context.Context, msg string, args ...any)
	InfoContext(ctx context.Context, msg string, args ...any)
	WarnContext(ctx context.Context, msg string, args ...any)
	ErrorContext(ctx context.Context, msg string, args ...any)
}

type MuxClientOption added in v1.3.0

type MuxClientOption func(*muxClient)

func WithMuxClientLocalAddr added in v1.3.0

func WithMuxClientLocalAddr(addr net.Addr) MuxClientOption

WithMuxClientLocalAddr sets the address returned by LocalAddr when no underlying connection is established yet.

func WithMuxClientRemoteAddr added in v1.3.0

func WithMuxClientRemoteAddr(addr net.Addr) MuxClientOption

WithMuxClientRemoteAddr sets the address returned by RemoteAddr when no underlying connection is established yet.

type PipeType added in v1.3.0

type PipeType int

PipeType represents the type flowing through the wrapper pipeline.

const (
	PipeTypeListener   PipeType = iota // net.Listener
	PipeTypeDialer                     // Dialer
	PipeTypeConn                       // net.Conn
	PipeTypeTaggedConn                 // TaggedConn
)

func (PipeType) String added in v1.3.0

func (p PipeType) String() string

type PollConnOption added in v1.3.0

type PollConnOption func(*pollConn)

func WithPollBufSize added in v1.3.0

func WithPollBufSize(size uint16) PollConnOption

WithPollBufSize sets the read buffer size for reading responses from the underlying connection. Default is 4096.

func WithPollInterval added in v1.3.0

func WithPollInterval(d time.Duration) PollConnOption

WithPollInterval sets the polling interval for idle cycles. When no user data is queued, PollConn waits this duration before sending an empty request to check for server-initiated data. Default is 100ms.

func WithPollRecvQueueSize added in v1.3.0

func WithPollRecvQueueSize(size uint16) PollConnOption

WithPollRecvQueueSize sets the capacity of the receive queue. When full, the poll loop blocks until the user reads, providing natural backpressure. Default is 32.

func WithPollSendQueueSize added in v1.3.0

func WithPollSendQueueSize(size uint16) PollConnOption

WithPollSendQueueSize sets the capacity of the send queue. Write calls block when this queue is full, providing natural backpressure. Default is 32.

type Scheme added in v1.3.0

type Scheme struct {
	Transport
	// contains filtered or unexported fields
}

func (Scheme) MarshalText added in v1.3.0

func (s Scheme) MarshalText() ([]byte, error)

func (Scheme) String added in v1.3.0

func (s Scheme) String() string

func (*Scheme) UnmarshalText added in v1.3.0

func (s *Scheme) UnmarshalText(text []byte, listener bool) error

type Server

type Server[ID comparable] struct {
	Logger Logger
	// contains filtered or unexported fields
}

Server initially accepts no connections, since there are no initial handlers. It's the duty of the caller to add handlers via SetRoute. The generic ID type is used to identify different handlers, e.g. packet header, http path, remote address, username, etc.

func (*Server[ID]) Close

func (s *Server[ID]) Close() error

func (*Server[ID]) RemoveRoute

func (s *Server[ID]) RemoveRoute(id ID)

RemoveRoute removes a handler by its ID. It does not close any existing connections that were created by this handler.

func (*Server[ID]) Serve

func (s *Server[ID]) Serve(ctx context.Context, listener net.Listener) error

func (*Server[ID]) SetRoute

func (s *Server[ID]) SetRoute(id ID, handler Handler)

SetRoute sets a handler for a specific ID. If a handler already exists for this ID, it will be replaced. It does not close any existing connections that were created by the previous handler, but new connections will use the new handler.

func (*Server[ID]) Shutdown

func (s *Server[ID]) Shutdown(ctx context.Context) error

Shutdown gracefully shuts down the server without interrupting active connections. It stops accepting new connections and waits until all tracked connections finish or the provided context is done. If the context is done before all connections finish, Shutdown will force-close remaining connections and return the context error joined with any listener close error.

type ServerWrappers added in v1.3.0

type ServerWrappers struct {
	Wrappers
}

func (*ServerWrappers) UnmarshalText added in v1.3.0

func (ls *ServerWrappers) UnmarshalText(text []byte) error

type TaggedConn added in v1.3.0

type TaggedConn interface {
	// ReadTagged reads data into the provided buffer and returns the number of bytes read along with any error encountered.
	// The tag parameter is a pointer to an empty interface that will be populated with contextual information associated with the read data.
	ReadTagged([]byte, *any) (int, error)

	// WriteTagged writes data from the provided buffer and returns the number of bytes written along with any error encountered.
	// The tag parameter provides contextual information that may be required for the write operation, such as associating it with a specific request or metadata.
	// If unsure, you should pass the previously set tag from a ReadTagged call.
	// For example:
	// var tag any
	// n, err := conn.ReadTagged(buf, &tag)
	// if err != nil {
	//     // handle error
	// }
	// // process buf[:n] and tag as needed
	// _, err = conn.WriteTagged(responseBuf, tag)
	// if err != nil {
	//     // handle error
	// }
	WriteTagged([]byte, any) (int, error)

	Close() error
	LocalAddr() net.Addr
	RemoteAddr() net.Addr
	SetDeadline(t time.Time) error
	SetReadDeadline(t time.Time) error
	SetWriteDeadline(t time.Time) error
}

TaggedConn is a net.Conn extension that in its taggable type allows passing contextual information along with read/write operations. This is useful for protocols where the write path requires context from the read path (e.g. associating a response with a specific request in a tunnel), or when metadata needs to traverse through layers (e.g. Muxed Transport <-> Encryption <-> Demux).

type Transport added in v1.3.0

type Transport string

func (Transport) MarshalText added in v1.3.0

func (t Transport) MarshalText() ([]byte, error)

func (Transport) String added in v1.3.0

func (t Transport) String() string

func (*Transport) UnmarshalText added in v1.3.0

func (t *Transport) UnmarshalText(text []byte, listener bool) error

type Tun

type Tun struct {
	Logger     Logger
	Conn       net.Conn
	Peer       net.Conn
	BufferSize uint // BufferSize for io.Copy, default 32KB
	// contains filtered or unexported fields
}

Tun is an endpoint of a tunnel connection between two net.Conns. Conn is the underlying connection of the tunnel and Peer is the client/server communicating with the tunnel.

func (*Tun) Close

func (t *Tun) Close() error

func (*Tun) Relay

func (t *Tun) Relay(ctx context.Context)

Relay copies data between the two connections until

type TunHandler

type TunHandler func(ctx context.Context, conn net.Conn) (matched bool, connCtx context.Context, tunnel Tun)

type TunMaster

type TunMaster[ID comparable] struct {
	Server[ID]
}

TunMaster initially accepts no connections, since there are no known tunnel handlers. It's the duty of the caller to add tunnel handlers via SetHandler. The generic ID type is used to identify different tunnel handlers, e.g. by a client ID or username.

func (*TunMaster[ID]) SetRoute

func (m *TunMaster[ID]) SetRoute(id ID, handler TunHandler)

SetRoute sets a tunnel handler for a specific ID. If a handler already exists for this ID, it will be replaced. It does not close any existing tunnels that were created by the previous handler, but new tunnels will use the new handler.

type URI added in v1.3.0

type URI struct {
	Scheme `json:"scheme"`
	Addr   string `json:"addr"`
}

func (URI) MarshalText added in v1.3.0

func (u URI) MarshalText() ([]byte, error)

func (URI) String added in v1.3.0

func (u URI) String() string

func (*URI) UnmarshalText added in v1.3.0

func (u *URI) UnmarshalText(text []byte, server bool) error

type Wrapper added in v1.3.0

type Wrapper struct {
	Name     string
	Params   map[string]string
	Listener bool

	ListenerToListener func(net.Listener) (net.Listener, error)
	ListenerToConn     func(net.Listener) (net.Conn, error)

	DialerToDialer func(Dialer) (Dialer, error)
	DialerToConn   func(Dialer) (net.Conn, error)

	ConnToConn     func(net.Conn) (net.Conn, error)
	ConnToTagged   func(net.Conn) (TaggedConn, error)
	ConnToListener func(net.Conn) (net.Listener, error)
	ConnToDialer   func(net.Conn) (Dialer, error)

	TaggedToTagged   func(TaggedConn) (TaggedConn, error)
	TaggedToListener func(TaggedConn) (net.Listener, error)
}

Wrapper represents a transformation in the layer pipeline. Maximum one function field per input type (Listener, Dialer, Conn, TaggedConn) should be set. The Apply method applies the wrapper to a value, transforming it according to the set function field.

func (Wrapper) Apply added in v1.3.0

func (w Wrapper) Apply(v any) (any, error)

Apply transforms the pipeline value through this wrapper. The input must match the expected type of the set function field. Returns the transformed value or an error if the type doesn't match.

func (Wrapper) InputTypes added in v1.3.0

func (w Wrapper) InputTypes() []PipeType

func (Wrapper) MarshalText added in v1.3.0

func (w Wrapper) MarshalText() ([]byte, error)

func (Wrapper) OutputFor added in v1.3.0

func (w Wrapper) OutputFor(input PipeType) (PipeType, bool)

OutputFor returns the output PipeType when this wrapper receives the given input type. Returns (outputType, true) if the wrapper supports the input type, or (0, false) otherwise.

func (Wrapper) String added in v1.3.0

func (w Wrapper) String() string

func (*Wrapper) UnmarshalText added in v1.3.0

func (w *Wrapper) UnmarshalText(text []byte, listener bool) error

type Wrappers added in v1.3.0

type Wrappers []Wrapper

func (Wrappers) Apply added in v1.3.0

func (ws Wrappers) Apply(conn any) (any, error)

func (Wrappers) MarshalText added in v1.3.0

func (ws Wrappers) MarshalText() ([]byte, error)

func (Wrappers) String added in v1.3.0

func (ws Wrappers) String() string

func (*Wrappers) UnmarshalText added in v1.3.0

func (ws *Wrappers) UnmarshalText(text []byte, server bool) error

Directories

Path Synopsis
cli module
drivers
aesgcm module
dnst module
dtls module
dtlspsk module
ssh module
tls module
tlspsk module
utls module
proto
aesgcm module
dnst module
ssh module

Jump to

Keyboard shortcuts

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