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 }