snowflake-broker_test.go 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024
  1. package main
  2. import (
  3. "bytes"
  4. "container/heap"
  5. "encoding/hex"
  6. "fmt"
  7. "io"
  8. "log"
  9. "net/http"
  10. "net/http/httptest"
  11. "os"
  12. "sync"
  13. "testing"
  14. "time"
  15. . "github.com/smartystreets/goconvey/convey"
  16. "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/amp"
  17. "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages"
  18. )
  19. func NullLogger() *log.Logger {
  20. logger := log.New(os.Stdout, "", 0)
  21. logger.SetOutput(io.Discard)
  22. return logger
  23. }
  24. var promOnce sync.Once
  25. var (
  26. sdp = "v=0\r\n" +
  27. "o=- 123456789 987654321 IN IP4 0.0.0.0\r\n" +
  28. "s=-\r\n" +
  29. "t=0 0\r\n" +
  30. "a=fingerprint:sha-256 12:34\r\n" +
  31. "a=extmap-allow-mixed\r\n" +
  32. "a=group:BUNDLE 0\r\n" +
  33. "m=application 9 UDP/DTLS/SCTP webrtc-datachannel\r\n" +
  34. "c=IN IP4 0.0.0.0\r\n" +
  35. "a=setup:actpass\r\n" +
  36. "a=mid:0\r\n" +
  37. "a=sendrecv\r\n" +
  38. "a=sctp-port:5000\r\n" +
  39. "a=ice-ufrag:CoVEaiFXRGVzshXG\r\n" +
  40. "a=ice-pwd:aOrOZXraTfFKzyeBxIXYYKjSgRVPGhUx\r\n" +
  41. "a=candidate:1000 1 udp 2000 8.8.8.8 3000 typ host\r\n" +
  42. "a=end-of-candidates\r\n"
  43. sid = "ymbcCMto7KHNGYlp"
  44. )
  45. func createClientOffer(sdp, nat, fingerprint string) (*bytes.Reader, error) {
  46. clientRequest := &messages.ClientPollRequest{
  47. Offer: sdp,
  48. NAT: nat,
  49. Fingerprint: fingerprint,
  50. }
  51. encOffer, err := clientRequest.EncodeClientPollRequest()
  52. if err != nil {
  53. return nil, err
  54. }
  55. offer := bytes.NewReader(encOffer)
  56. return offer, nil
  57. }
  58. func createProxyAnswer(sdp, sid string) (*bytes.Reader, error) {
  59. proxyRequest, err := messages.EncodeAnswerRequest(sdp, sid)
  60. if err != nil {
  61. return nil, err
  62. }
  63. answer := bytes.NewReader(proxyRequest)
  64. return answer, nil
  65. }
  66. func decodeAMPArmorToString(r io.Reader) (string, error) {
  67. dec, err := amp.NewArmorDecoder(r)
  68. if err != nil {
  69. return "", err
  70. }
  71. p, err := io.ReadAll(dec)
  72. return string(p), err
  73. }
  74. func TestBroker(t *testing.T) {
  75. defaultBridgeValue, _ := hex.DecodeString("2B280B23E1107BB62ABFC40DDCC8824814F80A72")
  76. var defaultBridge [20]byte
  77. copy(defaultBridge[:], defaultBridgeValue)
  78. Convey("Context", t, func() {
  79. buf := new(bytes.Buffer)
  80. ctx := NewBrokerContext(log.New(buf, "", 0), "snowflake.torproject.net")
  81. i := &IPC{ctx}
  82. Convey("Adds Snowflake", func() {
  83. So(ctx.snowflakes.Len(), ShouldEqual, 0)
  84. So(len(ctx.idToSnowflake), ShouldEqual, 0)
  85. ctx.AddSnowflake("foo", "", NATUnrestricted, 0)
  86. So(ctx.snowflakes.Len(), ShouldEqual, 1)
  87. So(len(ctx.idToSnowflake), ShouldEqual, 1)
  88. })
  89. Convey("Broker goroutine matches clients with proxies", func() {
  90. p := new(ProxyPoll)
  91. p.id = "test"
  92. p.natType = "unrestricted"
  93. p.offerChannel = make(chan *ClientOffer)
  94. go func(ctx *BrokerContext) {
  95. ctx.proxyPolls <- p
  96. close(ctx.proxyPolls)
  97. }(ctx)
  98. ctx.Broker()
  99. So(ctx.snowflakes.Len(), ShouldEqual, 1)
  100. snowflake := heap.Pop(ctx.snowflakes).(*Snowflake)
  101. snowflake.offerChannel <- &ClientOffer{sdp: []byte("test offer")}
  102. offer := <-p.offerChannel
  103. So(ctx.idToSnowflake["test"], ShouldNotBeNil)
  104. So(offer.sdp, ShouldResemble, []byte("test offer"))
  105. So(ctx.snowflakes.Len(), ShouldEqual, 0)
  106. })
  107. Convey("Request an offer from the Snowflake Heap", func() {
  108. done := make(chan *ClientOffer)
  109. go func() {
  110. offer := ctx.RequestOffer("test", "", NATUnrestricted, 0)
  111. done <- offer
  112. }()
  113. request := <-ctx.proxyPolls
  114. request.offerChannel <- &ClientOffer{sdp: []byte("test offer")}
  115. offer := <-done
  116. So(offer.sdp, ShouldResemble, []byte("test offer"))
  117. })
  118. Convey("Responds to HTTP client offers...", func() {
  119. w := httptest.NewRecorder()
  120. data, err := createClientOffer(sdp, NATUnknown, "")
  121. r, err := http.NewRequest("POST", "snowflake.broker/client", data)
  122. So(err, ShouldBeNil)
  123. Convey("with error when no snowflakes are available.", func() {
  124. clientOffers(i, w, r)
  125. So(w.Code, ShouldEqual, http.StatusOK)
  126. So(w.Body.String(), ShouldEqual, `{"error":"no snowflake proxies currently available"}`)
  127. // Ensure that denial is correctly recorded in metrics
  128. ctx.metrics.printMetrics()
  129. So(buf.String(), ShouldContainSubstring, `client-denied-count 8
  130. client-restricted-denied-count 8
  131. client-unrestricted-denied-count 0
  132. client-snowflake-match-count 0
  133. client-snowflake-timeout-count 0
  134. client-http-count 8
  135. client-http-ips ??=8
  136. client-ampcache-count 0
  137. client-ampcache-ips
  138. client-sqs-count 0
  139. client-sqs-ips
  140. `)
  141. })
  142. Convey("with a proxy answer if available.", func() {
  143. done := make(chan bool)
  144. // Prepare a fake proxy to respond with.
  145. snowflake := ctx.AddSnowflake("test", "", NATUnrestricted, 0)
  146. go func() {
  147. clientOffers(i, w, r)
  148. done <- true
  149. }()
  150. offer := <-snowflake.offerChannel
  151. So(offer.sdp, ShouldResemble, []byte(sdp))
  152. snowflake.answerChannel <- "test answer"
  153. <-done
  154. So(w.Body.String(), ShouldEqual, `{"answer":"test answer"}`)
  155. So(w.Code, ShouldEqual, http.StatusOK)
  156. // Ensure that match is correctly recorded in metrics
  157. ctx.metrics.printMetrics()
  158. So(buf.String(), ShouldContainSubstring, `client-denied-count 0
  159. client-restricted-denied-count 0
  160. client-unrestricted-denied-count 0
  161. client-snowflake-match-count 8
  162. client-snowflake-timeout-count 0
  163. client-http-count 8
  164. client-http-ips ??=8
  165. client-ampcache-count 0
  166. client-ampcache-ips
  167. client-sqs-count 0
  168. client-sqs-ips
  169. `)
  170. })
  171. Convey("with unrestricted proxy to unrestricted client if there are no restricted proxies", func() {
  172. snowflake := ctx.AddSnowflake("test", "", NATUnrestricted, 0)
  173. offerData, err := createClientOffer(sdp, NATUnrestricted, "")
  174. So(err, ShouldBeNil)
  175. r, err := http.NewRequest("POST", "snowflake.broker/client", offerData)
  176. done := make(chan bool)
  177. go func() {
  178. clientOffers(i, w, r)
  179. done <- true
  180. }()
  181. select {
  182. case <-snowflake.offerChannel:
  183. case <-time.After(250 * time.Millisecond):
  184. So(false, ShouldBeTrue)
  185. return
  186. }
  187. snowflake.answerChannel <- "test answer"
  188. <-done
  189. So(w.Body.String(), ShouldEqual, `{"answer":"test answer"}`)
  190. })
  191. Convey("Times out when no proxy responds.", func() {
  192. if testing.Short() {
  193. return
  194. }
  195. done := make(chan bool)
  196. snowflake := ctx.AddSnowflake("fake", "", NATUnrestricted, 0)
  197. go func() {
  198. clientOffers(i, w, r)
  199. // Takes a few seconds here...
  200. done <- true
  201. }()
  202. offer := <-snowflake.offerChannel
  203. So(offer.sdp, ShouldResemble, []byte(sdp))
  204. <-done
  205. So(w.Code, ShouldEqual, http.StatusOK)
  206. So(w.Body.String(), ShouldEqual, `{"error":"timed out waiting for answer!"}`)
  207. })
  208. })
  209. Convey("Responds to HTTP legacy client offers...", func() {
  210. w := httptest.NewRecorder()
  211. // legacy offer starts with {
  212. offer := bytes.NewReader([]byte(fmt.Sprintf(`{%v}`, sdp)))
  213. r, err := http.NewRequest("POST", "snowflake.broker/client", offer)
  214. So(err, ShouldBeNil)
  215. r.Header.Set("Snowflake-NAT-TYPE", "restricted")
  216. Convey("with 503 when no snowflakes are available.", func() {
  217. clientOffers(i, w, r)
  218. So(w.Code, ShouldEqual, http.StatusServiceUnavailable)
  219. So(w.Body.String(), ShouldEqual, "")
  220. // Ensure that denial is correctly recorded in metrics
  221. ctx.metrics.printMetrics()
  222. So(buf.String(), ShouldContainSubstring, `client-denied-count 8
  223. client-restricted-denied-count 8
  224. client-unrestricted-denied-count 0
  225. client-snowflake-match-count 0
  226. client-snowflake-timeout-count 0
  227. client-http-count 8
  228. client-http-ips ??=8
  229. client-ampcache-count 0
  230. client-ampcache-ips
  231. client-sqs-count 0
  232. client-sqs-ips
  233. `)
  234. })
  235. Convey("with a proxy answer if available.", func() {
  236. done := make(chan bool)
  237. // Prepare a fake proxy to respond with.
  238. snowflake := ctx.AddSnowflake("fake", "", NATUnrestricted, 0)
  239. go func() {
  240. clientOffers(i, w, r)
  241. done <- true
  242. }()
  243. offer := <-snowflake.offerChannel
  244. So(offer.sdp, ShouldResemble, []byte(fmt.Sprintf(`{%v}`, sdp)))
  245. snowflake.answerChannel <- "fake answer"
  246. <-done
  247. So(w.Body.String(), ShouldEqual, "fake answer")
  248. So(w.Code, ShouldEqual, http.StatusOK)
  249. // Ensure that match is correctly recorded in metrics
  250. ctx.metrics.printMetrics()
  251. So(buf.String(), ShouldContainSubstring, `client-denied-count 0
  252. client-restricted-denied-count 0
  253. client-unrestricted-denied-count 0
  254. client-snowflake-match-count 8
  255. client-snowflake-timeout-count 0
  256. client-http-count 8
  257. client-http-ips ??=8
  258. client-ampcache-count 0
  259. client-ampcache-ips
  260. client-sqs-count 0
  261. client-sqs-ips
  262. `)
  263. })
  264. Convey("Times out when no proxy responds.", func() {
  265. if testing.Short() {
  266. return
  267. }
  268. done := make(chan bool)
  269. snowflake := ctx.AddSnowflake("fake", "", NATUnrestricted, 0)
  270. go func() {
  271. clientOffers(i, w, r)
  272. // Takes a few seconds here...
  273. done <- true
  274. }()
  275. offer := <-snowflake.offerChannel
  276. So(offer.sdp, ShouldResemble, []byte(fmt.Sprintf(`{%v}`, sdp)))
  277. <-done
  278. So(w.Code, ShouldEqual, http.StatusGatewayTimeout)
  279. })
  280. })
  281. Convey("Responds to AMP client offers...", func() {
  282. w := httptest.NewRecorder()
  283. encPollReq := []byte("1.0\n{\"offer\": \"fake\", \"nat\": \"unknown\"}")
  284. r, err := http.NewRequest("GET", "/amp/client/"+amp.EncodePath(encPollReq), nil)
  285. So(err, ShouldBeNil)
  286. Convey("with status 200 when request is badly formatted.", func() {
  287. r, err := http.NewRequest("GET", "/amp/client/bad", nil)
  288. So(err, ShouldBeNil)
  289. ampClientOffers(i, w, r)
  290. body, err := decodeAMPArmorToString(w.Body)
  291. So(err, ShouldBeNil)
  292. So(body, ShouldEqual, `{"error":"cannot decode URL path"}`)
  293. })
  294. Convey("with error when no snowflakes are available.", func() {
  295. ampClientOffers(i, w, r)
  296. So(w.Code, ShouldEqual, http.StatusOK)
  297. body, err := decodeAMPArmorToString(w.Body)
  298. So(err, ShouldBeNil)
  299. So(body, ShouldEqual, `{"error":"no snowflake proxies currently available"}`)
  300. // Ensure that denial is correctly recorded in metrics
  301. ctx.metrics.printMetrics()
  302. So(buf.String(), ShouldContainSubstring, `client-denied-count 8
  303. client-restricted-denied-count 8
  304. client-unrestricted-denied-count 0
  305. client-snowflake-match-count 0
  306. client-snowflake-timeout-count 0
  307. client-http-count 0
  308. client-http-ips
  309. client-ampcache-count 8
  310. client-ampcache-ips ??=8
  311. client-sqs-count 0
  312. client-sqs-ips
  313. `)
  314. })
  315. Convey("with a proxy answer if available.", func() {
  316. done := make(chan bool)
  317. // Prepare a fake proxy to respond with.
  318. snowflake := ctx.AddSnowflake("fake", "", NATUnrestricted, 0)
  319. go func() {
  320. ampClientOffers(i, w, r)
  321. done <- true
  322. }()
  323. offer := <-snowflake.offerChannel
  324. So(offer.sdp, ShouldResemble, []byte("fake"))
  325. snowflake.answerChannel <- "fake answer"
  326. <-done
  327. body, err := decodeAMPArmorToString(w.Body)
  328. So(err, ShouldBeNil)
  329. So(body, ShouldEqual, `{"answer":"fake answer"}`)
  330. So(w.Code, ShouldEqual, http.StatusOK)
  331. // Ensure that match is correctly recorded in metrics
  332. ctx.metrics.printMetrics()
  333. So(buf.String(), ShouldContainSubstring, `client-denied-count 0
  334. client-restricted-denied-count 0
  335. client-unrestricted-denied-count 0
  336. client-snowflake-match-count 8
  337. client-snowflake-timeout-count 0
  338. client-http-count 0
  339. client-http-ips
  340. client-ampcache-count 8
  341. client-ampcache-ips ??=8
  342. client-sqs-count 0
  343. client-sqs-ips
  344. `)
  345. })
  346. Convey("Times out when no proxy responds.", func() {
  347. if testing.Short() {
  348. return
  349. }
  350. done := make(chan bool)
  351. snowflake := ctx.AddSnowflake("fake", "", NATUnrestricted, 0)
  352. go func() {
  353. ampClientOffers(i, w, r)
  354. // Takes a few seconds here...
  355. done <- true
  356. }()
  357. offer := <-snowflake.offerChannel
  358. So(offer.sdp, ShouldResemble, []byte("fake"))
  359. <-done
  360. So(w.Code, ShouldEqual, http.StatusOK)
  361. body, err := decodeAMPArmorToString(w.Body)
  362. So(err, ShouldBeNil)
  363. So(body, ShouldEqual, `{"error":"timed out waiting for answer!"}`)
  364. })
  365. })
  366. Convey("Responds to proxy polls...", func() {
  367. done := make(chan bool)
  368. w := httptest.NewRecorder()
  369. data := bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0", "AcceptedRelayPattern": "snowflake.torproject.net"}`))
  370. r, err := http.NewRequest("POST", "snowflake.broker/proxy", data)
  371. So(err, ShouldBeNil)
  372. Convey("with a client offer if available.", func() {
  373. go func(i *IPC) {
  374. proxyPolls(i, w, r)
  375. done <- true
  376. }(i)
  377. // Pass a fake client offer to this proxy
  378. p := <-ctx.proxyPolls
  379. So(p.id, ShouldEqual, "ymbcCMto7KHNGYlp")
  380. p.offerChannel <- &ClientOffer{sdp: []byte("fake offer"), fingerprint: defaultBridge[:]}
  381. <-done
  382. So(w.Code, ShouldEqual, http.StatusOK)
  383. So(w.Body.String(), ShouldEqual, `{"Status":"client match","Offer":"fake offer","NAT":"","RelayURL":"wss://snowflake.torproject.net/"}`)
  384. })
  385. Convey("return empty 200 OK when no client offer is available.", func() {
  386. go func(i *IPC) {
  387. proxyPolls(i, w, r)
  388. done <- true
  389. }(i)
  390. p := <-ctx.proxyPolls
  391. So(p.id, ShouldEqual, "ymbcCMto7KHNGYlp")
  392. // nil means timeout
  393. p.offerChannel <- nil
  394. <-done
  395. So(w.Body.String(), ShouldEqual, `{"Status":"no match","Offer":"","NAT":"","RelayURL":""}`)
  396. So(w.Code, ShouldEqual, http.StatusOK)
  397. })
  398. })
  399. Convey("Responds to proxy answers...", func() {
  400. done := make(chan bool)
  401. s := ctx.AddSnowflake(sid, "", NATUnrestricted, 0)
  402. w := httptest.NewRecorder()
  403. data, err := createProxyAnswer(sdp, sid)
  404. So(err, ShouldBeNil)
  405. Convey("by passing to the client if valid.", func() {
  406. r, err := http.NewRequest("POST", "snowflake.broker/answer", data)
  407. So(err, ShouldBeNil)
  408. go func(i *IPC) {
  409. proxyAnswers(i, w, r)
  410. done <- true
  411. }(i)
  412. answer := <-s.answerChannel
  413. <-done
  414. So(w.Code, ShouldEqual, http.StatusOK)
  415. So(answer, ShouldResemble, sdp)
  416. })
  417. Convey("with client gone status if the proxy ID is not recognized", func() {
  418. data, err := createProxyAnswer(sdp, "invalid")
  419. r, err := http.NewRequest("POST", "snowflake.broker/answer", data)
  420. So(err, ShouldBeNil)
  421. proxyAnswers(i, w, r)
  422. So(w.Code, ShouldEqual, http.StatusOK)
  423. b, err := io.ReadAll(w.Body)
  424. So(err, ShouldBeNil)
  425. So(b, ShouldResemble, []byte(`{"Status":"client gone"}`))
  426. })
  427. Convey("with error if the proxy gives invalid answer", func() {
  428. data := bytes.NewReader(nil)
  429. r, err := http.NewRequest("POST", "snowflake.broker/answer", data)
  430. So(err, ShouldBeNil)
  431. proxyAnswers(i, w, r)
  432. So(w.Code, ShouldEqual, http.StatusBadRequest)
  433. })
  434. Convey("with error if the proxy writes too much data", func() {
  435. data := bytes.NewReader(make([]byte, 100001))
  436. r, err := http.NewRequest("POST", "snowflake.broker/answer", data)
  437. So(err, ShouldBeNil)
  438. proxyAnswers(i, w, r)
  439. So(w.Code, ShouldEqual, http.StatusBadRequest)
  440. })
  441. })
  442. })
  443. Convey("End-To-End", t, func() {
  444. ctx := NewBrokerContext(NullLogger(), "snowflake.torproject.net")
  445. i := &IPC{ctx}
  446. Convey("Check for client/proxy data race", func() {
  447. proxy_done := make(chan bool)
  448. client_done := make(chan bool)
  449. go ctx.Broker()
  450. // Make proxy poll
  451. wp := httptest.NewRecorder()
  452. datap := bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0","AcceptedRelayPattern":"snowflake.torproject.net"}`))
  453. rp, err := http.NewRequest("POST", "snowflake.broker/proxy", datap)
  454. So(err, ShouldBeNil)
  455. go func(i *IPC) {
  456. proxyPolls(i, wp, rp)
  457. proxy_done <- true
  458. }(i)
  459. // Client offer
  460. wc := httptest.NewRecorder()
  461. datac, err := createClientOffer(sdp, NATUnknown, "")
  462. So(err, ShouldBeNil)
  463. rc, err := http.NewRequest("POST", "snowflake.broker/client", datac)
  464. So(err, ShouldBeNil)
  465. go func() {
  466. clientOffers(i, wc, rc)
  467. client_done <- true
  468. }()
  469. <-proxy_done
  470. So(wp.Code, ShouldEqual, http.StatusOK)
  471. // Proxy answers
  472. wp = httptest.NewRecorder()
  473. datap, err = createProxyAnswer(sdp, sid)
  474. So(err, ShouldBeNil)
  475. rp, err = http.NewRequest("POST", "snowflake.broker/answer", datap)
  476. So(err, ShouldBeNil)
  477. go func(i *IPC) {
  478. proxyAnswers(i, wp, rp)
  479. proxy_done <- true
  480. }(i)
  481. <-proxy_done
  482. <-client_done
  483. })
  484. Convey("Ensure correct snowflake brokering", func() {
  485. done := make(chan bool)
  486. polled := make(chan bool)
  487. // Proxy polls with its ID first...
  488. dataP := bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0","AcceptedRelayPattern":"snowflake.torproject.net"}`))
  489. wP := httptest.NewRecorder()
  490. rP, err := http.NewRequest("POST", "snowflake.broker/proxy", dataP)
  491. So(err, ShouldBeNil)
  492. go func() {
  493. proxyPolls(i, wP, rP)
  494. polled <- true
  495. }()
  496. // Manually do the Broker goroutine action here for full control.
  497. p := <-ctx.proxyPolls
  498. So(p.id, ShouldEqual, "ymbcCMto7KHNGYlp")
  499. s := ctx.AddSnowflake(p.id, "", NATUnrestricted, 0)
  500. go func() {
  501. offer := <-s.offerChannel
  502. p.offerChannel <- offer
  503. }()
  504. So(ctx.idToSnowflake["ymbcCMto7KHNGYlp"], ShouldNotBeNil)
  505. // Client request blocks until proxy answer arrives.
  506. wC := httptest.NewRecorder()
  507. dataC, err := createClientOffer(sdp, NATUnknown, "")
  508. So(err, ShouldBeNil)
  509. rC, err := http.NewRequest("POST", "snowflake.broker/client", dataC)
  510. So(err, ShouldBeNil)
  511. go func() {
  512. clientOffers(i, wC, rC)
  513. done <- true
  514. }()
  515. <-polled
  516. So(wP.Code, ShouldEqual, http.StatusOK)
  517. So(wP.Body.String(), ShouldResemble, fmt.Sprintf(`{"Status":"client match","Offer":%#q,"NAT":"unknown","RelayURL":"wss://snowflake.torproject.net/"}`, sdp))
  518. So(ctx.idToSnowflake[sid], ShouldNotBeNil)
  519. // Follow up with the answer request afterwards
  520. wA := httptest.NewRecorder()
  521. dataA, err := createProxyAnswer(sdp, sid)
  522. So(err, ShouldBeNil)
  523. rA, err := http.NewRequest("POST", "snowflake.broker/answer", dataA)
  524. So(err, ShouldBeNil)
  525. proxyAnswers(i, wA, rA)
  526. So(wA.Code, ShouldEqual, http.StatusOK)
  527. <-done
  528. So(wC.Code, ShouldEqual, http.StatusOK)
  529. So(wC.Body.String(), ShouldEqual, fmt.Sprintf(`{"answer":%#q}`, sdp))
  530. })
  531. })
  532. }
  533. func TestSnowflakeHeap(t *testing.T) {
  534. Convey("SnowflakeHeap", t, func() {
  535. h := new(SnowflakeHeap)
  536. heap.Init(h)
  537. So(h.Len(), ShouldEqual, 0)
  538. s1 := new(Snowflake)
  539. s2 := new(Snowflake)
  540. s3 := new(Snowflake)
  541. s4 := new(Snowflake)
  542. s1.clients = 4
  543. s2.clients = 5
  544. s3.clients = 3
  545. s4.clients = 1
  546. heap.Push(h, s1)
  547. So(h.Len(), ShouldEqual, 1)
  548. heap.Push(h, s2)
  549. So(h.Len(), ShouldEqual, 2)
  550. heap.Push(h, s3)
  551. So(h.Len(), ShouldEqual, 3)
  552. heap.Push(h, s4)
  553. So(h.Len(), ShouldEqual, 4)
  554. heap.Remove(h, 0)
  555. So(h.Len(), ShouldEqual, 3)
  556. r := heap.Pop(h).(*Snowflake)
  557. So(h.Len(), ShouldEqual, 2)
  558. So(r.clients, ShouldEqual, 3)
  559. So(r.index, ShouldEqual, -1)
  560. r = heap.Pop(h).(*Snowflake)
  561. So(h.Len(), ShouldEqual, 1)
  562. So(r.clients, ShouldEqual, 4)
  563. So(r.index, ShouldEqual, -1)
  564. r = heap.Pop(h).(*Snowflake)
  565. So(h.Len(), ShouldEqual, 0)
  566. So(r.clients, ShouldEqual, 5)
  567. So(r.index, ShouldEqual, -1)
  568. })
  569. }
  570. func TestInvalidGeoipFile(t *testing.T) {
  571. Convey("Geoip", t, func() {
  572. // Make sure things behave properly if geoip file fails to load
  573. ctx := NewBrokerContext(NullLogger(), "")
  574. if err := ctx.metrics.LoadGeoipDatabases("invalid_filename", "invalid_filename6"); err != nil {
  575. log.Printf("loading geo ip databases returned error: %v", err)
  576. }
  577. ctx.metrics.UpdateCountryStats("127.0.0.1", "", NATUnrestricted)
  578. So(ctx.metrics.geoipdb, ShouldBeNil)
  579. })
  580. }
  581. func TestMetrics(t *testing.T) {
  582. Convey("Test metrics...", t, func() {
  583. done := make(chan bool)
  584. buf := new(bytes.Buffer)
  585. ctx := NewBrokerContext(log.New(buf, "", 0), "snowflake.torproject.net")
  586. i := &IPC{ctx}
  587. err := ctx.metrics.LoadGeoipDatabases("test_geoip", "test_geoip6")
  588. So(err, ShouldBeNil)
  589. //Test addition of proxy polls
  590. Convey("for proxy polls", func() {
  591. w := httptest.NewRecorder()
  592. data := bytes.NewReader([]byte("{\"Sid\":\"ymbcCMto7KHNGYlp\",\"Version\":\"1.0\",\"AcceptedRelayPattern\":\"snowflake.torproject.net\"}"))
  593. r, err := http.NewRequest("POST", "snowflake.broker/proxy", data)
  594. r.RemoteAddr = "129.97.208.23:8888" //CA geoip
  595. So(err, ShouldBeNil)
  596. go func(i *IPC) {
  597. proxyPolls(i, w, r)
  598. done <- true
  599. }(i)
  600. p := <-ctx.proxyPolls //manually unblock poll
  601. p.offerChannel <- nil
  602. <-done
  603. w = httptest.NewRecorder()
  604. data = bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0","Type":"standalone","AcceptedRelayPattern":"snowflake.torproject.net"}`))
  605. r, err = http.NewRequest("POST", "snowflake.broker/proxy", data)
  606. r.RemoteAddr = "129.97.208.23:8888" //CA geoip
  607. So(err, ShouldBeNil)
  608. go func(i *IPC) {
  609. proxyPolls(i, w, r)
  610. done <- true
  611. }(i)
  612. p = <-ctx.proxyPolls //manually unblock poll
  613. p.offerChannel <- nil
  614. <-done
  615. w = httptest.NewRecorder()
  616. data = bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0","Type":"badge","AcceptedRelayPattern":"snowflake.torproject.net"}`))
  617. r, err = http.NewRequest("POST", "snowflake.broker/proxy", data)
  618. r.RemoteAddr = "129.97.208.23:8888" //CA geoip
  619. So(err, ShouldBeNil)
  620. go func(i *IPC) {
  621. proxyPolls(i, w, r)
  622. done <- true
  623. }(i)
  624. p = <-ctx.proxyPolls //manually unblock poll
  625. p.offerChannel <- nil
  626. <-done
  627. w = httptest.NewRecorder()
  628. data = bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0","Type":"webext","AcceptedRelayPattern":"snowflake.torproject.net"}`))
  629. r, err = http.NewRequest("POST", "snowflake.broker/proxy", data)
  630. r.RemoteAddr = "129.97.208.23:8888" //CA geoip
  631. So(err, ShouldBeNil)
  632. go func(i *IPC) {
  633. proxyPolls(i, w, r)
  634. done <- true
  635. }(i)
  636. p = <-ctx.proxyPolls //manually unblock poll
  637. p.offerChannel <- nil
  638. <-done
  639. ctx.metrics.printMetrics()
  640. metricsStr := buf.String()
  641. So(metricsStr, ShouldStartWith, "snowflake-stats-end "+time.Now().UTC().Format("2006-01-02 15:04:05")+" (86400 s)\nsnowflake-ips CA=4\n")
  642. So(metricsStr, ShouldContainSubstring, "\nsnowflake-ips-standalone 1\n")
  643. So(metricsStr, ShouldContainSubstring, "\nsnowflake-ips-badge 1\n")
  644. So(metricsStr, ShouldContainSubstring, "\nsnowflake-ips-webext 1\n")
  645. So(metricsStr, ShouldEndWith, `snowflake-ips-total 4
  646. snowflake-idle-count 8
  647. snowflake-proxy-poll-with-relay-url-count 8
  648. snowflake-proxy-poll-without-relay-url-count 0
  649. snowflake-proxy-rejected-for-relay-url-count 0
  650. client-denied-count 0
  651. client-restricted-denied-count 0
  652. client-unrestricted-denied-count 0
  653. client-snowflake-match-count 0
  654. client-snowflake-timeout-count 0
  655. client-http-count 0
  656. client-http-ips
  657. client-ampcache-count 0
  658. client-ampcache-ips
  659. client-sqs-count 0
  660. client-sqs-ips
  661. snowflake-ips-nat-restricted 0
  662. snowflake-ips-nat-unrestricted 0
  663. snowflake-ips-nat-unknown 1
  664. `)
  665. })
  666. //Test addition of client failures
  667. Convey("for no proxies available", func() {
  668. w := httptest.NewRecorder()
  669. data, err := createClientOffer(sdp, NATUnknown, "")
  670. So(err, ShouldBeNil)
  671. r, err := http.NewRequest("POST", "snowflake.broker/client", data)
  672. r.RemoteAddr = "129.97.208.23:8888" //CA geoip
  673. So(err, ShouldBeNil)
  674. clientOffers(i, w, r)
  675. ctx.metrics.printMetrics()
  676. So(buf.String(), ShouldContainSubstring, `client-denied-count 8
  677. client-restricted-denied-count 8
  678. client-unrestricted-denied-count 0
  679. client-snowflake-match-count 0
  680. client-snowflake-timeout-count 0
  681. client-http-count 8
  682. client-http-ips CA=8
  683. client-ampcache-count 0
  684. client-ampcache-ips
  685. client-sqs-count 0
  686. client-sqs-ips `)
  687. // Test reset
  688. buf.Reset()
  689. ctx.metrics.zeroMetrics()
  690. ctx.metrics.printMetrics()
  691. So(buf.String(), ShouldContainSubstring, "\nsnowflake-ips \n")
  692. So(buf.String(), ShouldContainSubstring, "\nsnowflake-ips-standalone 0\n")
  693. So(buf.String(), ShouldContainSubstring, "\nsnowflake-ips-badge 0\n")
  694. So(buf.String(), ShouldContainSubstring, "\nsnowflake-ips-webext 0\n")
  695. So(buf.String(), ShouldContainSubstring, `snowflake-ips-total 0
  696. snowflake-idle-count 0
  697. snowflake-proxy-poll-with-relay-url-count 0
  698. snowflake-proxy-poll-without-relay-url-count 0
  699. snowflake-proxy-rejected-for-relay-url-count 0
  700. client-denied-count 0
  701. client-restricted-denied-count 0
  702. client-unrestricted-denied-count 0
  703. client-snowflake-match-count 0
  704. client-snowflake-timeout-count 0
  705. client-http-count 0
  706. client-http-ips
  707. client-ampcache-count 0
  708. client-ampcache-ips
  709. client-sqs-count 0
  710. client-sqs-ips
  711. snowflake-ips-nat-restricted 0
  712. snowflake-ips-nat-unrestricted 0
  713. snowflake-ips-nat-unknown 0
  714. `)
  715. })
  716. //Test addition of client matches
  717. Convey("for client-proxy match", func() {
  718. w := httptest.NewRecorder()
  719. data, err := createClientOffer(sdp, NATUnknown, "")
  720. So(err, ShouldBeNil)
  721. r, err := http.NewRequest("POST", "snowflake.broker/client", data)
  722. So(err, ShouldBeNil)
  723. // Prepare a fake proxy to respond with.
  724. snowflake := ctx.AddSnowflake("fake", "", NATUnrestricted, 0)
  725. go func() {
  726. clientOffers(i, w, r)
  727. done <- true
  728. }()
  729. offer := <-snowflake.offerChannel
  730. So(offer.sdp, ShouldResemble, []byte(sdp))
  731. snowflake.answerChannel <- "fake answer"
  732. <-done
  733. ctx.metrics.printMetrics()
  734. So(buf.String(), ShouldContainSubstring, "client-denied-count 0\nclient-restricted-denied-count 0\nclient-unrestricted-denied-count 0\nclient-snowflake-match-count 8")
  735. })
  736. //Test rounding boundary
  737. Convey("binning boundary", func() {
  738. w := httptest.NewRecorder()
  739. data, err := createClientOffer(sdp, NATRestricted, "")
  740. So(err, ShouldBeNil)
  741. r, err := http.NewRequest("POST", "snowflake.broker/client", data)
  742. So(err, ShouldBeNil)
  743. clientOffers(i, w, r)
  744. w = httptest.NewRecorder()
  745. data, err = createClientOffer(sdp, NATRestricted, "")
  746. So(err, ShouldBeNil)
  747. r, err = http.NewRequest("POST", "snowflake.broker/client", data)
  748. So(err, ShouldBeNil)
  749. clientOffers(i, w, r)
  750. w = httptest.NewRecorder()
  751. data, err = createClientOffer(sdp, NATRestricted, "")
  752. So(err, ShouldBeNil)
  753. r, err = http.NewRequest("POST", "snowflake.broker/client", data)
  754. So(err, ShouldBeNil)
  755. clientOffers(i, w, r)
  756. w = httptest.NewRecorder()
  757. data, err = createClientOffer(sdp, NATRestricted, "")
  758. So(err, ShouldBeNil)
  759. r, err = http.NewRequest("POST", "snowflake.broker/client", data)
  760. So(err, ShouldBeNil)
  761. clientOffers(i, w, r)
  762. w = httptest.NewRecorder()
  763. data, err = createClientOffer(sdp, NATRestricted, "")
  764. So(err, ShouldBeNil)
  765. r, err = http.NewRequest("POST", "snowflake.broker/client", data)
  766. So(err, ShouldBeNil)
  767. clientOffers(i, w, r)
  768. w = httptest.NewRecorder()
  769. data, err = createClientOffer(sdp, NATRestricted, "")
  770. So(err, ShouldBeNil)
  771. r, err = http.NewRequest("POST", "snowflake.broker/client", data)
  772. So(err, ShouldBeNil)
  773. clientOffers(i, w, r)
  774. w = httptest.NewRecorder()
  775. data, err = createClientOffer(sdp, NATRestricted, "")
  776. So(err, ShouldBeNil)
  777. r, err = http.NewRequest("POST", "snowflake.broker/client", data)
  778. So(err, ShouldBeNil)
  779. clientOffers(i, w, r)
  780. w = httptest.NewRecorder()
  781. data, err = createClientOffer(sdp, NATRestricted, "")
  782. So(err, ShouldBeNil)
  783. r, err = http.NewRequest("POST", "snowflake.broker/client", data)
  784. So(err, ShouldBeNil)
  785. clientOffers(i, w, r)
  786. ctx.metrics.printMetrics()
  787. So(buf.String(), ShouldContainSubstring, "client-denied-count 8\nclient-restricted-denied-count 8\nclient-unrestricted-denied-count 0\n")
  788. w = httptest.NewRecorder()
  789. data, err = createClientOffer(sdp, NATRestricted, "")
  790. So(err, ShouldBeNil)
  791. r, err = http.NewRequest("POST", "snowflake.broker/client", data)
  792. So(err, ShouldBeNil)
  793. clientOffers(i, w, r)
  794. buf.Reset()
  795. ctx.metrics.printMetrics()
  796. So(buf.String(), ShouldContainSubstring, "client-denied-count 16\nclient-restricted-denied-count 16\nclient-unrestricted-denied-count 0\n")
  797. })
  798. //Test unique ip
  799. Convey("proxy counts by unique ip", func() {
  800. w := httptest.NewRecorder()
  801. data := bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0","AcceptedRelayPattern":"snowflake.torproject.net"}`))
  802. r, err := http.NewRequest("POST", "snowflake.broker/proxy", data)
  803. r.RemoteAddr = "129.97.208.23:8888" //CA geoip
  804. So(err, ShouldBeNil)
  805. go func(i *IPC) {
  806. proxyPolls(i, w, r)
  807. done <- true
  808. }(i)
  809. p := <-ctx.proxyPolls //manually unblock poll
  810. p.offerChannel <- nil
  811. <-done
  812. data = bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0","AcceptedRelayPattern":"snowflake.torproject.net"}`))
  813. r, err = http.NewRequest("POST", "snowflake.broker/proxy", data)
  814. if err != nil {
  815. log.Printf("unable to get NewRequest with error: %v", err)
  816. }
  817. r.RemoteAddr = "129.97.208.23:8888" //CA geoip
  818. go func(i *IPC) {
  819. proxyPolls(i, w, r)
  820. done <- true
  821. }(i)
  822. p = <-ctx.proxyPolls //manually unblock poll
  823. p.offerChannel <- nil
  824. <-done
  825. ctx.metrics.printMetrics()
  826. metricsStr := buf.String()
  827. So(metricsStr, ShouldContainSubstring, "snowflake-ips CA=1\n")
  828. So(metricsStr, ShouldContainSubstring, "snowflake-ips-total 1\n")
  829. })
  830. //Test NAT types
  831. Convey("proxy counts by NAT type", func() {
  832. w := httptest.NewRecorder()
  833. data := bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.2","Type":"unknown","NAT":"restricted","AcceptedRelayPattern":"snowflake.torproject.net"}`))
  834. r, err := http.NewRequest("POST", "snowflake.broker/proxy", data)
  835. r.RemoteAddr = "129.97.208.23:8888" //CA geoip
  836. So(err, ShouldBeNil)
  837. go func(i *IPC) {
  838. proxyPolls(i, w, r)
  839. done <- true
  840. }(i)
  841. p := <-ctx.proxyPolls //manually unblock poll
  842. p.offerChannel <- nil
  843. <-done
  844. ctx.metrics.printMetrics()
  845. So(buf.String(), ShouldContainSubstring, "snowflake-ips-nat-restricted 1\nsnowflake-ips-nat-unrestricted 0\nsnowflake-ips-nat-unknown 0")
  846. data = bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.2","Type":"unknown","NAT":"unrestricted","AcceptedRelayPattern":"snowflake.torproject.net"}`))
  847. r, err = http.NewRequest("POST", "snowflake.broker/proxy", data)
  848. if err != nil {
  849. log.Printf("unable to get NewRequest with error: %v", err)
  850. }
  851. r.RemoteAddr = "129.97.208.24:8888" //CA geoip
  852. go func(i *IPC) {
  853. proxyPolls(i, w, r)
  854. done <- true
  855. }(i)
  856. p = <-ctx.proxyPolls //manually unblock poll
  857. p.offerChannel <- nil
  858. <-done
  859. ctx.metrics.printMetrics()
  860. So(buf.String(), ShouldContainSubstring, "snowflake-ips-nat-restricted 1\nsnowflake-ips-nat-unrestricted 1\nsnowflake-ips-nat-unknown 0")
  861. })
  862. Convey("client failures by NAT type", func() {
  863. w := httptest.NewRecorder()
  864. data, err := createClientOffer(sdp, NATRestricted, "")
  865. So(err, ShouldBeNil)
  866. r, err := http.NewRequest("POST", "snowflake.broker/client", data)
  867. So(err, ShouldBeNil)
  868. clientOffers(i, w, r)
  869. ctx.metrics.printMetrics()
  870. So(buf.String(), ShouldContainSubstring, "client-denied-count 8\nclient-restricted-denied-count 8\nclient-unrestricted-denied-count 0\nclient-snowflake-match-count 0")
  871. buf.Reset()
  872. ctx.metrics.zeroMetrics()
  873. data, err = createClientOffer(sdp, NATUnrestricted, "")
  874. So(err, ShouldBeNil)
  875. r, err = http.NewRequest("POST", "snowflake.broker/client", data)
  876. So(err, ShouldBeNil)
  877. clientOffers(i, w, r)
  878. ctx.metrics.printMetrics()
  879. So(buf.String(), ShouldContainSubstring, "client-denied-count 8\nclient-restricted-denied-count 0\nclient-unrestricted-denied-count 8\nclient-snowflake-match-count 0")
  880. buf.Reset()
  881. ctx.metrics.zeroMetrics()
  882. data, err = createClientOffer(sdp, NATUnknown, "")
  883. So(err, ShouldBeNil)
  884. r, err = http.NewRequest("POST", "snowflake.broker/client", data)
  885. So(err, ShouldBeNil)
  886. clientOffers(i, w, r)
  887. ctx.metrics.printMetrics()
  888. So(buf.String(), ShouldContainSubstring, "client-denied-count 8\nclient-restricted-denied-count 8\nclient-unrestricted-denied-count 0\nclient-snowflake-match-count 0")
  889. })
  890. Convey("for country stats order", func() {
  891. stats := map[string]int{
  892. "IT": 50,
  893. "FR": 200,
  894. "TZ": 100,
  895. "CN": 250,
  896. "RU": 150,
  897. "CA": 1,
  898. "BE": 1,
  899. "PH": 1,
  900. }
  901. ctx.metrics.countryStats.counts = stats
  902. So(ctx.metrics.countryStats.Display(), ShouldEqual, "CN=250,FR=200,RU=150,TZ=100,IT=50,BE=1,CA=1,PH=1")
  903. })
  904. })
  905. }