github.com/MetalBlockchain/metalgo@v1.11.9/utils/dynamicip/updater.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package dynamicip
     5  
     6  import (
     7  	"context"
     8  	"net/netip"
     9  	"time"
    10  
    11  	"go.uber.org/zap"
    12  
    13  	"github.com/MetalBlockchain/metalgo/utils"
    14  	"github.com/MetalBlockchain/metalgo/utils/logging"
    15  )
    16  
    17  const ipResolutionTimeout = 10 * time.Second
    18  
    19  var _ Updater = (*updater)(nil)
    20  
    21  // Updater periodically updates this node's public IP.
    22  // Dispatch() and Stop() should only be called once.
    23  type Updater interface {
    24  	// Start periodically resolving and updating our public IP.
    25  	// Doesn't return until after Stop() is called.
    26  	// Should be called in a goroutine.
    27  	Dispatch(log logging.Logger)
    28  	// Stop resolving and updating our public IP.
    29  	Stop()
    30  }
    31  
    32  type updater struct {
    33  	// The IP we periodically modify.
    34  	dynamicIP *utils.Atomic[netip.AddrPort]
    35  	// Used to find out what our public IP is.
    36  	resolver Resolver
    37  	// The parent of all contexts passed into resolver.Resolve().
    38  	// Cancelling causes Dispatch() to eventually return.
    39  	rootCtx context.Context
    40  	// Cancelling causes Dispatch() to eventually return.
    41  	// All in-flight calls to resolver.Resolve() will be cancelled.
    42  	rootCtxCancel context.CancelFunc
    43  	// Closed when Dispatch() has returned.
    44  	doneChan chan struct{}
    45  	// How often we update the public IP.
    46  	updateFreq time.Duration
    47  }
    48  
    49  // Returns a new Updater that updates [dynamicIP]
    50  // every [updateFreq]. Uses [resolver] to find
    51  // out what our public IP is.
    52  func NewUpdater(
    53  	dynamicIP *utils.Atomic[netip.AddrPort],
    54  	resolver Resolver,
    55  	updateFreq time.Duration,
    56  ) Updater {
    57  	ctx, cancel := context.WithCancel(context.Background())
    58  	return &updater{
    59  		dynamicIP:     dynamicIP,
    60  		resolver:      resolver,
    61  		rootCtx:       ctx,
    62  		rootCtxCancel: cancel,
    63  		doneChan:      make(chan struct{}),
    64  		updateFreq:    updateFreq,
    65  	}
    66  }
    67  
    68  // Start updating [u.dynamicIP] every [u.updateFreq].
    69  // Stops when [dynamicIP.stopChan] is closed.
    70  func (u *updater) Dispatch(log logging.Logger) {
    71  	ticker := time.NewTicker(u.updateFreq)
    72  	defer func() {
    73  		ticker.Stop()
    74  		close(u.doneChan)
    75  	}()
    76  
    77  	var (
    78  		initialAddrPort = u.dynamicIP.Get()
    79  		oldAddr         = initialAddrPort.Addr()
    80  		port            = initialAddrPort.Port()
    81  	)
    82  	for {
    83  		select {
    84  		case <-ticker.C:
    85  			ctx, cancel := context.WithTimeout(u.rootCtx, ipResolutionTimeout)
    86  			newAddr, err := u.resolver.Resolve(ctx)
    87  			cancel()
    88  			if err != nil {
    89  				log.Warn("couldn't resolve public IP. If this machine's IP recently changed, it may be sharing the wrong public IP with peers",
    90  					zap.Error(err),
    91  				)
    92  				continue
    93  			}
    94  
    95  			if newAddr != oldAddr {
    96  				u.dynamicIP.Set(netip.AddrPortFrom(newAddr, port))
    97  				log.Info("updated public IP",
    98  					zap.Stringer("oldIP", oldAddr),
    99  					zap.Stringer("newIP", newAddr),
   100  				)
   101  				oldAddr = newAddr
   102  			}
   103  		case <-u.rootCtx.Done():
   104  			return
   105  		}
   106  	}
   107  }
   108  
   109  func (u *updater) Stop() {
   110  	// Cause Dispatch() to return and cancel all
   111  	// in-flight calls to resolver.Resolve().
   112  	u.rootCtxCancel()
   113  	// Wait until Dispatch() has returned.
   114  	<-u.doneChan
   115  }