github.com/neatlab/neatio@v1.7.3-0.20220425043230-d903e92fcc75/network/p2p/nat/natupnp.go (about)

     1  package nat
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/huin/goupnp"
    11  	"github.com/huin/goupnp/dcps/internetgateway1"
    12  	"github.com/huin/goupnp/dcps/internetgateway2"
    13  )
    14  
    15  const soapRequestTimeout = 3 * time.Second
    16  
    17  type upnp struct {
    18  	dev     *goupnp.RootDevice
    19  	service string
    20  	client  upnpClient
    21  }
    22  
    23  type upnpClient interface {
    24  	GetExternalIPAddress() (string, error)
    25  	AddPortMapping(string, uint16, string, uint16, string, bool, string, uint32) error
    26  	DeletePortMapping(string, uint16, string) error
    27  	GetNATRSIPStatus() (sip bool, nat bool, err error)
    28  }
    29  
    30  func (n *upnp) ExternalIP() (addr net.IP, err error) {
    31  	ipString, err := n.client.GetExternalIPAddress()
    32  	if err != nil {
    33  		return nil, err
    34  	}
    35  	ip := net.ParseIP(ipString)
    36  	if ip == nil {
    37  		return nil, errors.New("bad IP in response")
    38  	}
    39  	return ip, nil
    40  }
    41  
    42  func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) error {
    43  	ip, err := n.internalAddress()
    44  	if err != nil {
    45  		return nil
    46  	}
    47  	protocol = strings.ToUpper(protocol)
    48  	lifetimeS := uint32(lifetime / time.Second)
    49  	n.DeleteMapping(protocol, extport, intport)
    50  	return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
    51  }
    52  
    53  func (n *upnp) internalAddress() (net.IP, error) {
    54  	devaddr, err := net.ResolveUDPAddr("udp4", n.dev.URLBase.Host)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  	ifaces, err := net.Interfaces()
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	for _, iface := range ifaces {
    63  		addrs, err := iface.Addrs()
    64  		if err != nil {
    65  			return nil, err
    66  		}
    67  		for _, addr := range addrs {
    68  			switch x := addr.(type) {
    69  			case *net.IPNet:
    70  				if x.Contains(devaddr.IP) {
    71  					return x.IP, nil
    72  				}
    73  			}
    74  		}
    75  	}
    76  	return nil, fmt.Errorf("could not find local address in same net as %v", devaddr)
    77  }
    78  
    79  func (n *upnp) DeleteMapping(protocol string, extport, intport int) error {
    80  	return n.client.DeletePortMapping("", uint16(extport), strings.ToUpper(protocol))
    81  }
    82  
    83  func (n *upnp) String() string {
    84  	return "UPNP " + n.service
    85  }
    86  
    87  func discoverUPnP() Interface {
    88  	found := make(chan *upnp, 2)
    89  
    90  	go discover(found, internetgateway1.URN_WANConnectionDevice_1, func(dev *goupnp.RootDevice, sc goupnp.ServiceClient) *upnp {
    91  		switch sc.Service.ServiceType {
    92  		case internetgateway1.URN_WANIPConnection_1:
    93  			return &upnp{dev, "IGDv1-IP1", &internetgateway1.WANIPConnection1{ServiceClient: sc}}
    94  		case internetgateway1.URN_WANPPPConnection_1:
    95  			return &upnp{dev, "IGDv1-PPP1", &internetgateway1.WANPPPConnection1{ServiceClient: sc}}
    96  		}
    97  		return nil
    98  	})
    99  
   100  	go discover(found, internetgateway2.URN_WANConnectionDevice_2, func(dev *goupnp.RootDevice, sc goupnp.ServiceClient) *upnp {
   101  		switch sc.Service.ServiceType {
   102  		case internetgateway2.URN_WANIPConnection_1:
   103  			return &upnp{dev, "IGDv2-IP1", &internetgateway2.WANIPConnection1{ServiceClient: sc}}
   104  		case internetgateway2.URN_WANIPConnection_2:
   105  			return &upnp{dev, "IGDv2-IP2", &internetgateway2.WANIPConnection2{ServiceClient: sc}}
   106  		case internetgateway2.URN_WANPPPConnection_1:
   107  			return &upnp{dev, "IGDv2-PPP1", &internetgateway2.WANPPPConnection1{ServiceClient: sc}}
   108  		}
   109  		return nil
   110  	})
   111  	for i := 0; i < cap(found); i++ {
   112  		if c := <-found; c != nil {
   113  			return c
   114  		}
   115  	}
   116  	return nil
   117  }
   118  
   119  func discover(out chan<- *upnp, target string, matcher func(*goupnp.RootDevice, goupnp.ServiceClient) *upnp) {
   120  	devs, err := goupnp.DiscoverDevices(target)
   121  	if err != nil {
   122  		out <- nil
   123  		return
   124  	}
   125  	found := false
   126  	for i := 0; i < len(devs) && !found; i++ {
   127  		if devs[i].Root == nil {
   128  			continue
   129  		}
   130  		devs[i].Root.Device.VisitServices(func(service *goupnp.Service) {
   131  			if found {
   132  				return
   133  			}
   134  
   135  			sc := goupnp.ServiceClient{
   136  				SOAPClient: service.NewSOAPClient(),
   137  				RootDevice: devs[i].Root,
   138  				Location:   devs[i].Location,
   139  				Service:    service,
   140  			}
   141  			sc.SOAPClient.HTTPClient.Timeout = soapRequestTimeout
   142  			upnp := matcher(devs[i].Root, sc)
   143  			if upnp == nil {
   144  				return
   145  			}
   146  
   147  			if _, nat, err := upnp.client.GetNATRSIPStatus(); err != nil || !nat {
   148  				return
   149  			}
   150  			out <- upnp
   151  			found = true
   152  		})
   153  	}
   154  	if !found {
   155  		out <- nil
   156  	}
   157  }