proxy.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build !ts_omit_outboundproxy
  4. // HTTP proxy code
  5. package main
  6. import (
  7. "context"
  8. "flag"
  9. "io"
  10. "log"
  11. "net"
  12. "net/http"
  13. "net/http/httputil"
  14. "strings"
  15. "tailscale.com/feature"
  16. "tailscale.com/net/proxymux"
  17. "tailscale.com/net/socks5"
  18. "tailscale.com/net/tsdial"
  19. "tailscale.com/types/logger"
  20. )
  21. func init() {
  22. hookRegisterOutboundProxyFlags.Set(registerOutboundProxyFlags)
  23. hookOutboundProxyListen.Set(outboundProxyListen)
  24. }
  25. func registerOutboundProxyFlags() {
  26. flag.StringVar(&args.socksAddr, "socks5-server", "", `optional [ip]:port to run a SOCK5 server (e.g. "localhost:1080")`)
  27. flag.StringVar(&args.httpProxyAddr, "outbound-http-proxy-listen", "", `optional [ip]:port to run an outbound HTTP proxy (e.g. "localhost:8080")`)
  28. }
  29. // outboundProxyListen creates listeners for local SOCKS and HTTP proxies, if
  30. // the respective addresses are not empty. args.socksAddr and args.httpProxyAddr
  31. // can be the same, in which case the SOCKS5 Listener will receive connections
  32. // that look like they're speaking SOCKS and httpListener will receive
  33. // everything else.
  34. //
  35. // socksListener and httpListener can be nil, if their respective addrs are
  36. // empty.
  37. //
  38. // The returned func closes over those two (possibly nil) listeners and
  39. // starts the respective servers on the listener when called.
  40. func outboundProxyListen() proxyStartFunc {
  41. socksAddr, httpAddr := args.socksAddr, args.httpProxyAddr
  42. if socksAddr == httpAddr && socksAddr != "" && !strings.HasSuffix(socksAddr, ":0") {
  43. ln, err := net.Listen("tcp", socksAddr)
  44. if err != nil {
  45. log.Fatalf("proxy listener: %v", err)
  46. }
  47. return mkProxyStartFunc(proxymux.SplitSOCKSAndHTTP(ln))
  48. }
  49. var socksListener, httpListener net.Listener
  50. var err error
  51. if socksAddr != "" {
  52. socksListener, err = net.Listen("tcp", socksAddr)
  53. if err != nil {
  54. log.Fatalf("SOCKS5 listener: %v", err)
  55. }
  56. if strings.HasSuffix(socksAddr, ":0") {
  57. // Log kernel-selected port number so integration tests
  58. // can find it portably.
  59. log.Printf("SOCKS5 listening on %v", socksListener.Addr())
  60. }
  61. }
  62. if httpAddr != "" {
  63. httpListener, err = net.Listen("tcp", httpAddr)
  64. if err != nil {
  65. log.Fatalf("HTTP proxy listener: %v", err)
  66. }
  67. if strings.HasSuffix(httpAddr, ":0") {
  68. // Log kernel-selected port number so integration tests
  69. // can find it portably.
  70. log.Printf("HTTP proxy listening on %v", httpListener.Addr())
  71. }
  72. }
  73. return mkProxyStartFunc(socksListener, httpListener)
  74. }
  75. func mkProxyStartFunc(socksListener, httpListener net.Listener) proxyStartFunc {
  76. return func(logf logger.Logf, dialer *tsdial.Dialer) {
  77. var addrs []string
  78. if httpListener != nil {
  79. hs := &http.Server{Handler: httpProxyHandler(dialer.UserDial)}
  80. go func() {
  81. log.Fatalf("HTTP proxy exited: %v", hs.Serve(httpListener))
  82. }()
  83. addrs = append(addrs, httpListener.Addr().String())
  84. }
  85. if socksListener != nil {
  86. ss := &socks5.Server{
  87. Logf: logger.WithPrefix(logf, "socks5: "),
  88. Dialer: dialer.UserDial,
  89. }
  90. go func() {
  91. log.Fatalf("SOCKS5 server exited: %v", ss.Serve(socksListener))
  92. }()
  93. addrs = append(addrs, socksListener.Addr().String())
  94. }
  95. if set, ok := feature.HookProxySetSelfProxy.GetOk(); ok {
  96. set(addrs...)
  97. }
  98. }
  99. }
  100. // httpProxyHandler returns an HTTP proxy http.Handler using the
  101. // provided backend dialer.
  102. func httpProxyHandler(dialer func(ctx context.Context, netw, addr string) (net.Conn, error)) http.Handler {
  103. rp := &httputil.ReverseProxy{
  104. Director: func(r *http.Request) {}, // no change
  105. Transport: &http.Transport{
  106. DialContext: dialer,
  107. },
  108. }
  109. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  110. if r.Method != "CONNECT" {
  111. backURL := r.RequestURI
  112. if strings.HasPrefix(backURL, "/") || backURL == "*" {
  113. http.Error(w, "bogus RequestURI; must be absolute URL or CONNECT", 400)
  114. return
  115. }
  116. rp.ServeHTTP(w, r)
  117. return
  118. }
  119. // CONNECT support:
  120. dst := r.RequestURI
  121. c, err := dialer(r.Context(), "tcp", dst)
  122. if err != nil {
  123. w.Header().Set("Tailscale-Connect-Error", err.Error())
  124. http.Error(w, err.Error(), 500)
  125. return
  126. }
  127. defer c.Close()
  128. cc, ccbuf, err := w.(http.Hijacker).Hijack()
  129. if err != nil {
  130. http.Error(w, err.Error(), 500)
  131. return
  132. }
  133. defer cc.Close()
  134. io.WriteString(cc, "HTTP/1.1 200 OK\r\n\r\n")
  135. var clientSrc io.Reader = ccbuf
  136. if ccbuf.Reader.Buffered() == 0 {
  137. // In the common case (with no
  138. // buffered data), read directly from
  139. // the underlying client connection to
  140. // save some memory, letting the
  141. // bufio.Reader/Writer get GC'ed.
  142. clientSrc = cc
  143. }
  144. errc := make(chan error, 1)
  145. go func() {
  146. _, err := io.Copy(cc, c)
  147. errc <- err
  148. }()
  149. go func() {
  150. _, err := io.Copy(c, clientSrc)
  151. errc <- err
  152. }()
  153. <-errc
  154. })
  155. }