2020-04-16 08:39:20 -07:00
|
|
|
package nat
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/huin/goupnp"
|
|
|
|
"github.com/huin/goupnp/dcps/internetgateway1"
|
|
|
|
"github.com/huin/goupnp/dcps/internetgateway2"
|
|
|
|
)
|
|
|
|
|
2020-06-12 07:07:17 -07:00
|
|
|
const (
|
|
|
|
soapRequestTimeout = 3 * time.Second
|
|
|
|
mapRetry = 20
|
|
|
|
)
|
2020-04-16 08:39:20 -07:00
|
|
|
|
|
|
|
// upnpClient is the interface used by goupnp for their client implementations
|
|
|
|
type upnpClient interface {
|
|
|
|
// attempts to map connection using the provided protocol from the external
|
|
|
|
// port to the internal port for the lease duration.
|
|
|
|
AddPortMapping(
|
|
|
|
newRemoteHost string,
|
|
|
|
newExternalPort uint16,
|
|
|
|
newProtocol string,
|
|
|
|
newInternalPort uint16,
|
|
|
|
newInternalClient string,
|
|
|
|
newEnabled bool,
|
|
|
|
newPortMappingDescription string,
|
|
|
|
newLeaseDuration uint32) error
|
|
|
|
|
|
|
|
// attempt to remove any mapping from the external port.
|
|
|
|
DeletePortMapping(
|
|
|
|
newRemoteHost string,
|
|
|
|
newExternalPort uint16,
|
|
|
|
newProtocol string) error
|
|
|
|
|
|
|
|
// attempts to return the external IP address, formatted as a string.
|
|
|
|
GetExternalIPAddress() (ip string, err error)
|
|
|
|
|
|
|
|
// returns if there is rsip available, nat enabled, or an unexpected error.
|
|
|
|
GetNATRSIPStatus() (newRSIPAvailable bool, natEnabled bool, err error)
|
|
|
|
}
|
|
|
|
|
|
|
|
type upnpRouter struct {
|
2020-06-07 17:02:29 -07:00
|
|
|
dev *goupnp.RootDevice
|
2020-04-16 08:39:20 -07:00
|
|
|
client upnpClient
|
|
|
|
}
|
|
|
|
|
2020-06-07 17:02:29 -07:00
|
|
|
func (r *upnpRouter) localIP() (net.IP, error) {
|
2020-04-16 08:39:20 -07:00
|
|
|
// attempt to get an address on the router
|
2020-06-07 17:02:29 -07:00
|
|
|
deviceAddr, err := net.ResolveUDPAddr("udp4", r.dev.URLBase.Host)
|
2020-04-16 08:39:20 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
deviceIP := deviceAddr.IP
|
|
|
|
|
|
|
|
netInterfaces, err := net.Interfaces()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// attempt to find one of my ips that the router would know about
|
|
|
|
for _, netInterface := range netInterfaces {
|
|
|
|
addrs, err := netInterface.Addrs()
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, addr := range addrs {
|
|
|
|
// this is pretty janky, but it seems to be the best way to get the
|
|
|
|
// ip mask and properly check if the ip references the device we are
|
|
|
|
// connected to
|
|
|
|
ipNet, ok := addr.(*net.IPNet)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if ipNet.Contains(deviceIP) {
|
|
|
|
return ipNet.IP, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("couldn't find the local address in the same network as %s", deviceIP)
|
|
|
|
}
|
|
|
|
|
2020-06-07 17:02:29 -07:00
|
|
|
func (r *upnpRouter) ExternalIP() (net.IP, error) {
|
|
|
|
str, err := r.client.GetExternalIPAddress()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2020-04-16 08:39:20 -07:00
|
|
|
}
|
2020-06-07 17:02:29 -07:00
|
|
|
|
|
|
|
ip := net.ParseIP(str)
|
|
|
|
if ip == nil {
|
|
|
|
return nil, fmt.Errorf("invalid IP %s", str)
|
|
|
|
}
|
|
|
|
return ip, nil
|
|
|
|
}
|
|
|
|
|
2020-06-12 07:07:17 -07:00
|
|
|
func (r *upnpRouter) MapPort(protocol string, intport, extport uint16,
|
|
|
|
desc string, duration time.Duration) error {
|
2020-06-07 17:02:29 -07:00
|
|
|
ip, err := r.localIP()
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
lifetime := uint32(duration / time.Second)
|
2020-06-12 07:07:17 -07:00
|
|
|
|
|
|
|
for i := 0; i < mapRetry; i++ {
|
|
|
|
externalPort := extport + uint16(i)
|
|
|
|
err = r.client.AddPortMapping("", externalPort, protocol, intport,
|
|
|
|
ip.String(), true, desc, lifetime)
|
|
|
|
if err == nil {
|
|
|
|
fmt.Printf("Mapped external port %d to local %s:%d\n", externalPort,
|
|
|
|
ip.String(), intport)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
fmt.Printf("Unable to map port, retry with port %d\n", externalPort+1)
|
|
|
|
}
|
|
|
|
return err
|
2020-06-07 17:02:29 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *upnpRouter) UnmapPort(protocol string, extport uint16) error {
|
|
|
|
return r.client.DeletePortMapping("", extport, protocol)
|
2020-04-16 08:39:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func gateway1(client goupnp.ServiceClient) upnpClient {
|
|
|
|
switch client.Service.ServiceType {
|
|
|
|
case internetgateway1.URN_WANIPConnection_1:
|
|
|
|
return &internetgateway1.WANIPConnection1{ServiceClient: client}
|
|
|
|
case internetgateway1.URN_WANPPPConnection_1:
|
|
|
|
return &internetgateway1.WANPPPConnection1{ServiceClient: client}
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func gateway2(client goupnp.ServiceClient) upnpClient {
|
|
|
|
switch client.Service.ServiceType {
|
|
|
|
case internetgateway2.URN_WANIPConnection_1:
|
|
|
|
return &internetgateway2.WANIPConnection1{ServiceClient: client}
|
|
|
|
case internetgateway2.URN_WANIPConnection_2:
|
|
|
|
return &internetgateway2.WANIPConnection2{ServiceClient: client}
|
|
|
|
case internetgateway2.URN_WANPPPConnection_1:
|
|
|
|
return &internetgateway2.WANPPPConnection1{ServiceClient: client}
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-07 17:02:29 -07:00
|
|
|
func getUpnpClient(client goupnp.ServiceClient) upnpClient {
|
|
|
|
c := gateway1(client)
|
|
|
|
if c != nil {
|
|
|
|
return c
|
2020-04-16 08:39:20 -07:00
|
|
|
}
|
2020-06-07 17:02:29 -07:00
|
|
|
return gateway2(client)
|
2020-04-16 08:39:20 -07:00
|
|
|
}
|
|
|
|
|
2020-06-07 17:02:29 -07:00
|
|
|
func getRootDevice(dev *goupnp.MaybeRootDevice) *upnpRouter {
|
|
|
|
var router *upnpRouter
|
|
|
|
dev.Root.Device.VisitServices(func(service *goupnp.Service) {
|
|
|
|
c := goupnp.ServiceClient{
|
|
|
|
SOAPClient: service.NewSOAPClient(),
|
|
|
|
RootDevice: dev.Root,
|
|
|
|
Location: dev.Location,
|
2020-04-16 08:39:20 -07:00
|
|
|
Service: service,
|
|
|
|
}
|
2020-06-07 17:02:29 -07:00
|
|
|
c.SOAPClient.HTTPClient.Timeout = soapRequestTimeout
|
|
|
|
client := getUpnpClient(c)
|
2020-04-16 08:39:20 -07:00
|
|
|
if client == nil {
|
2020-06-07 17:02:29 -07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
router = &upnpRouter{dev.Root, client}
|
|
|
|
if router == nil {
|
|
|
|
return
|
2020-04-16 08:39:20 -07:00
|
|
|
}
|
2020-06-07 17:02:29 -07:00
|
|
|
if _, nat, err := router.client.GetNATRSIPStatus(); err != nil || !nat {
|
|
|
|
router = nil
|
|
|
|
return
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return router
|
|
|
|
}
|
2020-04-16 08:39:20 -07:00
|
|
|
|
2020-06-07 17:02:29 -07:00
|
|
|
func discover(target string) *upnpRouter {
|
|
|
|
devs, err := goupnp.DiscoverDevices(target)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
for i := 0; i < len(devs); i++ {
|
|
|
|
if devs[i].Root == nil {
|
2020-04-16 08:39:20 -07:00
|
|
|
continue
|
|
|
|
}
|
2020-06-07 17:02:29 -07:00
|
|
|
u := getRootDevice(&devs[i])
|
|
|
|
if u != nil {
|
|
|
|
return u
|
2020-04-16 08:39:20 -07:00
|
|
|
}
|
|
|
|
}
|
2020-06-07 17:02:29 -07:00
|
|
|
return gateway2(client)
|
2020-04-16 08:39:20 -07:00
|
|
|
}
|
2020-06-07 17:02:29 -07:00
|
|
|
|
|
|
|
func getUPnPRouter() *upnpRouter {
|
2020-06-12 07:07:17 -07:00
|
|
|
targets := []string{
|
|
|
|
internetgateway1.URN_WANConnectionDevice_1,
|
|
|
|
internetgateway2.URN_WANConnectionDevice_2,
|
2020-06-07 17:02:29 -07:00
|
|
|
}
|
|
|
|
|
2020-06-12 07:07:17 -07:00
|
|
|
routers := make(chan *upnpRouter, len(targets))
|
|
|
|
|
|
|
|
for _, urn := range targets {
|
|
|
|
go func(urn string) {
|
|
|
|
routers <- discover(urn)
|
|
|
|
}(urn)
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < len(targets); i++ {
|
|
|
|
if r := <-routers; r != nil {
|
|
|
|
return r
|
|
|
|
}
|
2020-06-07 17:02:29 -07:00
|
|
|
}
|
2020-06-12 07:07:17 -07:00
|
|
|
|
|
|
|
return nil
|
2020-06-07 17:02:29 -07:00
|
|
|
}
|
2020-06-07 17:02:29 -07:00
|
|
|
|
|
|
|
func getUPnPRouter() *upnpRouter {
|
2020-06-12 07:07:17 -07:00
|
|
|
targets := []string{
|
|
|
|
internetgateway1.URN_WANConnectionDevice_1,
|
|
|
|
internetgateway2.URN_WANConnectionDevice_2,
|
2020-06-07 17:02:29 -07:00
|
|
|
}
|
|
|
|
|
2020-06-12 07:07:17 -07:00
|
|
|
routers := make(chan *upnpRouter, len(targets))
|
|
|
|
|
|
|
|
for _, urn := range targets {
|
|
|
|
go func(urn string) {
|
|
|
|
routers <- discover(urn)
|
|
|
|
}(urn)
|
2020-06-07 17:02:29 -07:00
|
|
|
}
|
2020-06-12 07:07:17 -07:00
|
|
|
|
|
|
|
for i := 0; i < len(targets); i++ {
|
|
|
|
if r := <-routers; r != nil {
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2020-06-07 17:02:29 -07:00
|
|
|
}
|