master
go 356 lines 8.88 KB
Raw
1 package libp2p
2
3 import (
4 "context"
5 "fmt"
6 "runtime/debug"
7 "sort"
8 "time"
9
10 "github.com/cenkalti/backoff/v4"
11 offroute "github.com/ipfs/boxo/routing/offline"
12 ds "github.com/ipfs/go-datastore"
13 dht "github.com/libp2p/go-libp2p-kad-dht"
14 ddht "github.com/libp2p/go-libp2p-kad-dht/dual"
15 "github.com/libp2p/go-libp2p-kad-dht/fullrt"
16 pubsub "github.com/libp2p/go-libp2p-pubsub"
17 namesys "github.com/libp2p/go-libp2p-pubsub-router"
18 record "github.com/libp2p/go-libp2p-record"
19 routinghelpers "github.com/libp2p/go-libp2p-routing-helpers"
20 "github.com/libp2p/go-libp2p/core/host"
21 "github.com/libp2p/go-libp2p/core/peer"
22 "github.com/libp2p/go-libp2p/core/routing"
23 "go.uber.org/fx"
24
25 config "github.com/ipfs/kubo/config"
26 "github.com/ipfs/kubo/core/node/helpers"
27 "github.com/ipfs/kubo/core/shutdown"
28 "github.com/ipfs/kubo/repo"
29 irouting "github.com/ipfs/kubo/routing"
30 )
31
32 type Router struct {
33 routing.Routing
34
35 Priority int // less = more important
36 }
37
38 type p2pRouterOut struct {
39 fx.Out
40
41 Router Router `group:"routers"`
42 }
43
44 type processInitialRoutingIn struct {
45 fx.In
46
47 Router routing.Routing `name:"initialrouting"`
48
49 // For setting up experimental DHT client
50 Host host.Host
51 Repo repo.Repo
52 Validator record.Validator
53 }
54
55 type processInitialRoutingOut struct {
56 fx.Out
57
58 Router Router `group:"routers"`
59 ContentRouter routing.ContentRouting `group:"content-routers"`
60
61 DHT *ddht.DHT
62 DHTClient routing.Routing `name:"dhtc"`
63 }
64
65 type AddrInfoChan chan peer.AddrInfo
66
67 func BaseRouting(cfg *config.Config) any {
68 return func(lc fx.Lifecycle, in processInitialRoutingIn) (out processInitialRoutingOut, err error) {
69 var dualDHT *ddht.DHT
70 if dht, ok := in.Router.(*ddht.DHT); ok {
71 dualDHT = dht
72
73 lc.Append(fx.Hook{
74 OnStop: func(ctx context.Context) error {
75 return shutdown.CloseWithCtx(ctx, "dht-dual", dualDHT.Close)
76 },
77 })
78 }
79
80 if cr, ok := in.Router.(routinghelpers.ComposableRouter); ok {
81 for _, r := range cr.Routers() {
82 if dht, ok := r.(*ddht.DHT); ok {
83 dualDHT = dht
84 lc.Append(fx.Hook{
85 OnStop: func(ctx context.Context) error {
86 return shutdown.CloseWithCtx(ctx, "dht-dual-composable", dualDHT.Close)
87 },
88 })
89 break
90 }
91 }
92 }
93
94 if dualDHT != nil && cfg.Routing.AcceleratedDHTClient.WithDefault(config.DefaultAcceleratedDHTClient) {
95 cfg, err := in.Repo.Config()
96 if err != nil {
97 return out, err
98 }
99 // Use auto-config resolution for actual connectivity
100 bspeers, err := cfg.BootstrapPeersWithAutoConf()
101 if err != nil {
102 return out, err
103 }
104
105 fullRTClient, err := fullrt.NewFullRT(in.Host,
106 dht.DefaultPrefix,
107 fullrt.DHTOption(
108 dht.Validator(in.Validator),
109 dht.Datastore(in.Repo.Datastore()),
110 dht.BootstrapPeers(bspeers...),
111 dht.BucketSize(20),
112 ),
113 )
114 if err != nil {
115 return out, err
116 }
117
118 lc.Append(fx.Hook{
119 OnStop: func(ctx context.Context) error {
120 return shutdown.CloseWithCtx(ctx, "dht-fullrt", fullRTClient.Close)
121 },
122 })
123
124 // we want to also use the default HTTP routers, so wrap the FullRT client
125 // in a parallel router that calls them in parallel
126 addrFunc := httpRouterAddrFunc(in.Host, cfg.Addresses)
127 httpRouters, err := constructDefaultHTTPRouters(cfg, addrFunc)
128 if err != nil {
129 return out, err
130 }
131 routers := []*routinghelpers.ParallelRouter{
132 {Router: fullRTClient, DoNotWaitForSearchValue: true},
133 }
134 routers = append(routers, httpRouters...)
135 router := routinghelpers.NewComposableParallel(routers)
136
137 return processInitialRoutingOut{
138 Router: Router{
139 Priority: 1000,
140 Routing: router,
141 },
142 DHT: dualDHT,
143 DHTClient: fullRTClient,
144 ContentRouter: fullRTClient,
145 }, nil
146 }
147
148 return processInitialRoutingOut{
149 Router: Router{
150 Priority: 1000,
151 Routing: in.Router,
152 },
153 DHT: dualDHT,
154 DHTClient: dualDHT,
155 ContentRouter: in.Router,
156 }, nil
157 }
158 }
159
160 type p2pOnlineContentRoutingIn struct {
161 fx.In
162
163 ContentRouter []routing.ContentRouting `group:"content-routers"`
164 }
165
166 // ContentRouting will get all routers that can do contentRouting and add them
167 // all together using a TieredRouter. It will be used for topic discovery.
168 func ContentRouting(in p2pOnlineContentRoutingIn) routing.ContentRouting {
169 var routers []routing.Routing
170 for _, cr := range in.ContentRouter {
171 routers = append(routers,
172 &routinghelpers.Compose{
173 ContentRouting: cr,
174 },
175 )
176 }
177
178 return routinghelpers.Tiered{
179 Routers: routers,
180 }
181 }
182
183 // ContentDiscovery narrows down the given content routing facility so that it
184 // only does discovery.
185 func ContentDiscovery(in irouting.ProvideManyRouter) routing.ContentDiscovery {
186 return in
187 }
188
189 type p2pOnlineRoutingIn struct {
190 fx.In
191
192 Routers []Router `group:"routers"`
193 Validator record.Validator
194 }
195
196 // Routing will get all routers obtained from different methods (delegated
197 // routers, pub-sub, and so on) and add them all together using a ParallelRouter.
198 func Routing(in p2pOnlineRoutingIn) irouting.ProvideManyRouter {
199 routers := in.Routers
200
201 sort.SliceStable(routers, func(i, j int) bool {
202 return routers[i].Priority < routers[j].Priority
203 })
204
205 var cRouters []*routinghelpers.ParallelRouter
206 for _, v := range routers {
207 cRouters = append(cRouters, &routinghelpers.ParallelRouter{
208 IgnoreError: true,
209 DoNotWaitForSearchValue: true,
210 Router: v.Routing,
211 })
212 }
213
214 return routinghelpers.NewComposableParallel(cRouters)
215 }
216
217 // OfflineRouting provides a special Router to the routers list when we are
218 // creating an offline node.
219 func OfflineRouting(dstore ds.Datastore, validator record.Validator) p2pRouterOut {
220 return p2pRouterOut{
221 Router: Router{
222 Routing: offroute.NewOfflineRouter(dstore, validator),
223 Priority: 10000,
224 },
225 }
226 }
227
228 type p2pPSRoutingIn struct {
229 fx.In
230
231 Validator record.Validator
232 Host host.Host
233 PubSub *pubsub.PubSub `optional:"true"`
234 }
235
236 func PubsubRouter(mctx helpers.MetricsCtx, lc fx.Lifecycle, in p2pPSRoutingIn) (p2pRouterOut, *namesys.PubsubValueStore, error) {
237 psRouter, err := namesys.NewPubsubValueStore(
238 helpers.LifecycleCtx(mctx, lc),
239 in.Host,
240 in.PubSub,
241 in.Validator,
242 namesys.WithRebroadcastInterval(time.Minute),
243 )
244 if err != nil {
245 return p2pRouterOut{}, nil, err
246 }
247
248 return p2pRouterOut{
249 Router: Router{
250 Routing: &routinghelpers.Compose{
251 ValueStore: &routinghelpers.LimitedValueStore{
252 ValueStore: psRouter,
253 Namespaces: []string{"ipns"},
254 },
255 },
256 Priority: 100,
257 },
258 }, psRouter, nil
259 }
260
261 func autoRelayFeeder(cfgPeering config.Peering, peerChan chan<- peer.AddrInfo) fx.Option {
262 return fx.Invoke(func(lc fx.Lifecycle, h host.Host, dht *ddht.DHT) {
263 ctx, cancel := context.WithCancel(context.Background())
264 done := make(chan struct{})
265
266 defer func() {
267 if r := recover(); r != nil {
268 fmt.Println("Recovering from unexpected error in AutoRelayFeeder:", r)
269 debug.PrintStack()
270 }
271 }()
272 go func() {
273 defer close(done)
274
275 // Feed peers more often right after the bootstrap, then backoff
276 bo := backoff.NewExponentialBackOff()
277 bo.InitialInterval = 15 * time.Second
278 bo.Multiplier = 3
279 bo.MaxInterval = 1 * time.Hour
280 bo.MaxElapsedTime = 0 // never stop
281 t := backoff.NewTicker(bo)
282 defer t.Stop()
283 for {
284 select {
285 case <-t.C:
286 case <-ctx.Done():
287 return
288 }
289
290 // Always feed trusted IDs (Peering.Peers in the config)
291 for _, trustedPeer := range cfgPeering.Peers {
292 if len(trustedPeer.Addrs) == 0 {
293 continue
294 }
295 select {
296 case peerChan <- trustedPeer:
297 case <-ctx.Done():
298 return
299 }
300 }
301
302 // Additionally, feed closest peers discovered via DHT
303 if dht != nil {
304 closestPeers, err := dht.WAN.GetClosestPeers(ctx, h.ID().String())
305 if err == nil {
306 for _, p := range closestPeers {
307 addrs := h.Peerstore().Addrs(p)
308 if len(addrs) == 0 {
309 continue
310 }
311 dhtPeer := peer.AddrInfo{ID: p, Addrs: addrs}
312 select {
313 case peerChan <- dhtPeer:
314 case <-ctx.Done():
315 return
316 }
317 }
318 }
319 }
320
321 // Additionally, feed all connected swarm peers as potential relay candidates.
322 // This includes peers from HTTP routing, manual swarm connect, mDNS discovery, etc.
323 // (fixes https://github.com/ipfs/kubo/issues/10899)
324 connectedPeers := h.Network().Peers()
325 for _, p := range connectedPeers {
326 addrs := h.Peerstore().Addrs(p)
327 if len(addrs) == 0 {
328 continue
329 }
330 swarmPeer := peer.AddrInfo{ID: p, Addrs: addrs}
331 select {
332 case peerChan <- swarmPeer:
333 case <-ctx.Done():
334 return
335 }
336 }
337 }
338 }()
339
340 lc.Append(fx.Hook{
341 OnStop: func(ctx context.Context) error {
342 cancel()
343 // Wait for the feeder goroutine to exit but bound by
344 // the shutdown deadline so a stuck DHT call (downstream
345 // bug ignoring ctx) cannot block fx.Stop. Mirrors the
346 // reprovideAlert pattern in provider.go.
347 select {
348 case <-done:
349 return nil
350 case <-ctx.Done():
351 return ctx.Err()
352 }
353 },
354 })
355 })
356 }