main.go 8.4 KB

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