Documentation
¶
Overview ¶
Client side of the DNS tunnel: dials out through upstream DNS resolvers.
Package dnstunnel implements a high-performance DNS tunnel with optional Noise_NK Curve25519 AEAD encryption.
Embedding the tunnel in other programs ¶
The tunnel can be used as a library so external programs can borrow it for unified, encrypted access to backend services:
// Client side: every Dial opens an independent tunnel session.
cli, err := dnstunnel.NewClient(dnstunnel.ClientConfig{
Domain: "tunnel.example.com",
Servers: []string{"8.8.8.8:53", "1.1.1.1:53"},
RecordType: "txt",
PublicKey: serverPubKey, // optional Noise_NK key
})
conn, err := cli.Dial(ctx) // stream access (net.Conn)
pconn, err := cli.DialUDP(ctx) // datagram access (net.PacketConn)
// Server side: terminates sessions and forwards to the backend.
srv, err := dnstunnel.NewServer(dnstunnel.ServerConfig{
ListenAddr: ":53",
TargetAddr: "tcp://127.0.0.1:22", // or "udp://127.0.0.1:51820"
Domain: "tunnel.example.com",
PrivateKey: serverPrivKey,
})
err = srv.Run(ctx) // blocks; returns nil on clean ctx cancellation
Dial returns a net.Conn and DialUDP a net.PacketConn, so the tunnel plugs directly into http.Transport.DialContext, database drivers, SSH clients and anything else that consumes standard connection interfaces.
The server routes sessions by the marker inside the session ID: plain stream sessions follow the configured target scheme (tcp:// backends receive a byte stream), while sessions whose ID carries the UDP marker (created by DialUDP) are forwarded as length-framed datagrams over UDP. The session transport must match the target scheme — a UDP-marker session against a tcp:// target is refused, because datagram semantics cannot be preserved toward a stream backend. Stream sessions against udp:// targets are the legacy pre-datagram behavior (datagram boundaries are not preserved) and are kept only for compatibility with older clients.
Client-declared targets ¶
A client may declare the backend it wants per configuration (ClientConfig Target) or per session. The server validates the declaration against ServerConfig AllowTargets — a list of patterns such as "tcp://127.0.0.1:*" or "udp://10.8.0.*:51820" where each of scheme, host and port may be "*" and host wildcards never cross a dot. An empty AllowTargets list means clients cannot override the target. Every exchange answers with the transport that actually applies ("tcp" or "udp"), declared or default, so callers always know which kind of local socket to bind; Client.DefaultTarget probes it without declaring anything.
Server-side of the DNS tunnel: terminates tunnel sessions and forwards their byte streams (or framed UDP datagrams) to a configured backend.
Index ¶
- Variables
- func FormatNoiseKey(key [32]byte) (hexStr, b64Str string)
- func ParseNoiseKey(s string) ([32]byte, error)
- type Client
- type ClientConfig
- type DNSClientTunnel
- func (t *DNSClientTunnel) Close() error
- func (t *DNSClientTunnel) LocalAddr() net.Addr
- func (t *DNSClientTunnel) Read(p []byte) (int, error)
- func (t *DNSClientTunnel) RemoteAddr() net.Addr
- func (t *DNSClientTunnel) SetDeadline(deadline time.Time) error
- func (t *DNSClientTunnel) SetReadDeadline(deadline time.Time) error
- func (t *DNSClientTunnel) SetWriteDeadline(deadline time.Time) error
- func (t *DNSClientTunnel) Transport() string
- func (t *DNSClientTunnel) Write(p []byte) (int, error)
- type DNSServer
- type NoiseCipherState
- type NoiseKeyPair
- type NoiseSession
- type Server
- type ServerConfig
Constants ¶
This section is empty.
Variables ¶
var Version = "1.4.0"
Version is the release version of the tool. Release builds override it via -ldflags "-X github.com/NNdroid/dns_custom.Version=<version>".
Functions ¶
func FormatNoiseKey ¶
FormatNoiseKey formats a 32-byte key to hex and base64
func ParseNoiseKey ¶
ParseNoiseKey parses a 32-byte key from hex or base64 string
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is the library entry point for dialing out through the DNS tunnel. One Client can open any number of independent tunnel sessions via Dial / DialUDP.
func NewClient ¶
func NewClient(cfg ClientConfig) (*Client, error)
NewClient validates the configuration and returns a Client. The public key, when set, is parsed once here so a typo fails at startup instead of on the first dial.
func (*Client) DefaultTarget ¶
DefaultTarget asks the server which transport its default target uses ("tcp" or "udp"). It opens a throwaway session, declares no target, and reads the server's answer — this is how a caller that has no declared target learns which local socket to bind. A server that predates target declarations (or refuses the empty declaration) yields an error.
func (*Client) Dial ¶
Dial opens a new tunnel session and returns it as a stream net.Conn. Each call establishes an independent session (Noise handshake, target declaration, pollers, adaptive window) terminated on the server's backend. When the client has a declared target it must be tcp:// — datagram targets need DialUDP.
func (*Client) DialUDP ¶
DialUDP opens a new tunnel session that carries UDP datagrams to the server's UDP backend. Datagrams are length-framed over the tunnel byte stream, so datagram boundaries survive the trip (unlike the legacy stream mode, which reassembles upstream chunks without preserving boundaries).
When the client has a declared target it must be udp://; the declaration is validated by the server's allow list. Without a declaration the session uses the server's default target, which must itself be udp://.
type ClientConfig ¶
type ClientConfig struct {
Domain string `json:"domain"`
Servers []string `json:"servers"`
RecordType string `json:"record_type"`
PublicKey string `json:"pubkey"`
Target string `json:"target,omitempty"`
EDNS0 bool `json:"edns0,omitempty"`
Logger *zap.SugaredLogger `json:"-"`
// Dialer optionally controls sockets used by UDP, TCP, DoT and DoH paths.
Dialer *net.Dialer `json:"-"`
}
ClientConfig configures a Client. Logger may be left nil for a silent client; the CLI injects its own zap logger here.
Target optionally declares the backend the client wants sessions forwarded to ("tcp://host:port" or "udp://host:port"; host:port alone means tcp). The server only honors it when the target passes its allow_targets list, and the server's answer tells the caller which transport actually applies (see DefaultTarget and DNSClientTunnel.Transport). Leave empty to use whatever default target the server is configured with.
type DNSClientTunnel ¶
type DNSClientTunnel struct {
// contains filtered or unexported fields
}
DNSClientTunnel is one tunnel session: a reliable ordered byte stream over DNS queries, optionally encrypted with Noise_NK. It implements net.Conn, so it can be handed directly to io.Copy, http.Transport.DialContext, database drivers and anything else that consumes connections.
func NewDNSClientTunnel ¶
func NewDNSClientTunnel(ctx context.Context, servers []string, domain string, recordType string, pubKeyStr string) (*DNSClientTunnel, error)
NewDNSClientTunnel opens a single stream tunnel session. Library users usually want Client.Dial instead, which is this constructor behind a reusable, pre-validated Client.
func (*DNSClientTunnel) Close ¶
func (t *DNSClientTunnel) Close() error
func (*DNSClientTunnel) LocalAddr ¶
func (t *DNSClientTunnel) LocalAddr() net.Addr
LocalAddr and RemoteAddr are pseudo addresses identifying this tunnel session; the tunnel has no real socket-level endpoints.
func (*DNSClientTunnel) RemoteAddr ¶
func (t *DNSClientTunnel) RemoteAddr() net.Addr
func (*DNSClientTunnel) SetDeadline ¶
func (t *DNSClientTunnel) SetDeadline(deadline time.Time) error
SetDeadline sets both the read and the write deadline. A zero time disables the deadline. An expired deadline unblocks pending Read/Write calls with os.ErrDeadlineExceeded.
func (*DNSClientTunnel) SetReadDeadline ¶
func (t *DNSClientTunnel) SetReadDeadline(deadline time.Time) error
func (*DNSClientTunnel) SetWriteDeadline ¶
func (t *DNSClientTunnel) SetWriteDeadline(deadline time.Time) error
func (*DNSClientTunnel) Transport ¶
func (t *DNSClientTunnel) Transport() string
Transport reports the backend transport the server confirmed for this session ("tcp" or "udp"). It is set once the target declaration exchange completes; sessions without a declared target learn nothing here and follow the server's default.
type DNSServer ¶
type DNSServer struct {
// contains filtered or unexported fields
}
func NewDNSServer ¶
func NewDNSServer(cfg ServerConfig) (*DNSServer, error)
NewDNSServer builds the tunnel DNS handler. Use this when embedding the handler in an externally managed dns.Server; most callers want NewServer instead.
type NoiseCipherState ¶
type NoiseCipherState struct {
// contains filtered or unexported fields
}
NoiseCipherState wraps the AEAD derived from a Noise_NK handshake.
The nonce is NOT a stream counter: it is supplied explicitly by the caller and is derived from the transport sequence number (upstream = dataSeq, downstream = serverSeq). An auto-incrementing nonce implicitly assumes one encryption per successful delivery in both directions, which DNS cannot provide - queries and answers are lost, duplicated, reordered and retransmitted. With a sequence-derived nonce every message is self-describing: it can arrive any number of times, in any order, and still decrypt, which is exactly what the reliability layer needs.
type NoiseKeyPair ¶
NoiseKeyPair represents a Curve25519 public/private keypair
func GenerateNoiseKeyPair ¶
func GenerateNoiseKeyPair() (*NoiseKeyPair, error)
GenerateNoiseKeyPair generates a random Curve25519 keypair
type NoiseSession ¶
type NoiseSession struct {
SendCipher *NoiseCipherState
RecvCipher *NoiseCipherState
}
NoiseSession manages bidirectional encrypted channel derived from Noise_NK handshake
func NewClientNoiseSession ¶
func NewClientNoiseSession(serverPubkey [32]byte) (*NoiseSession, []byte, error)
NewClientNoiseSession initiates Noise_NK handshake against server public key Returns (NoiseSession, clientEphemeralPubkeyBytes, error)
func NewServerNoiseSession ¶
func NewServerNoiseSession(serverPrivkey [32]byte, clientEPub []byte) (*NoiseSession, error)
NewServerNoiseSession derives keys on server side using server static private key and client ephemeral public key
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the library entry point for terminating DNS tunnel sessions and forwarding them to a backend. Run binds the authoritative DNS listener and blocks until the context is cancelled or the listener fails.
func NewServer ¶
func NewServer(cfg ServerConfig) (*Server, error)
NewServer validates the configuration, loads the Noise private key (if any) and returns a ready-to-run Server.
type ServerConfig ¶
type ServerConfig struct {
ListenAddr string `json:"listen"`
TargetAddr string `json:"target"`
Domain string `json:"domain"`
PrivateKey string `json:"privkey"`
AllowTargets []string `json:"allow_targets,omitempty"`
MaxSessions int `json:"max_sessions,omitempty"` // concurrent session cap; 0 = unlimited
EDNS0 bool `json:"edns0,omitempty"` // announce 1232-byte UDP answers via EDNS0 (both ends must agree)
Logger *zap.SugaredLogger `json:"-"`
}
ServerConfig configures a Server. Logger may be left nil for a silent server; the CLI injects its own zap logger here.
AllowTargets gates client-declared targets (see flagTarget). It is a list of patterns like "tcp://127.0.0.1:*" or "udp://10.8.0.*:51820"; scheme, host and port may each be "*". An empty list means clients cannot override the target: every session uses TargetAddr. The special pattern "*" allows any target.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
dns_custom
command
Command dns_custom is the CLI for the dnstunnel library: it loads the JSON configuration, injects logging and runs the tunnel as a standalone server or client process.
|
Command dns_custom is the CLI for the dnstunnel library: it loads the JSON configuration, injects logging and runs the tunnel as a standalone server or client process. |