transport.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. /*
  2. * Copyright (c) 2019 Yawning Angel <yawning at schwanenlied dot me>
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. */
  17. package meeklite
  18. import (
  19. "crypto/tls"
  20. "errors"
  21. "fmt"
  22. "net"
  23. "net/http"
  24. "net/url"
  25. "strconv"
  26. "strings"
  27. "sync"
  28. utls "github.com/refraction-networking/utls"
  29. "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/lyrebird/transports/base"
  30. "golang.org/x/net/http2"
  31. )
  32. var (
  33. errProtocolNegotiated = errors.New("meek_lite: protocol negotiated")
  34. )
  35. type roundTripper struct {
  36. sync.Mutex
  37. clientHelloID *utls.ClientHelloID
  38. dialFn base.DialFunc
  39. transport http.RoundTripper
  40. initConn net.Conn
  41. }
  42. func (rt *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
  43. // Note: This isn't protected with a lock, since the meeklite ioWorker
  44. // serializes RoundTripper requests.
  45. //
  46. // This also assumes that req.URL.Host will remain constant for the
  47. // lifetime of the roundTripper, which is a valid assumption for meeklite.
  48. if rt.transport == nil {
  49. if err := rt.getTransport(req); err != nil {
  50. return nil, err
  51. }
  52. }
  53. return rt.transport.RoundTrip(req)
  54. }
  55. func (rt *roundTripper) getTransport(req *http.Request) error {
  56. switch strings.ToLower(req.URL.Scheme) {
  57. case "http":
  58. rt.transport = newHTTPTransport(rt.dialFn, nil)
  59. return nil
  60. case "https":
  61. default:
  62. return fmt.Errorf("meek_lite: invalid URL scheme: '%v'", req.URL.Scheme)
  63. }
  64. _, err := rt.dialTLS("tcp", getDialTLSAddr(req.URL))
  65. switch err {
  66. case errProtocolNegotiated:
  67. case nil:
  68. // Should never happen.
  69. panic("meek_lite: dialTLS returned no error when determining transport")
  70. default:
  71. return err
  72. }
  73. return nil
  74. }
  75. func (rt *roundTripper) dialTLS(network, addr string) (net.Conn, error) {
  76. // Unlike rt.transport, this is protected by a critical section
  77. // since past the initial manual call from getTransport, the HTTP
  78. // client will be the caller.
  79. rt.Lock()
  80. defer rt.Unlock()
  81. // If we have the connection from when we determined the HTTPS
  82. // transport to use, return that.
  83. if conn := rt.initConn; conn != nil {
  84. rt.initConn = nil
  85. return conn, nil
  86. }
  87. rawConn, err := rt.dialFn(network, addr)
  88. if err != nil {
  89. return nil, err
  90. }
  91. var host string
  92. if host, _, err = net.SplitHostPort(addr); err != nil {
  93. host = addr
  94. }
  95. conn := utls.UClient(rawConn, &utls.Config{
  96. ServerName: host,
  97. // `crypto/tls` gradually ramps up the record size. While this is
  98. // a good optimization and is a relatively common server feature,
  99. // neither Firefox nor Chromium appear to use such optimizations.
  100. DynamicRecordSizingDisabled: true,
  101. }, *rt.clientHelloID)
  102. if err = conn.Handshake(); err != nil {
  103. conn.Close()
  104. return nil, err
  105. }
  106. if rt.transport != nil {
  107. return conn, nil
  108. }
  109. // No http.Transport constructed yet, create one based on the results
  110. // of ALPN.
  111. switch conn.ConnectionState().NegotiatedProtocol {
  112. case http2.NextProtoTLS:
  113. // The remote peer is speaking HTTP 2 + TLS.
  114. rt.transport = &http2.Transport{DialTLS: rt.dialTLSHTTP2}
  115. default:
  116. // Assume the remote peer is speaking HTTP 1.x + TLS.
  117. rt.transport = newHTTPTransport(nil, rt.dialTLS)
  118. }
  119. // Stash the connection just established for use servicing the
  120. // actual request (should be near-immediate).
  121. rt.initConn = conn
  122. return nil, errProtocolNegotiated
  123. }
  124. func (rt *roundTripper) dialTLSHTTP2(network, addr string, cfg *tls.Config) (net.Conn, error) {
  125. return rt.dialTLS(network, addr)
  126. }
  127. func getDialTLSAddr(u *url.URL) string {
  128. host, port, err := net.SplitHostPort(u.Host)
  129. if err == nil {
  130. return net.JoinHostPort(host, port)
  131. }
  132. pInt, _ := net.LookupPort("tcp", u.Scheme)
  133. return net.JoinHostPort(u.Host, strconv.Itoa(pInt))
  134. }
  135. func newRoundTripper(dialFn base.DialFunc, clientHelloID *utls.ClientHelloID) http.RoundTripper {
  136. return &roundTripper{
  137. clientHelloID: clientHelloID,
  138. dialFn: dialFn,
  139. }
  140. }
  141. func newHTTPTransport(dialFn, dialTLSFn base.DialFunc) *http.Transport {
  142. base := (http.DefaultTransport).(*http.Transport)
  143. return &http.Transport{
  144. Dial: dialFn,
  145. DialTLS: dialTLSFn,
  146. // Use default configuration values, taken from the runtime.
  147. MaxIdleConns: base.MaxIdleConns,
  148. IdleConnTimeout: base.IdleConnTimeout,
  149. TLSHandshakeTimeout: base.TLSHandshakeTimeout,
  150. ExpectContinueTimeout: base.ExpectContinueTimeout,
  151. }
  152. }
  153. func init() {
  154. // Attempt to increase compatibility, there's an encrypted link
  155. // underneath, and this doesn't (shouldn't) affect the external
  156. // fingerprint.
  157. utls.EnableWeakCiphers()
  158. }