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]