refactor: update Go dependencies and refactor relay client structures

- Updated golang.org/x/crypto from v0.41.0 to v0.43.0 - Updated google.golang.org/protobuf from v1.36.6 to v1.36.10 - Updated golang.org/x/sys to v0.37.0 and cleaned up indirect requires - Renamed IncommingConn.id to leaseID and added LocalID/RemoteID methods - Removed context parameter from GetRelayInfo and changed RequestConnection return type - Added localID/remoteID fields to SecureConnection and updated randpool import

lemon-mint committed Oct 27, 2025 at 15:55 UTC 2e45701e9dcab8d2a79c6143ca75f889d8309745
7 files changed +234 -37
go.mod
+3 -6
@@ -7,11 +7,8 @@ require (
7 github.com/hashicorp/yamux v0.1.2
8 github.com/planetscale/vtprotobuf v0.6.0
9 github.com/valyala/bytebufferpool v1.0.0
10 - golang.org/x/crypto v0.41.0
11 - google.golang.org/protobuf v1.36.6
10 + golang.org/x/crypto v0.43.0
11 + google.golang.org/protobuf v1.36.10
12 )
13
14 -require (
15 - github.com/google/go-cmp v0.7.0 // indirect
16 - golang.org/x/sys v0.35.0 // indirect
17 -)
14 +require golang.org/x/sys v0.37.0 // indirect
go.sum
+6 -6
@@ -8,9 +8,9 @@ github.com/planetscale/vtprotobuf v0.6.0 h1:nBeETjudeJ5ZgBHUz1fVHvbqUKnYOXNhsIEa
8 github.com/planetscale/vtprotobuf v0.6.0/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
9 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
10 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
11 -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
12 -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
13 -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
14 -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
15 -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
16 -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
11 +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
12 +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
13 +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
14 +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
15 +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
16 +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
relaydns/client.go
+18 -11
@@ -1,7 +1,6 @@
1 package relaydns
2
3 import (
4 - "context"
4 "crypto/rand"
5 "errors"
6 "io"
@@ -21,11 +20,19 @@ var (
20
21 type IncommingConn struct {
22 *cryptoops.SecureConnection
24 - id string
23 + leaseID string
24 }
25
27 -func (i *IncommingConn) ID() string {
28 - return i.id
26 +func (i *IncommingConn) LeaseID() string {
27 + return i.leaseID
28 +}
29 +
30 +func (i *IncommingConn) LocalID() string {
31 + return i.LocalID()
32 +}
33 +
34 +func (i *IncommingConn) RemoteID() string {
35 + return i.RemoteID()
36 }
37
38 // RelayClient는 RelayServer에 연결하여 서비스를 요청하는 클라이언트입니다.
@@ -176,12 +183,12 @@ func (g *RelayClient) handleConnectionRequestStream(stream *yamux.Stream) {
183
184 g.incommingConnCh <- &IncommingConn{
185 SecureConnection: secConn,
179 - id: req.LeaseId,
186 + leaseID: req.LeaseId,
187 }
188 }
189
190 // GetRelayInfo는 서버의 릴레이 정보를 요청합니다.
184 -func (g *RelayClient) GetRelayInfo(ctx context.Context) (*rdverb.RelayInfo, error) {
191 +func (g *RelayClient) GetRelayInfo() (*rdverb.RelayInfo, error) {
192 // 새 스트림 열기
193 stream, err := g.sess.OpenStream()
194 if err != nil {
@@ -357,7 +364,7 @@ func (g *RelayClient) deleteLease(cred *cryptoops.Credential, identity *rdsec.Id
364 }
365
366 // requestConnection은 다른 클라이언트로의 연결을 요청합니다.
360 -func (g *RelayClient) RequestConnection(leaseID string, alpn string, clientCred *cryptoops.Credential) (rdverb.ResponseCode, io.ReadWriteCloser, error) {
367 +func (g *RelayClient) RequestConnection(leaseID string, alpn string, clientCred *cryptoops.Credential) (rdverb.ResponseCode, *cryptoops.SecureConnection, error) {
368 // 새 스트림 열기
369 stream, err := g.sess.OpenStream()
370 if err != nil {
@@ -463,15 +470,15 @@ func (g *RelayClient) DeregisterLease(cred *cryptoops.Credential) error {
470 PublicKey: cred.PublicKey(),
471 }
472
473 + g.leasesMu.Lock()
474 + delete(g.leases, identity.Id)
475 + g.leasesMu.Unlock()
476 +
477 resp, err := g.deleteLease(cred, identity)
478 if err != nil || resp != rdverb.ResponseCode_RESPONSE_CODE_ACCEPTED {
479 return err
480 }
481
471 - g.leasesMu.Lock()
472 - delete(g.leases, identity.Id)
473 - g.leasesMu.Unlock()
474 -
482 return nil
483 }
484
relaydns/core/cryptoops/handshaker.go
+19 -5
@@ -19,7 +19,7 @@ import (
19 "google.golang.org/protobuf/proto"
20
21 "github.com/gosuda/relaydns/relaydns/core/proto/rdsec"
22 - "github.com/gosuda/relaydns/relaydns/internal/randpool"
22 + "github.com/gosuda/relaydns/relaydns/utils/randpool"
23 "github.com/valyala/bytebufferpool"
24 )
25
@@ -100,13 +100,25 @@ func NewHandshaker(credential *Credential) *Handshaker {
100
101 // SecureConnection represents a secured connection with encryption capabilities
102 type SecureConnection struct {
103 - conn io.ReadWriteCloser
103 + conn io.ReadWriteCloser
104 +
105 + localID string
106 + remoteID string
107 +
108 encryptor cipher.AEAD
109 decryptor cipher.AEAD
110
111 readBuffer *bytebufferpool.ByteBuffer
112 }
113
114 +func (sc *SecureConnection) LocalID() string {
115 + return sc.localID
116 +}
117 +
118 +func (sc *SecureConnection) RemoteID() string {
119 + return sc.remoteID
120 +}
121 +
122 // Write encrypts and writes data to the underlying connection
123 func (sc *SecureConnection) Write(p []byte) (int, error) {
124 const fragSize = maxRawPacketSize / 2
@@ -301,7 +313,7 @@ func (h *Handshaker) ClientHandshake(conn io.ReadWriteCloser, alpn string) (*Sec
313 wipeMemory(ephemeralPriv)
314
315 // Create secure connection
304 - return h.createSecureConnection(conn, clientEncryptKey, clientDecryptKey)
316 + return h.createSecureConnection(conn, clientEncryptKey, clientDecryptKey, serverInitPayload.GetIdentity().GetId())
317 }
318
319 // ServerHandshake performs the server-side of the handshake
@@ -391,7 +403,7 @@ func (h *Handshaker) ServerHandshake(conn io.ReadWriteCloser, alpns []string) (*
403 }
404
405 // Create secure connection
394 - return h.createSecureConnection(conn, serverEncryptKey, serverDecryptKey)
406 + return h.createSecureConnection(conn, serverEncryptKey, serverDecryptKey, clientInitPayload.GetIdentity().GetId())
407 }
408
409 // validateClientInit validates the client init message
@@ -498,7 +510,7 @@ func (h *Handshaker) deriveServerSessionKeys(serverPriv, clientPub, clientNonce,
510 }
511
512 // createSecureConnection creates a new SecureConnection with the given keys and nonces
501 -func (h *Handshaker) createSecureConnection(conn io.ReadWriteCloser, encryptKey, decryptKey []byte) (*SecureConnection, error) {
513 +func (h *Handshaker) createSecureConnection(conn io.ReadWriteCloser, encryptKey, decryptKey []byte, remoteID string) (*SecureConnection, error) {
514 // Create AEAD instances
515 encryptor, err := chacha20poly1305.New(encryptKey)
516 if err != nil {
@@ -515,6 +527,8 @@ func (h *Handshaker) createSecureConnection(conn io.ReadWriteCloser, encryptKey,
527
528 secureConn := &SecureConnection{
529 conn: conn,
530 + localID: h.credential.id,
531 + remoteID: remoteID,
532 encryptor: encryptor,
533 decryptor: decryptor,
534 readBuffer: readBuffer,
relaydns/utils/randpool/randpool.go renamed
relaydns/utils/wsstream/wsstream.go renamed
+9 -9
@@ -1,4 +1,4 @@
1 -package relaydns
1 +package wsstream
2
3 import (
4 "io"
@@ -6,14 +6,14 @@ import (
6 "github.com/gorilla/websocket"
7 )
8
9 -type wsStream struct {
10 - c *websocket.Conn
9 +type WsStream struct {
10 + Conn *websocket.Conn
11 currentReader io.Reader
12 }
13
14 -func (g *wsStream) Read(p []byte) (n int, err error) {
14 +func (g *WsStream) Read(p []byte) (n int, err error) {
15 if g.currentReader == nil {
16 - _, reader, err := g.c.NextReader()
16 + _, reader, err := g.Conn.NextReader()
17 if err != nil {
18 return 0, err
19 }
@@ -29,14 +29,14 @@ func (g *wsStream) Read(p []byte) (n int, err error) {
29 return n, err
30 }
31
32 -func (g *wsStream) Write(p []byte) (n int, err error) {
33 - err = g.c.WriteMessage(websocket.BinaryMessage, p)
32 +func (g *WsStream) Write(p []byte) (n int, err error) {
33 + err = g.Conn.WriteMessage(websocket.BinaryMessage, p)
34 if err != nil {
35 return 0, err
36 }
37 return len(p), nil
38 }
39
40 -func (g *wsStream) Close() error {
41 - return g.c.Close()
40 +func (g *WsStream) Close() error {
41 + return g.Conn.Close()
42 }
sdk/sdk.go new
+179
@@ -0,0 +1,179 @@
1 +package sdk
2 +
3 +import (
4 + "context"
5 + "errors"
6 + "io"
7 + "slices"
8 + "sync"
9 +
10 + "github.com/gorilla/websocket"
11 + "github.com/gosuda/relaydns/relaydns"
12 + "github.com/gosuda/relaydns/relaydns/core/cryptoops"
13 + "github.com/gosuda/relaydns/relaydns/core/proto/rdverb"
14 + "github.com/gosuda/relaydns/relaydns/utils/wsstream"
15 +)
16 +
17 +func NewCredential() (*cryptoops.Credential, error) {
18 + return cryptoops.NewCredential()
19 +}
20 +
21 +func webSocketDialer() func(context.Context, string) (io.ReadWriteCloser, error) {
22 + return func(ctx context.Context, url string) (io.ReadWriteCloser, error) {
23 + wsConn, _, err := websocket.DefaultDialer.Dial(url, nil)
24 + if err != nil {
25 + return nil, err
26 + }
27 + return &wsstream.WsStream{Conn: wsConn}, nil
28 + }
29 +}
30 +
31 +type RDClientConfig struct {
32 + BootstrapServers []string
33 + Dialer func(context.Context, string) (io.ReadWriteCloser, error)
34 +}
35 +
36 +type Option func(*RDClientConfig)
37 +
38 +type rdRelay struct {
39 + addr string
40 + client *relaydns.RelayClient
41 + stop chan struct{}
42 +}
43 +
44 +type RDConnection struct {
45 + via *rdRelay
46 + localAddr string
47 + remoteAddr string
48 + conn io.ReadWriteCloser
49 +}
50 +
51 +type RDListener struct {
52 + mu sync.Mutex
53 +
54 + cred *cryptoops.Credential
55 + conns map[*RDConnection]struct{}
56 +
57 + connCh chan *RDConnection
58 +}
59 +
60 +type RDClient struct {
61 + mu sync.Mutex
62 +
63 + relays map[string]*rdRelay
64 + listeners map[string]*RDListener
65 +
66 + stopch chan struct{}
67 +}
68 +
69 +var (
70 + ErrNoAvailableRelay = errors.New("no available relay")
71 +)
72 +
73 +func NewClient(opt ...Option) (*RDClient, error) {
74 + return &RDClient{}, nil
75 +}
76 +
77 +func (g *RDClient) Dial(cred *cryptoops.Credential, leaseID string, alpn string) (*RDConnection, error) {
78 + var relays []*rdRelay
79 +
80 + g.mu.Lock()
81 + for _, server := range g.relays {
82 + relays = append(relays, server)
83 + }
84 + g.mu.Unlock()
85 +
86 + var wg sync.WaitGroup
87 + var availableRelaysMu sync.Mutex
88 + var availableRelays []*rdRelay
89 +
90 + for _, relay := range relays {
91 + wg.Add(1)
92 + go func(relay *rdRelay) {
93 + defer wg.Done()
94 + info, err := relay.client.GetRelayInfo()
95 + if err != nil {
96 + return
97 + }
98 +
99 + if slices.Contains(info.Leases, leaseID) {
100 + availableRelaysMu.Lock()
101 + availableRelays = append(availableRelays, relay)
102 + availableRelaysMu.Unlock()
103 + }
104 + }(relay)
105 + }
106 + wg.Wait()
107 +
108 + if len(availableRelays) == 0 {
109 + return nil, ErrNoAvailableRelay
110 + }
111 +
112 + for _, relay := range availableRelays {
113 + code, conn, err := relay.client.RequestConnection(leaseID, alpn, cred)
114 + if err != nil || code != rdverb.ResponseCode_RESPONSE_CODE_ACCEPTED {
115 + continue
116 + }
117 + return &RDConnection{via: relay, conn: conn, localAddr: conn.LocalID(), remoteAddr: conn.RemoteID()}, nil
118 + }
119 +
120 + return nil, ErrNoAvailableRelay
121 +}
122 +
123 +func (g *RDClient) Listen(cred *cryptoops.Credential, name string, alpns []string) (*RDListener, error) {
124 +
125 +}
126 +
127 +func (g *RDClient) listenerWorker(server *rdRelay) {
128 + for {
129 + select {
130 + case <-server.stop:
131 + return
132 + case conn := <-server.client.IncommingConnection():
133 + lease := conn.LeaseID()
134 +
135 + g.mu.Lock()
136 + listener, ok := g.listeners[lease]
137 + g.mu.Unlock()
138 +
139 + if !ok {
140 + continue
141 + }
142 +
143 + rdConn := &RDConnection{via: server, conn: conn, localAddr: conn.LocalID(), remoteAddr: conn.RemoteID()}
144 +
145 + listener.mu.Lock()
146 + listener.conns[rdConn] = struct{}{}
147 + listener.mu.Unlock()
148 +
149 + listener.connCh <- rdConn
150 + }
151 + }
152 +}
153 +
154 +func (g *RDClient) Close() error {
155 + var errs []error
156 +
157 + close(g.stopch)
158 +
159 + g.mu.Lock()
160 + for _, listener := range g.listeners {
161 + if err := listener.Close(); err != nil {
162 + errs = append(errs, err)
163 + }
164 + }
165 + g.mu.Unlock()
166 +
167 + g.mu.Lock()
168 + for _, server := range g.relays {
169 + if err := server.client.Close(); err != nil {
170 + errs = append(errs, err)
171 + }
172 + }
173 + g.mu.Unlock()
174 +
175 + if len(errs) > 0 {
176 + return errs[0]
177 + }
178 + return nil
179 +}