cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
eea3d11e402bb09b4e4ae65e73101064d4683c1f

Branches

Tags

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

Clone

HTTPS

Download ZIP

datagramsession/manager.go

134lines · modecode

1package datagramsession
2
3import (
4 "context"
5 "io"
6
7 "github.com/google/uuid"
8 "github.com/rs/zerolog"
9 "golang.org/x/sync/errgroup"
10)
11
12const (
13 requestChanCapacity = 16
14)
15
16// Manager defines the APIs to manage sessions from the same transport.
17type Manager interface {
18 // Serve starts the event loop
19 Serve(ctx context.Context) error
20 // RegisterSession starts tracking a session. Caller is responsible for starting the session
21 RegisterSession(ctx context.Context, sessionID uuid.UUID, dstConn io.ReadWriteCloser) (*Session, error)
22 // UnregisterSession stops tracking the session and terminates it
23 UnregisterSession(ctx context.Context, sessionID uuid.UUID) error
24}
25
26type manager struct {
27 registrationChan chan *registerSessionEvent
28 unregistrationChan chan *unregisterSessionEvent
29 datagramChan chan *newDatagram
30 transport transport
31 sessions map[uuid.UUID]*Session
32 log *zerolog.Logger
33}
34
35func NewManager(transport transport, log *zerolog.Logger) Manager {
36 return &manager{
37 registrationChan: make(chan *registerSessionEvent),
38 unregistrationChan: make(chan *unregisterSessionEvent),
39 // datagramChan is buffered, so it can read more datagrams from transport while the event loop is processing other events
40 datagramChan: make(chan *newDatagram, requestChanCapacity),
41 transport: transport,
42 sessions: make(map[uuid.UUID]*Session),
43 log: log,
44 }
45}
46
47func (m *manager) Serve(ctx context.Context) error {
48 errGroup, ctx := errgroup.WithContext(ctx)
49 errGroup.Go(func() error {
50 for {
51 sessionID, payload, err := m.transport.ReceiveFrom()
52 if err != nil {
53 m.log.Err(err).Msg("Failed to receive datagram from transport, closing session manager")
54 return err
55 }
56 datagram := &newDatagram{
57 sessionID: sessionID,
58 payload: payload,
59 }
60 select {
61 case <-ctx.Done():
62 return ctx.Err()
63 // Only the event loop routine can update/lookup the sessions map to avoid concurrent access
64 // Send the datagram to the event loop. It will find the session to send to
65 case m.datagramChan <- datagram:
66 }
67 }
68 })
69 errGroup.Go(func() error {
70 for {
71 select {
72 case <-ctx.Done():
73 return ctx.Err()
74 case datagram := <-m.datagramChan:
75 m.sendToSession(datagram)
76 case registration := <-m.registrationChan:
77 m.registerSession(ctx, registration)
78 // TODO: TUN-5422: Unregister inactive session upon timeout
79 case unregistration := <-m.unregistrationChan:
80 m.unregisterSession(unregistration)
81 }
82 }
83 })
84 return errGroup.Wait()
85}
86
87func (m *manager) RegisterSession(ctx context.Context, sessionID uuid.UUID, originProxy io.ReadWriteCloser) (*Session, error) {
88 event := newRegisterSessionEvent(sessionID, originProxy)
89 select {
90 case <-ctx.Done():
91 return nil, ctx.Err()
92 case m.registrationChan <- event:
93 session := <-event.resultChan
94 return session, nil
95 }
96}
97
98func (m *manager) registerSession(ctx context.Context, registration *registerSessionEvent) {
99 session := newSession(registration.sessionID, m.transport, registration.originProxy)
100 m.sessions[registration.sessionID] = session
101 registration.resultChan <- session
102}
103
104func (m *manager) UnregisterSession(ctx context.Context, sessionID uuid.UUID) error {
105 event := &unregisterSessionEvent{sessionID: sessionID}
106 select {
107 case <-ctx.Done():
108 return ctx.Err()
109 case m.unregistrationChan <- event:
110 return nil
111 }
112}
113
114func (m *manager) unregisterSession(unregistration *unregisterSessionEvent) {
115 session, ok := m.sessions[unregistration.sessionID]
116 if ok {
117 delete(m.sessions, unregistration.sessionID)
118 session.close()
119 }
120}
121
122func (m *manager) sendToSession(datagram *newDatagram) {
123 session, ok := m.sessions[datagram.sessionID]
124 if !ok {
125 m.log.Error().Str("sessionID", datagram.sessionID.String()).Msg("session not found")
126 return
127 }
128 // session writes to destination over a connected UDP socket, which should not be blocking, so this call doesn't
129 // need to run in another go routine
130 _, err := session.writeToDst(datagram.payload)
131 if err != nil {
132 m.log.Err(err).Str("sessionID", datagram.sessionID.String()).Msg("Failed to write payload to session")
133 }
134}
135