httpTransformer.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. /*
  2. * Copyright (c) 2023, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package transforms
  20. import (
  21. "bytes"
  22. "context"
  23. "math"
  24. "net"
  25. "net/textproto"
  26. "strconv"
  27. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  28. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  29. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
  30. )
  31. type HTTPTransformerParameters struct {
  32. // ProtocolTransformName specifies the name associated with
  33. // ProtocolTransformSpec and is used for metrics.
  34. ProtocolTransformName string
  35. // ProtocolTransformSpec specifies a transform to apply to the HTTP request.
  36. // See: "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/transforms".
  37. //
  38. // HTTP transforms include strategies discovered by the Geneva team,
  39. // https://geneva.cs.umd.edu.
  40. ProtocolTransformSpec Spec
  41. // ProtocolTransformSeed specifies the seed to use for generating random
  42. // data in the ProtocolTransformSpec transform. To replay a transform,
  43. // specify the same seed.
  44. ProtocolTransformSeed *prng.Seed
  45. }
  46. const (
  47. // httpTransformerReadWriteHeader HTTPTransformer is waiting to finish
  48. // reading and writing the next HTTP request header.
  49. httpTransformerReadWriteHeader = 0
  50. // httpTransformerReadWriteBody HTTPTransformer is waiting to finish reading
  51. // and writing the current HTTP request body.
  52. httpTransformerReadWriteBody = 1
  53. )
  54. // HTTPTransformer wraps a net.Conn, intercepting Write calls and applying the
  55. // specified protocol transform.
  56. //
  57. // The HTTP request to be written (input to the Write) is converted to a
  58. // string, transformed, and converted back to binary and then actually written
  59. // to the underlying net.Conn.
  60. //
  61. // HTTPTransformer is not safe for concurrent use.
  62. type HTTPTransformer struct {
  63. transform Spec
  64. seed *prng.Seed
  65. // state is the HTTPTransformer state. Possible values are
  66. // httpTransformerReadWriteHeader and httpTransformerReadWriteBody.
  67. state int64
  68. // b is used to buffer the accumulated bytes of the current HTTP request
  69. // header until the entire header is received and written.
  70. b bytes.Buffer
  71. // remain is the number of remaining HTTP request bytes to write to the
  72. // underlying net.Conn. Set to the value of Content-Length (HTTP request
  73. // body bytes) plus the length of the transformed HTTP header once the
  74. // current request header is received.
  75. remain uint64
  76. net.Conn
  77. }
  78. // Write implements the net.Conn interface.
  79. //
  80. // Note: it is assumed that the underlying transport, net.Conn, is a reliable
  81. // stream transport, i.e. TCP, therefore it is required that the caller stop
  82. // calling Write() on an instance of HTTPTransformer after an error is returned
  83. // because, following this assumption, the connection will have failed when a
  84. // Write() call to the underlying net.Conn fails; a new connection must be
  85. // established, net.Conn, and wrapped with a new HTTPTransformer. For this
  86. // reason, the return value may be the number of bytes buffered internally
  87. // and not the number of bytes written to the underlying net.Conn when a non-nil
  88. // error is returned.
  89. //
  90. // Warning: Does not handle chunked encoding and multiple HTTP requests written
  91. // in a single Write(). Must be called synchronously.
  92. func (t *HTTPTransformer) Write(b []byte) (int, error) {
  93. if t.state == httpTransformerReadWriteHeader {
  94. // Do not need to check return value https://github.com/golang/go/blob/1e9ff255a130200fcc4ec5e911d28181fce947d5/src/bytes/buffer.go#L164
  95. t.b.Write(b)
  96. // Wait until the entire HTTP request header has been read. Must check
  97. // all accumulated bytes incase the "\r\n\r\n" separator is written over
  98. // multiple Write() calls; from reading the go1.19.5 net/http code the
  99. // entire HTTP request is written in a single Write() call.
  100. sep := []byte("\r\n\r\n")
  101. headerBodyLines := bytes.SplitN(t.b.Bytes(), sep, 2) // split header and body
  102. if len(headerBodyLines) <= 1 {
  103. // b buffered in t.b and the entire HTTP request header has not been
  104. // recieved so another Write() call is expected.
  105. return len(b), nil
  106. } // else: HTTP request header has been read
  107. // read Content-Length before applying transform
  108. var headerLines [][]byte
  109. lines := bytes.Split(headerBodyLines[0], []byte("\r\n"))
  110. if len(lines) > 1 {
  111. // skip request line, e.g. "GET /foo HTTP/1.1"
  112. headerLines = lines[1:]
  113. }
  114. var cl []byte
  115. contentLengthHeader := []byte("Content-Length:")
  116. for _, header := range headerLines {
  117. if bytes.HasPrefix(header, contentLengthHeader) {
  118. cl = textproto.TrimBytes(header[len(contentLengthHeader):])
  119. break
  120. }
  121. }
  122. if len(cl) == 0 {
  123. // Irrecoverable error because either Content-Length header
  124. // missing, or Content-Length header value is empty, e.g.
  125. // "Content-Length: ", and request body length cannot be
  126. // determined.
  127. return len(b), errors.TraceNew("Content-Length missing")
  128. }
  129. contentLength, err := strconv.ParseUint(string(cl), 10, 63)
  130. if err != nil {
  131. // Irrecoverable error because Content-Length is malformed and
  132. // request body length cannot be determined.
  133. return len(b), errors.Trace(err)
  134. }
  135. t.remain = contentLength
  136. // transform and write header
  137. headerLen := len(headerBodyLines[0]) + len(sep)
  138. header := t.b.Bytes()[:headerLen]
  139. if t.transform != nil {
  140. newHeader, err := t.transform.Apply(t.seed, header)
  141. if err != nil {
  142. // TODO: consider logging an error and skiping transform
  143. // instead of returning an error, if the transform is broken
  144. // then all subsequent applications may fail.
  145. return len(b), errors.Trace(err)
  146. }
  147. // only allocate new slice if header length changed
  148. if len(newHeader) == len(header) {
  149. // Do not need to check return value. It is guaranteed that
  150. // n == len(newHeader) because t.b.Len() >= n if the header
  151. // size has not changed.
  152. copy(t.b.Bytes()[:len(header)], newHeader)
  153. } else {
  154. b := t.b.Bytes()
  155. t.b.Reset()
  156. // Do not need to check return value of bytes.Buffer.Write() https://github.com/golang/go/blob/1e9ff255a130200fcc4ec5e911d28181fce947d5/src/bytes/buffer.go#L164
  157. t.b.Write(newHeader)
  158. t.b.Write(b[len(header):])
  159. }
  160. header = newHeader
  161. }
  162. if math.MaxUint64-t.remain < uint64(len(header)) {
  163. // Irrecoverable error because request is malformed:
  164. // Content-Length + len(header) > math.MaxUint64.
  165. return len(b), errors.TraceNew("t.remain + uint64(len(header)) overflows")
  166. }
  167. t.remain += uint64(len(header))
  168. if uint64(t.b.Len()) > t.remain {
  169. // Should never happen, multiple requests written in a single
  170. // Write() are not supported.
  171. return len(b), errors.TraceNew("multiple HTTP requests in single Write() not supported")
  172. }
  173. n, err := t.b.WriteTo(t.Conn)
  174. t.remain -= uint64(n)
  175. if t.remain > 0 {
  176. t.state = httpTransformerReadWriteBody
  177. }
  178. // Do not wrap any I/O err returned by Conn
  179. return len(b), err
  180. }
  181. // HTTP request header has been transformed. Write any remaining bytes of
  182. // HTTP request header and then write HTTP request body.
  183. // Must write buffered bytes first, in-order, to write bytes to underlying
  184. // net.Conn in the same order they were received in.
  185. //
  186. // Already checked that t.b does not contain bytes of a subsequent HTTP
  187. // request when the header is parsed, i.e. at this point it is guaranteed
  188. // that t.b.Len() <= t.remain.
  189. //
  190. // In practise the buffer will be empty by this point because its entire
  191. // contents will have been written in the first call to t.b.WriteTo(t.Conn)
  192. // when the header is received, parsed, and transformed; otherwise the
  193. // underlying transport will have failed and the caller will not invoke
  194. // Write() again on this instance. See HTTPTransformer.Write() comment.
  195. wrote, err := t.b.WriteTo(t.Conn)
  196. t.remain -= uint64(wrote)
  197. if err != nil {
  198. // b not written or buffered
  199. // Do not wrap any I/O err returned by Conn
  200. return 0, err
  201. }
  202. if uint64(len(b)) > t.remain {
  203. return len(b), errors.TraceNew("multiple HTTP requests in single Write() not supported")
  204. }
  205. n, err := t.Conn.Write(b)
  206. t.remain -= uint64(n)
  207. if t.remain <= 0 {
  208. // Entire request, header and body, has been written. Return to
  209. // waiting for next HTTP request header to arrive.
  210. t.state = httpTransformerReadWriteHeader
  211. t.remain = 0
  212. }
  213. // Do not wrap any I/O err returned by Conn
  214. return n, err
  215. }
  216. func WrapDialerWithHTTPTransformer(dialer common.Dialer, params *HTTPTransformerParameters) common.Dialer {
  217. return func(ctx context.Context, network, addr string) (net.Conn, error) {
  218. conn, err := dialer(ctx, network, addr)
  219. if err != nil {
  220. return nil, errors.Trace(err)
  221. }
  222. return &HTTPTransformer{
  223. Conn: conn,
  224. transform: params.ProtocolTransformSpec,
  225. seed: params.ProtocolTransformSeed,
  226. }, nil
  227. }
  228. }