quic_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. //go:build !PSIPHON_DISABLE_QUIC
  2. // +build !PSIPHON_DISABLE_QUIC
  3. /*
  4. * Copyright (c) 2018, Psiphon Inc.
  5. * All rights reserved.
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. *
  20. */
  21. package quic
  22. import (
  23. "context"
  24. "fmt"
  25. "io"
  26. "net"
  27. "runtime"
  28. "strings"
  29. "sync/atomic"
  30. "testing"
  31. "time"
  32. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  33. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  34. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
  35. "golang.org/x/sync/errgroup"
  36. )
  37. func TestQUIC(t *testing.T) {
  38. for quicVersion := range supportedVersionNumbers {
  39. t.Run(fmt.Sprintf("%s", quicVersion), func(t *testing.T) {
  40. if isGQUIC(quicVersion) && !GQUICEnabled() {
  41. t.Skipf("gQUIC is not enabled")
  42. }
  43. runQUIC(t, quicVersion, GQUICEnabled(), false)
  44. })
  45. if isIETF(quicVersion) {
  46. t.Run(fmt.Sprintf("%s (invoke anti-probing)", quicVersion), func(t *testing.T) {
  47. runQUIC(t, quicVersion, GQUICEnabled(), true)
  48. })
  49. }
  50. if isIETF(quicVersion) {
  51. t.Run(fmt.Sprintf("%s (disable gQUIC)", quicVersion), func(t *testing.T) {
  52. runQUIC(t, quicVersion, false, false)
  53. })
  54. }
  55. }
  56. }
  57. func runQUIC(
  58. t *testing.T,
  59. quicVersion string,
  60. enableGQUIC bool,
  61. invokeAntiProbing bool) {
  62. initGoroutines := getGoroutines()
  63. clients := 10
  64. bytesToSend := 1 << 20
  65. serverReceivedBytes := int64(0)
  66. clientReceivedBytes := int64(0)
  67. // Intermittently, on some platforms, the client connection termination
  68. // packet is not received even when sent/received locally; set a brief
  69. // idle timeout to ensure the server-side client handler doesn't block too
  70. // long on Read, causing the test to fail.
  71. //
  72. // In realistic network conditions, and especially under adversarial
  73. // network conditions, we should not expect to regularly receive client
  74. // connection termination packets.
  75. serverIdleTimeout = 1 * time.Second
  76. irregularTunnelLogger := func(_ string, err error, _ common.LogFields) {
  77. if !invokeAntiProbing {
  78. t.Errorf("unexpected irregular tunnel event: %v", err)
  79. }
  80. }
  81. obfuscationKey := prng.HexString(32)
  82. listener, err := Listen(
  83. nil,
  84. irregularTunnelLogger,
  85. "127.0.0.1:0",
  86. obfuscationKey,
  87. enableGQUIC)
  88. if err != nil {
  89. t.Fatalf("Listen failed: %s", err)
  90. }
  91. serverAddress := listener.Addr().String()
  92. testGroup, testCtx := errgroup.WithContext(context.Background())
  93. testGroup.Go(func() error {
  94. if invokeAntiProbing {
  95. // The quic-go server can still handshake new sessions even if
  96. // Accept isn't called.
  97. return nil
  98. }
  99. var serverGroup errgroup.Group
  100. for i := 0; i < clients; i++ {
  101. conn, err := listener.Accept()
  102. if err != nil {
  103. return errors.Trace(err)
  104. }
  105. serverGroup.Go(func() error {
  106. b := make([]byte, 1024)
  107. for {
  108. n, err := conn.Read(b)
  109. atomic.AddInt64(&serverReceivedBytes, int64(n))
  110. if err == io.EOF {
  111. return nil
  112. } else if err != nil {
  113. return errors.Trace(err)
  114. }
  115. _, err = conn.Write(b[:n])
  116. if err != nil {
  117. return errors.Trace(err)
  118. }
  119. }
  120. })
  121. }
  122. err := serverGroup.Wait()
  123. if err != nil {
  124. return errors.Trace(err)
  125. }
  126. return nil
  127. })
  128. for i := 0; i < clients; i++ {
  129. disablePathMTUDiscovery := i%2 == 0
  130. testGroup.Go(func() error {
  131. ctx, cancelFunc := context.WithTimeout(
  132. context.Background(), 1*time.Second)
  133. defer cancelFunc()
  134. remoteAddr, err := net.ResolveUDPAddr("udp", serverAddress)
  135. if err != nil {
  136. return errors.Trace(err)
  137. }
  138. packetConn, err := net.ListenPacket("udp4", "127.0.0.1:0")
  139. if err != nil {
  140. return errors.Trace(err)
  141. }
  142. clientObfuscationKey := obfuscationKey
  143. if invokeAntiProbing {
  144. clientObfuscationKey = prng.HexString(32)
  145. packetConn = &countReadsConn{PacketConn: packetConn}
  146. }
  147. obfuscationPaddingSeed, err := prng.NewSeed()
  148. if err != nil {
  149. return errors.Trace(err)
  150. }
  151. var clientHelloSeed *prng.Seed
  152. if isClientHelloRandomized(quicVersion) {
  153. clientHelloSeed, err = prng.NewSeed()
  154. if err != nil {
  155. return errors.Trace(err)
  156. }
  157. }
  158. quicSNIAddress, _, err := net.SplitHostPort(serverAddress)
  159. if err != nil {
  160. return errors.Trace(err)
  161. }
  162. conn, err := Dial(
  163. ctx,
  164. packetConn,
  165. remoteAddr,
  166. quicSNIAddress,
  167. quicVersion,
  168. clientHelloSeed,
  169. clientObfuscationKey,
  170. obfuscationPaddingSeed,
  171. nil,
  172. disablePathMTUDiscovery)
  173. if invokeAntiProbing {
  174. if err == nil {
  175. return errors.TraceNew(
  176. "unexpected dial success with invalid client hello random")
  177. }
  178. readCount := packetConn.(*countReadsConn).getReadCount()
  179. if readCount > 0 {
  180. return errors.Tracef(
  181. "unexpected %d read packets with invalid client hello random",
  182. readCount)
  183. }
  184. return nil
  185. }
  186. if err != nil {
  187. return errors.Trace(err)
  188. }
  189. // Cancel should interrupt dialing only
  190. cancelFunc()
  191. var clientGroup errgroup.Group
  192. clientGroup.Go(func() error {
  193. defer conn.Close()
  194. b := make([]byte, 1024)
  195. bytesRead := 0
  196. for bytesRead < bytesToSend {
  197. n, err := conn.Read(b)
  198. bytesRead += n
  199. atomic.AddInt64(&clientReceivedBytes, int64(n))
  200. if err == io.EOF {
  201. break
  202. } else if err != nil {
  203. return errors.Trace(err)
  204. }
  205. }
  206. return nil
  207. })
  208. clientGroup.Go(func() error {
  209. b := make([]byte, bytesToSend)
  210. _, err := conn.Write(b)
  211. if err != nil {
  212. return errors.Trace(err)
  213. }
  214. return nil
  215. })
  216. return clientGroup.Wait()
  217. })
  218. }
  219. go func() {
  220. testGroup.Wait()
  221. }()
  222. <-testCtx.Done()
  223. listener.Close()
  224. err = testGroup.Wait()
  225. if err != nil {
  226. t.Errorf("goroutine failed: %s", err)
  227. }
  228. bytes := atomic.LoadInt64(&serverReceivedBytes)
  229. expectedBytes := int64(clients * bytesToSend)
  230. if invokeAntiProbing {
  231. expectedBytes = 0
  232. }
  233. if bytes != expectedBytes {
  234. t.Errorf("unexpected serverReceivedBytes: %d vs. %d", bytes, expectedBytes)
  235. }
  236. bytes = atomic.LoadInt64(&clientReceivedBytes)
  237. if bytes != expectedBytes {
  238. t.Errorf("unexpected clientReceivedBytes: %d vs. %d", bytes, expectedBytes)
  239. }
  240. _, err = listener.Accept()
  241. if err == nil {
  242. t.Error("unexpected Accept after Close")
  243. }
  244. // Check for unexpected dangling goroutines after shutdown.
  245. //
  246. // quic-go.packetHandlerMap.listen shutdown is async and some quic-go
  247. // goroutines and/or timers dangle so this test makes allowances for these
  248. // known dangling goroutinees.
  249. expectedDanglingGoroutines := []string{
  250. "quic-go.(*packetHandlerMap).Retire.func1",
  251. "quic-go.(*packetHandlerMap).ReplaceWithClosed.func1",
  252. "quic-go.(*packetHandlerMap).RetireResetToken.func1",
  253. "gquic-go.(*packetHandlerMap).removeByConnectionIDAsString.func1",
  254. }
  255. sleepTime := 100 * time.Millisecond
  256. // The longest expected dangling goroutine is in gquic-go and is launched by a timer
  257. // that fires after ClosedSessionDeleteTimeout, which is 1m. Allow one extra second
  258. // to ensure this period elapses and the time.AfterFunc runs.
  259. //
  260. // To avoid taking 1m to run this test every time, the dangling goroutine check exits
  261. // early once no dangling goroutines are found. Note that this doesn't account for
  262. // any timers still pending at the early exit time.
  263. n := int((61 * time.Second) / sleepTime)
  264. for i := 0; i < n; i++ {
  265. // Sleep before making any checks, since quic-go.packetHandlerMap.listen
  266. // shutdown is asynchronous.
  267. time.Sleep(100 * time.Millisecond)
  268. // After the full 61s, no dangling goroutines are expected.
  269. if i == n-1 {
  270. expectedDanglingGoroutines = []string{}
  271. }
  272. hasDangling, onlyExpectedDangling := checkDanglingGoroutines(
  273. t, initGoroutines, expectedDanglingGoroutines)
  274. if !hasDangling {
  275. break
  276. } else if !onlyExpectedDangling {
  277. t.Fatalf("unexpected dangling goroutines")
  278. }
  279. }
  280. }
  281. type countReadsConn struct {
  282. net.PacketConn
  283. readCount int32
  284. }
  285. func (conn *countReadsConn) ReadFrom(p []byte) (int, net.Addr, error) {
  286. n, addr, err := conn.PacketConn.ReadFrom(p)
  287. if n > 0 {
  288. atomic.AddInt32(&conn.readCount, 1)
  289. }
  290. return n, addr, err
  291. }
  292. func (conn *countReadsConn) getReadCount() int {
  293. return int(atomic.LoadInt32(&conn.readCount))
  294. }
  295. func getGoroutines() []runtime.StackRecord {
  296. n, _ := runtime.GoroutineProfile(nil)
  297. r := make([]runtime.StackRecord, n)
  298. runtime.GoroutineProfile(r)
  299. return r
  300. }
  301. func checkDanglingGoroutines(
  302. t *testing.T,
  303. initGoroutines []runtime.StackRecord,
  304. expectedDanglingGoroutines []string) (bool, bool) {
  305. hasDangling := false
  306. onlyExpectedDangling := true
  307. current := getGoroutines()
  308. for _, g := range current {
  309. found := false
  310. for _, h := range initGoroutines {
  311. if g == h {
  312. found = true
  313. break
  314. }
  315. }
  316. if !found {
  317. stack := g.Stack()
  318. funcNames := make([]string, len(stack))
  319. skip := false
  320. isExpected := false
  321. for i := 0; i < len(stack); i++ {
  322. funcNames[i] = getFunctionName(stack[i])
  323. // The current goroutine won't have the same stack as in initGoroutines.
  324. if strings.Contains(funcNames[i], "checkDanglingGoroutines") {
  325. skip = true
  326. break
  327. }
  328. // testing.T.Run runs the the test function, f, in another goroutine. f is
  329. // the current goroutine, which captures initGoroutines.
  330. // https://github.com/golang/go/blob/release-branch.go1.13/src/testing/testing.go#L960-L961:
  331. //
  332. // go tRunner(t, f)
  333. // if !<-t.signal {
  334. // ...
  335. //
  336. // f may capture initGoroutines before or after testing.T.Run advances to
  337. // the channel receive, so the stack of the testing.T.Run goroutine may or
  338. // may not match initGoroutines. Skip it.
  339. if strings.Contains(funcNames[i], "testing.(*T).Run") {
  340. skip = true
  341. break
  342. }
  343. // This goroutine, created by Listener.clientRandomHistory,
  344. // terminates nondeterministically, based on garbage
  345. // collection. Skip it.
  346. if strings.Contains(funcNames[i], "go-cache-lru.(*janitor).Run") {
  347. skip = true
  348. break
  349. }
  350. for _, expected := range expectedDanglingGoroutines {
  351. if strings.Contains(funcNames[i], expected) {
  352. isExpected = true
  353. break
  354. }
  355. }
  356. if isExpected {
  357. break
  358. }
  359. }
  360. if !skip {
  361. hasDangling = true
  362. if !isExpected {
  363. onlyExpectedDangling = false
  364. s := strings.Join(funcNames, " <- ")
  365. t.Logf("found unexpected dangling goroutine: %s", s)
  366. }
  367. }
  368. }
  369. }
  370. return hasDangling, onlyExpectedDangling
  371. }
  372. func getFunctionName(pc uintptr) string {
  373. funcName := runtime.FuncForPC(pc).Name()
  374. index := strings.LastIndex(funcName, "/")
  375. if index != -1 {
  376. funcName = funcName[index+1:]
  377. }
  378. return funcName
  379. }