github.com/decred/dcrlnd@v0.7.6/netann/host_ann.go (about)

     1  package netann
     2  
     3  import (
     4  	"net"
     5  	"sync"
     6  
     7  	"github.com/decred/dcrlnd/lnwire"
     8  	"github.com/decred/dcrlnd/ticker"
     9  )
    10  
    11  // HostAnnouncerConfig is the main config for the HostAnnouncer.
    12  type HostAnnouncerConfig struct {
    13  	// Hosts is the set of hosts we should watch for IP changes.
    14  	Hosts []string
    15  
    16  	// RefreshTicker ticks each time we should check for any address
    17  	// changes.
    18  	RefreshTicker ticker.Ticker
    19  
    20  	// LookupHost performs DNS resolution on a given host and returns its
    21  	// addresses.
    22  	LookupHost func(string) (net.Addr, error)
    23  
    24  	// AdvertisedIPs is the set of IPs that we've already announced with
    25  	// our current NodeAnnouncement. This set will be constructed to avoid
    26  	// unnecessary node NodeAnnouncement updates.
    27  	AdvertisedIPs map[string]struct{}
    28  
    29  	// AnnounceNewIPs announces a new set of IP addresses for the backing
    30  	// Lightning node. The first set of addresses is the new set of
    31  	// addresses that we should advertise, while the other set are the
    32  	// stale addresses that we should no longer advertise.
    33  	AnnounceNewIPs func([]net.Addr, map[string]struct{}) error
    34  }
    35  
    36  // HostAnnouncer is a sub-system that allows a user to specify a set of hosts
    37  // for lnd that will be continually resolved to notice any IP address changes.
    38  // If the target IP address for a host changes, then we'll generate a new
    39  // NodeAnnouncement that includes these new IPs.
    40  type HostAnnouncer struct {
    41  	cfg HostAnnouncerConfig
    42  
    43  	quit chan struct{}
    44  	wg   sync.WaitGroup
    45  
    46  	startOnce sync.Once
    47  	stopOnce  sync.Once
    48  }
    49  
    50  // NewHostAnnouncer returns a new instance of the HostAnnouncer.
    51  func NewHostAnnouncer(cfg HostAnnouncerConfig) *HostAnnouncer {
    52  	return &HostAnnouncer{
    53  		cfg:  cfg,
    54  		quit: make(chan struct{}),
    55  	}
    56  }
    57  
    58  // Start starts the HostAnnouncer.
    59  func (h *HostAnnouncer) Start() error {
    60  	h.startOnce.Do(func() {
    61  		h.wg.Add(1)
    62  		go h.hostWatcher()
    63  	})
    64  
    65  	return nil
    66  }
    67  
    68  // Stop signals the HostAnnouncer for a graceful stop.
    69  func (h *HostAnnouncer) Stop() error {
    70  	h.stopOnce.Do(func() {
    71  		log.Info("HostAnnouncer shutting down")
    72  		close(h.quit)
    73  		h.wg.Wait()
    74  	})
    75  
    76  	return nil
    77  }
    78  
    79  // hostWatcher periodically attempts to resolve the IP for each host, updating
    80  // them if they change within the interval.
    81  func (h *HostAnnouncer) hostWatcher() {
    82  	defer h.wg.Done()
    83  
    84  	ipMapping := make(map[string]net.Addr)
    85  	refreshHosts := func() {
    86  
    87  		// We'll now run through each of our hosts to check if they had
    88  		// their backing IPs changed. If so, we'll want to re-announce
    89  		// them.
    90  		var addrsToUpdate []net.Addr
    91  		addrsToRemove := make(map[string]struct{})
    92  		for _, host := range h.cfg.Hosts {
    93  			newAddr, err := h.cfg.LookupHost(host)
    94  			if err != nil {
    95  				log.Warnf("unable to resolve IP for "+
    96  					"host %v: %v", host, err)
    97  				continue
    98  			}
    99  
   100  			// If nothing has changed since the last time we
   101  			// checked, then we don't need to do any updates.
   102  			oldAddr, oldAddrFound := ipMapping[host]
   103  			if oldAddrFound && oldAddr.String() == newAddr.String() {
   104  				continue
   105  			}
   106  
   107  			// Update the IP mapping now, as if this is the first
   108  			// time then we don't need to send a new announcement.
   109  			ipMapping[host] = newAddr
   110  
   111  			// If this IP has already been announced, then we'll
   112  			// skip it to avoid triggering an unnecessary node
   113  			// announcement update.
   114  			_, ipAnnounced := h.cfg.AdvertisedIPs[newAddr.String()]
   115  			if ipAnnounced {
   116  				continue
   117  			}
   118  
   119  			// If we've reached this point, then the old address
   120  			// was found, and the new address we just looked up
   121  			// differs from the old one.
   122  			log.Debugf("IP change detected! %v: %v -> %v", host,
   123  				oldAddr, newAddr)
   124  
   125  			// If we had already advertised an addr for this host,
   126  			// then we'll need to remove that old stale address.
   127  			if oldAddr != nil {
   128  				addrsToRemove[oldAddr.String()] = struct{}{}
   129  			}
   130  
   131  			addrsToUpdate = append(addrsToUpdate, newAddr)
   132  		}
   133  
   134  		// If we don't have any addresses to update, then we can skip
   135  		// things around until the next round.
   136  		if len(addrsToUpdate) == 0 {
   137  			log.Debugf("No IP changes detected for hosts: %v",
   138  				h.cfg.Hosts)
   139  			return
   140  		}
   141  
   142  		// Now that we know the set of IPs we need to update, we'll do
   143  		// them all in a single batch.
   144  		err := h.cfg.AnnounceNewIPs(addrsToUpdate, addrsToRemove)
   145  		if err != nil {
   146  			log.Warnf("unable to announce new IPs: %v", err)
   147  		}
   148  	}
   149  
   150  	refreshHosts()
   151  
   152  	h.cfg.RefreshTicker.Resume()
   153  
   154  	for {
   155  		select {
   156  		case <-h.cfg.RefreshTicker.Ticks():
   157  			log.Debugf("HostAnnouncer checking for any IP " +
   158  				"changes...")
   159  
   160  			refreshHosts()
   161  
   162  		case <-h.quit:
   163  			return
   164  		}
   165  	}
   166  }
   167  
   168  // NodeAnnUpdater describes a function that's able to update our current node
   169  // announcement on disk. It returns the updated node announcement given a set
   170  // of updates to be applied to the current node announcement.
   171  type NodeAnnUpdater func(refresh bool, modifier ...NodeAnnModifier,
   172  ) (lnwire.NodeAnnouncement, error)
   173  
   174  // IPAnnouncer is a factory function that generates a new function that uses
   175  // the passed annUpdater function to to announce new IP changes for a given
   176  // host.
   177  func IPAnnouncer(annUpdater NodeAnnUpdater) func([]net.Addr, map[string]struct{}) error {
   178  	return func(newAddrs []net.Addr, oldAddrs map[string]struct{}) error {
   179  		_, err := annUpdater(true, func(currentNodeAnn *lnwire.NodeAnnouncement) {
   180  			// To ensure we don't duplicate any addresses, we'll
   181  			// filter out the same of addresses we should no longer
   182  			// advertise.
   183  			filteredAddrs := make(
   184  				[]net.Addr, 0, len(currentNodeAnn.Addresses),
   185  			)
   186  			for _, addr := range currentNodeAnn.Addresses {
   187  				if _, ok := oldAddrs[addr.String()]; ok {
   188  					continue
   189  				}
   190  
   191  				filteredAddrs = append(filteredAddrs, addr)
   192  			}
   193  
   194  			filteredAddrs = append(filteredAddrs, newAddrs...)
   195  			currentNodeAnn.Addresses = filteredAddrs
   196  		})
   197  		return err
   198  	}
   199  }