github.com/decred/dcrlnd@v0.7.6/nat/upnp.go (about)

     1  package nat
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"sync"
     8  
     9  	upnp "github.com/NebulousLabs/go-upnp"
    10  )
    11  
    12  // Compile-time check to ensure UPnP implements the Traversal interface.
    13  var _ Traversal = (*UPnP)(nil)
    14  
    15  // UPnP is a concrete implementation of the Traversal interface that uses the
    16  // UPnP technique.
    17  type UPnP struct {
    18  	device *upnp.IGD
    19  
    20  	forwardedPortsMtx sync.Mutex
    21  	forwardedPorts    map[uint16]struct{}
    22  }
    23  
    24  // DiscoverUPnP scans the local network for a UPnP enabled device.
    25  func DiscoverUPnP(ctx context.Context) (*UPnP, error) {
    26  	// Scan the local network for a UPnP-enabled device.
    27  	device, err := upnp.DiscoverCtx(ctx)
    28  	if err != nil {
    29  		return nil, err
    30  	}
    31  
    32  	u := &UPnP{
    33  		device:         device,
    34  		forwardedPorts: make(map[uint16]struct{}),
    35  	}
    36  
    37  	// We'll then attempt to retrieve the external IP address of this
    38  	// device to ensure it is not behind multiple NATs.
    39  	if _, err := u.ExternalIP(); err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	return u, nil
    44  }
    45  
    46  // ExternalIP returns the external IP address of the UPnP enabled device.
    47  func (u *UPnP) ExternalIP() (net.IP, error) {
    48  	ip, err := u.device.ExternalIP()
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	if isPrivateIP(net.ParseIP(ip)) {
    54  		return nil, ErrMultipleNAT
    55  	}
    56  
    57  	return net.ParseIP(ip), nil
    58  }
    59  
    60  // AddPortMapping enables port forwarding for the given port.
    61  func (u *UPnP) AddPortMapping(port uint16) error {
    62  	u.forwardedPortsMtx.Lock()
    63  	defer u.forwardedPortsMtx.Unlock()
    64  
    65  	if err := u.device.Forward(port, ""); err != nil {
    66  		return err
    67  	}
    68  
    69  	u.forwardedPorts[port] = struct{}{}
    70  
    71  	return nil
    72  }
    73  
    74  // DeletePortMapping disables port forwarding for the given port.
    75  func (u *UPnP) DeletePortMapping(port uint16) error {
    76  	u.forwardedPortsMtx.Lock()
    77  	defer u.forwardedPortsMtx.Unlock()
    78  
    79  	if _, exists := u.forwardedPorts[port]; !exists {
    80  		return fmt.Errorf("port %d is not being forwarded", port)
    81  	}
    82  
    83  	if err := u.device.Clear(port); err != nil {
    84  		return err
    85  	}
    86  
    87  	delete(u.forwardedPorts, port)
    88  
    89  	return nil
    90  }
    91  
    92  // ForwardedPorts returns a list of ports currently being forwarded.
    93  func (u *UPnP) ForwardedPorts() []uint16 {
    94  	u.forwardedPortsMtx.Lock()
    95  	defer u.forwardedPortsMtx.Unlock()
    96  
    97  	ports := make([]uint16, 0, len(u.forwardedPorts))
    98  	for port := range u.forwardedPorts {
    99  		ports = append(ports, port)
   100  	}
   101  
   102  	return ports
   103  }
   104  
   105  // Name returns the name of the specific NAT traversal technique used.
   106  func (u *UPnP) Name() string {
   107  	return "UPnP"
   108  }