httpNormalizer_test.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  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. std_errors "errors"
  23. "io"
  24. "net"
  25. "strings"
  26. "testing"
  27. "time"
  28. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  29. )
  30. type httpNormalizerTest struct {
  31. name string
  32. input string
  33. maxHeaderSize int
  34. prohibitedHeaders []string
  35. headerOrder []string
  36. chunkSize int
  37. connReadErrs []error
  38. validateMeekCookie func([]byte) ([]byte, error)
  39. wantOutput string
  40. wantError error
  41. }
  42. func runHTTPNormalizerTest(tt *httpNormalizerTest, useNormalizer bool) error {
  43. conn := testConn{
  44. readErrs: tt.connReadErrs,
  45. readBuffer: []byte(tt.input),
  46. }
  47. passthroughMessage := "passthrough"
  48. passthroughConn := testConn{
  49. readBuffer: []byte(passthroughMessage),
  50. }
  51. var normalizer net.Conn
  52. if useNormalizer {
  53. n := NewHTTPNormalizer(&conn)
  54. n.maxReqLineAndHeadersSize = tt.maxHeaderSize
  55. n.headerWriteOrder = tt.headerOrder
  56. n.prohibitedHeaders = tt.prohibitedHeaders
  57. n.validateMeekCookie = tt.validateMeekCookie
  58. if n.validateMeekCookie != nil {
  59. n.passthroughAddress = "127.0.0.1:0"
  60. n.passthroughDialer = func(network, address string) (net.Conn, error) {
  61. if network != "tcp" {
  62. return nil, errors.Tracef("expected network tcp but got \"%s\"", network)
  63. }
  64. if address != n.passthroughAddress {
  65. return nil, errors.Tracef("expected address \"%s\" but got \"%s\"", n.passthroughAddress, address)
  66. }
  67. return &passthroughConn, nil // return underlying conn
  68. }
  69. }
  70. normalizer = n
  71. } else {
  72. normalizer = &conn
  73. }
  74. defer normalizer.Close()
  75. remain := len(tt.wantOutput)
  76. var acc []byte
  77. var err error
  78. // Write input bytes to normalizer in chunks and then check
  79. // output.
  80. for {
  81. if remain <= 0 {
  82. break
  83. }
  84. b := make([]byte, tt.chunkSize)
  85. expectedErr := len(conn.readErrs) > 0
  86. var n int
  87. n, err = normalizer.Read(b)
  88. if err != nil && !expectedErr {
  89. // err checked outside loop
  90. break
  91. }
  92. if n > 0 {
  93. remain -= n
  94. acc = append(acc, b[:n]...)
  95. }
  96. }
  97. // Calling Read on an instance of HTTPNormalizer will return io.EOF once a
  98. // passthrough has been activated.
  99. if tt.validateMeekCookie != nil && err == io.EOF {
  100. // wait for passthrough to complete
  101. timeout := time.After(time.Second)
  102. for len(passthroughConn.ReadBuffer()) != 0 || len(conn.ReadBuffer()) != 0 {
  103. select {
  104. case <-timeout:
  105. return errors.TraceNew("timed out waiting for passthrough to complete")
  106. case <-time.After(10 * time.Millisecond):
  107. }
  108. }
  109. // Subsequent reads should return EOF
  110. b := make([]byte, 1)
  111. _, err := normalizer.Read(b)
  112. if err != io.EOF {
  113. return errors.TraceNew("expected EOF")
  114. }
  115. // Subsequent writes should not impact conn or passthroughConn
  116. _, err = normalizer.Write([]byte("ignored"))
  117. if !std_errors.Is(err, ErrPassthroughActive) {
  118. return errors.Tracef("expected error io.EOF but got %v", err)
  119. }
  120. if string(acc) != "" {
  121. return errors.TraceNew("expected to read no bytes")
  122. }
  123. if string(passthroughConn.ReadBuffer()) != "" {
  124. return errors.TraceNew("expected read buffer to be emptied")
  125. }
  126. if string(passthroughConn.WriteBuffer()) != tt.wantOutput {
  127. return errors.Tracef("expected \"%s\" of len %d but got \"%s\" of len %d", escapeNewlines(tt.wantOutput), len(tt.wantOutput), escapeNewlines(string(passthroughConn.WriteBuffer())), len(passthroughConn.WriteBuffer()))
  128. }
  129. if string(conn.ReadBuffer()) != "" {
  130. return errors.TraceNew("expected read buffer to be emptied")
  131. }
  132. if string(conn.WriteBuffer()) != passthroughMessage {
  133. return errors.Tracef("expected \"%s\" of len %d but got \"%s\" of len %d", escapeNewlines(passthroughMessage), len(passthroughMessage), escapeNewlines(string(conn.WriteBuffer())), len(conn.WriteBuffer()))
  134. }
  135. }
  136. if tt.wantError == nil {
  137. if err != nil {
  138. return errors.TraceMsg(err, "unexpected error")
  139. }
  140. } else {
  141. // tt.wantError != nil
  142. if err == nil {
  143. return errors.Tracef("expected error %v", tt.wantError)
  144. } else if !strings.Contains(err.Error(), tt.wantError.Error()) {
  145. return errors.Tracef("expected error %v got %v", tt.wantError, err)
  146. }
  147. }
  148. if tt.wantError == nil && string(acc) != tt.wantOutput {
  149. return errors.Tracef("expected \"%s\" of len %d but got \"%s\" of len %d", escapeNewlines(tt.wantOutput), len(tt.wantOutput), escapeNewlines(string(acc)), len(acc))
  150. }
  151. return nil
  152. }
  153. func TestHTTPNormalizerHTTPRequest(t *testing.T) {
  154. tests := []httpNormalizerTest{
  155. {
  156. name: "no cookie in chunks",
  157. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  158. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  159. chunkSize: 1,
  160. },
  161. {
  162. name: "no cookie in single read",
  163. input: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  164. headerOrder: []string{"Host", "Content-Length"},
  165. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  166. chunkSize: 999,
  167. },
  168. {
  169. name: "no cookie, first read lands in body",
  170. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  171. headerOrder: []string{"Host", "Content-Length"},
  172. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  173. chunkSize: 40, // first read goes up to and including "b"
  174. },
  175. {
  176. name: "no cookie with spaces",
  177. input: "POST / HTTP/1.1\r\n Content-Length: 4 \r\n\r\nabcd",
  178. headerOrder: []string{"Host", "Content-Length"},
  179. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  180. chunkSize: 1,
  181. },
  182. {
  183. name: "cookie and range",
  184. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n Cookie: X\r\nRange: 1234 \r\n\r\nabcd",
  185. headerOrder: []string{"Host", "Content-Length", "Cookie", "Range"},
  186. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\nCookie: X\r\nRange: 1234\r\n\r\nabcd",
  187. chunkSize: 1,
  188. },
  189. {
  190. name: "partial write and errors",
  191. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  192. headerOrder: []string{"Host", "Content-Length"},
  193. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  194. chunkSize: 1,
  195. connReadErrs: []error{std_errors.New("err1"), std_errors.New("err2")},
  196. },
  197. {
  198. name: "Content-Length missing",
  199. input: "POST / HTTP/1.1\r\n\r\nabcd",
  200. wantOutput: "POST / HTTP/1.1\r\n\r\nabcd", // set to ensure all bytes are read
  201. wantError: std_errors.New("Content-Length missing"),
  202. chunkSize: 1,
  203. },
  204. {
  205. name: "invalid Content-Length header value",
  206. input: "POST / HTTP/1.1\r\nContent-Length: X\r\n\r\nabcd",
  207. wantOutput: "POST / HTTP/1.1\r\nContent-Length: X\r\nHost: example.com\r\n\r\nabcd", // set to ensure all bytes are read
  208. wantError: std_errors.New("strconv.ParseUint: parsing \"X\": invalid syntax"),
  209. chunkSize: 1,
  210. },
  211. {
  212. name: "incorrect Content-Length header value",
  213. input: "POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabcd",
  214. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 3\r\n\r\nabc",
  215. chunkSize: 1,
  216. },
  217. {
  218. name: "single HTTP request written in a single write",
  219. input: "POST / HTTP/1.1\r\nRemoved: removed\r\nContent-Length: 4\r\n\r\nabcd",
  220. headerOrder: []string{"Host", "Content-Length"},
  221. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  222. chunkSize: 999,
  223. },
  224. {
  225. name: "multiple HTTP requests written in a single write",
  226. input: "POST / HTTP/1.1\r\nRemoved: removed\r\nContent-Length: 4\r\n\r\nabcdPOST / HTTP/1.1\r\nContent-Length: 2\r\n\r\n12",
  227. headerOrder: []string{"Host", "Content-Length"},
  228. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcdPOST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 2\r\n\r\n12",
  229. chunkSize: 999,
  230. },
  231. {
  232. name: "multiple HTTP requests written in chunks",
  233. input: "POST / HTTP/1.1\r\nRemoved: removed\r\nContent-Length: 4\r\n\r\nabcdPOST / HTTP/1.1\r\nContent-Length: 2\r\n\r\n12",
  234. headerOrder: []string{"Host", "Content-Length"},
  235. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcdPOST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 2\r\n\r\n12",
  236. chunkSize: 3,
  237. },
  238. {
  239. name: "multiple HTTP requests first read lands in middle of last request",
  240. input: "POST / HTTP/1.1\r\nRemoved: removed\r\nContent-Length: 4\r\n\r\nabcdPOST / HTTP/1.1\r\nContent-Length: 2\r\n\r\n12POST / HTTP/1.1\r\nContent-Length: 2\r\n\r\nxyx",
  241. headerOrder: []string{"Host", "Content-Length"},
  242. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcdPOST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 2\r\n\r\n12",
  243. chunkSize: 109,
  244. },
  245. {
  246. name: "longer",
  247. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  248. headerOrder: []string{"Host", "Content-Length"},
  249. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  250. chunkSize: 1,
  251. },
  252. {
  253. name: "shorter",
  254. input: "POST / HTTP/1.1111111111111111111\r\nContent-Length: 4\r\n\r\nabcd",
  255. headerOrder: []string{"Host", "Content-Length"},
  256. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  257. chunkSize: 1,
  258. },
  259. {
  260. name: "missing cookie",
  261. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  262. validateMeekCookie: func([]byte) ([]byte, error) {
  263. return nil, errors.TraceNew("invalid cookie")
  264. },
  265. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  266. chunkSize: 1,
  267. wantError: io.EOF,
  268. },
  269. {
  270. name: "invalid cookie",
  271. input: "POST / HTTP/1.1\r\nCookie: invalid\r\nContent-Length: 4\r\n\r\nabcd",
  272. validateMeekCookie: func([]byte) ([]byte, error) {
  273. return nil, errors.TraceNew("invalid cookie")
  274. },
  275. wantOutput: "POST / HTTP/1.1\r\nCookie: invalid\r\nContent-Length: 4\r\n\r\nabcd",
  276. chunkSize: 1,
  277. wantError: io.EOF,
  278. },
  279. {
  280. name: "valid cookie",
  281. input: "POST / HTTP/1.1\r\nHost: example.com\r\nCookie: valid\r\nContent-Length: 4\r\nRange: unused\r\nSkipped: skipped\r\n\r\nabcd",
  282. headerOrder: []string{"Host", "Cookie", "Content-Length", "Range"},
  283. validateMeekCookie: func([]byte) ([]byte, error) {
  284. return nil, nil
  285. },
  286. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nCookie: valid\r\nContent-Length: 4\r\nRange: unused\r\n\r\nabcd",
  287. chunkSize: 1,
  288. },
  289. {
  290. name: "exceeds max Request-Line, and headers, size",
  291. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\nCookie: X\r\nRange: 1234 \r\n\r\nabcd",
  292. maxHeaderSize: 47, // up to end of Cookie header
  293. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 4\r\nCookie: X\r\nRange: 1234 \r\n\r\nabcd",
  294. chunkSize: 1,
  295. wantError: std_errors.New("exceeds maxReqLineAndHeadersSize"),
  296. },
  297. }
  298. for _, tt := range tests {
  299. t.Run(tt.name, func(t *testing.T) {
  300. err := runHTTPNormalizerTest(&tt, true)
  301. if err != nil {
  302. t.Fatalf("runHTTPNormalizerTest failed: %v", err)
  303. }
  304. })
  305. }
  306. }
  307. // Caveats:
  308. // - Does not test or handle mutiple requests in a single connection
  309. // - Does not test the scenario where the first request in a connection
  310. // passes validation and then a subsequent request fails which triggers
  311. // a passthrough - in this scenario both the listener and passthrough
  312. // listener will receive bytes.
  313. func TestHTTPNormalizerHTTPServer(t *testing.T) {
  314. type test struct {
  315. name string
  316. request string
  317. maxHeaderSize int
  318. prohibitedHeaders []string
  319. wantPassthrough bool
  320. wantRecv string
  321. }
  322. tests := []test{
  323. {
  324. name: "valid cookie",
  325. request: "POST / HTTP/1.1\r\nCookie: valid\r\nContent-Length: 4\r\n\r\nabcd",
  326. wantRecv: "POST / HTTP/1.1\r\nHost: example.com\r\nCookie: valid\r\nContent-Length: 4\r\n\r\nabcd",
  327. },
  328. {
  329. name: "missing cookie",
  330. request: "POST HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  331. wantPassthrough: true,
  332. wantRecv: "POST HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  333. },
  334. {
  335. name: "invalid cookie",
  336. request: "POST HTTP/1.1\r\nCookie: invalid\r\nContent-Length: 4\r\n\r\nabcd",
  337. wantPassthrough: true,
  338. wantRecv: "POST HTTP/1.1\r\nCookie: invalid\r\nContent-Length: 4\r\n\r\nabcd",
  339. },
  340. {
  341. name: "valid cookie with prohibited headers",
  342. request: "POST / HTTP/1.1\r\nCookie: valid\r\nProhibited: prohibited\r\nContent-Length: 4\r\n\r\nabcd",
  343. prohibitedHeaders: []string{"Prohibited"},
  344. wantPassthrough: true,
  345. wantRecv: "POST / HTTP/1.1\r\nCookie: valid\r\nProhibited: prohibited\r\nContent-Length: 4\r\n\r\nabcd",
  346. },
  347. {
  348. name: "valid cookie but exceeds max header size",
  349. request: "POST / HTTP/1.1\r\nCookie: valid\r\nContent-Length: 4\r\n\r\nabcd",
  350. wantPassthrough: true,
  351. maxHeaderSize: 32, // up to end of Cookie header
  352. wantRecv: "POST / HTTP/1.1\r\nCookie: valid\r\nContent-Length: 4\r\n\r\nabcd",
  353. },
  354. }
  355. for _, tt := range tests {
  356. t.Run(tt.name, func(t *testing.T) {
  357. listener, err := net.Listen("tcp", "127.0.0.1:0")
  358. if err != nil {
  359. t.Fatalf("net.Listen failed %v", err)
  360. }
  361. defer listener.Close()
  362. passthrough, err := net.Listen("tcp", "127.0.0.1:0")
  363. if err != nil {
  364. t.Fatalf("net.Listen failed %v", err)
  365. }
  366. defer passthrough.Close()
  367. listener = WrapListenerWithHTTPNormalizer(listener)
  368. normalizer := listener.(*HTTPNormalizerListener)
  369. normalizer.PassthroughAddress = passthrough.Addr().String()
  370. normalizer.PassthroughDialer = net.Dial
  371. normalizer.MaxReqLineAndHeadersSize = tt.maxHeaderSize
  372. normalizer.ProhibitedHeaders = tt.prohibitedHeaders
  373. normalizer.PassthroughLogPassthrough = func(clientIP string, tunnelError error, logFields map[string]interface{}) {}
  374. validateMeekCookieResult := "payload"
  375. normalizer.ValidateMeekCookie = func(clientIP string, cookie []byte) ([]byte, error) {
  376. if string(cookie) == "valid" {
  377. return []byte(validateMeekCookieResult), nil
  378. }
  379. return nil, std_errors.New("invalid cookie")
  380. }
  381. normalizer.HeaderWriteOrder = []string{"Host", "Cookie", "Content-Length"}
  382. type listenerState struct {
  383. lType string // listener type, "listener" or "passthrough"
  384. err error
  385. recv []byte
  386. validateMeekCookieResult []byte // set if listener is "passthrough"
  387. }
  388. runListener := func(listener net.Listener, listenerType string, recv chan *listenerState) {
  389. conn, err := listener.Accept()
  390. if err != nil {
  391. recv <- &listenerState{
  392. lType: listenerType,
  393. err: errors.TraceMsg(err, "listener.Accept failed"),
  394. }
  395. return
  396. }
  397. defer conn.Close()
  398. b := make([]byte, len(tt.wantRecv))
  399. // A single Read should be sufficient because multiple requests
  400. // in a single connection are not supported by this test.
  401. n, err := conn.Read(b)
  402. if err != nil {
  403. recv <- &listenerState{
  404. lType: listenerType,
  405. err: errors.TraceMsg(err, "conn.Read failed"),
  406. }
  407. return
  408. }
  409. b = b[:n]
  410. var validateMeekCookieResult []byte
  411. if n, ok := conn.(*HTTPNormalizer); ok {
  412. validateMeekCookieResult = n.ValidateMeekCookieResult
  413. }
  414. _, err = conn.Write([]byte(listenerType))
  415. if err != nil {
  416. if std_errors.Is(err, ErrPassthroughActive) {
  417. return
  418. }
  419. recv <- &listenerState{
  420. lType: listenerType,
  421. err: errors.TraceMsg(err, "conn.Write failed"),
  422. validateMeekCookieResult: validateMeekCookieResult,
  423. }
  424. return
  425. }
  426. recv <- &listenerState{
  427. lType: listenerType,
  428. recv: b,
  429. err: nil,
  430. validateMeekCookieResult: validateMeekCookieResult,
  431. }
  432. }
  433. recv := make(chan *listenerState)
  434. listenerType := "listener"
  435. passthroughType := "passthrough"
  436. go runListener(listener, listenerType, recv)
  437. go runListener(passthrough, passthroughType, recv)
  438. conn, err := net.Dial("tcp", listener.Addr().String())
  439. if err != nil {
  440. t.Fatalf("net.Dial failed %v", err)
  441. }
  442. defer conn.Close()
  443. n, err := conn.Write([]byte(tt.request))
  444. if err != nil {
  445. t.Fatalf("conn.Write failed %v", err)
  446. }
  447. if n != len(tt.request) {
  448. t.Fatalf("expected to write %d bytes but wrote %d", len(tt.request), n)
  449. }
  450. // read response
  451. b := make([]byte, 512)
  452. n, err = conn.Read(b)
  453. if err != nil {
  454. t.Fatalf("conn.Read failed %v", err)
  455. }
  456. b = b[:n]
  457. if tt.wantPassthrough && string(b) != passthroughType {
  458. t.Fatalf("expected passthrough but got response from listener")
  459. } else if !tt.wantPassthrough && string(b) != listenerType {
  460. t.Fatalf("expected no passthrough but got response from passthrough")
  461. }
  462. r := <-recv
  463. if r.err != nil {
  464. t.Fatalf("listener failed %v", r.err)
  465. }
  466. if !bytes.Equal(r.recv, []byte(tt.wantRecv)) {
  467. t.Fatalf("expected \"%s\" of len %d but got \"%s\" of len %d", escapeNewlines(string(tt.wantRecv)), len(tt.wantRecv), escapeNewlines(string(r.recv)), len(r.recv))
  468. }
  469. if r.lType != "passthrough" && string(r.validateMeekCookieResult) != validateMeekCookieResult {
  470. t.Fatalf("expected validateMeekCookieResult value \"%s\" but got \"%s\"", validateMeekCookieResult, string(r.validateMeekCookieResult))
  471. }
  472. // Check that other listener did not get a connection
  473. n, err = conn.Read(b)
  474. if err != nil && err != io.EOF {
  475. t.Fatalf("conn.Read failed %v", err)
  476. }
  477. if n != 0 {
  478. t.Fatalf("expected to read 0 bytes")
  479. }
  480. select {
  481. case r := <-recv:
  482. t.Fatalf("unexpected response from %s: %v \"%s\"", r.lType, r.err, string(r.recv))
  483. case <-time.After(10 * time.Millisecond):
  484. }
  485. })
  486. }
  487. }
  488. func BenchmarkHTTPNormalizer(b *testing.B) {
  489. inReq := "POST / HTTP/1.1\r\nContent-Length: 400\r\n\r\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  490. outReq := "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 400\r\n\r\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  491. input := ""
  492. output := ""
  493. // Concatenate many requests to simulate a single connection running over
  494. // the normalizer.
  495. for i := 0; i < 100; i++ {
  496. input += inReq
  497. output += outReq
  498. }
  499. // TODO: test different chunk sizes
  500. test := &httpNormalizerTest{
  501. name: "no cookie in chunks",
  502. input: input,
  503. wantOutput: output,
  504. chunkSize: 1,
  505. }
  506. for n := 0; n < b.N; n++ {
  507. // TODO: does test setup and teardown in runHTTPNormalizerTest skew
  508. // the benchmark
  509. err := runHTTPNormalizerTest(test, true)
  510. if err != nil {
  511. b.Fatalf("runHTTPNormalizerTest failed: %v", err)
  512. }
  513. }
  514. }