snowflake-broker_test.go 31 KB

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