Merge pull request #1109 from wpaulino/nat-traversal

server: add support for NAT traversal and watching dynamic IP changes
This commit is contained in:
Olaoluwa Osuntokun 2018-06-12 19:13:43 -07:00 committed by GitHub
commit f7156aa1d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 664 additions and 12 deletions

47
Gopkg.lock generated

@ -12,6 +12,25 @@
]
revision = "e06297f34865a50b8e473105e52cb64ad1b55da8"
[[projects]]
branch = "master"
name = "github.com/NebulousLabs/fastrand"
packages = ["."]
revision = "3cf7173006a0b7d2371fa1a220da7f9d48c7827c"
[[projects]]
name = "github.com/NebulousLabs/go-upnp"
packages = [
".",
"goupnp",
"goupnp/dcps/internetgateway1",
"goupnp/httpu",
"goupnp/scpd",
"goupnp/soap",
"goupnp/ssdp"
]
revision = "29b680b06c82d044ebea91bf3069038eb562df2a"
[[projects]]
name = "github.com/Yawning/aez"
packages = ["."]
@ -115,6 +134,17 @@
]
revision = "f2862b476edcef83412c7af8687c9cd8e4097c0f"
[[projects]]
name = "github.com/jackpal/gateway"
packages = ["."]
revision = "3e333950771011fed13be63e62b9f473c5e0d9bf"
version = "v1.0.4"
[[projects]]
name = "github.com/jackpal/go-nat-pmp"
packages = ["."]
revision = "28a68d0c24adce1da43f8df6a57340909ecd7fdd"
[[projects]]
name = "github.com/jessevdk/go-flags"
packages = ["."]
@ -267,6 +297,9 @@
name = "golang.org/x/net"
packages = [
"context",
"html",
"html/atom",
"html/charset",
"http2",
"http2/hpack",
"idna",
@ -291,12 +324,24 @@
packages = [
"collate",
"collate/build",
"encoding",
"encoding/charmap",
"encoding/htmlindex",
"encoding/internal",
"encoding/internal/identifier",
"encoding/japanese",
"encoding/korean",
"encoding/simplifiedchinese",
"encoding/traditionalchinese",
"encoding/unicode",
"internal/colltab",
"internal/gen",
"internal/tag",
"internal/triegen",
"internal/ucd",
"internal/utf8internal",
"language",
"runes",
"secure/bidirule",
"transform",
"unicode/bidi",
@ -359,6 +404,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "2133b0035a81c856475302a127bc26d30217a30d1e41708d3604f2de82e1ab31"
inputs-digest = "dc40dd185a90b723e4b3df4a077b4ad4f99648260661cac9d58121e8bd3474ef"
solver-name = "gps-cdcl"
solver-version = 1

@ -26,6 +26,10 @@
name = "github.com/grpc-ecosystem/grpc-gateway"
revision = "f2862b476edcef83412c7af8687c9cd8e4097c0f"
[[constraint]]
name = "github.com/jackpal/go-nat-pmp"
revision = "28a68d0c24adce1da43f8df6a57340909ecd7fdd"
[[constraint]]
name = "github.com/jessevdk/go-flags"
revision = "f88afde2fa19a30cf50ba4b05b3d13bc6bae3079"
@ -54,6 +58,10 @@
name = "github.com/miekg/dns"
revision = "79bfde677fa81ff8d27c4330c35bda075d360641"
[[constraint]]
name = "github.com/NebulousLabs/go-upnp"
revision = "29b680b06c82d044ebea91bf3069038eb562df2a"
[[constraint]]
name = "github.com/roasbeef/btcutil"
revision = "dfb640c57141f1c2113b92b4b16d2a89c30dd258"

@ -184,6 +184,7 @@ type config struct {
Listeners []string `long:"listen" description:"Add an interface/port to listen for peer connections"`
DisableListen bool `long:"nolisten" description:"Disable listening for incoming peer connections"`
ExternalIPs []string `long:"externalip" description:"Add an ip:port to the list of local addresses we claim to listen on to peers. If a port is not specified, the default (9735) will be used regardless of other parameters"`
NAT bool `long:"nat" description:"Toggle NAT traversal support (using either UPnP or NAT-PMP) to automatically advertise your external IP address to the network -- NOTE this does not support devices behind multiple NATs"`
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"`
@ -455,6 +456,11 @@ func loadConfig() (*config, error) {
}
}
if cfg.DisableListen && cfg.NAT {
return nil, errors.New("NAT traversal cannot be used when " +
"listening is disabled")
}
// Determine the active chain configuration and its parameters.
switch {
// At this moment, multiple active chains are not supported.
@ -772,26 +778,36 @@ func loadConfig() (*config, error) {
return nil, err
}
// Remove all Listeners if listening is disabled.
// Remove the listening addresses specified if listening is disabled.
if cfg.DisableListen {
ltndLog.Infof("Listening on the p2p interface is disabled!")
cfg.Listeners = nil
cfg.ExternalIPs = nil
}
// Add default port to all RPC listener addresses if needed and remove
// duplicate addresses.
cfg.RPCListeners = normalizeAddresses(cfg.RPCListeners,
strconv.Itoa(defaultRPCPort))
cfg.RPCListeners = normalizeAddresses(
cfg.RPCListeners, strconv.Itoa(defaultRPCPort),
)
// Add default port to all REST listener addresses if needed and remove
// duplicate addresses.
cfg.RESTListeners = normalizeAddresses(cfg.RESTListeners,
strconv.Itoa(defaultRESTPort))
cfg.RESTListeners = normalizeAddresses(
cfg.RESTListeners, strconv.Itoa(defaultRESTPort),
)
// Add default port to all listener addresses if needed and remove
// duplicate addresses.
cfg.Listeners = normalizeAddresses(cfg.Listeners,
strconv.Itoa(defaultPeerPort))
cfg.Listeners = normalizeAddresses(
cfg.Listeners, strconv.Itoa(defaultPeerPort),
)
// Add default port to all external IP addresses if needed and remove
// duplicate addresses.
cfg.ExternalIPs = normalizeAddresses(
cfg.ExternalIPs, strconv.Itoa(defaultPeerPort),
)
// Finally, ensure that we are only listening on localhost if Tor
// inbound support is enabled.

23
docs/nat_traversal.md Normal file

@ -0,0 +1,23 @@
# NAT Traversal
`lnd` has support for NAT traversal using a number of different techniques. At
the time of writing this documentation, UPnP and NAT-PMP are supported. NAT
traversal can be enabled through `lnd`'s `--nat` flag.
```shell
$ lnd ... --nat
```
On startup, `lnd` will try the different techniques until one is found that's
supported by your hardware. The underlying dependencies used for these
techniques rely on using system-specific binaries in order to detect your
gateway device's address. This is needed because we need to be able to reach the
gateway device to determine if it supports the specific NAT traversal technique
currently being tried. Because of this, due to uncommon setups, it is possible
that these binaries are not found in your system. If this is case, `lnd` will
exit stating such error.
As a bonus, `lnd` spawns a background thread that automatically detects IP
address changes and propagates the new address update to the rest of the
network. This is especially beneficial for users who were provided dynamic IP
addresses from their internet service provider.

@ -85,6 +85,14 @@ type NodeAnnouncement struct {
Addresses []net.Addr
}
// UpdateNodeAnnAddrs is a functional option that allows updating the addresses
// of the given node announcement.
func UpdateNodeAnnAddrs(addrs []net.Addr) func(*NodeAnnouncement) {
return func(nodeAnn *NodeAnnouncement) {
nodeAnn.Addresses = addrs
}
}
// A compile time check to ensure NodeAnnouncement implements the
// lnwire.Message interface.
var _ Message = (*NodeAnnouncement)(nil)

117
nat/pmp.go Normal file

@ -0,0 +1,117 @@
package nat
import (
"fmt"
"net"
"sync"
"time"
"github.com/jackpal/gateway"
natpmp "github.com/jackpal/go-nat-pmp"
)
// Compile-time check to ensure PMP implements the Traversal interface.
var _ Traversal = (*PMP)(nil)
// PMP is a concrete implementation of the Traversal interface that uses the
// NAT-PMP technique.
type PMP struct {
client *natpmp.Client
forwardedPortsMtx sync.Mutex
forwardedPorts map[uint16]struct{}
}
// DiscoverPMP attempts to scan the local network for a NAT-PMP enabled device
// within the given timeout.
func DiscoverPMP(timeout time.Duration) (*PMP, error) {
// Retrieve the gateway IP address of the local network.
gatewayIP, err := gateway.DiscoverGateway()
if err != nil {
return nil, err
}
pmp := &PMP{
client: natpmp.NewClientWithTimeout(gatewayIP, timeout),
forwardedPorts: make(map[uint16]struct{}),
}
// We'll then attempt to retrieve the external IP address of this
// device to ensure it is not behind multiple NATs.
if _, err := pmp.ExternalIP(); err != nil {
return nil, err
}
return pmp, nil
}
// ExternalIP returns the external IP address of the NAT-PMP enabled device.
func (p *PMP) ExternalIP() (net.IP, error) {
res, err := p.client.GetExternalAddress()
if err != nil {
return nil, err
}
ip := net.IP(res.ExternalIPAddress[:])
if isPrivateIP(ip) {
return nil, ErrMultipleNAT
}
return ip, nil
}
// AddPortMapping enables port forwarding for the given port.
func (p *PMP) AddPortMapping(port uint16) error {
p.forwardedPortsMtx.Lock()
defer p.forwardedPortsMtx.Unlock()
if _, exists := p.forwardedPorts[port]; exists {
return nil
}
_, err := p.client.AddPortMapping("tcp", int(port), int(port), 0)
if err != nil {
return err
}
p.forwardedPorts[port] = struct{}{}
return nil
}
// DeletePortMapping disables port forwarding for the given port.
func (p *PMP) DeletePortMapping(port uint16) error {
p.forwardedPortsMtx.Lock()
defer p.forwardedPortsMtx.Unlock()
if _, exists := p.forwardedPorts[port]; !exists {
return fmt.Errorf("port %d is not being forwarded", port)
}
_, err := p.client.AddPortMapping("tcp", int(port), 0, 0)
if err != nil {
return err
}
delete(p.forwardedPorts, port)
return nil
}
// ForwardedPorts returns a list of ports currently being forwarded.
func (p *PMP) ForwardedPorts() []uint16 {
p.forwardedPortsMtx.Lock()
defer p.forwardedPortsMtx.Unlock()
ports := make([]uint16, 0, len(p.forwardedPorts))
for port := range p.forwardedPorts {
ports = append(ports, port)
}
return ports
}
// Name returns the name of the specific NAT traversal technique used.
func (p *PMP) Name() string {
return "NAT-PMP"
}

58
nat/traversal.go Normal file

@ -0,0 +1,58 @@
package nat
import (
"errors"
"net"
)
var (
// private24BitBlock contains the set of private IPv4 addresses within
// the 10.0.0.0/8 adddress space.
private24BitBlock *net.IPNet
// private20BitBlock contains the set of private IPv4 addresses within
// the 172.16.0.0/12 address space.
private20BitBlock *net.IPNet
// private16BitBlock contains the set of private IPv4 addresses within
// the 192.168.0.0/16 address space.
private16BitBlock *net.IPNet
// ErrMultipleNAT is an error returned when multiple NATs have been
// detected.
ErrMultipleNAT = errors.New("multiple NATs detected")
)
func init() {
_, private24BitBlock, _ = net.ParseCIDR("10.0.0.0/8")
_, private20BitBlock, _ = net.ParseCIDR("172.16.0.0/12")
_, private16BitBlock, _ = net.ParseCIDR("192.168.0.0/16")
}
// Traversal is an interface that brings together the different NAT traversal
// techniques.
type Traversal interface {
// ExternalIP returns the external IP address.
ExternalIP() (net.IP, error)
// AddPortMapping adds a port mapping for the given port between the
// private and public addresses.
AddPortMapping(port uint16) error
// DeletePortMapping deletes a port mapping for the given port between
// the private and public addresses.
DeletePortMapping(port uint16) error
// ForwardedPorts returns the ports currently being forwarded using NAT
// traversal.
ForwardedPorts() []uint16
// Name returns the name of the specific NAT traversal technique used.
Name() string
}
// isPrivateIP determines if the IP is private.
func isPrivateIP(ip net.IP) bool {
return private24BitBlock.Contains(ip) ||
private20BitBlock.Contains(ip) || private16BitBlock.Contains(ip)
}

112
nat/upnp.go Normal file

@ -0,0 +1,112 @@
package nat
import (
"context"
"fmt"
"net"
"sync"
upnp "github.com/NebulousLabs/go-upnp"
)
// Compile-time check to ensure UPnP implements the Traversal interface.
var _ Traversal = (*UPnP)(nil)
// UPnP is a concrete implementation of the Traversal interface that uses the
// UPnP technique.
type UPnP struct {
device *upnp.IGD
forwardedPortsMtx sync.Mutex
forwardedPorts map[uint16]struct{}
}
// DiscoverUPnP scans the local network for a UPnP enabled device.
func DiscoverUPnP(ctx context.Context) (*UPnP, error) {
// Scan the local network for a UPnP-enabled device.
device, err := upnp.DiscoverCtx(ctx)
if err != nil {
return nil, err
}
u := &UPnP{
device: device,
forwardedPorts: make(map[uint16]struct{}),
}
// We'll then attempt to retrieve the external IP address of this
// device to ensure it is not behind multiple NATs.
if _, err := u.ExternalIP(); err != nil {
return nil, err
}
return u, nil
}
// ExternalIP returns the external IP address of the UPnP enabled device.
func (u *UPnP) ExternalIP() (net.IP, error) {
ip, err := u.device.ExternalIP()
if err != nil {
return nil, err
}
if isPrivateIP(net.ParseIP(ip)) {
return nil, ErrMultipleNAT
}
return net.ParseIP(ip), nil
}
// AddPortMapping enables port forwarding for the given port.
func (u *UPnP) AddPortMapping(port uint16) error {
u.forwardedPortsMtx.Lock()
defer u.forwardedPortsMtx.Unlock()
if _, exists := u.forwardedPorts[port]; exists {
return nil
}
if err := u.device.Forward(port, ""); err != nil {
return err
}
u.forwardedPorts[port] = struct{}{}
return nil
}
// DeletePortMapping disables port forwarding for the given port.
func (u *UPnP) DeletePortMapping(port uint16) error {
u.forwardedPortsMtx.Lock()
defer u.forwardedPortsMtx.Unlock()
if _, exists := u.forwardedPorts[port]; !exists {
return fmt.Errorf("port %d is not being forwarded", port)
}
if err := u.device.Clear(port); err != nil {
return err
}
delete(u.forwardedPorts, port)
return nil
}
// ForwardedPorts returns a list of ports currently being forwarded.
func (u *UPnP) ForwardedPorts() []uint16 {
u.forwardedPortsMtx.Lock()
defer u.forwardedPortsMtx.Unlock()
ports := make([]uint16, 0, len(u.forwardedPorts))
for port := range u.forwardedPorts {
ports = append(ports, port)
}
return ports
}
// Name returns the name of the specific NAT traversal technique used.
func (u *UPnP) Name() string {
return "UPnP"
}

@ -77,6 +77,18 @@
; (with host:port notation), the default port (9735) will be added to the
; address.
; externalip=
;
; Instead of explicitly stating your external IP address, you can also enable
; UPnP or NAT-PMP support on the daemon. Both techniques will be tried and
; require proper hardware support. In order to detect this hardware support,
; `lnd` uses a dependency that retrieves the router's gateway address by using
; different built-in binaries in each platform. Therefore, it is possible that
; we are unable to detect the hardware and `lnd` will exit with an error
; indicating this. This option will automatically retrieve your external IP
; address, even after it has changed in the case of dynamic IPs, and advertise
; it to the network using the ports the daemon is listening on. This does not
; support devices behind multiple NATs.
; nat=true
; Debug logging level.

261
server.go

@ -28,6 +28,7 @@ import (
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/nat"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/tor"
"github.com/roasbeef/btcd/btcec"
@ -35,6 +36,7 @@ import (
"github.com/roasbeef/btcd/connmgr"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
"golang.org/x/net/context"
)
var (
@ -85,6 +87,16 @@ type server struct {
// creating and setting up onion services, etc.
torController *tor.Controller
// natTraversal is the specific NAT traversal technique used to
// automatically set up port forwarding rules in order to advertise to
// the network that the node is accepting inbound connections.
natTraversal nat.Traversal
// lastDetectedIP is the last IP detected by the NAT traversal technique
// above. This IP will be watched periodically in a goroutine in order
// to handle dynamic IP changes.
lastDetectedIP net.IP
mu sync.RWMutex
peersByPub map[string]*peer
@ -300,9 +312,77 @@ func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl,
return nil, err
}
// If enabled, use either UPnP or NAT-PMP to automatically configure
// port forwarding for users behind a NAT.
if cfg.NAT {
srvrLog.Info("Scanning local network for a UPnP enabled device")
discoveryTimeout := time.Duration(10 * time.Second)
ctx, cancel := context.WithTimeout(
context.Background(), discoveryTimeout,
)
defer cancel()
upnp, err := nat.DiscoverUPnP(ctx)
if err == nil {
s.natTraversal = upnp
} else {
// If we were not able to discover a UPnP enabled device
// on the local network, we'll fall back to attempting
// to discover a NAT-PMP enabled device.
srvrLog.Errorf("Unable to discover a UPnP enabled "+
"device on the local network: %v", err)
srvrLog.Info("Scanning local network for a NAT-PMP " +
"enabled device")
pmp, err := nat.DiscoverPMP(discoveryTimeout)
if err != nil {
err := fmt.Errorf("Unable to discover a "+
"NAT-PMP enabled device on the local "+
"network: %v", err)
srvrLog.Error(err)
return nil, err
}
s.natTraversal = pmp
}
}
// If we were requested to automatically configure port forwarding,
// we'll use the ports that the server will be listening on.
externalIPs := cfg.ExternalIPs
if s.natTraversal != nil {
listenPorts := make([]uint16, 0, len(listenAddrs))
for _, listenAddr := range listenAddrs {
// At this point, the listen addresses should have
// already been normalized, so it's safe to ignore the
// errors.
_, portStr, _ := net.SplitHostPort(listenAddr)
port, _ := strconv.Atoi(portStr)
listenPorts = append(listenPorts, uint16(port))
}
ips, err := s.configurePortForwarding(listenPorts...)
if err != nil {
srvrLog.Errorf("Unable to automatically set up port "+
"forwarding using %s: %v",
s.natTraversal.Name(), err)
} else {
srvrLog.Infof("Automatically set up port forwarding "+
"using %s to advertise external IP",
s.natTraversal.Name())
externalIPs = append(externalIPs, ips...)
}
}
// If external IP addresses have been specified, add those to the list
// of this server's addresses.
selfAddrs := make([]net.Addr, 0, len(cfg.ExternalIPs))
externalIPs = normalizeAddresses(
externalIPs, strconv.Itoa(defaultPeerPort),
)
selfAddrs := make([]net.Addr, 0, len(externalIPs))
for _, ip := range cfg.ExternalIPs {
addr, err := parseAddr(ip)
if err != nil {
@ -620,6 +700,11 @@ func (s *server) Start() error {
}
}
if s.natTraversal != nil {
s.wg.Add(1)
go s.watchExternalIP()
}
// Start the notification server. This is used so channel management
// goroutines can be notified when a funding transaction reaches a
// sufficient number of confirmations, or when the input for the
@ -727,6 +812,172 @@ func (s *server) Stopped() bool {
return atomic.LoadInt32(&s.shutdown) != 0
}
// configurePortForwarding attempts to set up port forwarding for the diffrent
// ports that the server will be listening on.
//
// NOTE: This should only be used when using some kind of NAT traversal to
// automatically set up forwarding rules.
func (s *server) configurePortForwarding(ports ...uint16) ([]string, error) {
ip, err := s.natTraversal.ExternalIP()
if err != nil {
return nil, err
}
s.lastDetectedIP = ip
externalIPs := make([]string, 0, len(ports))
for _, port := range ports {
if err := s.natTraversal.AddPortMapping(port); err != nil {
srvrLog.Debugf("Unable to forward port %d: %v", port, err)
continue
}
hostIP := fmt.Sprintf("%v:%d", ip, port)
externalIPs = append(externalIPs, hostIP)
}
return externalIPs, nil
}
// removePortForwarding attempts to clear the forwarding rules for the different
// ports the server is currently listening on.
//
// NOTE: This should only be used when using some kind of NAT traversal to
// automatically set up forwarding rules.
func (s *server) removePortForwarding() {
forwardedPorts := s.natTraversal.ForwardedPorts()
for _, port := range forwardedPorts {
if err := s.natTraversal.DeletePortMapping(port); err != nil {
srvrLog.Errorf("Unable to remove forwarding rules for "+
"port %d: %v", port, err)
}
}
}
// watchExternalIP continously checks for an updated external IP address every
// 15 minutes. Once a new IP address has been detected, it will automatically
// handle port forwarding rules and send updated node announcements to the
// currently connected peers.
//
// NOTE: This MUST be run as a goroutine.
func (s *server) watchExternalIP() {
defer s.wg.Done()
// Before exiting, we'll make sure to remove the forwarding rules set
// up by the server.
defer s.removePortForwarding()
// Keep track of the external IPs set by the user to avoid replacing
// them when detecting a new IP.
ipsSetByUser := make(map[string]struct{})
for _, ip := range cfg.ExternalIPs {
ipsSetByUser[ip] = struct{}{}
}
forwardedPorts := s.natTraversal.ForwardedPorts()
ticker := time.NewTicker(15 * time.Minute)
defer ticker.Stop()
out:
for {
select {
case <-ticker.C:
// We'll start off by making sure a new IP address has
// been detected.
ip, err := s.natTraversal.ExternalIP()
if err != nil {
srvrLog.Debugf("Unable to retrieve the "+
"external IP address: %v", err)
continue
}
if ip.Equal(s.lastDetectedIP) {
continue
}
srvrLog.Infof("Detected new external IP address %s", ip)
// Next, we'll craft the new addresses that will be
// included in the new node announcement and advertised
// to the network. Each address will consist of the new
// IP detected and one of the currently advertised
// ports.
var newAddrs []net.Addr
for _, port := range forwardedPorts {
hostIP := fmt.Sprintf("%v:%d", ip, port)
addr, err := net.ResolveTCPAddr("tcp", hostIP)
if err != nil {
srvrLog.Debugf("Unable to resolve "+
"host %v: %v", addr, err)
continue
}
newAddrs = append(newAddrs, addr)
}
// Skip the update if we weren't able to resolve any of
// the new addresses.
if len(newAddrs) == 0 {
srvrLog.Debug("Skipping node announcement " +
"update due to not being able to " +
"resolve any new addresses")
continue
}
// Now, we'll need to update the addresses in our node's
// announcement in order to propogate the update
// throughout the network. We'll only include addresses
// that have a different IP from the previous one, as
// the previous IP is no longer valid.
currentNodeAnn, err := s.genNodeAnnouncement(false)
if err != nil {
srvrLog.Debugf("Unable to retrieve current "+
"node announcement: %v", err)
continue
}
for _, addr := range currentNodeAnn.Addresses {
host, _, err := net.SplitHostPort(addr.String())
if err != nil {
srvrLog.Debugf("Unable to determine "+
"host from address %v: %v",
addr, err)
continue
}
// We'll also make sure to include external IPs
// set manually by the user.
_, setByUser := ipsSetByUser[addr.String()]
if setByUser || host != s.lastDetectedIP.String() {
newAddrs = append(newAddrs, addr)
}
}
// Then, we'll generate a new timestamped node
// announcement with the updated addresses and broadcast
// it to our peers.
newNodeAnn, err := s.genNodeAnnouncement(
true, lnwire.UpdateNodeAnnAddrs(newAddrs),
)
if err != nil {
srvrLog.Debugf("Unable to generate new node "+
"announcement: %v", err)
continue
}
err = s.BroadcastMessage(nil, &newNodeAnn)
if err != nil {
srvrLog.Debugf("Unable to broadcast new node "+
"announcement to peers: %v", err)
continue
}
// Finally, update the last IP seen to the current one.
s.lastDetectedIP = ip
case <-s.quit:
break out
}
}
}
// initNetworkBootstrappers initializes a set of network peer bootstrappers
// based on the server, and currently active bootstrap mechanisms as defined
// within the current configuration.
@ -964,8 +1215,8 @@ func (s *server) initTorController() error {
// genNodeAnnouncement generates and returns the current fully signed node
// announcement. If refresh is true, then the time stamp of the announcement
// will be updated in order to ensure it propagates through the network.
func (s *server) genNodeAnnouncement(
refresh bool) (lnwire.NodeAnnouncement, error) {
func (s *server) genNodeAnnouncement(refresh bool,
updates ...func(*lnwire.NodeAnnouncement)) (lnwire.NodeAnnouncement, error) {
s.mu.Lock()
defer s.mu.Unlock()
@ -974,7 +1225,9 @@ func (s *server) genNodeAnnouncement(
return *s.currentNodeAnn, nil
}
var err error
for _, update := range updates {
update(s.currentNodeAnn)
}
newStamp := uint32(time.Now().Unix())
if newStamp <= s.currentNodeAnn.Timestamp {