cloudflare/cloudflared

Public

mirrored from https://github.com/cloudflare/cloudflaredAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
73a265f2fc62c3a4b995fe1a478d46a79b09a404

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

datagramsession/session_test.go

177lines · modecode

1package datagramsession
2
3import (
4 "bytes"
5 "context"
6 "fmt"
7 "io"
8 "net"
9 "sync"
10 "testing"
11 "time"
12
13 "github.com/google/uuid"
14 "github.com/stretchr/testify/require"
15 "golang.org/x/sync/errgroup"
16)
17
18// TestCloseSession makes sure a session will stop after context is done
19func TestSessionCtxDone(t *testing.T) {
20 testSessionReturns(t, closeByContext, time.Minute*2)
21}
22
23// TestCloseSession makes sure a session will stop after close method is called
24func TestCloseSession(t *testing.T) {
25 testSessionReturns(t, closeByCallingClose, time.Minute*2)
26}
27
28// TestCloseIdle makess sure a session will stop after there is no read/write for a period defined by closeAfterIdle
29func TestCloseIdle(t *testing.T) {
30 testSessionReturns(t, closeByTimeout, time.Millisecond*100)
31}
32
33func testSessionReturns(t *testing.T, closeBy closeMethod, closeAfterIdle time.Duration) {
34 sessionID := uuid.New()
35 cfdConn, originConn := net.Pipe()
36 payload := testPayload(sessionID)
37 transport := &mockQUICTransport{
38 reqChan: newDatagramChannel(1),
39 respChan: newDatagramChannel(1),
40 }
41 session := newSession(sessionID, transport, cfdConn)
42
43 ctx, cancel := context.WithCancel(context.Background())
44 sessionDone := make(chan struct{})
45 go func() {
46 session.Serve(ctx, closeAfterIdle)
47 close(sessionDone)
48 }()
49
50 go func() {
51 n, err := session.transportToDst(payload)
52 require.NoError(t, err)
53 require.Equal(t, len(payload), n)
54 }()
55
56 readBuffer := make([]byte, len(payload)+1)
57 n, err := originConn.Read(readBuffer)
58 require.NoError(t, err)
59 require.Equal(t, len(payload), n)
60
61 lastRead := time.Now()
62
63 switch closeBy {
64 case closeByContext:
65 cancel()
66 case closeByCallingClose:
67 session.close()
68 }
69
70 <-sessionDone
71 if closeBy == closeByTimeout {
72 require.True(t, time.Now().After(lastRead.Add(closeAfterIdle)))
73 }
74 // call cancelled again otherwise the linter will warn about possible context leak
75 cancel()
76}
77
78type closeMethod int
79
80const (
81 closeByContext closeMethod = iota
82 closeByCallingClose
83 closeByTimeout
84)
85
86func TestWriteToDstSessionPreventClosed(t *testing.T) {
87 testActiveSessionNotClosed(t, false, true)
88}
89
90func TestReadFromDstSessionPreventClosed(t *testing.T) {
91 testActiveSessionNotClosed(t, true, false)
92}
93
94func testActiveSessionNotClosed(t *testing.T, readFromDst bool, writeToDst bool) {
95 const closeAfterIdle = time.Millisecond * 100
96 const activeTime = time.Millisecond * 500
97
98 sessionID := uuid.New()
99 cfdConn, originConn := net.Pipe()
100 payload := testPayload(sessionID)
101 transport := &mockQUICTransport{
102 reqChan: newDatagramChannel(100),
103 respChan: newDatagramChannel(100),
104 }
105 session := newSession(sessionID, transport, cfdConn)
106
107 startTime := time.Now()
108 activeUntil := startTime.Add(activeTime)
109 ctx, cancel := context.WithCancel(context.Background())
110 errGroup, ctx := errgroup.WithContext(ctx)
111 errGroup.Go(func() error {
112 session.Serve(ctx, closeAfterIdle)
113 if time.Now().Before(startTime.Add(activeTime)) {
114 return fmt.Errorf("session closed while it's still active")
115 }
116 return nil
117 })
118
119 if readFromDst {
120 errGroup.Go(func() error {
121 for {
122 if time.Now().After(activeUntil) {
123 return nil
124 }
125 if _, err := originConn.Write(payload); err != nil {
126 return err
127 }
128 time.Sleep(closeAfterIdle / 2)
129 }
130 })
131 }
132 if writeToDst {
133 errGroup.Go(func() error {
134 readBuffer := make([]byte, len(payload))
135 for {
136 n, err := originConn.Read(readBuffer)
137 if err != nil {
138 if err == io.EOF || err == io.ErrClosedPipe {
139 return nil
140 }
141 return err
142 }
143 if !bytes.Equal(payload, readBuffer[:n]) {
144 return fmt.Errorf("payload %v is not equal to %v", readBuffer[:n], payload)
145 }
146 }
147 })
148 errGroup.Go(func() error {
149 for {
150 if time.Now().After(activeUntil) {
151 return nil
152 }
153 if _, err := session.transportToDst(payload); err != nil {
154 return err
155 }
156 time.Sleep(closeAfterIdle / 2)
157 }
158 })
159 }
160
161 require.NoError(t, errGroup.Wait())
162 cancel()
163}
164
165func TestMarkActiveNotBlocking(t *testing.T) {
166 const concurrentCalls = 50
167 session := newSession(uuid.New(), nil, nil)
168 var wg sync.WaitGroup
169 wg.Add(concurrentCalls)
170 for i := 0; i < concurrentCalls; i++ {
171 go func() {
172 session.markActive()
173 wg.Done()
174 }()
175 }
176 wg.Wait()
177}
178