snowflake-broker_test.go 30 KB

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