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 }