Skip to content

mkaaad/dns-tunnel

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DNS Tunnel

A DNS tunnel implementation in Go with two modes: Message Mode (simple send/receive) and TUN Network Layer Mode (virtual NIC for any IP traffic).

English | 中文

Overview

DNS Tunnel is a Go implementation of a secure communication channel over DNS protocol. It uses DNS TXT queries to transmit data, bypassing traditional network restrictions. The project supports two operating modes:

  • Message Mode: Send/receive encrypted text messages via DNS TXT queries (original API)
  • TUN Mode: Create a virtual network interface (TUN) to tunnel any IP traffic (HTTP, SSH, DNS, etc.) over DNS

Features

  • Dual mode: Message mode for simple text communication, TUN mode for full IP tunneling
  • Encryption (optional): ChaCha20-Poly1305 for secure communication when needed
  • DNS protocol: Transmits data through DNS TXT queries, bypassing firewalls
  • Configurable: Customizable DNS server, domain length limits, and session ID length
  • Asynchronous: Server uses goroutines for concurrent request handling
  • Lightweight: Minimal dependencies (miekg/dns, songgao/water, golang.org/x/crypto)

Architecture

Message Mode

Client → [encrypt → base32 → DNS TXT query] → Server → [decrypt → channel]

TUN Network Layer Mode

Client App → [Client TUN 10.0.0.2/24] → DNS queries → [Server DNS :53] → [Server TUN 10.0.0.1/24] → Target Network
                ↑                          DNS responses                         ↓
                └─────────────────── Bidirectional IP tunnel ────────────────────┘

Installation

git clone https://github.com/mkaaad/dns-tunnel.git
cd dns-tunnel
go build ./...

Usage

Message Mode

Simple text message tunnel (original API).

Server:

package main

import (
    "fmt"
    "github.com/mkaaad/dns-tunnel/server"
)

func main() {
    key := []byte("0123456789abcdef0123456789abcdef") // 32 bytes, or nil for no encryption
    msgChan := server.ListenAndServer("your-domain.com", key)

    go func() {
        for msg := range msgChan {
            fmt.Println("Received:", msg)
        }
    }()

    select {}
}
go run ./cmd/server your-domain.com "0123456789abcdef0123456789abcdef"

Client:

package main

import (
    "fmt"
    "github.com/mkaaad/dns-tunnel/client"
)

func main() {
    key := []byte("0123456789abcdef0123456789abcdef")
    c := client.NewClient("your-domain.com", key)
    err := c.Do("Hello, DNS tunnel!")
    if err != nil {
        panic(err)
    }
    fmt.Println("Message sent!")
}
go run ./cmd/client your-domain.com "0123456789abcdef0123456789abcdef" "Hello"

TUN Network Layer Mode (VPN-like)

Create a virtual network interface to tunnel any IP traffic. Requires root privileges.

Direct connection (server IP reachable):

Server:

sudo go run ./cmd/tunnel-server -domain example.com -tun 10.0.0.1/24

Client:

sudo go run ./cmd/tunnel-client \
  -domain example.com \
  -tun 10.0.0.2/24 \
  -gateway 10.0.0.1 \
  -dns <server-public-ip>:53 \
  -route 0.0.0.0/0

Public deployment (client only reaches internal DNS):

This is the classic DNS tunneling scenario. Client DNS queries reach your server through standard recursive resolution:

Client → Internal DNS → Root DNS → .com TLD → Authoritative NS → Your Tunnel Server :53

Requirements:

  1. Buy a domain (e.g., tunnel.com), set NS record to your server IP
  2. Open UDP 53 on your server
  3. Client omits -dns to use the system resolver (internal DNS):
# Client in restricted network — no -dns flag, queries go through internal DNS
sudo go run ./cmd/tunnel-client \
  -domain tunnel.com \
  -tun 10.0.0.2/24 \
  -gateway 10.0.0.1 \
  -route 0.0.0.0/0

The internal DNS will recursively resolve *.tunnel.com and eventually reach your server. No direct connectivity needed.

  1. Client creates TUN device tun0 with IP 10.0.0.2/24
  2. Automatically adds an exception route so DNS queries to 1.2.3.4 go through the original network interface (not TUN) — avoids routing loops
  3. Adds the route 0.0.0.0/0 via 10.0.0.1 dev tun0 — all other traffic goes through the tunnel
  4. Client reads IP packets from TUN, fragments them, base32-encodes, and sends as DNS TXT queries to 1.2.3.4:53
  5. Server reassembles, writes to its TUN, reads return traffic, and sends back in DNS responses
  6. Result: transparent IP tunnel — any application works without modification

Routing safety: When -dns is specified, the client automatically adds a /32 exception route for the DNS server IP via the original gateway, preventing DNS queries from looping through the TUN interface. When -dns is omitted (system resolver), DNS queries use the physical network directly — no loop possible.

Programmatic API (with optional encryption):

package main

import (
    "github.com/mkaaad/dns-tunnel/tunnel"
)

func main() {
    // Without encryption (faster)
    srv, _ := tunnel.NewServer("example.com", nil, "10.0.0.1/24")
    srv.ListenAndServe()

    // With encryption (ChaCha20-Poly1305)
    key := []byte("0123456789abcdef0123456789abcdef")
    srv, _ = tunnel.NewServer("example.com", key, "10.0.0.1/24")
    srv.ListenAndServe()
}
package main

import (
    "github.com/mkaaad/dns-tunnel/tunnel"
)

func main() {
    // Without encryption (faster)
    cli, _ := tunnel.NewClient("example.com", nil, "10.0.0.2/24", "1.2.3.4:53")
    cli.Run()

    // With encryption
    key := []byte("0123456789abcdef0123456789abcdef")
    cli, _ = tunnel.NewClient("example.com", key, "10.0.0.2/24", "1.2.3.4:53")
    cli.Run()
}

Advanced Configuration (Message Mode)

config := &client.ClientConfig{
    MaxLength:       253,          // Maximum DNS record length (1-253)
    MaxLabelLength:  63,           // Maximum label length (1-63)
    BaseDomain:      "example.com",
    Key:             key,          // 32-byte key (nil = no encryption)
    MessageIDLength: 4,            // Length of message IDs
    DNSServer:       "8.8.8.8:53", // Custom DNS server (optional)
}
c := client.NewClientWithConfig(config)

Project Structure

.
├── client/             # Message mode: client implementation
│   ├── client.go       # Client struct, Do() method, config
│   └── utils.go        # Encryption, splitting, random strings
├── server/             # Message mode: DNS server
│   └── server.go       # DNS handler, ListenAndServer, decryption
├── tun/                # TUN device management (multi-platform)
│   ├── interface.go    # Core Interface type wrapping *os.File
│   ├── tun.go          # Linux: NewTUN() using water + ip (build: linux,!android)
│   ├── tun_darwin.go   # macOS: ifconfig-based TUN config (build: darwin)
│   ├── tun_windows.go  # Windows: netsh-based TUN config (build: windows)
│   └── tun_android.go  # Android: NewTUNFromFD() for VpnService fds
├── tunnel/             # TUN network layer tunnel
│   ├── client.go       # Tunnel client: TUN ↔ DNS queries
│   ├── server.go       # Tunnel server: DNS queries ↔ TUN
│   ├── crypto.go       # ChaCha20-Poly1305 encrypt/decrypt (exported)
│   └── transport.go    # Protocol: fragmentation, base32, query parsing
├── cmd/
│   ├── client/         # Message mode client executable
│   ├── server/         # Message mode server executable
│   ├── tunnel-client/  # TUN mode client executable
│   └── tunnel-server/  # TUN mode server executable
├── native/             # Cross-platform shared library (.so/.dylib/.dll)
│   ├── mobile.go       # CGo exports: DnsTunnel_StartClientWithFD (Android)
│   ├── tun_client.go   # CGo exports: DnsTunnel_StartClient (Linux/macOS/Windows)
│   ├── build_linux.sh  # Build script for libdns-tunnel.so
│   ├── build_macos.sh  # Build script for libdns-tunnel.dylib
│   └── build_windows.sh# Build script for dns-tunnel.dll
├── go.mod
├── go.sum
└── README.md

Requirements

  • Go 1.24.4+ (as specified in go.mod)
  • Root privileges for server (binds to port 53) and tunnel mode (creates TUN devices)
  • TUN module: Linux kernel module (tun) — usually loaded by default
  • iproute2: For TUN IP configuration (ip addr, ip link, ip route)

Building and Testing

# Format code
go fmt ./...

# Check for issues
go vet ./...

# Build all packages
go build ./...

# Run tests (none currently implemented)
go test ./...

How It Works

TUN Mode Protocol

Each DNS transaction carries IP packet fragments in both directions:

Client → Server (DNS query):
  <base32_data>.<flags>.<session_id>.<base_domain>

Server → Client (DNS response):
  TXT: ["ok", base32_return_data_chunks...]
  • Flags: f = fragment (more to come), l = last fragment, p = poll (no data)
  • Fragmentation: IP packets split into 145-byte chunks (before base32 encoding) to fit DNS limits
  • Polling: Client sends keepalive queries every 200ms to fetch return traffic
  • Return data: Response carries full IP packet (base32 encoded, split across TXT strings)

Shared Library Integration

Build the tunnel client as a shared library (.so/.dylib/.dll) for use from other languages (C, Kotlin, Swift, Dart, etc.).

C API Reference

Three exported functions (defined in native/mobile.go and native/tun_client.go with //export directives):

// Start the DNS tunnel (Linux/macOS/Windows). Go creates the TUN device internally.
int DnsTunnel_StartClient(char* domain, char* dnsServer, char* tunAddr);

// Start the DNS tunnel with an externally created TUN fd (Android).
int DnsTunnel_StartClientWithFD(int tunFd, char* domain, char* dnsServer, char* tunAddr);

// Stop the tunnel.
void DnsTunnel_StopClient(void);

Linux (.so)

cd /root/code/dns-tunnel
CGO_ENABLED=1 go build -buildmode=c-shared -o libdns-tunnel.so ./native

Output: libdns-tunnel.so + libdns-tunnel.h.

macOS (.dylib)

CGO_ENABLED=1 GOOS=darwin go build -buildmode=c-shared -o libdns-tunnel.dylib ./native

Output: libdns-tunnel.dylib + libdns-tunnel.h.

Windows (.dll)

Requires MinGW-w64 cross-compiler.

CGO_ENABLED=1 GOOS=windows CC=x86_64-w64-mingw32-gcc \
  go build -buildmode=c-shared -o dns-tunnel.dll ./native

Output: dns-tunnel.dll + dns-tunnel.h.

Android (.so)

Prerequisites: Android NDK (NDK environment variable).

export NDK=/path/to/android-ndk
./native/build_android.sh

Output: libdns-tunnel.so (arm64-v8a) + libdns-tunnel.h.

Android (Kotlin) Usage

class TunnelVpnService : VpnService() {
    private var tunnelFd: Int? = null

    init { System.loadLibrary("dns-tunnel") }

    private external fun DnsTunnel_StartClientWithFD(
        fd: Int, domain: String, dnsServer: String, tunAddr: String): Int
    private external fun DnsTunnel_StopClient()

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val builder = Builder()
        builder.addAddress("10.0.0.2", 24)
        builder.addRoute("0.0.0.0", 0)
        builder.setMtu(1500)
        builder.addRoute("1.2.3.4", 32)  // DNS server exception

        val vpn = builder.establish() ?: return START_STICKY
        tunnelFd = vpn.detachFd()
        vpn.close()

        DnsTunnel_StartClientWithFD(tunnelFd!!, "example.com",
                                    "1.2.3.4:53", "10.0.0.2/24")
        return START_STICKY
    }

    override fun onDestroy() {
        DnsTunnel_StopClient()
        tunnelFd?.let { try { ParcelFileDescriptor.fromFd(it).close() } catch(_: Exception) {} }
        super.onDestroy()
    }
}

Architecture

Desktop (Linux/macOS/Windows):

App (C/Kotlin/Swift/Dart) → Go .so (native/tun_client.go) → [water TUN] → tunnel.Client
                                 DNS queries               ↓
                                 └─────────────────── DNS tunnel ──────→ Server

Android:

Android App → [VpnService: TUN fd] → Go .so (native/mobile.go) → tunnel.Client
                   ↑                      DNS queries               ↓
                   └─────────────────── DNS tunnel ────────────────→ Server

Performance

Metric With Encryption Without Encryption
Payload per fragment 105 bytes 145 bytes
Queries per 1500B IP packet ~15 ~11
Relative throughput baseline +38%

Limitations

  • Port 53: Server requires root privileges to bind to port 53
  • DNS limitations: Maximum label length 63 chars, total length 253 chars
  • Throughput: Limited by DNS query latency (typically 50-200ms RTT)
  • TUN device creation: Linux uses ip commands, macOS uses ifconfig, Windows uses netsh
  • Single-threaded poll: Return traffic polling at 200ms intervals adds latency

Security Considerations

  • Encryption is optional — the network layer doesn't require it (higher layers like TLS/SSH provide encryption)
  • When using encryption, use strong, randomly generated 32-byte keys
  • The server listens on all interfaces by default (0.0.0.0:53)
  • Consider firewall rules and DNS server configuration
  • Encryption uses ChaCha20-Poly1305, which is considered secure

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make changes following existing code patterns
  4. Run go fmt before committing
  5. Submit a pull request

License

MIT License


中文

概述

DNS隧道是一个用Go语言实现的DNS隧道工具,支持两种模式:消息模式(简单收发文本消息)和 TUN网络层模式(虚拟网卡,转发任意IP流量)。

功能特点

  • 双模式:消息模式用于简单文本通信,TUN模式用于完整的IP隧道
  • 加密可选:ChaCha20-Poly1305加密,不需要可不加
  • DNS协议:通过DNS TXT查询传输数据,绕过防火墙
  • 可配置:可自定义DNS服务器、域名长度限制和会话ID长度
  • 异步处理:服务器使用goroutine进行并发请求处理
  • 轻量级:依赖极少(miekg/dns, songgao/water, golang.org/x/crypto)

架构

消息模式

客户端 → [加密 → base32 → DNS TXT查询] → 服务端 → [解密 → 通道]

TUN网络层模式

应用 → [Client TUN 10.0.0.2] → DNS查询 → [服务端DNS :53] → [Server TUN 10.0.0.1] → 目标网络
        ↑                         DNS响应                        ↓
        └─────────────────── 双向IP隧道 ─────────────────────────┘

使用方法

TUN网络层模式(类似VPN)

服务端(公网IP 1.2.3.4):

sudo go run ./cmd/tunnel-server \
  -domain example.com \
  -tun 10.0.0.1/24

客户端:

sudo go run ./cmd/tunnel-client \
  -domain example.com \
  -tun 10.0.0.2/24 \
  -gateway 10.0.0.1 \
  -dns 1.2.3.4:53 \
  -route 0.0.0.0/0

工作流程:

  1. 客户端创建TUN设备,IP 10.0.0.2/24
  2. 自动添加DNS服务器IP的异常路由(1.2.3.4/32 via 原网关),避免环路
  3. 添加隧道路由(0.0.0.0/0 via 10.0.0.1 dev tun0),其余流量走隧道
  4. 客户端从TUN读取IP包 → 分片 → base32编码 → DNS TXT查询发送到 1.2.3.4:53
  5. 服务端重组 → 写入TUN → 读取回程流量 → DNS响应返回给客户端
  6. 结果:透明IP隧道,任何应用无需修改即可使用

编程接口(可选加密):

// 不加密(更快)
cli, _ := tunnel.NewClient("example.com", nil, "10.0.0.2/24", "1.2.3.4:53")
cli.Run()

// 加密
key := []byte("0123456789abcdef0123456789abcdef")
cli, _ = tunnel.NewClient("example.com", key, "10.0.0.2/24", "1.2.3.4:53")
cli.Run()

消息模式

// 服务端
msgChan := server.ListenAndServer("your-domain.com", key)

// 客户端
c := client.NewClient("your-domain.com", key)
c.Do("Hello, DNS tunnel!")

项目结构

.
├── client/             # 消息模式:客户端实现
├── server/             # 消息模式:DNS服务端
├── tun/                # TUN设备管理(多平台)
│   ├── interface.go    # 核心 Interface 类型,封装 *os.File
│   ├── tun.go          # Linux: water + ip 命令
│   ├── tun_darwin.go   # macOS: ifconfig 配置 TUN
│   ├── tun_windows.go  # Windows: netsh 配置 TUN
│   └── tun_android.go  # Android: fd-based TUN
├── tunnel/             # TUN网络层隧道
│   ├── client.go       # 隧道客户端:TUN ↔ DNS查询
│   ├── server.go       # 隧道服务端:DNS查询 ↔ TUN
│   ├── crypto.go       # 加解密(导出Encrypt/Decrypt)
│   └── transport.go    # 协议:分片、base32、查询解析
├── cmd/
│   ├── client/         # 消息模式客户端入口
│   ├── server/         # 消息模式服务端入口
│   ├── tunnel-client/  # TUN模式客户端入口
│   └── tunnel-server/  # TUN模式服务端入口
├── native/             # 跨平台共享库
│   ├── mobile.go       # CGo 导出: DnsTunnel_StartClientWithFD (Android)
│   ├── tun_client.go   # CGo 导出: DnsTunnel_StartClient (桌面)
│   ├── build_linux.sh
│   ├── build_macos.sh
│   └── build_windows.sh
├── go.mod
└── go.sum

安装

git clone https://github.com/mkaaad/dns-tunnel.git
cd dns-tunnel
go build ./...

消息模式

简单文本消息隧道(原始API)。

服务端:

package main

import (
    "fmt"
    "github.com/mkaaad/dns-tunnel/server"
)

func main() {
    key := []byte("0123456789abcdef0123456789abcdef") // 32字节,或nil不加密
    msgChan := server.ListenAndServer("your-domain.com", key)

    go func() {
        for msg := range msgChan {
            fmt.Println("Received:", msg)
        }
    }()

    select {}
}
sudo go run ./cmd/server your-domain.com "0123456789abcdef0123456789abcdef"

客户端:

package main

import (
    "fmt"
    "github.com/mkaaad/dns-tunnel/client"
)

func main() {
    key := []byte("0123456789abcdef0123456789abcdef")
    c := client.NewClient("your-domain.com", key)
    err := c.Do("Hello, DNS tunnel!")
    if err != nil {
        panic(err)
    }
    fmt.Println("Message sent!")
}
go run ./cmd/client your-domain.com "0123456789abcdef0123456789abcdef" "你好"

高级配置(消息模式)

config := &client.ClientConfig{
    MaxLength:       253,          // DNS记录最大长度 (1-253)
    MaxLabelLength:  63,           // 标签最大长度 (1-63)
    BaseDomain:      "example.com",
    Key:             key,          // 32字节密钥 (nil = 不加密)
    MessageIDLength: 4,            // 消息ID长度
    DNSServer:       "8.8.8.8:53", // 自定义DNS服务器 (可选)
}
c := client.NewClientWithConfig(config)

构建和测试

# 格式化代码
go fmt ./...

# 静态检查
go vet ./...

# 构建所有包
go build ./...

# 运行测试(目前未实现)
go test ./...

工作原理

TUN模式协议

每个DNS事务双向传输IP包分片:

客户端 → 服务端(DNS查询):
  <base32数据>.<标志>.<会话ID>.<基础域名>

服务端 → 客户端(DNS响应):
  TXT: ["ok", base32回传数据块...]
  • 标志f = 还有分片,l = 最后分片,p = 轮询(无数据)
  • 分片:IP包分割为145字节块(base32编码前),以适应DNS限制
  • 轮询:客户端每200ms发送保活查询以获取回程流量
  • 回传数据:响应携带完整IP包(base32编码,跨TXT字符串分割)

共享库集成

将隧道客户端编译为共享库(.so/.dylib/.dll),供其他语言(C、Kotlin、Swift、Dart 等)调用。

C API 参考

// 启动 DNS 隧道(Linux/macOS/Windows)。Go 内部创建 TUN 设备。
int DnsTunnel_StartClient(char* domain, char* dnsServer, char* tunAddr);

// 启动 DNS 隧道(Android)。传入 VpnService 的 TUN fd。
int DnsTunnel_StartClientWithFD(int tunFd, char* domain, char* dnsServer, char* tunAddr);

// 停止隧道
void DnsTunnel_StopClient(void);

Linux (.so)

CGO_ENABLED=1 go build -buildmode=c-shared -o libdns-tunnel.so ./native

输出:libdns-tunnel.so + libdns-tunnel.h

macOS (.dylib)

CGO_ENABLED=1 GOOS=darwin go build -buildmode=c-shared -o libdns-tunnel.dylib ./native

输出:libdns-tunnel.dylib + libdns-tunnel.h

Windows (.dll)

需要 MinGW-w64 交叉编译器。

CGO_ENABLED=1 GOOS=windows CC=x86_64-w64-mingw32-gcc \
  go build -buildmode=c-shared -o dns-tunnel.dll ./native

输出:dns-tunnel.dll + dns-tunnel.h

Android (.so)

需要 Android NDK(NDK 环境变量)。

export NDK=/path/to/android-ndk
./native/build_android.sh

输出:libdns-tunnel.so(arm64-v8a)+ libdns-tunnel.h

Android(Kotlin)用法

class TunnelVpnService : VpnService() {
    private var tunnelFd: Int? = null

    init { System.loadLibrary("dns-tunnel") }

    private external fun DnsTunnel_StartClientWithFD(
        fd: Int, domain: String, dnsServer: String, tunAddr: String): Int
    private external fun DnsTunnel_StopClient()

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val builder = Builder()
        builder.addAddress("10.0.0.2", 24)
        builder.addRoute("0.0.0.0", 0)
        builder.setMtu(1500)
        builder.addRoute("1.2.3.4", 32)  // DNS 服务器异常路由

        val vpn = builder.establish() ?: return START_STICKY
        tunnelFd = vpn.detachFd()
        vpn.close()

        DnsTunnel_StartClientWithFD(tunnelFd!!, "example.com",
                                    "1.2.3.4:53", "10.0.0.2/24")
        return START_STICKY
    }

    override fun onDestroy() {
        DnsTunnel_StopClient()
        tunnelFd?.let { try { ParcelFileDescriptor.fromFd(it).close() } catch(_: Exception) {} }
        super.onDestroy()
    }
}

架构

桌面端 (Linux/macOS/Windows):

应用 (C/Kotlin/Swift/Dart) → Go .so (native/tun_client.go) → [water TUN] → tunnel.Client
                                 DNS 查询               ↓
                                 └─────────────────── DNS 隧道 ──────→ 服务端

Android:

Android App → [VpnService: TUN fd] → Go .so (native/mobile.go) → tunnel.Client
                   ↑                      DNS 查询                  ↓
                   └─────────────────── DNS 隧道 ────────────────→ 服务端

性能

指标 加密 不加密
每分片负载 105 字节 145 字节
每1500B IP包查询数 ~15 ~11
相对吞吐量 基准 +38%

限制

  • 53端口:服务端需要root权限绑定53端口
  • DNS限制:标签最长63字符,总长度最长253字符
  • 吞吐量:受DNS查询延迟限制(通常50-200ms RTT)
  • TUN设备:Linux 使用 ip,macOS 使用 ifconfig,Windows 使用 netsh
  • 单线程轮询:200ms间隔的回传流量轮询增加延迟

安全考虑

  • 加密是可选的——网络层不需要(TLS/SSH等上层协议自带加密)
  • 使用加密时,使用强随机生成的32字节密钥
  • 服务端默认监听所有接口(0.0.0.0:53)
  • 建议配置防火墙规则
  • 加密使用ChaCha20-Poly1305,被认为是安全的

贡献

  1. Fork仓库
  2. 创建功能分支
  3. 按照现有代码风格修改
  4. 提交前运行 go fmt
  5. 提交Pull Request

许可证

MIT License

系统要求

  • Go 1.24.4+
  • Root权限:服务端绑定53端口,TUN模式创建虚拟网卡
  • TUN模块:Linux内核模块(通常默认加载)
  • iproute2:TUN的IP配置

About

DNS Tunnel is a Go implementation of a secure communication channel over DNS protocol. It uses DNS TXT queries to transmit encrypted data, bypassing traditional network restrictions while maintaining security through ChaCha20-Poly1305 encryption.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors