Don't consider timer and nextReq until stash and outgoing are empty.
[dnstt.git] / dnstt-client / main.go
bloba2e8f7736719e095cb379e23ac779bb195f81b0c
1 // dnstt-client is the client end of a DNS tunnel.
2 //
3 // Usage:
4 // dnstt-client [-doh URL|-dot ADDR|-udp ADDR] -pubkey-file PUBKEYFILE DOMAIN LOCALADDR
5 //
6 // Examples:
7 // dnstt-client -doh https://resolver.example/dns-query -pubkey-file server.pub t.example.com 127.0.0.1:7000
8 // dnstt-client -dot resolver.example:853 -pubkey-file server.pub t.example.com 127.0.0.1:7000
9 //
10 // The program supports DNS over HTTPS (DoH), DNS over TLS (DoT), and UDP DNS.
11 // Use one of these options:
12 // -doh https://resolver.example/dns-query
13 // -dot resolver.example:853
14 // -udp resolver.example:53
16 // You can give the server's public key as a file or as a hex string. Use
17 // "dnstt-server -gen-key" to get the public key.
18 // -pubkey-file server.pub
19 // -pubkey 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff
21 // DOMAIN is the root of the DNS zone reserved for the tunnel. See README for
22 // instructions on setting it up.
24 // LOCALADDR is the TCP address that will listen for connections and forward
25 // them over the tunnel.
26 package main
28 import (
29 "flag"
30 "fmt"
31 "io"
32 "log"
33 "net"
34 "os"
35 "sync"
36 "time"
38 "github.com/xtaci/kcp-go/v5"
39 "github.com/xtaci/smux"
40 "www.bamsoftware.com/git/dnstt.git/dns"
41 "www.bamsoftware.com/git/dnstt.git/noise"
42 "www.bamsoftware.com/git/dnstt.git/turbotunnel"
45 // smux streams will be closed after this much time without receiving data.
46 const idleTimeout = 2 * time.Minute
48 // dnsNameCapacity returns the number of bytes remaining for encoded data after
49 // including domain in a DNS name.
50 func dnsNameCapacity(domain dns.Name) int {
51 // Names must be 255 octets or shorter in total length.
52 // https://tools.ietf.org/html/rfc1035#section-2.3.4
53 capacity := 255
54 // Subtract the length of the null terminator.
55 capacity -= 1
56 for _, label := range domain {
57 // Subtract the length of the label and the length octet.
58 capacity -= len(label) + 1
60 // Each label may be up to 63 bytes long and requires 64 bytes to
61 // encode.
62 capacity = capacity * 63 / 64
63 // Base32 expands every 5 bytes to 8.
64 capacity = capacity * 5 / 8
65 return capacity
68 // readKeyFromFile reads a key from a named file.
69 func readKeyFromFile(filename string) ([]byte, error) {
70 f, err := os.Open(filename)
71 if err != nil {
72 return nil, err
74 defer f.Close()
75 return noise.ReadKey(f)
78 func handle(local *net.TCPConn, sess *smux.Session, conv uint32) error {
79 stream, err := sess.OpenStream()
80 if err != nil {
81 return fmt.Errorf("session %08x opening stream: %v", conv, err)
83 defer func() {
84 log.Printf("end stream %08x:%d", conv, stream.ID())
85 stream.Close()
86 }()
87 log.Printf("begin stream %08x:%d", conv, stream.ID())
89 var wg sync.WaitGroup
90 wg.Add(2)
91 go func() {
92 defer wg.Done()
93 _, err := io.Copy(stream, local)
94 if err == io.EOF {
95 // smux Stream.Write may return io.EOF.
96 err = nil
98 if err != nil {
99 log.Printf("stream %08x:%d copy stream←local: %v", conv, stream.ID(), err)
101 local.CloseRead()
102 stream.Close()
104 go func() {
105 defer wg.Done()
106 _, err := io.Copy(local, stream)
107 if err == io.EOF {
108 // smux Stream.WriteTo may return io.EOF.
109 err = nil
111 if err != nil && err != io.ErrClosedPipe {
112 log.Printf("stream %08x:%d copy local←stream: %v", conv, stream.ID(), err)
114 local.CloseWrite()
116 wg.Wait()
118 return err
121 func run(pubkey []byte, domain dns.Name, localAddr *net.TCPAddr, remoteAddr net.Addr, pconn net.PacketConn) error {
122 defer pconn.Close()
124 ln, err := net.ListenTCP("tcp", localAddr)
125 if err != nil {
126 return fmt.Errorf("opening local listener: %v", err)
128 defer ln.Close()
130 mtu := dnsNameCapacity(domain) - 8 - 1 - numPadding - 1 // clientid + padding length prefix + padding + data length prefix
131 if mtu < 80 {
132 return fmt.Errorf("domain %s leaves only %d bytes for payload", domain, mtu)
134 log.Printf("effective MTU %d", mtu)
136 // Open a KCP conn on the PacketConn.
137 conn, err := kcp.NewConn2(remoteAddr, nil, 0, 0, pconn)
138 if err != nil {
139 return fmt.Errorf("opening KCP conn: %v", err)
141 defer func() {
142 log.Printf("end session %08x", conn.GetConv())
143 conn.Close()
145 log.Printf("begin session %08x", conn.GetConv())
146 // Permit coalescing the payloads of consecutive sends.
147 conn.SetStreamMode(true)
148 // Disable the dynamic congestion window (limit only by the maximum of
149 // local and remote static windows).
150 conn.SetNoDelay(
151 0, // default nodelay
152 0, // default interval
153 0, // default resend
154 1, // nc=1 => congestion window off
156 if rc := conn.SetMtu(mtu); !rc {
157 panic(rc)
160 // Put a Noise channel on top of the KCP conn.
161 rw, err := noise.NewClient(conn, pubkey)
162 if err != nil {
163 return err
166 // Start a smux session on the Noise channel.
167 smuxConfig := smux.DefaultConfig()
168 smuxConfig.Version = 2
169 smuxConfig.KeepAliveTimeout = idleTimeout
170 sess, err := smux.Client(rw, smuxConfig)
171 if err != nil {
172 return fmt.Errorf("opening smux session: %v", err)
174 defer sess.Close()
176 for {
177 local, err := ln.Accept()
178 if err != nil {
179 if err, ok := err.(net.Error); ok && err.Temporary() {
180 continue
182 return err
184 go func() {
185 defer local.Close()
186 err := handle(local.(*net.TCPConn), sess, conn.GetConv())
187 if err != nil {
188 log.Printf("handle: %v", err)
194 func main() {
195 var dohURL string
196 var dotAddr string
197 var pubkeyFilename string
198 var pubkeyString string
199 var udpAddr string
201 flag.Usage = func() {
202 fmt.Fprintf(flag.CommandLine.Output(), `Usage:
203 %[1]s [-doh URL|-dot ADDR|-udp ADDR] -pubkey-file PUBKEYFILE DOMAIN LOCALADDR
205 Examples:
206 %[1]s -doh https://resolver.example/dns-query -pubkey-file server.pub t.example.com 127.0.0.1:7000
207 %[1]s -dot resolver.example:853 -pubkey-file server.pub t.example.com 127.0.0.1:7000
209 `, os.Args[0])
210 flag.PrintDefaults()
212 flag.StringVar(&dohURL, "doh", "", "URL of DoH resolver")
213 flag.StringVar(&dotAddr, "dot", "", "address of DoT resolver")
214 flag.StringVar(&pubkeyString, "pubkey", "", fmt.Sprintf("server public key (%d hex digits)", noise.KeyLen*2))
215 flag.StringVar(&pubkeyFilename, "pubkey-file", "", "read server public key from file")
216 flag.StringVar(&udpAddr, "udp", "", "address of UDP DNS resolver")
217 flag.Parse()
219 log.SetFlags(log.LstdFlags | log.LUTC)
221 if flag.NArg() != 2 {
222 flag.Usage()
223 os.Exit(1)
225 domain, err := dns.ParseName(flag.Arg(0))
226 if err != nil {
227 fmt.Fprintf(os.Stderr, "invalid domain %+q: %v\n", flag.Arg(0), err)
228 os.Exit(1)
230 localAddr, err := net.ResolveTCPAddr("tcp", flag.Arg(1))
231 if err != nil {
232 fmt.Fprintln(os.Stderr, err)
233 os.Exit(1)
236 var pubkey []byte
237 if pubkeyFilename != "" && pubkeyString != "" {
238 fmt.Fprintf(os.Stderr, "only one of -pubkey and -pubkey-file may be used\n")
239 os.Exit(1)
240 } else if pubkeyFilename != "" {
241 var err error
242 pubkey, err = readKeyFromFile(pubkeyFilename)
243 if err != nil {
244 fmt.Fprintf(os.Stderr, "cannot read pubkey from file: %v\n", err)
245 os.Exit(1)
247 } else if pubkeyString != "" {
248 var err error
249 pubkey, err = noise.DecodeKey(pubkeyString)
250 if err != nil {
251 fmt.Fprintf(os.Stderr, "pubkey format error: %v\n", err)
252 os.Exit(1)
255 if len(pubkey) == 0 {
256 fmt.Fprintf(os.Stderr, "the -pubkey or -pubkey-file option is required\n")
257 os.Exit(1)
260 // Iterate over the remote resolver address options and select one and
261 // only one.
262 var remoteAddr net.Addr
263 var pconn net.PacketConn
264 for _, opt := range []struct {
265 s string
266 f func(string) (net.Addr, net.PacketConn, error)
268 // -doh
269 {dohURL, func(s string) (net.Addr, net.PacketConn, error) {
270 addr := turbotunnel.DummyAddr{}
271 pconn, err := NewHTTPPacketConn(dohURL, 32)
272 return addr, pconn, err
274 // -dot
275 {dotAddr, func(s string) (net.Addr, net.PacketConn, error) {
276 addr := turbotunnel.DummyAddr{}
277 pconn, err := NewTLSPacketConn(dotAddr)
278 return addr, pconn, err
280 // -udp
281 {udpAddr, func(s string) (net.Addr, net.PacketConn, error) {
282 addr, err := net.ResolveUDPAddr("udp", s)
283 if err != nil {
284 return nil, nil, err
286 pconn, err := net.ListenUDP("udp", nil)
287 return addr, pconn, err
290 if opt.s == "" {
291 continue
293 if pconn != nil {
294 fmt.Fprintf(os.Stderr, "only one of -doh, -dot, and -udp may be given\n")
295 os.Exit(1)
297 var err error
298 remoteAddr, pconn, err = opt.f(opt.s)
299 if err != nil {
300 fmt.Fprintln(os.Stderr, err)
301 os.Exit(1)
304 if pconn == nil {
305 fmt.Fprintf(os.Stderr, "one of -doh, -dot, or -udp is required\n")
306 os.Exit(1)
309 pconn = NewDNSPacketConn(pconn, remoteAddr, domain)
310 err = run(pubkey, domain, localAddr, remoteAddr, pconn)
311 if err != nil {
312 log.Fatal(err)