matcher_test.go 37 KB

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