Merge pull request #1109 from wpaulino/nat-traversal
server: add support for NAT traversal and watching dynamic IP changes
This commit is contained in:
commit
f7156aa1d6
47
Gopkg.lock
generated
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"
|
||||
|
30
config.go
30
config.go
@ -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
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
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
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
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
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 {
|
||||
|
Loading…
Reference in New Issue
Block a user