Skip to content

Commit c56c16b

Browse files
committed
feat: add CoderVPN protocol definition & implementaion
1 parent 5246f8d commit c56c16b

File tree

7 files changed

+3511
-0
lines changed

7 files changed

+3511
-0
lines changed

Makefile

Lines changed: 8 additions & 0 deletions
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

Lines changed: 135 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy