master
go 363 lines 11.4 KB
Raw
1 package libp2p
2
3 import (
4 "context"
5 "fmt"
6 "os"
7 "path/filepath"
8 "time"
9
10 logging "github.com/ipfs/go-log/v2"
11 version "github.com/ipfs/kubo"
12 "github.com/ipfs/kubo/config"
13 p2pforge "github.com/ipshipyard/p2p-forge/client"
14 "github.com/libp2p/go-libp2p"
15 "github.com/libp2p/go-libp2p/core/event"
16 "github.com/libp2p/go-libp2p/core/host"
17 p2pbhost "github.com/libp2p/go-libp2p/p2p/host/basic"
18 ma "github.com/multiformats/go-multiaddr"
19 manet "github.com/multiformats/go-multiaddr/net"
20 mamask "github.com/whyrusleeping/multiaddr-filter"
21
22 "github.com/caddyserver/certmagic"
23 "go.uber.org/fx"
24 )
25
26 func AddrFilters(filters []string) func() (*ma.Filters, Libp2pOpts, error) {
27 return func() (filter *ma.Filters, opts Libp2pOpts, err error) {
28 filter = ma.NewFilters()
29 opts.Opts = append(opts.Opts, libp2p.ConnectionGater((*filtersConnectionGater)(filter)))
30 for _, s := range filters {
31 f, err := mamask.NewMask(s)
32 if err != nil {
33 return filter, opts, fmt.Errorf("incorrectly formatted address filter in config: %s", s)
34 }
35 filter.AddFilter(*f, ma.ActionDeny)
36 }
37 return filter, opts, nil
38 }
39 }
40
41 // Sources for deadListenerFinding.Source.
42 const (
43 deadListenerSourceAddrFilters = "Swarm.AddrFilters"
44 deadListenerSourceNoAnnounce = "Addresses.NoAnnounce"
45 )
46
47 // deadListenerFinding is one resolved listener killed by a CIDR rule:
48 // `Swarm.AddrFilters` (gater RSTs inbound) or `Addresses.NoAnnounce`
49 // (listener never advertised).
50 type deadListenerFinding struct {
51 Listener string // resolved listen multiaddr (interface-bound)
52 Source string // deadListenerSourceAddrFilters or deadListenerSourceNoAnnounce
53 Rule string // matching CIDR rule from Source
54 }
55
56 // findDeadListeners returns one finding per (listener, rule, source)
57 // triple whose IP component falls inside a CIDR in addrFilters or
58 // noAnnounce.
59 //
60 // listenAddrs must be already-resolved interface addresses (output of
61 // `host.Network().InterfaceListenAddresses()`). Without resolution, the
62 // unspecified address itself can match a broad filter (`::` is in
63 // `::/3`) even when the listener accepts globally-routable peers.
64 //
65 // NoAnnounce matches on loopback are skipped: stripping loopback from
66 // identify and DHT records is normal operator intent, not a bug.
67 // AddrFilters matches on loopback are always reported, since that is
68 // the misconfiguration this check exists to catch.
69 //
70 // Listeners without an IP component (`/dns`, `/dnsaddr`) and
71 // unparseable rules are skipped silently.
72 func findDeadListeners(listenAddrs []ma.Multiaddr, addrFilters []string, noAnnounce []string) []deadListenerFinding {
73 check := func(source string, rules []string) []deadListenerFinding {
74 var out []deadListenerFinding
75 for _, r := range rules {
76 mask, err := mamask.NewMask(r)
77 if err != nil {
78 // Malformed CIDR (caught upstream for AddrFilters) or
79 // an exact-match multiaddr in NoAnnounce. Skip either way.
80 continue
81 }
82 f := ma.NewFilters()
83 f.AddFilter(*mask, ma.ActionDeny)
84 for _, l := range listenAddrs {
85 if !f.AddrBlocked(l) {
86 continue
87 }
88 if source == deadListenerSourceNoAnnounce && isLoopbackMultiaddr(l) {
89 // Suppressing loopback announcement is operator-intent,
90 // not a misconfiguration.
91 continue
92 }
93 out = append(out, deadListenerFinding{
94 Listener: l.String(),
95 Source: source,
96 Rule: r,
97 })
98 }
99 }
100 return out
101 }
102
103 findings := check(deadListenerSourceAddrFilters, addrFilters)
104 findings = append(findings, check(deadListenerSourceNoAnnounce, noAnnounce)...)
105 return findings
106 }
107
108 // isLoopbackMultiaddr reports whether m's IP component is loopback
109 // (`127.0.0.0/8` or `::1`). Returns false if m has no IP component.
110 func isLoopbackMultiaddr(m ma.Multiaddr) bool {
111 ip, err := manet.ToIP(m)
112 if err != nil {
113 return false
114 }
115 return ip.IsLoopback()
116 }
117
118 // logDeadListenerFinding writes one ERROR line per finding, naming
119 // the listener, the matching CIDR rule, and where to remove it from.
120 // Each line stands alone so operators can grep and act on it.
121 func logDeadListenerFinding(f deadListenerFinding) {
122 switch f.Source {
123 case deadListenerSourceAddrFilters:
124 log.Errorf(
125 "Addresses.Swarm listener %q matches Swarm.AddrFilters rule %q, "+
126 "so Kubo rejects every incoming connection to it. Remove %q "+
127 "from Swarm.AddrFilters to allow connections to this listener.",
128 f.Listener, f.Rule, f.Rule,
129 )
130 case deadListenerSourceNoAnnounce:
131 log.Errorf(
132 "Addresses.Swarm listener %q matches Addresses.NoAnnounce rule %q, "+
133 "so Kubo will not advertise it to other peers. Remove %q from "+
134 "Addresses.NoAnnounce to advertise this listener.",
135 f.Listener, f.Rule, f.Rule,
136 )
137 }
138 }
139
140 // MonitorDeadListeners runs findDeadListeners at startup and on every
141 // EvtLocalAddressesUpdated. Listen addresses change at runtime (NAT
142 // mapping, new interface, AutoTLS cert), so a one-shot check would
143 // miss listeners that appear later.
144 //
145 // Findings are deduplicated against the previous run: a stable
146 // misconfiguration is logged once.
147 //
148 // If subscribing to the event bus fails, the runtime monitor is
149 // disabled and only the startup check runs. The check is diagnostic
150 // and must never abort node startup.
151 func MonitorDeadListeners(addrFilters []string, noAnnounce []string) func(fx.Lifecycle, host.Host) error {
152 return func(lc fx.Lifecycle, h host.Host) error {
153 seen := make(map[deadListenerFinding]struct{})
154 runCheck := func() {
155 listenAddrs, err := h.Network().InterfaceListenAddresses()
156 if err != nil {
157 log.Warnf("dead-listener check: read InterfaceListenAddresses: %s", err)
158 return
159 }
160 next := make(map[deadListenerFinding]struct{})
161 for _, f := range findDeadListeners(listenAddrs, addrFilters, noAnnounce) {
162 next[f] = struct{}{}
163 if _, ok := seen[f]; ok {
164 continue
165 }
166 logDeadListenerFinding(f)
167 }
168 seen = next
169 }
170
171 // Startup check, always runs even if the runtime monitor below
172 // cannot be wired up.
173 runCheck()
174
175 sub, err := h.EventBus().Subscribe(new(event.EvtLocalAddressesUpdated))
176 if err != nil {
177 log.Errorf("dead-listener check: subscribe to EvtLocalAddressesUpdated failed (%s); runtime monitor disabled, startup check already ran", err)
178 return nil
179 }
180
181 ctx, cancel := context.WithCancel(context.Background())
182 lc.Append(fx.Hook{
183 OnStop: func(_ context.Context) error {
184 cancel()
185 return nil
186 },
187 })
188
189 go func() {
190 defer sub.Close()
191 for {
192 select {
193 case <-ctx.Done():
194 return
195 case _, ok := <-sub.Out():
196 if !ok {
197 return
198 }
199 runCheck()
200 }
201 }
202 }()
203 return nil
204 }
205 }
206
207 func makeAddrsFactory(announce []string, appendAnnounce []string, noAnnounce []string) (p2pbhost.AddrsFactory, error) {
208 var err error // To assign to the slice in the for loop
209 existing := make(map[string]bool) // To avoid duplicates
210
211 annAddrs := make([]ma.Multiaddr, len(announce))
212 for i, addr := range announce {
213 annAddrs[i], err = ma.NewMultiaddr(addr)
214 if err != nil {
215 return nil, err
216 }
217 existing[addr] = true
218 }
219
220 var appendAnnAddrs []ma.Multiaddr
221 for _, addr := range appendAnnounce {
222 if existing[addr] {
223 // skip AppendAnnounce that is on the Announce list already
224 continue
225 }
226 appendAddr, err := ma.NewMultiaddr(addr)
227 if err != nil {
228 return nil, err
229 }
230 appendAnnAddrs = append(appendAnnAddrs, appendAddr)
231 }
232
233 filters := ma.NewFilters()
234 noAnnAddrs := map[string]bool{}
235 for _, addr := range noAnnounce {
236 f, err := mamask.NewMask(addr)
237 if err == nil {
238 filters.AddFilter(*f, ma.ActionDeny)
239 continue
240 }
241 maddr, err := ma.NewMultiaddr(addr)
242 if err != nil {
243 return nil, err
244 }
245 noAnnAddrs[string(maddr.Bytes())] = true
246 }
247
248 return func(allAddrs []ma.Multiaddr) []ma.Multiaddr {
249 var addrs []ma.Multiaddr
250 if len(annAddrs) > 0 {
251 addrs = annAddrs
252 } else {
253 addrs = allAddrs
254 }
255 addrs = append(addrs, appendAnnAddrs...)
256
257 var out []ma.Multiaddr
258 for _, maddr := range addrs {
259 // Drop empty multiaddrs. Since go-multiaddr v0.15 made
260 // Multiaddr a slice type, a zero-value Multiaddr encodes to
261 // zero bytes and would otherwise reach the host's signed peer
262 // record, where peers render it as "/" and reject the address.
263 // See https://github.com/libp2p/js-libp2p/issues/3478#issuecomment-4322093929
264 if len(maddr) == 0 {
265 continue
266 }
267 // check for exact matches
268 ok := noAnnAddrs[string(maddr.Bytes())]
269 // check for /ipcidr matches
270 if !ok && !filters.AddrBlocked(maddr) {
271 out = append(out, maddr)
272 }
273 }
274 return out
275 }, nil
276 }
277
278 func AddrsFactory(announce []string, appendAnnounce []string, noAnnounce []string) any {
279 return func(params struct {
280 fx.In
281 ForgeMgr *p2pforge.P2PForgeCertMgr `optional:"true"`
282 },
283 ) (opts Libp2pOpts, err error) {
284 var addrsFactory p2pbhost.AddrsFactory
285 announceAddrsFactory, err := makeAddrsFactory(announce, appendAnnounce, noAnnounce)
286 if err != nil {
287 return opts, err
288 }
289 if params.ForgeMgr == nil {
290 addrsFactory = announceAddrsFactory
291 } else {
292 addrsFactory = func(multiaddrs []ma.Multiaddr) []ma.Multiaddr {
293 forgeProcessing := params.ForgeMgr.AddressFactory()(multiaddrs)
294 announceProcessing := announceAddrsFactory(forgeProcessing)
295 return announceProcessing
296 }
297 }
298 opts.Opts = append(opts.Opts, libp2p.AddrsFactory(addrsFactory))
299 return
300 }
301 }
302
303 func ListenOn(addresses []string) any {
304 return func() (opts Libp2pOpts) {
305 return Libp2pOpts{
306 Opts: []libp2p.Option{
307 libp2p.ListenAddrStrings(addresses...),
308 },
309 }
310 }
311 }
312
313 func P2PForgeCertMgr(repoPath string, cfg config.AutoTLS, atlsLog *logging.ZapEventLogger) any {
314 return func() (*p2pforge.P2PForgeCertMgr, error) {
315 storagePath := filepath.Join(repoPath, "p2p-forge-certs")
316 rawLogger := atlsLog.Desugar()
317
318 // TODO: this should not be necessary after
319 // https://github.com/ipshipyard/p2p-forge/pull/42 but keep it here for
320 // now to help tracking down any remaining conditions causing
321 // https://github.com/ipshipyard/p2p-forge/issues/8
322 certmagic.Default.Logger = rawLogger.Named("default_fixme")
323 certmagic.DefaultACME.Logger = rawLogger.Named("default_acme_client_fixme")
324
325 registrationDelay := cfg.RegistrationDelay.WithDefault(config.DefaultAutoTLSRegistrationDelay)
326 if cfg.Enabled == config.True && cfg.RegistrationDelay.IsDefault() {
327 // Skip delay if user explicitly enabled AutoTLS.Enabled in config
328 // and did not set custom AutoTLS.RegistrationDelay
329 registrationDelay = 0 * time.Second
330 }
331
332 certStorage := &certmagic.FileStorage{Path: storagePath}
333 certMgr, err := p2pforge.NewP2PForgeCertMgr(
334 p2pforge.WithLogger(rawLogger.Sugar()),
335 p2pforge.WithForgeDomain(cfg.DomainSuffix.WithDefault(config.DefaultDomainSuffix)),
336 p2pforge.WithForgeRegistrationEndpoint(cfg.RegistrationEndpoint.WithDefault(config.DefaultRegistrationEndpoint)),
337 p2pforge.WithRegistrationDelay(registrationDelay),
338 p2pforge.WithCAEndpoint(cfg.CAEndpoint.WithDefault(config.DefaultCAEndpoint)),
339 p2pforge.WithForgeAuth(cfg.RegistrationToken.WithDefault(os.Getenv(p2pforge.ForgeAuthEnv))),
340 p2pforge.WithUserAgent(version.GetUserAgentVersion()),
341 p2pforge.WithCertificateStorage(certStorage),
342 p2pforge.WithShortForgeAddrs(cfg.ShortAddrs.WithDefault(config.DefaultAutoTLSShortAddrs)),
343 )
344 if err != nil {
345 return nil, err
346 }
347
348 return certMgr, nil
349 }
350 }
351
352 func StartP2PAutoTLS(lc fx.Lifecycle, certMgr *p2pforge.P2PForgeCertMgr, h host.Host) {
353 lc.Append(fx.Hook{
354 OnStart: func(ctx context.Context) error {
355 certMgr.ProvideHost(h)
356 return certMgr.Start()
357 },
358 OnStop: func(ctx context.Context) error {
359 certMgr.Stop()
360 return nil
361 },
362 })
363 }