httpTransformer.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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. // httpTransformerReadWriteReqLineAndHeaders HTTPTransformer is waiting to
  48. // finish reading and writing the Request-Line, and headers, of the next
  49. // request.
  50. httpTransformerReadWriteReqLineAndHeaders = 0
  51. // httpTransformerReadWriteBody HTTPTransformer is waiting to finish
  52. // reading, and writing, the current request body.
  53. httpTransformerReadWriteBody = 1
  54. )
  55. // HTTPTransformer wraps a net.Conn, intercepting Write calls and applying the
  56. // specified protocol transform.
  57. //
  58. // The HTTP request to be written (input to the Write) is converted to a
  59. // string, transformed, and converted back to binary and then actually written
  60. // to the underlying net.Conn.
  61. //
  62. // HTTPTransformer is not safe for concurrent use.
  63. type HTTPTransformer struct {
  64. transform Spec
  65. seed *prng.Seed
  66. // state is the HTTPTransformer state. Possible values are
  67. // httpTransformerReadWriteReqLineAndHeaders and
  68. // httpTransformerReadWriteBody.
  69. state int64
  70. // b is used to buffer the accumulated bytes of the current request until
  71. // the Request-Line and all headers are received and written.
  72. b bytes.Buffer
  73. // remain is the number of remaining request bytes to write to the
  74. // underlying net.Conn. Set to the value of Content-Length (request body
  75. // bytes) plus the length of the transformed Request-Line, and headers,
  76. // once the Request-Line, and headers, of the current request are received.
  77. remain uint64
  78. net.Conn
  79. }
  80. // Write implements the net.Conn interface.
  81. //
  82. // Note: it is assumed that the underlying transport, net.Conn, is a reliable
  83. // stream transport, i.e. TCP, therefore it is required that the caller stop
  84. // calling Write() on an instance of HTTPTransformer after an error is returned
  85. // because, following this assumption, the connection will have failed when a
  86. // Write() call to the underlying net.Conn fails; a new connection must be
  87. // established, net.Conn, and wrapped with a new HTTPTransformer. For this
  88. // reason, the return value may be the number of bytes buffered internally
  89. // and not the number of bytes written to the underlying net.Conn when a non-nil
  90. // error is returned.
  91. //
  92. // Warning: Does not handle chunked encoding and multiple HTTP requests written
  93. // in a single Write(). Must be called synchronously.
  94. func (t *HTTPTransformer) Write(b []byte) (int, error) {
  95. if t.state == httpTransformerReadWriteReqLineAndHeaders {
  96. // Do not need to check return value. Applies to all subsequent
  97. // calls to t.b.Write() and this comment will not be repeated for
  98. // each. See https://github.com/golang/go/blob/1e9ff255a130200fcc4ec5e911d28181fce947d5/src/bytes/buffer.go#L164.
  99. t.b.Write(b)
  100. // Wait until the Request-Line, and all headers, have been read. Must
  101. // check all accumulated bytes incase the "\r\n\r\n" separator is
  102. // written over multiple Write() calls; from reading the go1.19.5
  103. // net/http code the entire HTTP request is written in a single Write()
  104. // call.
  105. sep := []byte("\r\n\r\n")
  106. headerBodyLines := bytes.SplitN(t.b.Bytes(), sep, 2) // split Request-Line, and headers, from body
  107. if len(headerBodyLines) <= 1 {
  108. // b buffered in t.b and the Request-Line, and all headers, have not
  109. // been recieved so another Write() call is expected.
  110. return len(b), nil
  111. } // else: Request-Line, and all headers, have been read
  112. // read Content-Length before applying transform
  113. var headerLines [][]byte
  114. lines := bytes.Split(headerBodyLines[0], []byte("\r\n"))
  115. if len(lines) > 1 {
  116. // skip Request-Line, e.g. "GET /foo HTTP/1.1"
  117. headerLines = lines[1:]
  118. }
  119. var cl []byte
  120. contentLengthHeader := []byte("Content-Length:")
  121. for _, header := range headerLines {
  122. if bytes.HasPrefix(header, contentLengthHeader) {
  123. cl = textproto.TrimBytes(header[len(contentLengthHeader):])
  124. break
  125. }
  126. }
  127. if len(cl) == 0 {
  128. // Irrecoverable error because either Content-Length header
  129. // is missing, or Content-Length header value is empty, e.g.
  130. // "Content-Length: ", and request body length cannot be
  131. // determined.
  132. return len(b), errors.TraceNew("Content-Length missing")
  133. }
  134. contentLength, err := strconv.ParseUint(string(cl), 10, 63)
  135. if err != nil {
  136. // Irrecoverable error because Content-Length is malformed and
  137. // request body length cannot be determined.
  138. return len(b), errors.Trace(err)
  139. }
  140. t.remain = contentLength
  141. // transform, and write, Request-Line and headers.
  142. reqLineAndHeadersLen := len(headerBodyLines[0]) + len(sep)
  143. reqLineAndHeaders := t.b.Bytes()[:reqLineAndHeadersLen]
  144. if t.transform != nil {
  145. newReqLineAndHeaders, err := t.transform.Apply(t.seed, reqLineAndHeaders)
  146. if err != nil {
  147. // TODO: consider logging an error and skiping transform
  148. // instead of returning an error, if the transform is broken
  149. // then all subsequent applications may fail.
  150. return len(b), errors.Trace(err)
  151. }
  152. // perf: only allocate new slice if length changed, otherwise the
  153. // transformed data can be copied directly over the original in t.b.
  154. if len(newReqLineAndHeaders) == len(reqLineAndHeaders) {
  155. // Do not need to check return value. It is guaranteed that
  156. // n == len(newReqLineAndHeaders) because t.b.Len() >= n if the
  157. // transformed data is the same size as the original data.
  158. copy(t.b.Bytes()[:reqLineAndHeadersLen], newReqLineAndHeaders)
  159. } else {
  160. // Copy any request body bytes received before resetting the
  161. // buffer.
  162. var reqBody []byte
  163. reqBodyLen := t.b.Len() - reqLineAndHeadersLen // number of request body bytes received
  164. if reqBodyLen > 0 {
  165. reqBody = make([]byte, reqBodyLen)
  166. copy(reqBody, t.b.Bytes()[reqLineAndHeadersLen:])
  167. }
  168. // Reset the buffer and write transformed Request-Line, and
  169. // headers, and any request body bytes received into it.
  170. t.b.Reset()
  171. t.b.Write(newReqLineAndHeaders)
  172. if len(reqBody) > 0 {
  173. t.b.Write(reqBody)
  174. }
  175. }
  176. reqLineAndHeaders = newReqLineAndHeaders
  177. }
  178. if math.MaxUint64-t.remain < uint64(len(reqLineAndHeaders)) {
  179. // Irrecoverable error because request is malformed:
  180. // Content-Length + len(reqLineAndHeaders) > math.MaxUint64.
  181. return len(b), errors.TraceNew("t.remain + uint64(len(header)) overflows")
  182. }
  183. t.remain += uint64(len(reqLineAndHeaders))
  184. if uint64(t.b.Len()) > t.remain {
  185. // Should never happen, multiple requests written in a single
  186. // Write() are not supported.
  187. return len(b), errors.TraceNew("multiple HTTP requests in single Write() not supported")
  188. }
  189. n, err := t.b.WriteTo(t.Conn)
  190. t.remain -= uint64(n)
  191. if t.remain > 0 {
  192. t.state = httpTransformerReadWriteBody
  193. }
  194. // Do not wrap any I/O err returned by Conn
  195. return len(b), err
  196. }
  197. // Request-Line, and headers, have been transformed. Write any remaining
  198. // bytes of these and then write request body.
  199. // Must write buffered bytes first, in-order, to write bytes to underlying
  200. // net.Conn in the same order they were received in.
  201. //
  202. // Already checked that t.b does not contain bytes of a subsequent HTTP
  203. // request when the Request-Line, and headers, are parsed, i.e. at this
  204. // point it is guaranteed that t.b.Len() <= t.remain.
  205. //
  206. // In practise the buffer will be empty by this point because its entire
  207. // contents will have been written in the first call to t.b.WriteTo(t.Conn)
  208. // when the Request-Line, and headers, are received, parsed, and
  209. // transformed; otherwise the underlying transport will have failed and the
  210. // caller will not invoke Write() again on this instance. See
  211. // HTTPTransformer.Write() comment.
  212. wrote, err := t.b.WriteTo(t.Conn)
  213. t.remain -= uint64(wrote)
  214. if err != nil {
  215. // b not written or buffered
  216. // Do not wrap any I/O err returned by Conn
  217. return 0, err
  218. }
  219. if uint64(len(b)) > t.remain {
  220. return len(b), errors.TraceNew("multiple HTTP requests in single Write() not supported")
  221. }
  222. n, err := t.Conn.Write(b)
  223. t.remain -= uint64(n)
  224. if t.remain <= 0 {
  225. // Entire request has been written. Return to waiting for next HTTP
  226. // request to arrive.
  227. t.state = httpTransformerReadWriteReqLineAndHeaders
  228. t.remain = 0
  229. }
  230. // Do not wrap any I/O err returned by Conn
  231. return n, err
  232. }
  233. // GetMetrics implements the common.MetricsSource interface.
  234. func (t *HTTPTransformer) GetMetrics() common.LogFields {
  235. // Relay any metrics from the underlying conn.
  236. m, ok := t.Conn.(common.MetricsSource)
  237. if ok {
  238. return m.GetMetrics()
  239. }
  240. return nil
  241. }
  242. func WrapDialerWithHTTPTransformer(dialer common.Dialer, params *HTTPTransformerParameters) common.Dialer {
  243. return func(ctx context.Context, network, addr string) (net.Conn, error) {
  244. conn, err := dialer(ctx, network, addr)
  245. if err != nil {
  246. return nil, errors.Trace(err)
  247. }
  248. return &HTTPTransformer{
  249. Conn: conn,
  250. transform: params.ProtocolTransformSpec,
  251. seed: params.ProtocolTransformSeed,
  252. }, nil
  253. }
  254. }