package autorelay import ( "context" "errors" "sync" "github.com/libp2p/go-libp2p/core/event" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/network" basic "github.com/libp2p/go-libp2p/p2p/host/basic" "github.com/libp2p/go-libp2p/p2p/host/eventbus" logging "github.com/ipfs/go-log/v2" ma "github.com/multiformats/go-multiaddr" ) var log = logging.Logger("autorelay") type AutoRelay struct { refCount sync.WaitGroup ctx context.Context ctxCancel context.CancelFunc conf *config mx sync.Mutex status network.Reachability relayFinder *relayFinder host host.Host addrsF basic.AddrsFactory metricsTracer MetricsTracer } func NewAutoRelay(bhost *basic.BasicHost, opts ...Option) (*AutoRelay, error) { r := &AutoRelay{ host: bhost, addrsF: bhost.AddrsFactory, status: network.ReachabilityUnknown, } conf := defaultConfig for _, opt := range opts { if err := opt(&conf); err != nil { return nil, err } } r.ctx, r.ctxCancel = context.WithCancel(context.Background()) r.conf = &conf r.relayFinder = newRelayFinder(bhost, conf.peerSource, &conf) r.metricsTracer = &wrappedMetricsTracer{conf.metricsTracer} bhost.AddrsFactory = r.hostAddrs return r, nil } func (r *AutoRelay) Start() { r.refCount.Add(1) go func() { r.background() r.refCount.Done() }() } func (r *AutoRelay) background() { subReachability, err := r.host.EventBus().Subscribe(new(event.EvtLocalReachabilityChanged), eventbus.Name("autorelay (background)")) if err != nil { log.Debug("failed to subscribe to the EvtLocalReachabilityChanged") return } for { select { case <-r.ctx.Done(): subReachability.Close() return case ev, ok := <-subReachability.Out(): if !ok { subReachability.Close() return } // TODO: push changed addresses evt := ev.(event.EvtLocalReachabilityChanged) switch evt.Reachability { case network.ReachabilityPrivate, network.ReachabilityUnknown: err := r.relayFinder.Start() if errors.Is(err, errAlreadyRunning) { log.Debug("tried to start already running relay finder") } else if err != nil { log.Errorw("failed to start relay finder", "error", err) } else { r.metricsTracer.RelayFinderStatus(true) } case network.ReachabilityPublic: r.relayFinder.Stop() r.metricsTracer.RelayFinderStatus(false) } r.mx.Lock() r.status = evt.Reachability r.mx.Unlock() } } } func (r *AutoRelay) hostAddrs(addrs []ma.Multiaddr) []ma.Multiaddr { return r.relayAddrs(r.addrsF(addrs)) } func (r *AutoRelay) relayAddrs(addrs []ma.Multiaddr) []ma.Multiaddr { r.mx.Lock() if r.status != network.ReachabilityPrivate { r.mx.Unlock() return addrs } a := r.relayFinder.relayAddrs(addrs) r.mx.Unlock() return a } func (r *AutoRelay) Close() error { r.ctxCancel() err := r.relayFinder.Stop() r.refCount.Wait() return err }