Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f7ddbb7

Browse files
authored
feat: add CoderVPN protocol definition & implementation (#14855)
closes #14731 Defines and implements the CoderVPN control protocol, which will be used to communicate with desktop client applications.
1 parent 38d8e3a commit f7ddbb7

File tree

7 files changed

+3554
-0
lines changed

7 files changed

+3554
-0
lines changed

Makefile

+8
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,7 @@ gen: \
488488
agent/proto/agent.pb.go \
489489
provisionersdk/proto/provisioner.pb.go \
490490
provisionerd/proto/provisionerd.pb.go \
491+
vpn/vpn.proto \
491492
coderd/database/dump.sql \
492493
$(DB_GEN_FILES) \
493494
site/src/api/typesGenerated.ts \
@@ -517,6 +518,7 @@ gen/mark-fresh:
517518
agent/proto/agent.pb.go \
518519
provisionersdk/proto/provisioner.pb.go \
519520
provisionerd/proto/provisionerd.pb.go \
521+
vpn/vpn.proto \
520522
coderd/database/dump.sql \
521523
$(DB_GEN_FILES) \
522524
site/src/api/typesGenerated.ts \
@@ -600,6 +602,12 @@ provisionerd/proto/provisionerd.pb.go: provisionerd/proto/provisionerd.proto
600602
--go-drpc_opt=paths=source_relative \
601603
./provisionerd/proto/provisionerd.proto
602604

605+
vpn/vpn.pb.go: vpn/vpn.proto
606+
protoc \
607+
--go_out=. \
608+
--go_opt=paths=source_relative \
609+
./vpn/vpn.proto
610+
603611
site/src/api/typesGenerated.ts: $(wildcard scripts/apitypings/*) $(shell find ./codersdk $(FIND_EXCLUSIONS) -type f -name '*.go')
604612
go run ./scripts/apitypings/ > $@
605613
./scripts/pnpm_install.sh

vpn/serdes.go

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package vpn
2+
3+
import (
4+
"context"
5+
"encoding/binary"
6+
"io"
7+
"sync"
8+
9+
"google.golang.org/protobuf/proto"
10+
11+
"cdr.dev/slog"
12+
)
13+
14+
// MaxLength is the largest possible CoderVPN Protocol message size. This is set
15+
// so that a misbehaving peer can't cause us to allocate a huge amount of memory.
16+
const MaxLength = 0x1000000 // 16MiB
17+
18+
// serdes SERializes and DESerializes protobuf messages to and from the conn.
19+
type serdes[S rpcMessage, R receivableRPCMessage[RR], RR any] struct {
20+
ctx context.Context
21+
logger slog.Logger
22+
conn io.ReadWriteCloser
23+
sendCh <-chan S
24+
recvCh chan<- R
25+
closeOnce sync.Once
26+
wg sync.WaitGroup
27+
}
28+
29+
func (s *serdes[_, R, RR]) recvLoop() {
30+
s.logger.Debug(s.ctx, "starting recvLoop")
31+
defer s.closeIdempotent()
32+
defer close(s.recvCh)
33+
for {
34+
var length uint32
35+
if err := binary.Read(s.conn, binary.BigEndian, &length); err != nil {
36+
s.logger.Debug(s.ctx, "failed to read length", slog.Error(err))
37+
return
38+
}
39+
if length > MaxLength {
40+
s.logger.Critical(s.ctx, "message length exceeds max",
41+
slog.F("length", length))
42+
return
43+
}
44+
s.logger.Debug(s.ctx, "about to read message", slog.F("length", length))
45+
mb := make([]byte, length)
46+
if n, err := io.ReadFull(s.conn, mb); err != nil {
47+
s.logger.Debug(s.ctx, "failed to read message",
48+
slog.Error(err),
49+
slog.F("num_bytes_read", n))
50+
return
51+
}
52+
msg := R(new(RR))
53+
if err := proto.Unmarshal(mb, msg); err != nil {
54+
s.logger.Critical(s.ctx, "failed to unmarshal message", slog.Error(err))
55+
return
56+
}
57+
select {
58+
case s.recvCh <- msg:
59+
s.logger.Debug(s.ctx, "passed received message to speaker")
60+
case <-s.ctx.Done():
61+
s.logger.Debug(s.ctx, "recvLoop canceled", slog.Error(s.ctx.Err()))
62+
}
63+
}
64+
}
65+
66+
func (s *serdes[S, _, _]) sendLoop() {
67+
s.logger.Debug(s.ctx, "starting sendLoop")
68+
defer s.closeIdempotent()
69+
for {
70+
select {
71+
case <-s.ctx.Done():
72+
s.logger.Debug(s.ctx, "sendLoop canceled", slog.Error(s.ctx.Err()))
73+
return
74+
case msg, ok := <-s.sendCh:
75+
if !ok {
76+
s.logger.Debug(s.ctx, "sendCh closed")
77+
return
78+
}
79+
mb, err := proto.Marshal(msg)
80+
if err != nil {
81+
s.logger.Critical(s.ctx, "failed to marshal message", slog.Error(err))
82+
return
83+
}
84+
if err := binary.Write(s.conn, binary.BigEndian, uint32(len(mb))); err != nil {
85+
s.logger.Debug(s.ctx, "failed to write length", slog.Error(err))
86+
return
87+
}
88+
if _, err := s.conn.Write(mb); err != nil {
89+
s.logger.Debug(s.ctx, "failed to write message", slog.Error(err))
90+
return
91+
}
92+
}
93+
}
94+
}
95+
96+
func (s *serdes[_, _, _]) closeIdempotent() {
97+
s.closeOnce.Do(func() {
98+
if err := s.conn.Close(); err != nil {
99+
s.logger.Error(s.ctx, "failed to close connection", slog.Error(err))
100+
} else {
101+
s.logger.Info(s.ctx, "closed connection")
102+
}
103+
})
104+
}
105+
106+
func (s *serdes[_, _, _]) Close() error {
107+
s.closeIdempotent()
108+
s.wg.Wait()
109+
return nil
110+
}
111+
112+
func (s *serdes[_, _, _]) start() {
113+
s.wg.Add(2)
114+
go func() {
115+
defer s.wg.Done()
116+
s.recvLoop()
117+
}()
118+
go func() {
119+
defer s.wg.Done()
120+
s.sendLoop()
121+
}()
122+
}
123+
124+
func newSerdes[S rpcMessage, R receivableRPCMessage[RR], RR any](
125+
ctx context.Context, logger slog.Logger, conn io.ReadWriteCloser,
126+
sendCh <-chan S, recvCh chan<- R,
127+
) *serdes[S, R, RR] {
128+
return &serdes[S, R, RR]{
129+
ctx: ctx,
130+
logger: logger.Named("serdes"),
131+
conn: conn,
132+
sendCh: sendCh,
133+
recvCh: recvCh,
134+
}
135+
}

0 commit comments

Comments
 (0)