From 7dfe633b90097e823a2ae0f89354a733c8fcef18 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Tue, 8 Oct 2024 13:48:23 -0700 Subject: [PATCH] Add backoff for updating local IP addresses on error --- p2p/host/basic/basic_host.go | 25 ++++++-- p2p/host/basic/internal/backoff/backoff.go | 52 ++++++++++++++++ .../basic/internal/backoff/backoff_test.go | 59 +++++++++++++++++++ 3 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 p2p/host/basic/internal/backoff/backoff.go create mode 100644 p2p/host/basic/internal/backoff/backoff_test.go diff --git a/p2p/host/basic/basic_host.go b/p2p/host/basic/basic_host.go index 761a39b664..3263f6e421 100644 --- a/p2p/host/basic/basic_host.go +++ b/p2p/host/basic/basic_host.go @@ -21,6 +21,7 @@ import ( "github.com/libp2p/go-libp2p/core/record" "github.com/libp2p/go-libp2p/core/transport" "github.com/libp2p/go-libp2p/p2p/host/autonat" + "github.com/libp2p/go-libp2p/p2p/host/basic/internal/backoff" "github.com/libp2p/go-libp2p/p2p/host/eventbus" "github.com/libp2p/go-libp2p/p2p/host/pstoremanager" "github.com/libp2p/go-libp2p/p2p/host/relaysvc" @@ -98,6 +99,8 @@ type BasicHost struct { addrChangeChan chan struct{} addrMu sync.RWMutex + updateLocalIPv4Backoff backoff.ExpBackoff + updateLocalIPv6Backoff backoff.ExpBackoff filteredInterfaceAddrs []ma.Multiaddr allInterfaceAddrs []ma.Multiaddr @@ -367,18 +370,32 @@ func (h *BasicHost) updateLocalIpAddr() { if r, err := netroute.New(); err != nil { log.Debugw("failed to build Router for kernel's routing table", "error", err) } else { - if _, _, localIPv4, err := r.Route(net.IPv4zero); err != nil { + + var localIPv4 net.IP + var ran bool + err, ran = h.updateLocalIPv4Backoff.Run(func() error { + _, _, localIPv4, err = r.Route(net.IPv4zero) + return err + }) + + if ran && err != nil { log.Debugw("failed to fetch local IPv4 address", "error", err) - } else if localIPv4.IsGlobalUnicast() { + } else if ran && localIPv4.IsGlobalUnicast() { maddr, err := manet.FromIP(localIPv4) if err == nil { h.filteredInterfaceAddrs = append(h.filteredInterfaceAddrs, maddr) } } - if _, _, localIPv6, err := r.Route(net.IPv6unspecified); err != nil { + var localIPv6 net.IP + err, ran = h.updateLocalIPv6Backoff.Run(func() error { + _, _, localIPv6, err = r.Route(net.IPv6unspecified) + return err + }) + + if ran && err != nil { log.Debugw("failed to fetch local IPv6 address", "error", err) - } else if localIPv6.IsGlobalUnicast() { + } else if ran && localIPv6.IsGlobalUnicast() { maddr, err := manet.FromIP(localIPv6) if err == nil { h.filteredInterfaceAddrs = append(h.filteredInterfaceAddrs, maddr) diff --git a/p2p/host/basic/internal/backoff/backoff.go b/p2p/host/basic/internal/backoff/backoff.go new file mode 100644 index 0000000000..3d1fd23778 --- /dev/null +++ b/p2p/host/basic/internal/backoff/backoff.go @@ -0,0 +1,52 @@ +package backoff + +import ( + "time" +) + +var since = time.Since + +const defaultDelay = 100 * time.Millisecond +const defaultMaxDelay = 1 * time.Minute + +type ExpBackoff struct { + Delay time.Duration + MaxDelay time.Duration + + failures int + lastRun time.Time +} + +func (b *ExpBackoff) init() { + if b.Delay == 0 { + b.Delay = defaultDelay + } + if b.MaxDelay == 0 { + b.MaxDelay = defaultMaxDelay + } +} + +func (b *ExpBackoff) calcDelay() time.Duration { + delay := b.Delay * time.Duration(1<<(b.failures-1)) + delay = min(delay, b.MaxDelay) + return delay +} + +func (b *ExpBackoff) Run(f func() error) (err error, ran bool) { + b.init() + + if b.failures != 0 { + if since(b.lastRun) < b.calcDelay() { + return nil, false + } + } + + b.lastRun = time.Now() + err = f() + if err == nil { + b.failures = 0 + } else { + b.failures++ + } + return err, true +} diff --git a/p2p/host/basic/internal/backoff/backoff_test.go b/p2p/host/basic/internal/backoff/backoff_test.go new file mode 100644 index 0000000000..6396b4e649 --- /dev/null +++ b/p2p/host/basic/internal/backoff/backoff_test.go @@ -0,0 +1,59 @@ +package backoff + +import ( + "errors" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestBackoff(t *testing.T) { + origSince := since + defer func() { since = origSince }() + + var timeSince time.Duration + since = func(time.Time) time.Duration { + return timeSince + } + + var maybeErr error + b := &ExpBackoff{} + f := func() error { return maybeErr } + + err, ran := b.Run(f) + require.True(t, ran) + require.NoError(t, err) + + maybeErr = errors.New("some error") + err, ran = b.Run(f) + require.True(t, ran) + require.Error(t, err) + + // Rerun again + _, ran = b.Run(f) + require.False(t, ran) + + timeSince = 100*time.Millisecond + 1 + err, ran = b.Run(f) + require.True(t, ran) + require.Error(t, err) + + timeSince = 100*time.Millisecond + 1 + _, ran = b.Run(f) + require.False(t, ran) + + timeSince = 200*time.Millisecond + 1 + err, ran = b.Run(f) + require.True(t, ran) + require.Error(t, err) + + for timeSince < defaultMaxDelay*4 { + timeSince *= 2 + err, ran = b.Run(f) + require.True(t, ran) + require.Error(t, err) + } + + require.Equal(t, defaultMaxDelay, b.calcDelay()) +}