a27ac66eed
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.
109 lines
2.3 KiB
Go
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"
|
|
}
|