fix codes and readme

Kim committed Oct 21, 2025 at 18:27 UTC 586ef072f1bc65ba0691998915030f5c621138e3
6 files changed +81 -69
README.md
+8 -9
@@ -15,15 +15,14 @@ without depending on centralized reverse-proxy services.
15 ## Architecture Overview
16
17 ```
18 -┌──────────────┐ pubsub (GossipSub) ┌──────────────┐
19 -│ relaydns │ <--------------------------> │ client(s) │
20 -│ server │ │ (imported in │
21 -│ (director) │ │ your app) │
22 -└──────────────┘ └──────────────┘
23 - │ │
24 - │ TCP stream (e.g. SSH, HTTP, custom) │
25 - ▼ ▼
26 - Your users Your local service
18 +┌────────────┐ pubsub (GossipSub) ┌─────────────┐
19 +│ relay dns │ <---------------------> │ client(s) │
20 +│ server │ │(in your app)│
21 +└────────────┘ └─────────────┘
22 + │ │
23 + │ TCP stream (e.g. SSH, HTTP, custom) │
24 + ▼ ▼
25 + Your users Your local service
26 ```
27
28 ## Getting Started
cmd/server/main.go
+2 -2
@@ -55,7 +55,7 @@ func runServer(cmd *cobra.Command, args []string) error {
55 }
56 relaydns.ConnectBootstraps(ctx, h, flagBootstraps)
57
58 - d, err := relaydns.NewDirector(ctx, h, protocol, topic)
58 + d, err := relaydns.NewRelayServer(ctx, h, protocol, topic)
59 if err != nil {
60 return err
61 }
@@ -79,7 +79,7 @@ func runServer(cmd *cobra.Command, args []string) error {
79
80 // serveTCPIngress listens on addr for raw TCP (e.g., SSH) and proxies
81 // incoming connections to a chosen peer over libp2p stream using Director.
82 -func serveTCPIngress(ctx context.Context, addr string, d *relaydns.Director) {
82 +func serveTCPIngress(ctx context.Context, addr string, d *relaydns.RelayServer) {
83 ln, err := net.Listen("tcp", addr)
84 if err != nil {
85 log.Error().Err(err).Msgf("tcp ingress listen failed: %s", addr)
cmd/server/view.go
+1 -1
@@ -15,7 +15,7 @@ import (
15 )
16
17 // serveHTTP builds the HTTP mux.
18 -func serveHTTP(ctx context.Context, addr string, d *relaydns.Director, h host.Host, cancel context.CancelFunc) {
18 +func serveHTTP(ctx context.Context, addr string, d *relaydns.RelayServer, h host.Host, cancel context.CancelFunc) {
19 if addr == "" {
20 return
21 }
relaydns/client.go
+1
@@ -204,6 +204,7 @@ func (b *RelayClient) Start(ctx context.Context) error {
204 sortMultiaddrs(addrs, b.cfg.PreferQUIC, b.cfg.PreferLocal)
205 addrs = uniq(addrs)
206 if len(addrs) > 0 {
207 + // Always attempt (re)connect; ConnectBootstraps handles dedupe and quiet logging
208 ConnectBootstraps(ctx, b.h, addrs)
209 }
210 }
relaydns/host.go
+36 -24
@@ -1,14 +1,15 @@
1 package relaydns
2
3 import (
4 - "context"
5 - "fmt"
4 + "context"
5 + "fmt"
6
7 - "github.com/libp2p/go-libp2p"
8 - "github.com/libp2p/go-libp2p/core/host"
9 - "github.com/libp2p/go-libp2p/core/peer"
10 - ma "github.com/multiformats/go-multiaddr"
11 - "github.com/rs/zerolog/log"
7 + "github.com/libp2p/go-libp2p"
8 + "github.com/libp2p/go-libp2p/core/host"
9 + "github.com/libp2p/go-libp2p/core/network"
10 + "github.com/libp2p/go-libp2p/core/peer"
11 + ma "github.com/multiformats/go-multiaddr"
12 + "github.com/rs/zerolog/log"
13 )
14
15 func MakeHost(ctx context.Context, port int, enableRelay bool) (host.Host, error) {
@@ -39,21 +40,32 @@ func MakeHost(ctx context.Context, port int, enableRelay bool) (host.Host, error
40 }
41
42 func ConnectBootstraps(ctx context.Context, h host.Host, addrs []string) {
42 - for _, s := range addrs {
43 - m, err := ma.NewMultiaddr(s)
44 - if err != nil {
45 - log.Warn().Err(err).Msgf("bootstrap bad multiaddr %q", s)
46 - continue
47 - }
48 - ai, err := peer.AddrInfoFromP2pAddr(m)
49 - if err != nil {
50 - log.Warn().Err(err).Msgf("bootstrap missing /p2p/ in %q", s)
51 - continue
52 - }
53 - if err := h.Connect(ctx, *ai); err != nil {
54 - log.Warn().Err(err).Msgf("bootstrap connect %s", ai.ID)
55 - } else {
56 - log.Info().Msgf("connected bootstrap %s", ai.ID)
57 - }
58 - }
43 + // Connect once per unique peer ID; skip already-connected peers to avoid noisy logs.
44 + seen := make(map[string]struct{}, len(addrs))
45 + for _, s := range addrs {
46 + m, err := ma.NewMultiaddr(s)
47 + if err != nil {
48 + log.Warn().Err(err).Msgf("bootstrap bad multiaddr %q", s)
49 + continue
50 + }
51 + ai, err := peer.AddrInfoFromP2pAddr(m)
52 + if err != nil {
53 + log.Warn().Err(err).Msgf("bootstrap missing /p2p/ in %q", s)
54 + continue
55 + }
56 + pid := ai.ID.String()
57 + if _, ok := seen[pid]; ok {
58 + continue
59 + }
60 + seen[pid] = struct{}{}
61 +
62 + if h.Network().Connectedness(ai.ID) == network.Connected {
63 + // Already connected: skip loudly logging.
64 + continue
65 + }
66 + if err := h.Connect(ctx, *ai); err != nil {
67 + // Only warn on errors
68 + log.Warn().Err(err).Msgf("bootstrap connect %s", ai.ID)
69 + }
70 + }
71 }
relaydns/server.go renamed
+33 -33
@@ -22,7 +22,7 @@ import (
22 "github.com/rs/zerolog/log"
23 )
24
25 -type Director struct {
25 +type RelayServer struct {
26 ctx context.Context
27 h host.Host
28 protocol string
@@ -35,7 +35,7 @@ type Director struct {
35 deadTTL time.Duration
36 }
37
38 -func NewDirector(ctx context.Context, h host.Host, protocol, topic string) (*Director, error) {
38 +func NewRelayServer(ctx context.Context, h host.Host, protocol, topic string) (*RelayServer, error) {
39 ps, err := pubsub.NewGossipSub(ctx, h)
40 if err != nil {
41 return nil, err
@@ -48,7 +48,7 @@ func NewDirector(ctx context.Context, h host.Host, protocol, topic string) (*Dir
48 if err != nil {
49 return nil, err
50 }
51 - d := &Director{
51 + d := &RelayServer{
52 ctx: ctx,
53 h: h,
54 protocol: protocol,
@@ -63,14 +63,14 @@ func NewDirector(ctx context.Context, h host.Host, protocol, topic string) (*Dir
63 return d, nil
64 }
65
66 -func (d *Director) Close() error {
67 - d.sub.Cancel()
66 +func (s *RelayServer) Close() error {
67 + s.sub.Cancel()
68 return nil
69 }
70
71 -func (d *Director) collect() {
71 +func (s *RelayServer) collect() {
72 for {
73 - msg, err := d.sub.Next(d.ctx)
73 + msg, err := s.sub.Next(s.ctx)
74 if err != nil {
75 return
76 }
@@ -94,48 +94,48 @@ func (d *Director) collect() {
94 continue
95 }
96 now := time.Now()
97 - d.storeMu.Lock()
98 - _, existed := d.store[ad.Peer]
99 - d.store[ad.Peer] = HostEntry{Info: ad, AddrInfo: ai, LastSeen: now, Connected: true}
97 + s.storeMu.Lock()
98 + _, existed := s.store[ad.Peer]
99 + s.store[ad.Peer] = HostEntry{Info: ad, AddrInfo: ai, LastSeen: now, Connected: true}
100 // refresh picker snapshot
101 - snap := make([]HostEntry, 0, len(d.store))
102 - for _, v := range d.store {
101 + snap := make([]HostEntry, 0, len(s.store))
102 + for _, v := range s.store {
103 snap = append(snap, v)
104 }
105 - d.storeMu.Unlock()
105 + s.storeMu.Unlock()
106 if existed {
107 - log.Debug().Str("peer", ad.Peer).Str("name", ad.Name).Msg("director: updated client advert")
107 + log.Debug().Str("peer", ad.Peer).Str("name", ad.Name).Msg("server: updated client advert")
108 } else {
109 - log.Info().Str("peer", ad.Peer).Str("name", ad.Name).Msg("director: added client")
109 + log.Info().Str("peer", ad.Peer).Str("name", ad.Name).Msg("server: added client")
110 }
111 _ = snap // snapshot kept local; selection handled explicitly via /peer
112 }
113 }
114
115 -func (d *Director) gc() {
115 +func (s *RelayServer) gc() {
116 t := time.NewTicker(5 * time.Second)
117 defer t.Stop()
118 for {
119 select {
120 - case <-d.ctx.Done():
120 + case <-s.ctx.Done():
121 return
122 case <-t.C:
123 now := time.Now()
124 removed := make([]HostEntry, 0)
125 - d.storeMu.Lock()
126 - for k, v := range d.store {
125 + s.storeMu.Lock()
126 + for k, v := range s.store {
127 // Prefer client-provided TTL if present; fallback to default d.ttl
128 - ttl := d.ttl
128 + ttl := s.ttl
129 if v.Info.TTL > 0 {
130 ttl = time.Duration(v.Info.TTL) * time.Second
131 }
132 // mark disconnected after ttl
133 if now.Sub(v.LastSeen) > ttl && v.Connected {
134 v.Connected = false
135 - d.store[k] = v
135 + s.store[k] = v
136 }
137 // remove only after extended dead TTL
138 - deadAfter := d.deadTTL
138 + deadAfter := s.deadTTL
139 if v.Info.TTL > 0 {
140 da := time.Duration(v.Info.TTL) * time.Second * 5
141 if da > deadAfter {
@@ -144,14 +144,14 @@ func (d *Director) gc() {
144 }
145 if now.Sub(v.LastSeen) > deadAfter {
146 removed = append(removed, v)
147 - delete(d.store, k)
147 + delete(s.store, k)
148 }
149 }
150 - snap := make([]HostEntry, 0, len(d.store))
151 - for _, v := range d.store {
150 + snap := make([]HostEntry, 0, len(s.store))
151 + for _, v := range s.store {
152 snap = append(snap, v)
153 }
154 - d.storeMu.Unlock()
154 + s.storeMu.Unlock()
155 for _, r := range removed {
156 log.Info().Str("peer", r.Info.Peer).Str("name", r.Info.Name).Dur("idle", now.Sub(r.LastSeen)).Msg("director: removed stale client")
157 }
@@ -161,11 +161,11 @@ func (d *Director) gc() {
161 }
162
163 // Hosts returns a snapshot of current known hosts.
164 -func (d *Director) Hosts() []HostEntry {
165 - d.storeMu.Lock()
166 - defer d.storeMu.Unlock()
167 - list := make([]HostEntry, 0, len(d.store))
168 - for _, v := range d.store {
164 +func (s *RelayServer) Hosts() []HostEntry {
165 + s.storeMu.Lock()
166 + defer s.storeMu.Unlock()
167 + list := make([]HostEntry, 0, len(s.store))
168 + for _, v := range s.store {
169 list = append(list, v)
170 }
171 // Sort by last-seen (most recent first)
@@ -176,7 +176,7 @@ func (d *Director) Hosts() []HostEntry {
176 }
177
178 // ProxyHTTP proxies the given HTTP request to the specified peer and writes the response to w.
179 -func (d *Director) ProxyHTTP(w http.ResponseWriter, r *http.Request, peerID, pathSuffix string) {
179 +func (d *RelayServer) ProxyHTTP(w http.ResponseWriter, r *http.Request, peerID, pathSuffix string) {
180 d.storeMu.Lock()
181 entry, ok := d.store[peerID]
182 d.storeMu.Unlock()
@@ -268,7 +268,7 @@ func (d *Director) ProxyHTTP(w http.ResponseWriter, r *http.Request, peerID, pat
268
269 // ProxyTCP opens a libp2p stream to peerID using the Director protocol and
270 // pipes raw bytes between the accepted TCP connection and the libp2p stream.
271 -func (d *Director) ProxyTCP(c net.Conn, peerID string) error {
271 +func (d *RelayServer) ProxyTCP(c net.Conn, peerID string) error {
272 defer c.Close()
273 d.storeMu.Lock()
274 entry, ok := d.store[peerID]