matcher_test.go 38 KB

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