matcher_test.go 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266
  1. /*
  2. * Copyright (c) 2023, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package inproxy
  20. import (
  21. "context"
  22. "fmt"
  23. "runtime/debug"
  24. "strings"
  25. "sync"
  26. "testing"
  27. "time"
  28. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  29. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  30. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
  31. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/internal/testutils"
  32. )
  33. func TestMatcher(t *testing.T) {
  34. err := runTestMatcher()
  35. if err != nil {
  36. t.Error(errors.Trace(err).Error())
  37. }
  38. }
  39. func runTestMatcher() error {
  40. limitEntryCount := 50
  41. rateLimitQuantity := 100
  42. rateLimitInterval := 1000 * time.Millisecond
  43. logger := testutils.NewTestLogger()
  44. m := NewMatcher(
  45. &MatcherConfig{
  46. Logger: logger,
  47. AnnouncementLimitEntryCount: limitEntryCount,
  48. AnnouncementRateLimitQuantity: rateLimitQuantity,
  49. AnnouncementRateLimitInterval: rateLimitInterval,
  50. OfferLimitEntryCount: limitEntryCount,
  51. OfferRateLimitQuantity: rateLimitQuantity,
  52. OfferRateLimitInterval: rateLimitInterval,
  53. ProxyQualityState: NewProxyQuality(),
  54. AllowMatch: func(common.GeoIPData, common.GeoIPData) bool { return true },
  55. })
  56. err := m.Start()
  57. if err != nil {
  58. return errors.Trace(err)
  59. }
  60. defer m.Stop()
  61. makeID := func() ID {
  62. ID, err := MakeID()
  63. if err != nil {
  64. panic(err)
  65. }
  66. return ID
  67. }
  68. makeAnnouncement := func(properties *MatchProperties) *MatchAnnouncement {
  69. return &MatchAnnouncement{
  70. Properties: *properties,
  71. ProxyID: makeID(),
  72. ConnectionID: makeID(),
  73. }
  74. }
  75. makeOffer := func(properties *MatchProperties, useMediaStreams bool) *MatchOffer {
  76. return &MatchOffer{
  77. Properties: *properties,
  78. UseMediaStreams: useMediaStreams,
  79. }
  80. }
  81. checkMatchMetrics := func(metrics *MatchMetrics) error {
  82. if metrics.OfferQueueSize < 1 || metrics.AnnouncementQueueSize < 1 {
  83. return errors.TraceNew("unexpected match metrics")
  84. }
  85. return nil
  86. }
  87. proxyIP := randomIPAddress()
  88. proxyFunc := func(
  89. resultChan chan error,
  90. proxyIP string,
  91. matchProperties *MatchProperties,
  92. timeout time.Duration,
  93. waitBeforeAnswer chan struct{},
  94. answerSuccess bool) {
  95. ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
  96. defer cancelFunc()
  97. announcement := makeAnnouncement(matchProperties)
  98. offer, matchMetrics, err := m.Announce(ctx, proxyIP, announcement)
  99. if err != nil {
  100. resultChan <- errors.Trace(err)
  101. return
  102. }
  103. err = checkMatchMetrics(matchMetrics)
  104. if err != nil {
  105. resultChan <- errors.Trace(err)
  106. return
  107. }
  108. _, ok := negotiateProtocolVersion(
  109. matchProperties.ProtocolVersion,
  110. offer.Properties.ProtocolVersion,
  111. offer.UseMediaStreams)
  112. if !ok {
  113. resultChan <- errors.TraceNew("unexpected negotiateProtocolVersion failure")
  114. return
  115. }
  116. if waitBeforeAnswer != nil {
  117. <-waitBeforeAnswer
  118. }
  119. if answerSuccess {
  120. err = m.Answer(
  121. &MatchAnswer{
  122. ProxyID: announcement.ProxyID,
  123. ConnectionID: announcement.ConnectionID,
  124. })
  125. } else {
  126. m.AnswerError(announcement.ProxyID, announcement.ConnectionID)
  127. }
  128. resultChan <- errors.Trace(err)
  129. }
  130. clientIP := randomIPAddress()
  131. baseClientFunc := func(
  132. resultChan chan error,
  133. clientIP string,
  134. matchProperties *MatchProperties,
  135. useMediaStreams bool,
  136. timeout time.Duration) {
  137. ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
  138. defer cancelFunc()
  139. offer := makeOffer(matchProperties, useMediaStreams)
  140. _, matchAnnouncement, matchMetrics, err := m.Offer(ctx, clientIP, offer)
  141. if err != nil {
  142. resultChan <- errors.Trace(err)
  143. return
  144. }
  145. err = checkMatchMetrics(matchMetrics)
  146. if err != nil {
  147. resultChan <- errors.Trace(err)
  148. return
  149. }
  150. _, ok := negotiateProtocolVersion(
  151. matchAnnouncement.Properties.ProtocolVersion,
  152. offer.Properties.ProtocolVersion,
  153. offer.UseMediaStreams)
  154. if !ok {
  155. resultChan <- errors.TraceNew("unexpected negotiateProtocolVersion failure")
  156. return
  157. }
  158. resultChan <- nil
  159. }
  160. clientFunc := func(resultChan chan error, clientIP string,
  161. matchProperties *MatchProperties, timeout time.Duration) {
  162. baseClientFunc(resultChan, clientIP, matchProperties, false, timeout)
  163. }
  164. clientUsingMediaStreamsFunc := func(resultChan chan error, clientIP string,
  165. matchProperties *MatchProperties, timeout time.Duration) {
  166. baseClientFunc(resultChan, clientIP, matchProperties, true, timeout)
  167. }
  168. // Test: announce timeout
  169. proxyResultChan := make(chan error)
  170. matchProperties := &MatchProperties{
  171. ProtocolVersion: LatestProtocolVersion,
  172. CommonCompartmentIDs: []ID{makeID()},
  173. }
  174. go proxyFunc(proxyResultChan, proxyIP, matchProperties, 1*time.Microsecond, nil, true)
  175. err = <-proxyResultChan
  176. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  177. return errors.Tracef("unexpected result: %v", err)
  178. }
  179. if m.announcementQueue.getLen() != 0 {
  180. return errors.TraceNew("unexpected queue size")
  181. }
  182. // Test: limit announce entries by IP
  183. time.Sleep(rateLimitInterval)
  184. maxEntries := limitEntryCount
  185. maxEntriesProxyResultChan := make(chan error, maxEntries)
  186. // fill the queue with max entries for one IP; the first one will timeout sooner
  187. go proxyFunc(maxEntriesProxyResultChan, proxyIP, matchProperties, 10*time.Millisecond, nil, true)
  188. for i := 0; i < maxEntries-1; i++ {
  189. go proxyFunc(maxEntriesProxyResultChan, proxyIP, matchProperties, 100*time.Millisecond, nil, true)
  190. }
  191. // await goroutines filling queue
  192. for {
  193. time.Sleep(10 * time.Microsecond)
  194. m.announcementQueueMutex.Lock()
  195. queueLen := m.announcementQueue.getLen()
  196. m.announcementQueueMutex.Unlock()
  197. if queueLen == maxEntries {
  198. break
  199. }
  200. }
  201. // the next enqueue should fail with "max entries"
  202. go proxyFunc(proxyResultChan, proxyIP, matchProperties, 10*time.Millisecond, nil, true)
  203. err = <-proxyResultChan
  204. if err == nil || !strings.HasSuffix(err.Error(), "max entries for IP") {
  205. return errors.Tracef("unexpected result: %v", err)
  206. }
  207. // wait for first entry to timeout
  208. err = <-maxEntriesProxyResultChan
  209. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  210. return errors.Tracef("unexpected result: %v", err)
  211. }
  212. // now another enqueue succeeds as expected
  213. go proxyFunc(proxyResultChan, proxyIP, matchProperties, 10*time.Millisecond, nil, true)
  214. err = <-proxyResultChan
  215. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  216. return errors.Tracef("unexpected result: %v", err)
  217. }
  218. // drain remaining entries
  219. for i := 0; i < maxEntries-1; i++ {
  220. err = <-maxEntriesProxyResultChan
  221. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  222. return errors.Tracef("unexpected result: %v", err)
  223. }
  224. }
  225. // Test: offer timeout
  226. clientResultChan := make(chan error)
  227. go clientFunc(clientResultChan, clientIP, matchProperties, 1*time.Microsecond)
  228. err = <-clientResultChan
  229. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  230. return errors.Tracef("unexpected result: %v", err)
  231. }
  232. if m.offerQueue.Len() != 0 {
  233. return errors.TraceNew("unexpected queue size")
  234. }
  235. // Test: limit offer entries by IP
  236. time.Sleep(rateLimitInterval)
  237. maxEntries = limitEntryCount
  238. maxEntriesClientResultChan := make(chan error, maxEntries)
  239. // fill the queue with max entries for one IP; the first one will timeout sooner
  240. go clientFunc(maxEntriesClientResultChan, clientIP, matchProperties, 10*time.Millisecond)
  241. for i := 0; i < maxEntries-1; i++ {
  242. go clientFunc(maxEntriesClientResultChan, clientIP, matchProperties, 100*time.Millisecond)
  243. }
  244. // await goroutines filling queue
  245. for {
  246. time.Sleep(10 * time.Microsecond)
  247. m.offerQueueMutex.Lock()
  248. queueLen := m.offerQueue.Len()
  249. m.offerQueueMutex.Unlock()
  250. if queueLen == maxEntries {
  251. break
  252. }
  253. }
  254. // enqueue should fail with "max entries"
  255. go clientFunc(clientResultChan, clientIP, matchProperties, 10*time.Millisecond)
  256. err = <-clientResultChan
  257. if err == nil || !strings.HasSuffix(err.Error(), "max entries for IP") {
  258. return errors.Tracef("unexpected result: %v", err)
  259. }
  260. // wait for first entry to timeout
  261. err = <-maxEntriesClientResultChan
  262. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  263. return errors.Tracef("unexpected result: %v", err)
  264. }
  265. // now another enqueue succeeds as expected
  266. go clientFunc(clientResultChan, clientIP, matchProperties, 10*time.Millisecond)
  267. err = <-clientResultChan
  268. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  269. return errors.Tracef("unexpected result: %v", err)
  270. }
  271. // drain remaining entries
  272. for i := 0; i < maxEntries-1; i++ {
  273. err = <-maxEntriesClientResultChan
  274. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  275. return errors.Tracef("unexpected result: %v", err)
  276. }
  277. }
  278. // Test: announcement rate limit
  279. m.SetLimits(
  280. 0, rateLimitQuantity, rateLimitInterval, []ID{},
  281. 0, rateLimitQuantity, rateLimitInterval)
  282. time.Sleep(rateLimitInterval)
  283. maxEntries = rateLimitQuantity
  284. maxEntriesProxyResultChan = make(chan error, maxEntries)
  285. waitGroup := new(sync.WaitGroup)
  286. for i := 0; i < maxEntries; i++ {
  287. waitGroup.Add(1)
  288. go func() {
  289. defer waitGroup.Done()
  290. proxyFunc(maxEntriesProxyResultChan, proxyIP, matchProperties, 1*time.Microsecond, nil, true)
  291. }()
  292. }
  293. // Use a wait group to ensure all maxEntries have hit the rate limiter
  294. // without sleeping before the next attempt, as any sleep can increase
  295. // the rate limiter token count.
  296. waitGroup.Wait()
  297. // the next enqueue should fail with "rate exceeded"
  298. go proxyFunc(proxyResultChan, proxyIP, matchProperties, 10*time.Millisecond, nil, true)
  299. err = <-proxyResultChan
  300. if err == nil || !strings.HasSuffix(err.Error(), "rate exceeded for IP") {
  301. return errors.Tracef("unexpected result: %v", err)
  302. }
  303. // Test: offer rate limit
  304. maxEntries = rateLimitQuantity
  305. maxEntriesClientResultChan = make(chan error, maxEntries)
  306. waitGroup = new(sync.WaitGroup)
  307. for i := 0; i < rateLimitQuantity; i++ {
  308. waitGroup.Add(1)
  309. go func() {
  310. defer waitGroup.Done()
  311. clientFunc(maxEntriesClientResultChan, clientIP, matchProperties, 1*time.Microsecond)
  312. }()
  313. }
  314. waitGroup.Wait()
  315. // enqueue should fail with "rate exceeded"
  316. go clientFunc(clientResultChan, clientIP, matchProperties, 10*time.Millisecond)
  317. err = <-clientResultChan
  318. if err == nil || !strings.HasSuffix(err.Error(), "rate exceeded for IP") {
  319. return errors.Tracef("unexpected result: %v", err)
  320. }
  321. time.Sleep(rateLimitInterval)
  322. m.SetLimits(
  323. limitEntryCount, rateLimitQuantity, rateLimitInterval, []ID{},
  324. limitEntryCount, rateLimitQuantity, rateLimitInterval)
  325. // Test: basic match
  326. commonCompartmentIDs := []ID{makeID()}
  327. geoIPData1 := &MatchProperties{
  328. ProtocolVersion: LatestProtocolVersion,
  329. GeoIPData: common.GeoIPData{Country: "C1", ASN: "A1"},
  330. CommonCompartmentIDs: commonCompartmentIDs,
  331. }
  332. geoIPData2 := &MatchProperties{
  333. ProtocolVersion: LatestProtocolVersion,
  334. GeoIPData: common.GeoIPData{Country: "C2", ASN: "A2"},
  335. CommonCompartmentIDs: commonCompartmentIDs,
  336. }
  337. go proxyFunc(proxyResultChan, proxyIP, geoIPData1, 10*time.Millisecond, nil, true)
  338. go clientFunc(clientResultChan, clientIP, geoIPData2, 10*time.Millisecond)
  339. err = <-proxyResultChan
  340. if err != nil {
  341. return errors.Trace(err)
  342. }
  343. err = <-clientResultChan
  344. if err != nil {
  345. return errors.Trace(err)
  346. }
  347. // Test: answer error
  348. go proxyFunc(proxyResultChan, proxyIP, geoIPData1, 10*time.Millisecond, nil, false)
  349. go clientFunc(clientResultChan, clientIP, geoIPData2, 10*time.Millisecond)
  350. err = <-proxyResultChan
  351. if err != nil {
  352. return errors.Trace(err)
  353. }
  354. err = <-clientResultChan
  355. if err == nil || !strings.HasSuffix(err.Error(), "no answer") {
  356. return errors.Tracef("unexpected result: %v", err)
  357. }
  358. // Test: client is gone
  359. waitBeforeAnswer := make(chan struct{})
  360. go proxyFunc(proxyResultChan, proxyIP, geoIPData1, 100*time.Millisecond, waitBeforeAnswer, true)
  361. go clientFunc(clientResultChan, clientIP, geoIPData2, 10*time.Millisecond)
  362. err = <-clientResultChan
  363. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  364. return errors.Tracef("unexpected result: %v", err)
  365. }
  366. close(waitBeforeAnswer)
  367. err = <-proxyResultChan
  368. if err == nil || !strings.HasSuffix(err.Error(), "no pending answer") {
  369. return errors.Tracef("unexpected result: %v", err)
  370. }
  371. // Test: no compartment match
  372. compartment1 := &MatchProperties{
  373. ProtocolVersion: LatestProtocolVersion,
  374. GeoIPData: geoIPData1.GeoIPData,
  375. CommonCompartmentIDs: []ID{makeID()},
  376. }
  377. compartment2 := &MatchProperties{
  378. ProtocolVersion: LatestProtocolVersion,
  379. GeoIPData: geoIPData2.GeoIPData,
  380. PersonalCompartmentIDs: []ID{makeID()},
  381. }
  382. compartment3 := &MatchProperties{
  383. ProtocolVersion: LatestProtocolVersion,
  384. GeoIPData: geoIPData2.GeoIPData,
  385. CommonCompartmentIDs: []ID{makeID()},
  386. }
  387. compartment4 := &MatchProperties{
  388. ProtocolVersion: LatestProtocolVersion,
  389. GeoIPData: geoIPData2.GeoIPData,
  390. PersonalCompartmentIDs: []ID{makeID()},
  391. }
  392. proxy1ResultChan := make(chan error)
  393. proxy2ResultChan := make(chan error)
  394. client1ResultChan := make(chan error)
  395. client2ResultChan := make(chan error)
  396. go proxyFunc(proxy1ResultChan, proxyIP, compartment1, 10*time.Millisecond, nil, true)
  397. go proxyFunc(proxy2ResultChan, proxyIP, compartment2, 10*time.Millisecond, nil, true)
  398. go clientFunc(client1ResultChan, clientIP, compartment3, 10*time.Millisecond)
  399. go clientFunc(client2ResultChan, clientIP, compartment4, 10*time.Millisecond)
  400. err = <-proxy1ResultChan
  401. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  402. return errors.Tracef("unexpected result: %v", err)
  403. }
  404. err = <-proxy2ResultChan
  405. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  406. return errors.Tracef("unexpected result: %v", err)
  407. }
  408. err = <-client1ResultChan
  409. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  410. return errors.Tracef("unexpected result: %v", err)
  411. }
  412. err = <-client2ResultChan
  413. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  414. return errors.Tracef("unexpected result: %v", err)
  415. }
  416. // Test: common compartment match
  417. compartment1And3 := &MatchProperties{
  418. ProtocolVersion: LatestProtocolVersion,
  419. GeoIPData: geoIPData2.GeoIPData,
  420. CommonCompartmentIDs: []ID{
  421. compartment1.CommonCompartmentIDs[0],
  422. compartment3.CommonCompartmentIDs[0]},
  423. }
  424. go proxyFunc(proxyResultChan, proxyIP, compartment1, 10*time.Millisecond, nil, true)
  425. go clientFunc(clientResultChan, clientIP, compartment1And3, 10*time.Millisecond)
  426. err = <-proxyResultChan
  427. if err != nil {
  428. return errors.Trace(err)
  429. }
  430. err = <-clientResultChan
  431. if err != nil {
  432. return errors.Trace(err)
  433. }
  434. // Test: personal compartment match
  435. compartment2And4 := &MatchProperties{
  436. ProtocolVersion: LatestProtocolVersion,
  437. GeoIPData: geoIPData2.GeoIPData,
  438. PersonalCompartmentIDs: []ID{
  439. compartment2.PersonalCompartmentIDs[0],
  440. compartment4.PersonalCompartmentIDs[0]},
  441. }
  442. go proxyFunc(proxyResultChan, proxyIP, compartment2, 10*time.Millisecond, nil, true)
  443. go clientFunc(clientResultChan, clientIP, compartment2And4, 10*time.Millisecond)
  444. err = <-proxyResultChan
  445. if err != nil {
  446. return errors.Trace(err)
  447. }
  448. err = <-clientResultChan
  449. if err != nil {
  450. return errors.Trace(err)
  451. }
  452. // Test: no same-ASN match
  453. go proxyFunc(proxyResultChan, proxyIP, geoIPData1, 10*time.Millisecond, nil, true)
  454. go clientFunc(clientResultChan, clientIP, geoIPData1, 10*time.Millisecond)
  455. err = <-proxyResultChan
  456. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  457. return errors.Tracef("unexpected result: %v", err)
  458. }
  459. err = <-clientResultChan
  460. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  461. return errors.Tracef("unexpected result: %v", err)
  462. }
  463. // Test: AllowMatch disallow
  464. m.config.AllowMatch = func(proxy common.GeoIPData, client common.GeoIPData) bool {
  465. return proxy != geoIPData1.GeoIPData && client != geoIPData2.GeoIPData
  466. }
  467. go proxyFunc(proxyResultChan, proxyIP, compartment1, 10*time.Millisecond, nil, true)
  468. go clientFunc(clientResultChan, clientIP, compartment1And3, 10*time.Millisecond)
  469. err = <-proxyResultChan
  470. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  471. return errors.Tracef("unexpected result: %v", err)
  472. }
  473. err = <-clientResultChan
  474. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  475. return errors.Tracef("unexpected result: %v", err)
  476. }
  477. // Test: AllowMatch allow
  478. m.config.AllowMatch = func(proxy common.GeoIPData, client common.GeoIPData) bool {
  479. return proxy == geoIPData1.GeoIPData && client == geoIPData2.GeoIPData
  480. }
  481. go proxyFunc(proxyResultChan, proxyIP, compartment1, 10*time.Millisecond, nil, true)
  482. go clientFunc(clientResultChan, clientIP, compartment1And3, 10*time.Millisecond)
  483. err = <-proxyResultChan
  484. if err != nil {
  485. return errors.Trace(err)
  486. }
  487. err = <-clientResultChan
  488. if err != nil {
  489. return errors.Trace(err)
  490. }
  491. m.config.AllowMatch = func(proxy common.GeoIPData, client common.GeoIPData) bool {
  492. return true
  493. }
  494. // Test: downgrade-compatible protocol version match
  495. protocolVersion1 := &MatchProperties{
  496. ProtocolVersion: ProtocolVersion1,
  497. GeoIPData: common.GeoIPData{Country: "C1", ASN: "A1"},
  498. CommonCompartmentIDs: commonCompartmentIDs,
  499. }
  500. protocolVersion2 := &MatchProperties{
  501. ProtocolVersion: ProtocolVersion2,
  502. GeoIPData: common.GeoIPData{Country: "C2", ASN: "A2"},
  503. CommonCompartmentIDs: commonCompartmentIDs,
  504. }
  505. go proxyFunc(proxyResultChan, proxyIP, protocolVersion1, 10*time.Millisecond, nil, true)
  506. go clientFunc(clientResultChan, clientIP, protocolVersion2, 10*time.Millisecond)
  507. err = <-proxyResultChan
  508. if err != nil {
  509. return errors.Trace(err)
  510. }
  511. err = <-clientResultChan
  512. if err != nil {
  513. return errors.Trace(err)
  514. }
  515. // Test: no incompatible protocol version match
  516. go proxyFunc(proxyResultChan, proxyIP, protocolVersion1, 10*time.Millisecond, nil, true)
  517. go clientUsingMediaStreamsFunc(clientResultChan, clientIP, protocolVersion2, 10*time.Millisecond)
  518. err = <-proxyResultChan
  519. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  520. return errors.Tracef("unexpected result: %v", err)
  521. }
  522. err = <-clientResultChan
  523. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  524. return errors.Tracef("unexpected result: %v", err)
  525. }
  526. // Test: downgrade-compatible protocol version match
  527. // Test: proxy preferred NAT match
  528. client1Properties := &MatchProperties{
  529. ProtocolVersion: LatestProtocolVersion,
  530. GeoIPData: common.GeoIPData{Country: "C1", ASN: "A1"},
  531. NATType: NATTypeFullCone,
  532. CommonCompartmentIDs: commonCompartmentIDs,
  533. }
  534. client2Properties := &MatchProperties{
  535. ProtocolVersion: LatestProtocolVersion,
  536. GeoIPData: common.GeoIPData{Country: "C2", ASN: "A2"},
  537. NATType: NATTypeSymmetric,
  538. CommonCompartmentIDs: commonCompartmentIDs,
  539. }
  540. proxy1Properties := &MatchProperties{
  541. ProtocolVersion: LatestProtocolVersion,
  542. GeoIPData: common.GeoIPData{Country: "C3", ASN: "A3"},
  543. NATType: NATTypeNone,
  544. CommonCompartmentIDs: commonCompartmentIDs,
  545. }
  546. proxy2Properties := &MatchProperties{
  547. ProtocolVersion: LatestProtocolVersion,
  548. GeoIPData: common.GeoIPData{Country: "C4", ASN: "A4"},
  549. NATType: NATTypeSymmetric,
  550. CommonCompartmentIDs: commonCompartmentIDs,
  551. }
  552. go proxyFunc(proxy1ResultChan, proxyIP, proxy1Properties, 10*time.Millisecond, nil, true)
  553. go proxyFunc(proxy2ResultChan, proxyIP, proxy2Properties, 10*time.Millisecond, nil, true)
  554. time.Sleep(5 * time.Millisecond) // Hack to ensure both proxies are enqueued
  555. go clientFunc(clientResultChan, clientIP, client1Properties, 10*time.Millisecond)
  556. err = <-proxy1ResultChan
  557. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  558. return errors.Tracef("unexpected result: %v", err)
  559. }
  560. // proxy2 should match since it's the preferred NAT match
  561. err = <-proxy2ResultChan
  562. if err != nil {
  563. return errors.Trace(err)
  564. }
  565. err = <-clientResultChan
  566. if err != nil {
  567. return errors.Trace(err)
  568. }
  569. // Test: client preferred NAT match
  570. // Limitation: the current Matcher.matchAllOffers logic matches the first
  571. // enqueued client against the best proxy match, regardless of whether
  572. // there is another client in the queue that's a better match for that
  573. // proxy. As a result, this test only passes when the preferred matching
  574. // client is enqueued first, and the test is currently of limited utility.
  575. go clientFunc(client2ResultChan, clientIP, client2Properties, 20*time.Millisecond)
  576. time.Sleep(5 * time.Millisecond) // Hack to ensure client is enqueued
  577. go clientFunc(client1ResultChan, clientIP, client1Properties, 20*time.Millisecond)
  578. time.Sleep(5 * time.Millisecond) // Hack to ensure client is enqueued
  579. go proxyFunc(proxy1ResultChan, proxyIP, proxy1Properties, 20*time.Millisecond, nil, true)
  580. err = <-proxy1ResultChan
  581. if err != nil {
  582. return errors.Trace(err)
  583. }
  584. err = <-client1ResultChan
  585. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  586. return errors.Tracef("unexpected result: %v", err)
  587. }
  588. // client2 should match since it's the preferred NAT match
  589. err = <-client2ResultChan
  590. if err != nil {
  591. return errors.Trace(err)
  592. }
  593. // Test: priority supercedes preferred NAT match
  594. go proxyFunc(proxy1ResultChan, proxyIP, proxy1Properties, 10*time.Millisecond, nil, true)
  595. time.Sleep(5 * time.Millisecond) // Hack to ensure proxy is enqueued
  596. proxy2Properties.IsPriority = true
  597. go proxyFunc(proxy2ResultChan, proxyIP, proxy2Properties, 10*time.Millisecond, nil, true)
  598. time.Sleep(5 * time.Millisecond) // Hack to ensure proxy is enqueued
  599. go clientFunc(clientResultChan, clientIP, client2Properties, 10*time.Millisecond)
  600. err = <-proxy1ResultChan
  601. if err == nil || !strings.HasSuffix(err.Error(), "context deadline exceeded") {
  602. return errors.Tracef("unexpected result: %v", err)
  603. }
  604. // proxy2 should match since it's the priority, but not preferred NAT match
  605. err = <-proxy2ResultChan
  606. if err != nil {
  607. return errors.Trace(err)
  608. }
  609. err = <-clientResultChan
  610. if err != nil {
  611. return errors.Trace(err)
  612. }
  613. // Test: many matches
  614. // Reduce test log noise for this phase of the test
  615. logger.SetLogLevelDebug(false)
  616. matchCount := 10000
  617. proxyCount := matchCount
  618. clientCount := matchCount
  619. // Buffered so no goroutine will block reporting result
  620. proxyResultChan = make(chan error, matchCount)
  621. clientResultChan = make(chan error, matchCount)
  622. for proxyCount > 0 || clientCount > 0 {
  623. // Don't simply alternate enqueuing a proxy and a client
  624. if proxyCount > 0 && (clientCount == 0 || prng.FlipCoin()) {
  625. go proxyFunc(proxyResultChan, randomIPAddress(), geoIPData1, 10*time.Second, nil, true)
  626. proxyCount -= 1
  627. } else if clientCount > 0 {
  628. go clientFunc(clientResultChan, randomIPAddress(), geoIPData2, 10*time.Second)
  629. clientCount -= 1
  630. }
  631. }
  632. for i := 0; i < matchCount; i++ {
  633. err = <-proxyResultChan
  634. if err != nil {
  635. return errors.Trace(err)
  636. }
  637. err = <-clientResultChan
  638. if err != nil {
  639. return errors.Trace(err)
  640. }
  641. }
  642. return nil
  643. }
  644. func randomIPAddress() string {
  645. return fmt.Sprintf("%d.%d.%d.%d",
  646. prng.Range(0, 255),
  647. prng.Range(0, 255),
  648. prng.Range(0, 255),
  649. prng.Range(0, 255))
  650. }
  651. func TestMatcherMultiQueue(t *testing.T) {
  652. err := runTestMatcherMultiQueue()
  653. if err != nil {
  654. t.Error(errors.Trace(err).Error())
  655. }
  656. }
  657. func runTestMatcherMultiQueue() error {
  658. // Test: invalid compartment IDs
  659. q := newAnnouncementMultiQueue()
  660. err := q.enqueue(
  661. &announcementEntry{
  662. announcement: &MatchAnnouncement{
  663. Properties: MatchProperties{}}})
  664. if err == nil {
  665. return errors.TraceNew("unexpected success")
  666. }
  667. compartmentID, _ := MakeID()
  668. err = q.enqueue(
  669. &announcementEntry{
  670. announcement: &MatchAnnouncement{
  671. Properties: MatchProperties{
  672. CommonCompartmentIDs: []ID{compartmentID},
  673. PersonalCompartmentIDs: []ID{compartmentID},
  674. }}})
  675. if err == nil {
  676. return errors.TraceNew("unexpected success")
  677. }
  678. // Test: enqueue multiple candidates
  679. var otherCommonCompartmentIDs []ID
  680. var otherPersonalCompartmentIDs []ID
  681. numOtherCompartmentIDs := 10
  682. for i := 0; i < numOtherCompartmentIDs; i++ {
  683. commonCompartmentID, _ := MakeID()
  684. otherCommonCompartmentIDs = append(
  685. otherCommonCompartmentIDs, commonCompartmentID)
  686. personalCompartmentID, _ := MakeID()
  687. otherPersonalCompartmentIDs = append(
  688. otherPersonalCompartmentIDs, personalCompartmentID)
  689. }
  690. numOtherEntries := 10000
  691. for i := 0; i < numOtherEntries; i++ {
  692. ctx, cancel := context.WithDeadline(
  693. context.Background(), time.Now().Add(time.Duration(i+1)*time.Minute))
  694. defer cancel()
  695. err := q.enqueue(
  696. &announcementEntry{
  697. ctx: ctx,
  698. announcement: &MatchAnnouncement{
  699. Properties: MatchProperties{
  700. CommonCompartmentIDs: []ID{
  701. otherCommonCompartmentIDs[i%numOtherCompartmentIDs]},
  702. NATType: NATTypeSymmetric,
  703. }}})
  704. if err != nil {
  705. return errors.Trace(err)
  706. }
  707. err = q.enqueue(
  708. &announcementEntry{
  709. ctx: ctx,
  710. announcement: &MatchAnnouncement{
  711. Properties: MatchProperties{
  712. PersonalCompartmentIDs: []ID{
  713. otherPersonalCompartmentIDs[i%numOtherCompartmentIDs]},
  714. NATType: NATTypeSymmetric,
  715. }}})
  716. if err != nil {
  717. return errors.Trace(err)
  718. }
  719. }
  720. var matchingCommonCompartmentIDs []ID
  721. numMatchingCompartmentIDs := 2
  722. numMatchingEntries := 2
  723. var expectedMatches []*announcementEntry
  724. for i := 0; i < numMatchingCompartmentIDs; i++ {
  725. for j := 0; j < numMatchingEntries; j++ {
  726. commonCompartmentID, _ := MakeID()
  727. matchingCommonCompartmentIDs = append(
  728. matchingCommonCompartmentIDs, commonCompartmentID)
  729. ctx, cancel := context.WithDeadline(
  730. context.Background(), time.Now().Add(time.Duration(i+1)*time.Minute))
  731. defer cancel()
  732. a := &announcementEntry{
  733. ctx: ctx,
  734. announcement: &MatchAnnouncement{
  735. Properties: MatchProperties{
  736. CommonCompartmentIDs: matchingCommonCompartmentIDs[i : i+1],
  737. NATType: NATTypeNone,
  738. }}}
  739. expectedMatches = append(expectedMatches, a)
  740. err := q.enqueue(a)
  741. if err != nil {
  742. return errors.Trace(err)
  743. }
  744. }
  745. }
  746. // Test: inspect queue state
  747. if q.getLen() != numOtherEntries*2+numMatchingCompartmentIDs*numMatchingEntries {
  748. return errors.TraceNew("unexpected total entries count")
  749. }
  750. if len(q.commonCompartmentQueues) !=
  751. numOtherCompartmentIDs+numMatchingCompartmentIDs {
  752. return errors.TraceNew("unexpected compartment queue count")
  753. }
  754. if len(q.personalCompartmentQueues) != numOtherCompartmentIDs {
  755. return errors.TraceNew("unexpected compartment queue count")
  756. }
  757. // Test: find expected matches
  758. iter := q.startMatching(true, matchingCommonCompartmentIDs)
  759. if len(iter.compartmentQueues) != numMatchingCompartmentIDs {
  760. return errors.TraceNew("unexpected iterator state")
  761. }
  762. unlimited, partiallyLimited, strictlyLimited := iter.getNATCounts()
  763. if unlimited != numMatchingCompartmentIDs*numMatchingEntries ||
  764. partiallyLimited != 0 ||
  765. strictlyLimited != 0 {
  766. return errors.TraceNew("unexpected NAT counts")
  767. }
  768. match, _ := iter.getNext()
  769. if match == nil {
  770. return errors.TraceNew("unexpected missing match")
  771. }
  772. if match != expectedMatches[0] {
  773. return errors.TraceNew("unexpected match")
  774. }
  775. if !match.queueReference.dequeue() {
  776. return errors.TraceNew("unexpected already dequeued")
  777. }
  778. if match.queueReference.dequeue() {
  779. return errors.TraceNew("unexpected not already dequeued")
  780. }
  781. iter = q.startMatching(true, matchingCommonCompartmentIDs)
  782. if len(iter.compartmentQueues) != numMatchingCompartmentIDs {
  783. return errors.TraceNew("unexpected iterator state")
  784. }
  785. unlimited, partiallyLimited, strictlyLimited = iter.getNATCounts()
  786. if unlimited != numMatchingEntries*numMatchingCompartmentIDs-1 ||
  787. partiallyLimited != 0 ||
  788. strictlyLimited != 0 {
  789. return errors.TraceNew("unexpected NAT counts")
  790. }
  791. match, _ = iter.getNext()
  792. if match == nil {
  793. return errors.TraceNew("unexpected missing match")
  794. }
  795. if match != expectedMatches[1] {
  796. return errors.TraceNew("unexpected match")
  797. }
  798. if !match.queueReference.dequeue() {
  799. return errors.TraceNew("unexpected already dequeued")
  800. }
  801. if len(iter.compartmentQueues) != numMatchingCompartmentIDs {
  802. return errors.TraceNew("unexpected iterator state")
  803. }
  804. // Test: getNext after dequeue
  805. match, _ = iter.getNext()
  806. if match == nil {
  807. return errors.TraceNew("unexpected missing match")
  808. }
  809. if match != expectedMatches[2] {
  810. return errors.TraceNew("unexpected match")
  811. }
  812. if !match.queueReference.dequeue() {
  813. return errors.TraceNew("unexpected already dequeued")
  814. }
  815. match, _ = iter.getNext()
  816. if match == nil {
  817. return errors.TraceNew("unexpected missing match")
  818. }
  819. if match != expectedMatches[3] {
  820. return errors.TraceNew("unexpected match")
  821. }
  822. if !match.queueReference.dequeue() {
  823. return errors.TraceNew("unexpected already dequeued")
  824. }
  825. // Test: reinspect queue state after dequeues
  826. if q.getLen() != numOtherEntries*2 {
  827. return errors.TraceNew("unexpected total entries count")
  828. }
  829. if len(q.commonCompartmentQueues) != numOtherCompartmentIDs {
  830. return errors.TraceNew("unexpected compartment queue count")
  831. }
  832. if len(q.personalCompartmentQueues) != numOtherCompartmentIDs {
  833. return errors.TraceNew("unexpected compartment queue count")
  834. }
  835. // Test: priority
  836. q = newAnnouncementMultiQueue()
  837. var commonCompartmentIDs []ID
  838. numCompartmentIDs := 10
  839. for i := 0; i < numCompartmentIDs; i++ {
  840. commonCompartmentID, _ := MakeID()
  841. commonCompartmentIDs = append(
  842. commonCompartmentIDs, commonCompartmentID)
  843. }
  844. priorityProxyID, _ := MakeID()
  845. nonPriorityProxyID, _ := MakeID()
  846. ctx, cancel := context.WithDeadline(
  847. context.Background(), time.Now().Add(10*time.Minute))
  848. defer cancel()
  849. numEntries := 10000
  850. for i := 0; i < numEntries; i++ {
  851. // Enqueue every other announcement as a priority
  852. isPriority := i%2 == 0
  853. proxyID := priorityProxyID
  854. if !isPriority {
  855. proxyID = nonPriorityProxyID
  856. }
  857. err := q.enqueue(
  858. &announcementEntry{
  859. ctx: ctx,
  860. announcement: &MatchAnnouncement{
  861. ProxyID: proxyID,
  862. Properties: MatchProperties{
  863. IsPriority: isPriority,
  864. CommonCompartmentIDs: []ID{
  865. commonCompartmentIDs[prng.Intn(numCompartmentIDs)]},
  866. NATType: NATTypeUnknown,
  867. }}})
  868. if err != nil {
  869. return errors.Trace(err)
  870. }
  871. }
  872. iter = q.startMatching(true, commonCompartmentIDs)
  873. for i := 0; i < numEntries; i++ {
  874. match, isPriority := iter.getNext()
  875. if match == nil {
  876. return errors.TraceNew("unexpected missing match")
  877. }
  878. // First half, and only first half, of matches is priority
  879. expectPriority := i < numEntries/2
  880. if isPriority != expectPriority {
  881. return errors.TraceNew("unexpected isPriority")
  882. }
  883. expectedProxyID := priorityProxyID
  884. if !expectPriority {
  885. expectedProxyID = nonPriorityProxyID
  886. }
  887. if match.announcement.ProxyID != expectedProxyID {
  888. return errors.TraceNew("unexpected ProxyID")
  889. }
  890. if !match.queueReference.dequeue() {
  891. return errors.TraceNew("unexpected already dequeued")
  892. }
  893. }
  894. match, _ = iter.getNext()
  895. if match != nil {
  896. return errors.TraceNew("unexpected match")
  897. }
  898. return nil
  899. }
  900. // Benchmark numbers for the previous announcement queue implementation, with
  901. // increasingly slow performance when enqueuing and then finding a new,
  902. // distinct personal compartment ID proxy.
  903. //
  904. // pkg: github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/inproxy
  905. // BenchmarkMatcherQueue/insert_100_announcements-24 17528 68304 ns/op
  906. // BenchmarkMatcherQueue/match_last_of_100_announcements-24 521719 2243 ns/op
  907. // BenchmarkMatcherQueue/insert_10000_announcements-24 208 5780227 ns/op
  908. // BenchmarkMatcherQueue/match_last_of_10000_announcements-24 6796 177587 ns/op
  909. // BenchmarkMatcherQueue/insert_100000_announcements-24 21 50859464 ns/op
  910. // BenchmarkMatcherQueue/match_last_of_100000_announcements-24 538 2249389 ns/op
  911. // BenchmarkMatcherQueue/insert_1000000_announcements-24 3 499685555 ns/op
  912. // BenchmarkMatcherQueue/match_last_of_1000000_announcements-24 33 34299751 ns/op
  913. // BenchmarkMatcherQueue/insert_4999999_announcements-24 1 2606017042 ns/op
  914. // BenchmarkMatcherQueue/match_last_of_4999999_announcements-24 6 179171125 ns/op
  915. // PASS
  916. // ok github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/inproxy 17.585s
  917. //
  918. // Benchmark numbers for the current implemention, the announcementMultiQueue,
  919. // with constant time performance for the same scenario:
  920. //
  921. // BenchmarkMatcherQueue
  922. // BenchmarkMatcherQueue/insert_100_announcements-24 15422 77187 ns/op
  923. // BenchmarkMatcherQueue/match_last_of_100_announcements-24 965152 1217 ns/op
  924. // BenchmarkMatcherQueue/insert_10000_announcements-24 168 7322661 ns/op
  925. // BenchmarkMatcherQueue/match_last_of_10000_announcements-24 906748 1211 ns/op
  926. // BenchmarkMatcherQueue/insert_100000_announcements-24 16 64770370 ns/op
  927. // BenchmarkMatcherQueue/match_last_of_100000_announcements-24 972342 1243 ns/op
  928. // BenchmarkMatcherQueue/insert_1000000_announcements-24 2 701046271 ns/op
  929. // BenchmarkMatcherQueue/match_last_of_1000000_announcements-24 988050 1230 ns/op
  930. // BenchmarkMatcherQueue/insert_4999999_announcements-24 1 4523888833 ns/op
  931. // BenchmarkMatcherQueue/match_last_of_4999999_announcements-24 963894 1186 ns/op
  932. // PASS
  933. // ok github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/inproxy 22.439s
  934. func BenchmarkMatcherQueue(b *testing.B) {
  935. SetAllowCommonASNMatching(true)
  936. defer SetAllowCommonASNMatching(false)
  937. for _, size := range []int{100, 10000, 100000, 1000000, matcherAnnouncementQueueMaxSize - 1} {
  938. debug.FreeOSMemory()
  939. var m *Matcher
  940. commonCompartmentID, _ := MakeID()
  941. b.Run(fmt.Sprintf("insert %d announcements", size), func(b *testing.B) {
  942. for i := 0; i < b.N; i++ {
  943. // Matcher.Start is not called to start the matchWorker;
  944. // instead, matchOffer is invoked directly.
  945. m = NewMatcher(
  946. &MatcherConfig{
  947. Logger: testutils.NewTestLogger(),
  948. AllowMatch: func(common.GeoIPData, common.GeoIPData) bool { return true },
  949. })
  950. for j := 0; j < size; j++ {
  951. var commonCompartmentIDs, personalCompartmentIDs []ID
  952. if prng.FlipCoin() {
  953. personalCompartmentID, _ := MakeID()
  954. personalCompartmentIDs = []ID{personalCompartmentID}
  955. } else {
  956. commonCompartmentIDs = []ID{commonCompartmentID}
  957. }
  958. announcementEntry := &announcementEntry{
  959. ctx: context.Background(),
  960. limitIP: "127.0.0.1",
  961. announcement: &MatchAnnouncement{
  962. Properties: MatchProperties{
  963. CommonCompartmentIDs: commonCompartmentIDs,
  964. PersonalCompartmentIDs: personalCompartmentIDs,
  965. GeoIPData: common.GeoIPData{},
  966. NetworkType: NetworkTypeWiFi,
  967. NATType: NATTypePortRestrictedCone,
  968. PortMappingTypes: []PortMappingType{},
  969. },
  970. ProxyID: ID{},
  971. },
  972. offerChan: make(chan *MatchOffer, 1),
  973. }
  974. err := m.addAnnouncementEntry(announcementEntry)
  975. if err != nil {
  976. b.Fatal(errors.Trace(err).Error())
  977. }
  978. }
  979. }
  980. })
  981. b.Run(fmt.Sprintf("match last of %d announcements", size), func(b *testing.B) {
  982. queueSize := m.announcementQueue.getLen()
  983. if queueSize != size {
  984. b.Fatal(errors.Tracef("unexpected queue size: %d", queueSize).Error())
  985. }
  986. for i := 0; i < b.N; i++ {
  987. personalCompartmentID, _ := MakeID()
  988. announcementEntry :=
  989. &announcementEntry{
  990. ctx: context.Background(),
  991. limitIP: "127.0.0.1",
  992. announcement: &MatchAnnouncement{
  993. Properties: MatchProperties{
  994. ProtocolVersion: LatestProtocolVersion,
  995. PersonalCompartmentIDs: []ID{personalCompartmentID},
  996. GeoIPData: common.GeoIPData{},
  997. NetworkType: NetworkTypeWiFi,
  998. NATType: NATTypePortRestrictedCone,
  999. PortMappingTypes: []PortMappingType{},
  1000. },
  1001. ProxyID: ID{},
  1002. },
  1003. offerChan: make(chan *MatchOffer, 1),
  1004. }
  1005. offerEntry := &offerEntry{
  1006. ctx: context.Background(),
  1007. limitIP: "127.0.0.1",
  1008. offer: &MatchOffer{
  1009. Properties: MatchProperties{
  1010. ProtocolVersion: LatestProtocolVersion,
  1011. PersonalCompartmentIDs: []ID{personalCompartmentID},
  1012. GeoIPData: common.GeoIPData{},
  1013. NetworkType: NetworkTypeWiFi,
  1014. NATType: NATTypePortRestrictedCone,
  1015. PortMappingTypes: []PortMappingType{},
  1016. },
  1017. },
  1018. answerChan: make(chan *answerInfo, 1),
  1019. }
  1020. err := m.addAnnouncementEntry(announcementEntry)
  1021. if err != nil {
  1022. b.Fatal(errors.Trace(err).Error())
  1023. }
  1024. match, _ := m.matchOffer(offerEntry)
  1025. if match == nil {
  1026. b.Fatal(errors.TraceNew("unexpected no match").Error())
  1027. }
  1028. m.removeAnnouncementEntry(false, match)
  1029. }
  1030. })
  1031. }
  1032. }