lnd/nat/upnp.go
Robert Habermann a27ac66eed server: add periodic renewal of port forwarding
The check prevented the creation of port forwardings which were assumed
to be present already. After this change the port forwardings which
might have been removed from the NAT device can be re-created.
2019-04-13 13:49:31 +00:00

109 lines
2.3 KiB
Go

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 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"
}