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 {