github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/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  // discoverUPnP searches for Internet Gateway Devices
    88  // and returns the first one it can find on the local network.
    89  func discoverUPnP() Interface {
    90  	found := make(chan *upnp, 2)
    91  	// IGDv1
    92  	go discover(found, internetgateway1.URN_WANConnectionDevice_1, func(dev *goupnp.RootDevice, sc goupnp.ServiceClient) *upnp {
    93  		switch sc.Service.ServiceType {
    94  		case internetgateway1.URN_WANIPConnection_1:
    95  			return &upnp{dev, "IGDv1-IP1", &internetgateway1.WANIPConnection1{ServiceClient: sc}}
    96  		case internetgateway1.URN_WANPPPConnection_1:
    97  			return &upnp{dev, "IGDv1-PPP1", &internetgateway1.WANPPPConnection1{ServiceClient: sc}}
    98  		}
    99  		return nil
   100  	})
   101  	// IGDv2
   102  	go discover(found, internetgateway2.URN_WANConnectionDevice_2, func(dev *goupnp.RootDevice, sc goupnp.ServiceClient) *upnp {
   103  		switch sc.Service.ServiceType {
   104  		case internetgateway2.URN_WANIPConnection_1:
   105  			return &upnp{dev, "IGDv2-IP1", &internetgateway2.WANIPConnection1{ServiceClient: sc}}
   106  		case internetgateway2.URN_WANIPConnection_2:
   107  			return &upnp{dev, "IGDv2-IP2", &internetgateway2.WANIPConnection2{ServiceClient: sc}}
   108  		case internetgateway2.URN_WANPPPConnection_1:
   109  			return &upnp{dev, "IGDv2-PPP1", &internetgateway2.WANPPPConnection1{ServiceClient: sc}}
   110  		}
   111  		return nil
   112  	})
   113  	for i := 0; i < cap(found); i++ {
   114  		if c := <-found; c != nil {
   115  			return c
   116  		}
   117  	}
   118  	return nil
   119  }
   120  
   121  // finds devices matching the given target and calls matcher for all
   122  // advertised services of each device. The first non-nil service found
   123  // is sent into out. If no service matched, nil is sent.
   124  func discover(out chan<- *upnp, target string, matcher func(*goupnp.RootDevice, goupnp.ServiceClient) *upnp) {
   125  	devs, err := goupnp.DiscoverDevices(target)
   126  	if err != nil {
   127  		out <- nil
   128  		return
   129  	}
   130  	found := false
   131  	for i := 0; i < len(devs) && !found; i++ {
   132  		if devs[i].Root == nil {
   133  			continue
   134  		}
   135  		devs[i].Root.Device.VisitServices(func(service *goupnp.Service) {
   136  			if found {
   137  				return
   138  			}
   139  			// check for a matching IGD service
   140  			sc := goupnp.ServiceClient{
   141  				SOAPClient: service.NewSOAPClient(),
   142  				RootDevice: devs[i].Root,
   143  				Location:   devs[i].Location,
   144  				Service:    service,
   145  			}
   146  			sc.SOAPClient.HTTPClient.Timeout = soapRequestTimeout
   147  			upnp := matcher(devs[i].Root, sc)
   148  			if upnp == nil {
   149  				return
   150  			}
   151  			// check whether port mapping is enabled
   152  			if _, nat, err := upnp.client.GetNATRSIPStatus(); err != nil || !nat {
   153  				return
   154  			}
   155  			out <- upnp
   156  			found = true
   157  		})
   158  	}
   159  	if !found {
   160  		out <- nil
   161  	}
   162  }