mirror of
https://github.com/tladesignz/dnstt.git
synced 2026-10-10 08:08:14 +03:00
288 lines
7.3 KiB
Go
288 lines
7.3 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/xtaci/kcp-go/v5"
|
|
"github.com/xtaci/smux"
|
|
"www.bamsoftware.com/git/dnstt.git/dns"
|
|
"www.bamsoftware.com/git/dnstt.git/noise"
|
|
"www.bamsoftware.com/git/dnstt.git/turbotunnel"
|
|
)
|
|
|
|
const (
|
|
idleTimeout = 10 * time.Minute
|
|
initPollDelay = 500 * time.Millisecond
|
|
maxPollDelay = 10 * time.Second
|
|
pollDelayMultiplier = 2.0
|
|
)
|
|
|
|
func handle(local *net.TCPConn, sess *smux.Session, conv uint32) error {
|
|
stream, err := sess.OpenStream()
|
|
if err != nil {
|
|
return fmt.Errorf("session %08x opening stream: %v", conv, err)
|
|
}
|
|
defer func() {
|
|
log.Printf("end stream %08x:%d", conv, stream.ID())
|
|
stream.Close()
|
|
}()
|
|
log.Printf("begin stream %08x:%d", conv, stream.ID())
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
_, err := io.Copy(stream, local)
|
|
if err != nil {
|
|
log.Printf("stream %08x:%d copy stream←local: %v\n", conv, stream.ID(), err)
|
|
}
|
|
local.CloseRead()
|
|
stream.Close()
|
|
}()
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
_, err := io.Copy(local, stream)
|
|
if err == io.EOF {
|
|
// smux Stream.WriteTo may return io.EOF.
|
|
err = nil
|
|
}
|
|
if err != nil && err != io.ErrClosedPipe {
|
|
log.Printf("stream %08x:%d copy local←stream: %v\n", conv, stream.ID(), err)
|
|
}
|
|
local.CloseWrite()
|
|
}()
|
|
wg.Wait()
|
|
|
|
return err
|
|
}
|
|
|
|
// dnsNameCapacity returns the number of bytes remaining for encoded data after
|
|
// including domain in a DNS name.
|
|
func dnsNameCapacity(domain dns.Name) int {
|
|
// https://tools.ietf.org/html/rfc1035#section-2.3.4
|
|
// Names must be 255 octets or shorter in total length.
|
|
capacity := 255
|
|
// Subtract the length of the null terminator.
|
|
capacity -= 1
|
|
for _, label := range domain {
|
|
// Subtract the length of the label and the length octet.
|
|
capacity -= len(label) + 1
|
|
}
|
|
// Each label may be up to 63 bytes long and requires 64
|
|
capacity = capacity * 63 / 64
|
|
// Base32 expands every 5 bytes to 8.
|
|
capacity = capacity * 5 / 8
|
|
return capacity
|
|
}
|
|
|
|
func readKeyFromFile(filename string) ([]byte, error) {
|
|
f, err := os.Open(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
return noise.ReadKey(f)
|
|
}
|
|
|
|
func run(pubkey []byte, domain dns.Name, localAddr *net.TCPAddr, remoteAddr net.Addr, pconn net.PacketConn) error {
|
|
defer pconn.Close()
|
|
|
|
mtu := dnsNameCapacity(domain) - 8 - 1 - numPadding - 1 // clientid + padding length prefix + padding + data length prefix
|
|
if mtu < 80 {
|
|
return fmt.Errorf("domain %s leaves only %d bytes for payload", domain, mtu)
|
|
}
|
|
log.Printf("MTU %d\n", mtu)
|
|
|
|
// Open a KCP conn on the PacketConn.
|
|
conn, err := kcp.NewConn2(remoteAddr, nil, 0, 0, pconn)
|
|
if err != nil {
|
|
return fmt.Errorf("opening KCP conn: %v", err)
|
|
}
|
|
defer func() {
|
|
log.Printf("end session %08x", conn.GetConv())
|
|
conn.Close()
|
|
}()
|
|
log.Printf("begin session %08x", conn.GetConv())
|
|
// Permit coalescing the payloads of consecutive sends.
|
|
conn.SetStreamMode(true)
|
|
// Disable the dynamic congestion window (limit only by the
|
|
// maximum of local and remote static windows).
|
|
conn.SetNoDelay(
|
|
0, // default nodelay
|
|
0, // default interval
|
|
0, // default resend
|
|
1, // nc=1 => congestion window off
|
|
)
|
|
if rc := conn.SetMtu(mtu); !rc {
|
|
panic(rc)
|
|
}
|
|
|
|
// Put a Noise channel on top of the KCP conn.
|
|
rw, err := noise.NewClient(conn, pubkey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Start a smux session on the Noise channel.
|
|
smuxConfig := smux.DefaultConfig()
|
|
smuxConfig.Version = 2
|
|
smuxConfig.KeepAliveTimeout = idleTimeout
|
|
sess, err := smux.Client(rw, smuxConfig)
|
|
if err != nil {
|
|
return fmt.Errorf("opening smux session: %v", err)
|
|
}
|
|
defer sess.Close()
|
|
|
|
ln, err := net.ListenTCP("tcp", localAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("opening local listener: %v", err)
|
|
}
|
|
|
|
for {
|
|
local, err := ln.Accept()
|
|
if err != nil {
|
|
if err, ok := err.(net.Error); ok && err.Temporary() {
|
|
continue
|
|
}
|
|
return err
|
|
}
|
|
go func() {
|
|
defer local.Close()
|
|
err := handle(local.(*net.TCPConn), sess, conn.GetConv())
|
|
if err != nil {
|
|
log.Printf("handle: %v\n", err)
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
var dohURL string
|
|
var dotAddr string
|
|
var pubkeyFilename string
|
|
var pubkeyString string
|
|
var udpAddr string
|
|
|
|
flag.Usage = func() {
|
|
fmt.Fprintf(flag.CommandLine.Output(), `Usage:
|
|
%[1]s [-doh URL|-dot ADDR|-udp ADDR] -pubkey-file PUBKEYFILE DOMAIN LOCALADDR
|
|
|
|
Examples:
|
|
%[1]s -doh https://resolver.example/dns-query -pubkey-file server.pub t.example.com 127.0.0.1:7000
|
|
%[1]s -dot resolver.example:853 -pubkey-file server.pub t.example.com 127.0.0.1:7000
|
|
|
|
`, os.Args[0])
|
|
flag.PrintDefaults()
|
|
}
|
|
flag.StringVar(&dohURL, "doh", "", "URL of DoH resolver")
|
|
flag.StringVar(&dotAddr, "dot", "", "address of DoT resolver")
|
|
flag.StringVar(&pubkeyString, "pubkey", "", fmt.Sprintf("server public key (%d hex digits)", noise.KeyLen*2))
|
|
flag.StringVar(&pubkeyFilename, "pubkey-file", "", "read server public key from file")
|
|
flag.StringVar(&udpAddr, "udp", "", "address of UDP DNS resolver")
|
|
flag.Parse()
|
|
|
|
log.SetFlags(log.LstdFlags | log.LUTC)
|
|
|
|
if flag.NArg() != 2 {
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
domain, err := dns.ParseName(flag.Arg(0))
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "invalid domain %+q: %v\n", flag.Arg(0), err)
|
|
os.Exit(1)
|
|
}
|
|
localAddr, err := net.ResolveTCPAddr("tcp", flag.Arg(1))
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
var pubkey []byte
|
|
if pubkeyFilename != "" && pubkeyString != "" {
|
|
fmt.Fprintf(os.Stderr, "only one of -pubkey and -pubkey-file may be used\n")
|
|
os.Exit(1)
|
|
} else if pubkeyFilename != "" {
|
|
var err error
|
|
pubkey, err = readKeyFromFile(pubkeyFilename)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "cannot read pubkey from file: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
} else if pubkeyString != "" {
|
|
var err error
|
|
pubkey, err = noise.DecodeKey(pubkeyString)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "pubkey format error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
if len(pubkey) == 0 {
|
|
fmt.Fprintf(os.Stderr, "the -pubkey or -pubkey-file option is required\n")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Iterate over the remote resolver address options and select one and
|
|
// only one.
|
|
var remoteAddr net.Addr
|
|
var pconn net.PacketConn
|
|
for _, opt := range []struct {
|
|
s string
|
|
f func(string) (net.Addr, net.PacketConn, error)
|
|
}{
|
|
// -doh
|
|
{dohURL, func(s string) (net.Addr, net.PacketConn, error) {
|
|
addr := turbotunnel.DummyAddr{}
|
|
pconn, err := NewHTTPPacketConn(dohURL, 32)
|
|
return addr, pconn, err
|
|
}},
|
|
// -dot
|
|
{dotAddr, func(s string) (net.Addr, net.PacketConn, error) {
|
|
addr := turbotunnel.DummyAddr{}
|
|
pconn, err := NewTLSPacketConn(dotAddr)
|
|
return addr, pconn, err
|
|
}},
|
|
// -udp
|
|
{udpAddr, func(s string) (net.Addr, net.PacketConn, error) {
|
|
addr, err := net.ResolveUDPAddr("udp", s)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
pconn, err := net.ListenUDP("udp", nil)
|
|
return addr, pconn, err
|
|
}},
|
|
} {
|
|
if opt.s == "" {
|
|
continue
|
|
}
|
|
if pconn != nil {
|
|
fmt.Fprintf(os.Stderr, "only one of -doh, -dot, and -udp may be given\n")
|
|
os.Exit(1)
|
|
}
|
|
var err error
|
|
remoteAddr, pconn, err = opt.f(opt.s)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
if pconn == nil {
|
|
fmt.Fprintf(os.Stderr, "one of -doh, -dot, or -udp is required\n")
|
|
os.Exit(1)
|
|
}
|
|
|
|
pconn = NewDNSPacketConn(pconn, remoteAddr, domain)
|
|
err = run(pubkey, domain, localAddr, remoteAddr, pconn)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|