feat: add RESPONSE_CODE_UNKNOWN to ResponseCode enum
Introduce a new RESPONSE_CODE_UNKNOWN value at position 0 in the ResponseCode enum, shifting all existing values by +1. Update default return values in GetCode() methods from RESPONSE_CODE_ACCEPTED to RESPONSE_CODE_UNKNOWN to ensure uninitialized codes default to unknown rather than accepted. This improves clarity in protocol handling by distinguishing between unknown and accepted states.
lemon-mint committed
Oct 27, 2025 at 00:46 UTC
3049a225befc7df72474127c2025d6d1d2fa310e
6 files changed
+409
-391
README_RELAY.md
renamed
cmd/server/main.go
deleted
-155
@@ -1,155 +0,0 @@
1
-package main
2
-
3
-import (
4
- "context"
5
- "fmt"
6
- "net"
7
- "os"
8
- "os/signal"
9
- "sync"
10
- "syscall"
11
- "time"
12
-
13
- "github.com/rs/zerolog/log"
14
- "github.com/spf13/cobra"
15
-
16
- "github.com/gosuda/relaydns/relaydns"
17
-)
18
-
19
-var rootCmd = &cobra.Command{
20
- Use: "relayserver",
21
- Short: "A lightweight, DNS-driven peer-to-peer proxy layer built on libp2p",
22
- RunE: runServer,
23
-}
24
-
25
-var (
26
- flagP2pPort int // libp2p outbound TCP/UDP port (e.g. 4001)
27
- flagHttpPort int // admin UI + HTTP proxy port (e.g. 8080)
28
- flagTcpPort int // optional raw TCP ingress port (0 to disable)
29
-)
30
-
31
-func init() {
32
- flags := rootCmd.PersistentFlags()
33
- flags.IntVar(&flagHttpPort, "http-port", 8080, "admin UI and HTTP proxy port")
34
- flags.IntVar(&flagP2pPort, "p2p-port", 4001, "libp2p outbound TCP/UDP port")
35
- flags.IntVar(&flagTcpPort, "tcp-port", 0, "optional raw TCP ingress port (0 to disable)")
36
-}
37
-
38
-func main() {
39
- if err := rootCmd.Execute(); err != nil {
40
- log.Fatal().Err(err).Msg("execute root command")
41
- }
42
-}
43
-
44
-func runServer(cmd *cobra.Command, args []string) error {
45
- ctx, cancel := context.WithCancel(context.Background())
46
- defer cancel()
47
-
48
- // Wait group for tracking TCP ingress goroutines
49
- var tcpWg sync.WaitGroup
50
-
51
- h, err := relaydns.MakeHost(ctx, flagP2pPort, true)
52
- if err != nil {
53
- return err
54
- }
55
- d, err := relaydns.NewRelayServer(ctx, h, relaydns.DefaultProtocol, relaydns.DefaultTopic)
56
- if err != nil {
57
- return err
58
- }
59
-
60
- // Admin UI + per-peer HTTP proxy served here
61
- httpServer := serveHTTP(ctx, fmt.Sprintf(":%d", flagHttpPort), d, h, cancel)
62
-
63
- // Optional raw TCP ingress (e.g., SSH)
64
- if flagTcpPort > 0 {
65
- tcpWg.Add(1)
66
- go serveTCPIngress(ctx, fmt.Sprintf(":%d", flagTcpPort), d, &tcpWg)
67
- }
68
-
69
- // graceful shutdown
70
- sig := make(chan os.Signal, 1)
71
- signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
72
- <-sig
73
- log.Info().Msg("[server] shutting down...")
74
-
75
- // Cancel context to stop all goroutines
76
- cancel()
77
-
78
- // Shutdown HTTP server with timeout
79
- shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
80
- defer shutdownCancel()
81
- if httpServer != nil {
82
- if err := httpServer.Shutdown(shutdownCtx); err != nil {
83
- log.Error().Err(err).Msg("[server] http server shutdown error")
84
- }
85
- }
86
-
87
- // Close relay server (waits for background goroutines)
88
- if err := d.Close(); err != nil {
89
- log.Warn().Err(err).Msg("[server] relay server close error")
90
- }
91
-
92
- // Close libp2p host
93
- if err := h.Close(); err != nil {
94
- log.Warn().Err(err).Msg("[server] libp2p host close error")
95
- }
96
-
97
- // Wait for all TCP ingress goroutines to complete
98
- log.Debug().Msg("[server] waiting for TCP ingress goroutines...")
99
- tcpWg.Wait()
100
- log.Debug().Msg("[server] all TCP ingress goroutines stopped")
101
-
102
- log.Info().Msg("[server] shutdown complete")
103
- return nil
104
-}
105
-
106
-// serveTCPIngress listens on addr for raw TCP (e.g., SSH) and proxies
107
-// incoming connections to a chosen peer over libp2p stream using Director.
108
-func serveTCPIngress(ctx context.Context, addr string, d *relaydns.RelayServer, wg *sync.WaitGroup) {
109
- defer wg.Done()
110
-
111
- ln, err := net.Listen("tcp", addr)
112
- if err != nil {
113
- log.Error().Err(err).Msgf("tcp ingress listen failed: %s", addr)
114
- return
115
- }
116
- log.Info().Msgf("[server] tcp ingress: %s", addr)
117
-
118
- // Goroutine to close listener on context cancellation
119
- wg.Add(1)
120
- go func() {
121
- defer wg.Done()
122
- <-ctx.Done()
123
- _ = ln.Close()
124
- }()
125
-
126
- for {
127
- conn, err := ln.Accept()
128
- if err != nil {
129
- select {
130
- case <-ctx.Done():
131
- return
132
- default:
133
- }
134
- continue
135
- }
136
-
137
- // Launch connection handler with wait group tracking
138
- wg.Add(1)
139
- go func(c net.Conn) {
140
- defer wg.Done()
141
-
142
- hosts := d.Hosts()
143
- if len(hosts) == 0 {
144
- log.Warn().Msg("tcp ingress: no backend peers available")
145
- _ = c.Close()
146
- return
147
- }
148
- // pick most recent (Hosts() sorted by last seen)
149
- peerID := hosts[0].Info.Peer
150
- if err := d.ProxyTCP(c, peerID); err != nil {
151
- log.Warn().Err(err).Msgf("tcp ingress proxy failed to %s", peerID)
152
- }
153
- }(conn)
154
- }
155
-}
cmd/server/view.go
deleted
-201
@@ -1,201 +0,0 @@
1
-package main
2
-
3
-import (
4
- "context"
5
- "encoding/json"
6
- "fmt"
7
- "html/template"
8
- "net/http"
9
- "strings"
10
- "time"
11
-
12
- "github.com/libp2p/go-libp2p/core/host"
13
- "github.com/rs/zerolog/log"
14
-
15
- "github.com/gosuda/relaydns/relaydns"
16
-)
17
-
18
-// serveHTTP builds the HTTP mux and returns the server.
19
-func serveHTTP(ctx context.Context, addr string, d *relaydns.RelayServer, h host.Host, cancel context.CancelFunc) *http.Server {
20
- if addr == "" {
21
- return nil
22
- }
23
-
24
- mux := http.NewServeMux()
25
-
26
- // Index page
27
- mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
28
- if r.URL.Path != "/" {
29
- http.NotFound(w, r)
30
- return
31
- }
32
- rows := make([]relaydns.AdminRow, 0)
33
- for _, v := range d.Hosts() {
34
- ttl := ""
35
- if v.Info.TTL > 0 {
36
- ttl = fmt.Sprintf("%ds", v.Info.TTL)
37
- }
38
- // Derive a friendly kind from protocol id. Be lenient to variations.
39
- p := strings.ToLower(v.Info.Proto)
40
- kind := "TCP"
41
- if strings.Contains(p, "ssh") {
42
- kind = "SSH"
43
- } else if strings.Contains(p, "http") {
44
- kind = "HTTP"
45
- }
46
- rows = append(rows, relaydns.AdminRow{
47
- Peer: v.Info.Peer,
48
- Name: v.Info.Name,
49
- DNS: v.Info.DNS,
50
- LastSeen: time.Since(v.LastSeen).Round(time.Second).String() + " ago",
51
- Link: "/peer/" + v.Info.Peer + "/",
52
- TTL: ttl,
53
- Connected: v.Connected,
54
- Kind: kind,
55
- })
56
- }
57
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
58
- log.Debug().Int("clients", len(rows)).Msg("render admin index")
59
- _ = adminIndexTmpl.Execute(w, relaydns.AdminPage{
60
- NodeID: h.ID().String(),
61
- Addrs: relaydns.BuildAddrs(h),
62
- Rows: rows,
63
- })
64
- })
65
-
66
- // Per-peer proxy
67
- mux.HandleFunc("/peer/", func(w http.ResponseWriter, r *http.Request) {
68
- p := strings.TrimPrefix(r.URL.Path, "/peer/")
69
- parts := strings.SplitN(p, "/", 2)
70
- if len(parts) == 0 || parts[0] == "" {
71
- http.Error(w, "missing peer id", http.StatusBadRequest)
72
- return
73
- }
74
- peerID := parts[0]
75
- pathSuffix := "/"
76
- if len(parts) == 2 {
77
- pathSuffix = "/" + parts[1]
78
- }
79
- d.ProxyHTTP(w, r, peerID, pathSuffix)
80
- })
81
-
82
- // JSON hosts (namespaced snapshot with server peer and connected peers only)
83
- mux.HandleFunc("/hosts", func(w http.ResponseWriter, r *http.Request) {
84
- list := d.Hosts()
85
- peers := make([]string, 0, len(list))
86
- for _, v := range list {
87
- if v.Connected {
88
- peers = append(peers, v.Info.Peer)
89
- }
90
- }
91
- snap := relaydns.Hosts{
92
- ServerPeer: h.ID().String(),
93
- ServerAddrs: relaydns.BuildAddrs(h),
94
- Peers: peers,
95
- }
96
- w.Header().Set("Content-Type", "application/json")
97
- _ = json.NewEncoder(w).Encode(snap)
98
- })
99
-
100
- mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
101
- type info struct {
102
- Status string `json:"status"`
103
- Addrs []string `json:"multiaddrs"`
104
- }
105
- resp := info{Status: "ok", Addrs: relaydns.BuildAddrs(h)}
106
- w.Header().Set("Content-Type", "application/json")
107
- _ = json.NewEncoder(w).Encode(resp)
108
- })
109
-
110
- srv := &http.Server{
111
- Addr: addr,
112
- Handler: mux,
113
- }
114
-
115
- go func() {
116
- log.Info().Msgf("[server] http: %s", addr)
117
- if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
118
- log.Error().Err(err).Msg("[server] http error")
119
- cancel()
120
- }
121
- }()
122
-
123
- return srv
124
-}
125
-
126
-var adminIndexTmpl = template.Must(template.New("admin-index").Parse(`<!doctype html>
127
-<html lang="ko">
128
-<head>
129
- <meta charset="utf-8"/>
130
- <meta name="viewport" content="width=device-width, initial-scale=1" />
131
- <title>RelayDNS — Admin</title>
132
- <style>
133
- * { box-sizing: border-box }
134
- :root {
135
- --bg:#fafbff; --panel:#ffffff; --ink:#0f172a; --muted:#6b7280; --line:#e9eef5;
136
- --primary:#2563eb; --ok:#059669; --bad:#b91c1c; --ok-bg:#ecfdf5; --bad-bg:#fee2e2;
137
- }
138
- body { margin:0; background:var(--bg); color:var(--ink); font-family:sans-serif; font-size:16px; line-height:1.6 }
139
- .wrap { max-width: 980px; margin: 0 auto; padding: 32px 20px }
140
- header { display:flex; align-items:center; justify-content:space-between; padding: 20px 24px; background:var(--panel); border:1px solid var(--line); border-radius: 14px }
141
- .brand { font-weight:800; font-size:22px; letter-spacing:.2px }
142
- .status { color:var(--ok); font-weight:700 }
143
- main { margin-top: 22px }
144
- .section { background:var(--panel); border:1px solid var(--line); border-radius:14px; padding:18px; margin-bottom:14px }
145
- .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; color:#374151; word-break: break-all }
146
- .title { font-weight:800; margin:0 0 10px 0; font-size:18px }
147
- .muted { color:var(--muted); font-size:14px }
148
- .pill { display:inline-flex; align-items:center; gap:8px; padding:6px 10px; border-radius:999px; font-weight:800; font-size:13px }
149
- .pill.ok { background:var(--ok-bg); color:var(--ok) }
150
- .pill.bad { background:var(--bad-bg); color:var(--bad) }
151
- .pill .dot { width:8px; height:8px; border-radius:999px; background:var(--ok); display:inline-block }
152
- .pill.bad .dot { background:var(--bad) }
153
- .head { display:flex; align-items:center; justify-content:space-between; gap:12px }
154
- .btn { display:inline-block; background:var(--primary); color:#fff; text-decoration:none; border-radius:10px; padding:10px 14px; font-weight:800; margin-top:8px }
155
- </style>
156
- </head>
157
-<body>
158
- <div class="wrap">
159
- <header>
160
- <div class="brand">RelayDNS</div>
161
- <div class="status">Admin</div>
162
- </header>
163
- <main>
164
- <section class="section">
165
- <div class="title">Server</div>
166
- <div class="mono">Peer ID: {{.NodeID}}</div>
167
- {{if .Addrs}}
168
- <div class="muted" style="margin-top:6px">Multiaddrs</div>
169
- <div class="mono">{{range .Addrs}}{{.}}<br/>{{end}}</div>
170
- {{end}}
171
- <div class="muted" style="margin-top:6px">Known clients: {{len .Rows}}</div>
172
- </section>
173
- {{range .Rows}}
174
- <section class="section" id="peer-{{.Peer}}" data-peer="{{.Peer}}" data-name="{{.Name}}">
175
- <div class="head">
176
- <div class="title">{{if .Name}}{{.Name}}{{else}}(unnamed){{end}}</div>
177
- <div>
178
- <span class="muted" style="margin-right:8px">{{.Kind}}</span>
179
- {{if .Connected}}
180
- <span class="pill ok"><span class="dot"></span>Connected</span>
181
- {{else}}
182
- <span class="pill bad"><span class="dot"></span>Disconnected</span>
183
- {{end}}
184
- </div>
185
- </div>
186
- {{if .DNS}}<div class="muted">DNS: <span class="mono">{{.DNS}}</span></div>{{end}}
187
- <div class="muted">Peer</div>
188
- <div class="mono">{{.Peer}}</div>
189
- <div class="muted" style="margin-top:6px">Last seen: {{.LastSeen}}{{if .TTL}} · TTL: {{.TTL}}{{end}}</div>
190
- <a class="btn" href="{{.Link}}">Open</a>
191
- </section>
192
- {{else}}
193
- <section class="section">
194
- <div class="title">No clients discovered</div>
195
- <div class="muted">Start a client and ensure bootstraps are configured.</div>
196
- </section>
197
- {{end}}
198
- </main>
199
- </div>
200
-</body>
201
-</html>`))
relaydns/client.go
new
+369
@@ -0,0 +1,369 @@
1
+package relaydns
2
+
3
+import (
4
+ "context"
5
+ "crypto/rand"
6
+ "errors"
7
+ "io"
8
+ "sync"
9
+ "time"
10
+
11
+ "github.com/gosuda/relaydns/relaydns/core/cryptoops"
12
+ "github.com/gosuda/relaydns/relaydns/core/proto/rdsec"
13
+ "github.com/gosuda/relaydns/relaydns/core/proto/rdverb"
14
+ "github.com/hashicorp/yamux"
15
+)
16
+
17
+var (
18
+ ErrInvalidResponse = errors.New("invalid response")
19
+ ErrConnectionRejected = errors.New("connection rejected")
20
+)
21
+
22
+// RelayClient는 RelayServer에 연결하여 서비스를 요청하는 클라이언트입니다.
23
+type RelayClient struct {
24
+ conn io.ReadWriteCloser
25
+
26
+ sess *yamux.Session
27
+
28
+ streams map[uint32]*yamux.Stream
29
+ streamsMu sync.Mutex
30
+
31
+ leases map[string]*LeaseWithCred
32
+ leasesMu sync.Mutex
33
+
34
+ stopCh chan struct{}
35
+ waitGroup sync.WaitGroup
36
+}
37
+
38
+type LeaseWithCred struct {
39
+ Lease *rdverb.Lease
40
+ Cred *cryptoops.Credential
41
+}
42
+
43
+// NewRelayClient는 새로운 RelayClient 인스턴스를 생성합니다.
44
+func NewRelayClient(conn io.ReadWriteCloser) *RelayClient {
45
+ return &RelayClient{
46
+ conn: conn,
47
+ streams: make(map[uint32]*yamux.Stream),
48
+ leases: make(map[string]*LeaseWithCred),
49
+ stopCh: make(chan struct{}),
50
+ }
51
+}
52
+
53
+// Close는 서버와의 연결을 종료합니다.
54
+func (c *RelayClient) Close() error {
55
+ close(c.stopCh)
56
+ c.waitGroup.Wait()
57
+
58
+ err := c.conn.Close()
59
+ if err != nil {
60
+ return err
61
+ }
62
+ return nil
63
+}
64
+
65
+// leaseUpdateWorker는 리스 업데이트를 처리하는 워커입니다.
66
+func (c *RelayClient) leaseUpdateWorker() {
67
+ ticker := time.NewTicker(5 * time.Second)
68
+ var updateRequired = map[*LeaseWithCred]struct{}{}
69
+
70
+ defer ticker.Stop()
71
+ for {
72
+ select {
73
+ case <-c.stopCh:
74
+ return
75
+ case <-ticker.C:
76
+ clear(updateRequired)
77
+
78
+ c.leasesMu.Lock()
79
+ for _, lease := range c.leases {
80
+ if lease.Lease.Expires < int64(time.Now().Add(30*time.Second).Unix()) {
81
+ updateRequired[lease] = struct{}{}
82
+ }
83
+ }
84
+ c.leasesMu.Unlock()
85
+
86
+ for lease := range updateRequired {
87
+ lease.Lease.Expires = time.Now().Add(30 * time.Second).Unix()
88
+ c.updateLease(context.Background(), lease.Cred, lease.Lease)
89
+ }
90
+ }
91
+ }
92
+}
93
+
94
+// GetRelayInfo는 서버의 릴레이 정보를 요청합니다.
95
+func (c *RelayClient) GetRelayInfo(ctx context.Context) (*rdverb.RelayInfo, error) {
96
+ // 새 스트림 열기
97
+ stream, err := c.sess.OpenStream()
98
+ if err != nil {
99
+ return nil, err
100
+ }
101
+ defer stream.Close()
102
+
103
+ // 요청 패킷 생성
104
+ req := &rdverb.RelayInfoRequest{}
105
+ reqPayload, err := req.MarshalVT()
106
+ if err != nil {
107
+ return nil, err
108
+ }
109
+
110
+ // 요청 전송
111
+ err = writePacket(stream, &rdverb.Packet{
112
+ Type: rdverb.PacketType_PACKET_TYPE_RELAY_INFO_REQUEST,
113
+ Payload: reqPayload,
114
+ })
115
+ if err != nil {
116
+ return nil, err
117
+ }
118
+
119
+ // 응답 수신
120
+ respPacket, err := readPacket(stream)
121
+ if err != nil {
122
+ return nil, err
123
+ }
124
+
125
+ if respPacket.Type != rdverb.PacketType_PACKET_TYPE_RELAY_INFO_RESPONSE {
126
+ return nil, ErrInvalidResponse
127
+ }
128
+
129
+ var resp rdverb.RelayInfoResponse
130
+ err = resp.UnmarshalVT(respPacket.Payload)
131
+ if err != nil {
132
+ return nil, err
133
+ }
134
+
135
+ return resp.RelayInfo, nil
136
+}
137
+
138
+// updateLease는 서버에 리스 업데이트를 요청합니다.
139
+func (c *RelayClient) updateLease(ctx context.Context, cred *cryptoops.Credential, lease *rdverb.Lease) (rdverb.ResponseCode, error) {
140
+ // 새 스트림 열기
141
+ stream, err := c.sess.OpenStream()
142
+ if err != nil {
143
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
144
+ }
145
+ defer stream.Close()
146
+
147
+ // 요청 생성
148
+ timestamp := time.Now().Unix()
149
+ nonce := make([]byte, 12) // 12바이트 nonce
150
+ if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
151
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
152
+ }
153
+
154
+ req := &rdverb.LeaseUpdateRequest{
155
+ Lease: lease,
156
+ Nonce: nonce,
157
+ Timestamp: timestamp,
158
+ }
159
+
160
+ // 요청 직렬화 및 서명
161
+ reqPayload, err := req.MarshalVT()
162
+ if err != nil {
163
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
164
+ }
165
+
166
+ signedPayload := &rdsec.SignedPayload{
167
+ Data: reqPayload,
168
+ Signature: cred.Sign(reqPayload),
169
+ }
170
+
171
+ signedData, err := signedPayload.MarshalVT()
172
+ if err != nil {
173
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
174
+ }
175
+
176
+ // 요청 전송
177
+ err = writePacket(stream, &rdverb.Packet{
178
+ Type: rdverb.PacketType_PACKET_TYPE_LEASE_UPDATE_REQUEST,
179
+ Payload: signedData,
180
+ })
181
+ if err != nil {
182
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
183
+ }
184
+
185
+ // 응답 수신
186
+ respPacket, err := readPacket(stream)
187
+ if err != nil {
188
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
189
+ }
190
+
191
+ if respPacket.Type != rdverb.PacketType_PACKET_TYPE_LEASE_UPDATE_RESPONSE {
192
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, ErrInvalidResponse
193
+ }
194
+
195
+ var resp rdverb.LeaseUpdateResponse
196
+ err = resp.UnmarshalVT(respPacket.Payload)
197
+ if err != nil {
198
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
199
+ }
200
+
201
+ return resp.Code, nil
202
+}
203
+
204
+// deleteLease는 서버에 리스 삭제를 요청합니다.
205
+func (c *RelayClient) deleteLease(ctx context.Context, cred *cryptoops.Credential, identity *rdsec.Identity) (rdverb.ResponseCode, error) {
206
+ // 새 스트림 열기
207
+ stream, err := c.sess.OpenStream()
208
+ if err != nil {
209
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
210
+ }
211
+ defer stream.Close()
212
+
213
+ // 요청 생성
214
+ timestamp := time.Now().Unix()
215
+ nonce := make([]byte, 12) // 12바이트 nonce
216
+ if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
217
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
218
+ }
219
+
220
+ req := &rdverb.LeaseDeleteRequest{
221
+ Identity: identity,
222
+ Nonce: nonce,
223
+ Timestamp: timestamp,
224
+ }
225
+
226
+ // 요청 직렬화 및 서명
227
+ reqPayload, err := req.MarshalVT()
228
+ if err != nil {
229
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
230
+ }
231
+
232
+ signedPayload := &rdsec.SignedPayload{
233
+ Data: reqPayload,
234
+ Signature: cred.Sign(reqPayload),
235
+ }
236
+
237
+ signedData, err := signedPayload.MarshalVT()
238
+ if err != nil {
239
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
240
+ }
241
+
242
+ // 요청 전송
243
+ err = writePacket(stream, &rdverb.Packet{
244
+ Type: rdverb.PacketType_PACKET_TYPE_LEASE_DELETE_REQUEST,
245
+ Payload: signedData,
246
+ })
247
+ if err != nil {
248
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
249
+ }
250
+
251
+ // 응답 수신
252
+ respPacket, err := readPacket(stream)
253
+ if err != nil {
254
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
255
+ }
256
+
257
+ if respPacket.Type != rdverb.PacketType_PACKET_TYPE_LEASE_DELETE_RESPONSE {
258
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, ErrInvalidResponse
259
+ }
260
+
261
+ var resp rdverb.LeaseDeleteResponse
262
+ err = resp.UnmarshalVT(respPacket.Payload)
263
+ if err != nil {
264
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
265
+ }
266
+
267
+ return resp.Code, nil
268
+}
269
+
270
+// requestConnection은 다른 클라이언트로의 연결을 요청합니다.
271
+func (c *RelayClient) requestConnection(ctx context.Context, leaseID string, alpn string, clientCred *cryptoops.Credential) (rdverb.ResponseCode, io.ReadWriteCloser, error) {
272
+ // 새 스트림 열기
273
+ stream, err := c.sess.OpenStream()
274
+ if err != nil {
275
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, nil, err
276
+ }
277
+
278
+ clientIdentity := &rdsec.Identity{
279
+ Id: clientCred.ID(),
280
+ PublicKey: clientCred.PublicKey(),
281
+ }
282
+
283
+ // 요청 생성
284
+ req := &rdverb.ConnectionRequest{
285
+ LeaseId: leaseID,
286
+ ClientIdentity: clientIdentity,
287
+ }
288
+
289
+ reqPayload, err := req.MarshalVT()
290
+ if err != nil {
291
+ stream.Close()
292
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, nil, err
293
+ }
294
+
295
+ // 요청 전송
296
+ err = writePacket(stream, &rdverb.Packet{
297
+ Type: rdverb.PacketType_PACKET_TYPE_CONNECTION_REQUEST,
298
+ Payload: reqPayload,
299
+ })
300
+ if err != nil {
301
+ stream.Close()
302
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, nil, err
303
+ }
304
+
305
+ // 응답 수신
306
+ respPacket, err := readPacket(stream)
307
+ if err != nil {
308
+ stream.Close()
309
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, nil, err
310
+ }
311
+
312
+ if respPacket.Type != rdverb.PacketType_PACKET_TYPE_CONNECTION_RESPONSE {
313
+ stream.Close()
314
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, nil, ErrInvalidResponse
315
+ }
316
+
317
+ var resp rdverb.ConnectionResponse
318
+ err = resp.UnmarshalVT(respPacket.Payload)
319
+ if err != nil {
320
+ stream.Close()
321
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, nil, err
322
+ }
323
+
324
+ // 거절된 경우 스트림을 닫고 오류 코드 반환
325
+ if resp.Code != rdverb.ResponseCode_RESPONSE_CODE_ACCEPTED {
326
+ stream.Close()
327
+ return resp.Code, nil, ErrConnectionRejected
328
+ }
329
+
330
+ handshaker := cryptoops.NewHandshaker(clientCred)
331
+ secConn, err := handshaker.ClientHandshake(stream, alpn)
332
+ if err != nil {
333
+ stream.Close()
334
+ return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, nil, err
335
+ }
336
+
337
+ return resp.Code, secConn, nil
338
+}
339
+
340
+func (c *RelayClient) RegisterLease(ctx context.Context, cred *cryptoops.Credential, name string, alpns []string) error {
341
+ identity := &rdsec.Identity{
342
+ Id: cred.ID(),
343
+ PublicKey: cred.PublicKey(),
344
+ }
345
+
346
+ lease := &rdverb.Lease{
347
+ Identity: identity,
348
+ Expires: time.Now().Add(30 * time.Second).Unix(),
349
+ Name: name,
350
+ Alpn: alpns,
351
+ }
352
+
353
+ c.leasesMu.Lock()
354
+ c.leases[identity.Id] = &LeaseWithCred{
355
+ Lease: lease,
356
+ Cred: cred,
357
+ }
358
+ c.leasesMu.Unlock()
359
+
360
+ resp, err := c.updateLease(ctx, cred, lease)
361
+ if err != nil || resp != rdverb.ResponseCode_RESPONSE_CODE_ACCEPTED {
362
+ c.leasesMu.Lock()
363
+ delete(c.leases, identity.Id)
364
+ c.leasesMu.Unlock()
365
+ return err
366
+ }
367
+
368
+ return nil
369
+}
relaydns/core/proto/rdverb/rdverb.pb.go
+33
-29
@@ -89,31 +89,34 @@ func (PacketType) EnumDescriptor() ([]byte, []int) {
89
type ResponseCode int32
90
91
const (
92
- ResponseCode_RESPONSE_CODE_ACCEPTED ResponseCode = 0
93
- ResponseCode_RESPONSE_CODE_INVALID_EXPIRES ResponseCode = 1
94
- ResponseCode_RESPONSE_CODE_INVALID_IDENTITY ResponseCode = 2
95
- ResponseCode_RESPONSE_CODE_INVALID_NAME ResponseCode = 3
96
- ResponseCode_RESPONSE_CODE_INVALID_ALPN ResponseCode = 4
97
- ResponseCode_RESPONSE_CODE_REJECTED ResponseCode = 5
92
+ ResponseCode_RESPONSE_CODE_UNKNOWN ResponseCode = 0
93
+ ResponseCode_RESPONSE_CODE_ACCEPTED ResponseCode = 1
94
+ ResponseCode_RESPONSE_CODE_INVALID_EXPIRES ResponseCode = 2
95
+ ResponseCode_RESPONSE_CODE_INVALID_IDENTITY ResponseCode = 3
96
+ ResponseCode_RESPONSE_CODE_INVALID_NAME ResponseCode = 4
97
+ ResponseCode_RESPONSE_CODE_INVALID_ALPN ResponseCode = 5
98
+ ResponseCode_RESPONSE_CODE_REJECTED ResponseCode = 6
99
)
100
101
// Enum value maps for ResponseCode.
102
var (
103
ResponseCode_name = map[int32]string{
103
- 0: "RESPONSE_CODE_ACCEPTED",
104
- 1: "RESPONSE_CODE_INVALID_EXPIRES",
105
- 2: "RESPONSE_CODE_INVALID_IDENTITY",
106
- 3: "RESPONSE_CODE_INVALID_NAME",
107
- 4: "RESPONSE_CODE_INVALID_ALPN",
108
- 5: "RESPONSE_CODE_REJECTED",
104
+ 0: "RESPONSE_CODE_UNKNOWN",
105
+ 1: "RESPONSE_CODE_ACCEPTED",
106
+ 2: "RESPONSE_CODE_INVALID_EXPIRES",
107
+ 3: "RESPONSE_CODE_INVALID_IDENTITY",
108
+ 4: "RESPONSE_CODE_INVALID_NAME",
109
+ 5: "RESPONSE_CODE_INVALID_ALPN",
110
+ 6: "RESPONSE_CODE_REJECTED",
111
}
112
ResponseCode_value = map[string]int32{
111
- "RESPONSE_CODE_ACCEPTED": 0,
112
- "RESPONSE_CODE_INVALID_EXPIRES": 1,
113
- "RESPONSE_CODE_INVALID_IDENTITY": 2,
114
- "RESPONSE_CODE_INVALID_NAME": 3,
115
- "RESPONSE_CODE_INVALID_ALPN": 4,
116
- "RESPONSE_CODE_REJECTED": 5,
113
+ "RESPONSE_CODE_UNKNOWN": 0,
114
+ "RESPONSE_CODE_ACCEPTED": 1,
115
+ "RESPONSE_CODE_INVALID_EXPIRES": 2,
116
+ "RESPONSE_CODE_INVALID_IDENTITY": 3,
117
+ "RESPONSE_CODE_INVALID_NAME": 4,
118
+ "RESPONSE_CODE_INVALID_ALPN": 5,
119
+ "RESPONSE_CODE_REJECTED": 6,
120
}
121
)
122
@@ -505,7 +508,7 @@ func (x *LeaseUpdateResponse) GetCode() ResponseCode {
508
if x != nil {
509
return x.Code
510
}
508
- return ResponseCode_RESPONSE_CODE_ACCEPTED
511
+ return ResponseCode_RESPONSE_CODE_UNKNOWN
512
}
513
514
type LeaseDeleteRequest struct {
@@ -609,7 +612,7 @@ func (x *LeaseDeleteResponse) GetCode() ResponseCode {
612
if x != nil {
613
return x.Code
614
}
612
- return ResponseCode_RESPONSE_CODE_ACCEPTED
615
+ return ResponseCode_RESPONSE_CODE_UNKNOWN
616
}
617
618
type ConnectionRequest struct {
@@ -705,7 +708,7 @@ func (x *ConnectionResponse) GetCode() ResponseCode {
708
if x != nil {
709
return x.Code
710
}
708
- return ResponseCode_RESPONSE_CODE_ACCEPTED
711
+ return ResponseCode_RESPONSE_CODE_UNKNOWN
712
}
713
714
var File_relaydns_core_proto_rdverb_rdverb_proto protoreflect.FileDescriptor
@@ -755,14 +758,15 @@ const file_relaydns_core_proto_rdverb_rdverb_proto_rawDesc = "" +
758
" PACKET_TYPE_LEASE_DELETE_REQUEST\x10\x04\x12%\n" +
759
"!PACKET_TYPE_LEASE_DELETE_RESPONSE\x10\x05\x12\"\n" +
760
"\x1ePACKET_TYPE_CONNECTION_REQUEST\x10\x06\x12#\n" +
758
- "\x1fPACKET_TYPE_CONNECTION_RESPONSE\x10\a*\xcd\x01\n" +
759
- "\fResponseCode\x12\x1a\n" +
760
- "\x16RESPONSE_CODE_ACCEPTED\x10\x00\x12!\n" +
761
- "\x1dRESPONSE_CODE_INVALID_EXPIRES\x10\x01\x12\"\n" +
762
- "\x1eRESPONSE_CODE_INVALID_IDENTITY\x10\x02\x12\x1e\n" +
763
- "\x1aRESPONSE_CODE_INVALID_NAME\x10\x03\x12\x1e\n" +
764
- "\x1aRESPONSE_CODE_INVALID_ALPN\x10\x04\x12\x1a\n" +
765
- "\x16RESPONSE_CODE_REJECTED\x10\x05B\x8f\x01\n" +
761
+ "\x1fPACKET_TYPE_CONNECTION_RESPONSE\x10\a*\xe8\x01\n" +
762
+ "\fResponseCode\x12\x19\n" +
763
+ "\x15RESPONSE_CODE_UNKNOWN\x10\x00\x12\x1a\n" +
764
+ "\x16RESPONSE_CODE_ACCEPTED\x10\x01\x12!\n" +
765
+ "\x1dRESPONSE_CODE_INVALID_EXPIRES\x10\x02\x12\"\n" +
766
+ "\x1eRESPONSE_CODE_INVALID_IDENTITY\x10\x03\x12\x1e\n" +
767
+ "\x1aRESPONSE_CODE_INVALID_NAME\x10\x04\x12\x1e\n" +
768
+ "\x1aRESPONSE_CODE_INVALID_ALPN\x10\x05\x12\x1a\n" +
769
+ "\x16RESPONSE_CODE_REJECTED\x10\x06B\x8f\x01\n" +
770
"\n" +
771
"com.rdverbB\vRdverbProtoP\x01Z<github.com/gosuda/relaydns/relaydns/core/proto/rdverb;rdverb\xa2\x02\x03RXX\xaa\x02\x06Rdverb\xca\x02\x06Rdverb\xe2\x02\x12Rdverb\\GPBMetadata\xea\x02\x06Rdverbb\x06proto3"
772
relaydns/core/proto/rdverb/rdverb.proto
+7
-6
@@ -21,14 +21,15 @@ enum PacketType {
21
}
22
23
enum ResponseCode {
24
- RESPONSE_CODE_ACCEPTED = 0;
24
+ RESPONSE_CODE_UNKNOWN = 0;
25
+ RESPONSE_CODE_ACCEPTED = 1;
26
26
- RESPONSE_CODE_INVALID_EXPIRES = 1;
27
- RESPONSE_CODE_INVALID_IDENTITY = 2;
28
- RESPONSE_CODE_INVALID_NAME = 3;
29
- RESPONSE_CODE_INVALID_ALPN = 4;
27
+ RESPONSE_CODE_INVALID_EXPIRES = 2;
28
+ RESPONSE_CODE_INVALID_IDENTITY = 3;
29
+ RESPONSE_CODE_INVALID_NAME = 4;
30
+ RESPONSE_CODE_INVALID_ALPN = 5;
31
31
- RESPONSE_CODE_REJECTED = 5;
32
+ RESPONSE_CODE_REJECTED = 6;
33
}
34
35
message Packet {