cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
173190aa799ece6eee51a0f979e6720f7cbf4bf9

Branches

Tags

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

Clone

HTTPS

Download ZIP

datagramsession/session.go

118lines · modecode

1package datagramsession
2
3import (
4 "context"
5 "io"
6 "time"
7
8 "github.com/google/uuid"
9)
10
11const (
12 defaultCloseIdleAfter = time.Second * 210
13)
14
15// Each Session is a bidirectional pipe of datagrams between transport and dstConn
16// Currently the only implementation of transport is quic DatagramMuxer
17// Destination can be a connection with origin or with eyeball
18// When the destination is origin:
19// - Datagrams from edge are read by Manager from the transport. Manager finds the corresponding Session and calls the
20// write method of the Session to send to origin
21// - Datagrams from origin are read from conn and SentTo transport. Transport will return them to eyeball
22// When the destination is eyeball:
23// - Datagrams from eyeball are read from conn and SentTo transport. Transport will send them to cloudflared
24// - Datagrams from cloudflared are read by Manager from the transport. Manager finds the corresponding Session and calls the
25// write method of the Session to send to eyeball
26type Session struct {
27 id uuid.UUID
28 transport transport
29 dstConn io.ReadWriteCloser
30 // activeAtChan is used to communicate the last read/write time
31 activeAtChan chan time.Time
32 doneChan chan struct{}
33}
34
35func newSession(id uuid.UUID, transport transport, dstConn io.ReadWriteCloser) *Session {
36 return &Session{
37 id: id,
38 transport: transport,
39 dstConn: dstConn,
40 // activeAtChan has low capacity. It can be full when there are many concurrent read/write. markActive() will
41 // drop instead of blocking because last active time only needs to be an approximation
42 activeAtChan: make(chan time.Time, 2),
43 doneChan: make(chan struct{}),
44 }
45}
46
47func (s *Session) Serve(ctx context.Context, closeAfterIdle time.Duration) error {
48 serveCtx, cancel := context.WithCancel(ctx)
49 defer cancel()
50 go s.waitForCloseCondition(serveCtx, closeAfterIdle)
51 // QUIC implementation copies data to another buffer before returning https://github.com/lucas-clemente/quic-go/blob/v0.24.0/session.go#L1967-L1975
52 // This makes it safe to share readBuffer between iterations
53 readBuffer := make([]byte, s.transport.MTU())
54 for {
55 if err := s.dstToTransport(readBuffer); err != nil {
56 return err
57 }
58 }
59}
60
61func (s *Session) waitForCloseCondition(ctx context.Context, closeAfterIdle time.Duration) {
62 if closeAfterIdle == 0 {
63 // provide deafult is caller doesn't specify one
64 closeAfterIdle = defaultCloseIdleAfter
65 }
66 // Closing dstConn cancels read so Serve function can return
67 defer s.dstConn.Close()
68
69 checkIdleFreq := closeAfterIdle / 8
70 checkIdleTicker := time.NewTicker(checkIdleFreq)
71 defer checkIdleTicker.Stop()
72
73 activeAt := time.Now()
74 for {
75 select {
76 case <-ctx.Done():
77 return
78 case <-s.doneChan:
79 return
80 // TODO: TUN-5423 evaluate if using atomic is more efficient
81 case now := <-checkIdleTicker.C:
82 // The session is considered inactive if current time is after (last active time + allowed idle time)
83 if now.After(activeAt.Add(closeAfterIdle)) {
84 return
85 }
86 case activeAt = <-s.activeAtChan: // Update last active time
87 }
88 }
89}
90
91func (s *Session) dstToTransport(buffer []byte) error {
92 n, err := s.dstConn.Read(buffer)
93 s.markActive()
94 if n > 0 {
95 if err := s.transport.SendTo(s.id, buffer[:n]); err != nil {
96 return err
97 }
98 }
99 return err
100}
101
102func (s *Session) transportToDst(payload []byte) (int, error) {
103 s.markActive()
104 return s.dstConn.Write(payload)
105}
106
107// Sends the last active time to the idle checker loop without blocking. activeAtChan will only be full when there
108// are many concurrent read/write. It is fine to lose some precision
109func (s *Session) markActive() {
110 select {
111 case s.activeAtChan <- time.Now():
112 default:
113 }
114}
115
116func (s *Session) close() {
117 close(s.doneChan)
118}
119