client

package
v0.2.6 Latest Latest
Warning

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

Go to latest
Published: Mar 29, 2026 License: CC0-1.0 Imports: 29 Imported by: 0

Documentation

Overview

Package client provides a reusable DNS tunnel client library.

It provides configuration options for VayDNS features (DoH/DoT transports, per-query UDP, forged response filtering, rate limiting, dnstt wire compatibility, etc.).

Basic usage (xray-core compatible):

r, _ := client.NewResolver(client.ResolverTypeUDP, "8.8.8.8:53")
ts, _ := client.NewTunnelServer("t.example.com", "pubkey-hex")
t, _ := client.NewTunnel(r, ts)
t.InitiateResolverConnection()
t.InitiateDNSPacketConn(ts.Addr)
t.InitiateKCPConn(ts.MTU)
t.InitiateNoiseChannel()
t.InitiateSmuxSession()
stream, _ := t.OpenStream() // returns net.Conn
defer t.Close()

Index

Constants

View Source
const (
	DefaultIdleTimeout          = 60 * time.Second
	DefaultKeepAlive            = 10 * time.Second
	DefaultOpenStreamTimeout    = 10 * time.Second
	DefaultReconnectDelay       = 1 * time.Second
	DefaultReconnectMaxDelay    = 30 * time.Second
	DefaultSessionCheckInterval = 20 * time.Second
	DefaultUDPResponseTimeout   = 500 * time.Millisecond
	DefaultUDPWorkers           = 100
	DefaultMaxStreams           = 256
	DefaultHandshakeTimeout     = 15 * time.Second
)

Default timeouts for VayDNS mode.

View Source
const (
	DnsttIdleTimeout = 2 * time.Minute
	DnsttKeepAlive   = 10 * time.Second
)

Default timeouts for dnstt compatibility mode.

Variables

This section is empty.

Functions

func DNSNameCapacity

func DNSNameCapacity(domain dns.Name, maxQnameLen int, maxNumLabels int) int

DNSNameCapacity returns the number of raw bytes that can be encoded in a DNS query name, given the domain suffix and encoding constraints.

func NewUDPPacketConn

func NewUDPPacketConn(remoteAddr net.Addr, dialerControl func(network, address string, c syscall.RawConn) error, numWorkers int, responseTimeout time.Duration, ignoreErrors bool, queueSize int, overflowMode turbotunnel.QueueOverflowMode) (*UDPPacketConn, *ForgedStats, error)

NewUDPPacketConn creates a UDPPacketConn with numWorkers goroutines that each send one query at a time on a fresh UDP socket. The returned ForgedStats pointer is shared with the caller so DNSPacketConn can include per-query forged counts in its reporting.

func NewUTLSRoundTripper

func NewUTLSRoundTripper(config *utls.Config, id *utls.ClientHelloID) *utlsRoundTripper

NewUTLSRoundTripper creates a utlsRoundTripper with the given TLS configuration and ClientHelloID.

func SampleUTLSDistribution

func SampleUTLSDistribution(spec string) (*utls.ClientHelloID, error)

SampleUTLSDistribution parses a weighted distribution string (e.g., "3*Firefox,2*Chrome,1*iOS") and randomly selects a ClientHelloID.

func UTLSClientHelloIDMap

func UTLSClientHelloIDMap() []struct {
	Label string
	ID    *utls.ClientHelloID
}

UTLSClientHelloIDMap returns the list of supported uTLS fingerprint labels.

func UTLSDialContext

func UTLSDialContext(ctx context.Context, network, addr string, config *utls.Config, id *utls.ClientHelloID) (*utls.UConn, error)

utlsDialContext connects to the given network address and initiates a TLS handshake with the provided ClientHelloID, and returns the resulting TLS connection. UTLSDialContext connects to the given network address and initiates a TLS handshake with the provided ClientHelloID, and returns the resulting TLS connection.

func UTLSLookup

func UTLSLookup(label string) *utls.ClientHelloID

UTLSLookup returns a *utls.ClientHelloID by case-insensitive label match, or nil if there is no match.

Types

type DNSPacketConn

type DNSPacketConn struct {

	// QueuePacketConn is the direct receiver of ReadFrom and WriteTo calls.
	// recvLoop and sendLoop take the messages out of the receive and send
	// queues and actually put them on the network.
	*turbotunnel.QueuePacketConn
	// contains filtered or unexported fields
}

DNSPacketConn provides a packet-sending and -receiving interface over various forms of DNS. It handles the details of how packets and padding are encoded as a DNS name in the Question section of an upstream query, and as a TXT RR in downstream responses.

DNSPacketConn does not handle the mechanics of actually sending and receiving encoded DNS messages. That is rather the responsibility of some other net.PacketConn such as net.UDPConn, HTTPPacketConn, or TLSPacketConn, one of which must be provided to NewDNSPacketConn.

We don't have a need to match up a query and a response by ID. Queries and responses are vehicles for carrying data and for our purposes don't need to be correlated. When sending a query, we generate a random ID, and when receiving a response, we ignore the ID.

func NewDNSPacketConn

func NewDNSPacketConn(transport net.PacketConn, addr net.Addr, domain dns.Name, rateLimiter *RateLimiter, maxQnameLen int, maxNumLabels int, wireConfig turbotunnel.WireConfig, forgedStats *ForgedStats, rrType uint16, queueSize int, overflowMode turbotunnel.QueueOverflowMode) *DNSPacketConn

NewDNSPacketConn creates a new DNSPacketConn. transport, through its WriteTo and ReadFrom methods, handles the actual sending and receiving the DNS messages encoded by DNSPacketConn. addr is the address to be passed to transport.WriteTo whenever a message needs to be sent. maxQnameLen is the max total QNAME length (0 = 253 per RFC 1035). maxNumLabels is the max number of data labels (0 = unlimited). forgedStats is shared with the transport layer (e.g. UDPPacketConn) for consistent forged response tracking; if nil, a new instance is created.

func (*DNSPacketConn) TransportErrors

func (c *DNSPacketConn) TransportErrors() <-chan error

TransportErrors returns a channel that receives errors from the underlying transport goroutines (recvLoop and sendLoop).

type ForgedStats added in v0.2.1

type ForgedStats struct {
	Total    uint64
	SERVFAIL uint64
	NXDOMAIN uint64
	Other    uint64
}

ForgedStats tracks forged DNS response counters. It is shared between UDPPacketConn (per-query mode) and DNSPacketConn (shared socket mode) so that forged response visibility is consistent regardless of transport.

func (*ForgedStats) Record added in v0.2.1

func (s *ForgedStats) Record(rcode uint16)

Record increments the appropriate counter for the given RCODE and logs a summary at INFO level at milestone counts. Non-milestone forged responses are silently counted.

type HTTPPacketConn

type HTTPPacketConn struct {

	// QueuePacketConn is the direct receiver of ReadFrom and WriteTo calls.
	// sendLoop, via send, removes messages from the outgoing queue that
	// were placed there by WriteTo, and inserts messages into the incoming
	// queue to be returned from ReadFrom.
	*turbotunnel.QueuePacketConn
	// contains filtered or unexported fields
}

HTTPPacketConn is an HTTP-based transport for DNS messages, used for DNS over HTTPS (DoH). Its WriteTo and ReadFrom methods exchange DNS messages over HTTP requests and responses.

HTTPPacketConn deals only with already formatted DNS messages. It does not handle encoding information into the messages. That is rather the responsibility of DNSPacketConn.

https://tools.ietf.org/html/rfc8484

func NewHTTPPacketConn

func NewHTTPPacketConn(rt http.RoundTripper, urlString string, numSenders int, queueSize int, overflowMode turbotunnel.QueueOverflowMode) (*HTTPPacketConn, error)

NewHTTPPacketConn creates a new HTTPPacketConn configured to use the HTTP server at urlString as a DNS over HTTP resolver. client is the http.Client that will be used to make requests. urlString should include any necessary path components; e.g., "/dns-query". numSenders is the number of concurrent sender-receiver goroutines to run.

type Outbound

type Outbound struct {
	Resolvers     []Resolver
	TunnelServers []TunnelServer
	// contains filtered or unexported fields
}

Outbound provides a high-level API for creating tunnels from multiple resolvers and tunnel servers.

func NewOutbound

func NewOutbound(resolvers []Resolver, tunnelServers []TunnelServer) *Outbound

NewOutbound creates an Outbound with the given resolvers and tunnel servers.

func (*Outbound) Start

func (o *Outbound) Start(bind string) error

Start begins accepting connections on bind and forwarding them through the first resolver/server pair.

type RateLimiter

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

RateLimiter implements a token bucket rate limiter for DNS queries.

func NewRateLimiter

func NewRateLimiter(rps float64) *RateLimiter

NewRateLimiter creates a new token bucket rate limiter with the given queries-per-second rate. Returns nil for non-positive or invalid values, which means unlimited.

func (*RateLimiter) Wait

func (rl *RateLimiter) Wait()

Wait blocks until a token is available. It is safe to call on a nil receiver (no-op), which allows clean "unlimited" behavior without nil checks at call sites.

type Resolver

type Resolver struct {
	ResolverType ResolverType
	ResolverAddr string // UDP: "1.1.1.1:53", DoT: "resolver:853", DoH: "https://resolver/dns-query"

	// UTLSClientHelloID sets the uTLS fingerprint for DoH/DoT connections.
	// nil means no uTLS (plain TLS).
	UTLSClientHelloID *utls.ClientHelloID

	// RoundTripper overrides the HTTP transport for DoH. If set,
	// UTLSClientHelloID is ignored for DoH.
	RoundTripper http.RoundTripper

	// DialerControl is an optional callback for setting socket options
	// (SO_MARK, SO_BINDTODEVICE, etc.) on UDP sockets.
	DialerControl func(network, address string, c syscall.RawConn) error

	// UDP transport settings (only apply to ResolverTypeUDP).
	UDPWorkers      int           // concurrent UDP workers (0 = DefaultUDPWorkers)
	UDPSharedSocket bool          // use single shared socket instead of per-query
	UDPTimeout      time.Duration // per-query response timeout (0 = DefaultUDPResponseTimeout)
	UDPAcceptErrors bool          // pass through non-NOERROR responses (default: filter)
}

Resolver holds DNS resolver configuration.

func NewResolver

func NewResolver(resolverType ResolverType, resolverAddr string) (Resolver, error)

NewResolver creates a Resolver with the given type and address.

type ResolverType

type ResolverType string

ResolverType identifies the DNS transport to use.

const (
	ResolverTypeUDP ResolverType = "udp"
	ResolverTypeDOT ResolverType = "dot"
	ResolverTypeDOH ResolverType = "doh"
)

type TLSPacketConn

type TLSPacketConn struct {
	// QueuePacketConn is the direct receiver of ReadFrom and WriteTo calls.
	// recvLoop and sendLoop take the messages out of the receive and send
	// queues and actually put them on the network.
	*turbotunnel.QueuePacketConn
	// contains filtered or unexported fields
}

TLSPacketConn is a TLS- and TCP-based transport for DNS messages, used for DNS over TLS (DoT). Its WriteTo and ReadFrom methods exchange DNS messages over a TLS channel, prefixing each message with a two-octet length field as in DNS over TCP.

TLSPacketConn deals only with already formatted DNS messages. It does not handle encoding information into the messages. That is rather the responsibility of DNSPacketConn.

https://tools.ietf.org/html/rfc7858

func NewTLSPacketConn

func NewTLSPacketConn(addr string, dialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error), queueSize int, overflowMode turbotunnel.QueueOverflowMode) (*TLSPacketConn, error)

NewTLSPacketConn creates a new TLSPacketConn configured to use the TLS server at addr as a DNS over TLS resolver. It maintains a TLS connection to the resolver, reconnecting as necessary. It closes the connection if any reconnection attempt fails.

func (*TLSPacketConn) Close added in v0.2.6

func (c *TLSPacketConn) Close() error

Close tears down both the packet queue and any active TLS connection, ensuring the reconnect goroutine exits.

type Tunnel

type Tunnel struct {
	Resolver     Resolver
	TunnelServer TunnelServer

	// Session configuration. Zero values use defaults.
	IdleTimeout          time.Duration                 // default: 10s (2m with DnsttCompat)
	KeepAlive            time.Duration                 // default: 2s (10s with DnsttCompat)
	OpenStreamTimeout    time.Duration                 // default: 10s
	MaxStreams           int                           // default: 256 (0 = unlimited)
	ReconnectMinDelay    time.Duration                 // default: 1s
	ReconnectMaxDelay    time.Duration                 // default: 30s
	SessionCheckInterval time.Duration                 // default: 20s
	HandshakeTimeout     time.Duration                 // default: 30s
	PacketQueueSize      int                           // default: QueueSize (512)
	KCPWindowSize        int                           // default: PacketQueueSize/2
	QueueOverflowMode    turbotunnel.QueueOverflowMode // default: drop
	// contains filtered or unexported fields
}

Tunnel represents a DNS tunnel connection. Create with NewTunnel, then either call the step-by-step Initiate* methods (for embedding in frameworks like xray-core) or call ListenAndServe for a fully managed session.

func NewTunnel

func NewTunnel(resolver Resolver, tunnelServer TunnelServer) (*Tunnel, error)

NewTunnel creates a Tunnel with the given resolver and server configuration. Zero-value fields use sensible defaults.

func (*Tunnel) Close

func (t *Tunnel) Close() error

Close tears down the tunnel and all its layers.

func (*Tunnel) Handle

func (t *Tunnel) Handle(lconn *net.TCPConn) error

Handle forwards data between a local TCP connection and a tunnel stream.

func (*Tunnel) InitiateDNSPacketConn

func (t *Tunnel) InitiateDNSPacketConn(domain dns.Name) error

InitiateDNSPacketConn wraps the resolver connection with DNS encoding.

func (*Tunnel) InitiateKCPConn

func (t *Tunnel) InitiateKCPConn(mtu int) error

InitiateKCPConn opens a KCP connection over the DNS packet connection. If mtu is 0, it is auto-computed from the domain and QNAME constraints.

func (*Tunnel) InitiateNoiseChannel

func (t *Tunnel) InitiateNoiseChannel() error

InitiateNoiseChannel performs the Noise protocol handshake with a timeout. The timeout is controlled by HandshakeTimeout (default 30s).

func (*Tunnel) InitiateResolverConnection

func (t *Tunnel) InitiateResolverConnection() error

InitiateResolverConnection creates the underlying transport connection based on the Resolver configuration.

func (*Tunnel) InitiateSmuxSession

func (t *Tunnel) InitiateSmuxSession() error

InitiateSmuxSession establishes a multiplexed session over the Noise channel.

func (*Tunnel) ListenAndServe

func (t *Tunnel) ListenAndServe(listenAddr string) error

ListenAndServe starts a TCP listener and forwards connections through the tunnel with automatic session reconnection. This is the main entry point for the CLI.

func (*Tunnel) OpenStream

func (t *Tunnel) OpenStream() (net.Conn, error)

OpenStream opens a new multiplexed stream. Returns a net.Conn.

type TunnelServer

type TunnelServer struct {
	Addr   dns.Name
	PubKey string
	MTU    int // auto-computed if 0 when InitiateKCPConn is called

	// DnsttCompat enables the original dnstt wire format (8-byte ClientID,
	// padding prefixes). When true, ClientIDSize is forced to 8.
	DnsttCompat bool

	// ClientIDSize is the ClientID size in bytes (default: 2).
	// Ignored when DnsttCompat is true.
	ClientIDSize int

	// MaxQnameLen is the maximum QNAME wire length (default: 101, or 253 with DnsttCompat).
	MaxQnameLen int

	// MaxNumLabels is the maximum number of data labels (default: 0 = unlimited).
	MaxNumLabels int

	// RPS limits outgoing DNS queries per second (default: 0 = unlimited).
	RPS float64

	// RecordType selects the DNS record type for downstream data.
	// Supported values: "txt" (default), "cname", "a", "aaaa", "mx", "ns", "srv".
	RecordType string
	// contains filtered or unexported fields
}

TunnelServer holds tunnel server configuration (domain + public key).

func NewTunnelServer

func NewTunnelServer(addr string, pubKeyString string) (TunnelServer, error)

NewTunnelServer creates a TunnelServer from a domain string and hex-encoded public key.

type UDPPacketConn

type UDPPacketConn struct {
	*turbotunnel.QueuePacketConn
	// contains filtered or unexported fields
}

UDPPacketConn implements net.PacketConn using per-query UDP sockets. Each outgoing DNS query is sent from a fresh socket with a random source port, making the tunnel harder to fingerprint and more resilient to censorship injection attacks that target a single source port.

A pool of worker goroutines dequeues packets from the embedded QueuePacketConn, sends each as a DNS query on a new socket, and reads the response back into the incoming queue. When ignoreErrors is true (default), workers skip non-NOERROR responses and keep reading until a valid response arrives or the per-query timeout expires — this defeats forged error injection by censors.

Jump to

Keyboard shortcuts

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