github.com/theQRL/go-zond@v0.1.1/p2p/nat/natupnp.go (about)

     1  // Copyright 2015 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package nat
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"math"
    23  	"math/rand"
    24  	"net"
    25  	"strings"
    26  	"sync"
    27  	"time"
    28  
    29  	"github.com/huin/goupnp"
    30  	"github.com/huin/goupnp/dcps/internetgateway1"
    31  	"github.com/huin/goupnp/dcps/internetgateway2"
    32  )
    33  
    34  const (
    35  	soapRequestTimeout = 3 * time.Second
    36  	rateLimit          = 200 * time.Millisecond
    37  )
    38  
    39  type upnp struct {
    40  	dev         *goupnp.RootDevice
    41  	service     string
    42  	client      upnpClient
    43  	mu          sync.Mutex
    44  	lastReqTime time.Time
    45  	rand        *rand.Rand
    46  }
    47  
    48  type upnpClient interface {
    49  	GetExternalIPAddress() (string, error)
    50  	AddPortMapping(string, uint16, string, uint16, string, bool, string, uint32) error
    51  	DeletePortMapping(string, uint16, string) error
    52  	GetNATRSIPStatus() (sip bool, nat bool, err error)
    53  }
    54  
    55  func (n *upnp) natEnabled() bool {
    56  	var ok bool
    57  	var err error
    58  	n.withRateLimit(func() error {
    59  		_, ok, err = n.client.GetNATRSIPStatus()
    60  		return err
    61  	})
    62  	return err == nil && ok
    63  }
    64  
    65  func (n *upnp) ExternalIP() (addr net.IP, err error) {
    66  	var ipString string
    67  	n.withRateLimit(func() error {
    68  		ipString, err = n.client.GetExternalIPAddress()
    69  		return err
    70  	})
    71  
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	ip := net.ParseIP(ipString)
    76  	if ip == nil {
    77  		return nil, errors.New("bad IP in response")
    78  	}
    79  	return ip, nil
    80  }
    81  
    82  func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) (uint16, error) {
    83  	ip, err := n.internalAddress()
    84  	if err != nil {
    85  		return 0, nil // TODO: Shouldn't we return the error?
    86  	}
    87  	protocol = strings.ToUpper(protocol)
    88  	lifetimeS := uint32(lifetime / time.Second)
    89  	n.DeleteMapping(protocol, extport, intport)
    90  
    91  	err = n.withRateLimit(func() error {
    92  		return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
    93  	})
    94  	if err == nil {
    95  		return uint16(extport), nil
    96  	}
    97  
    98  	return uint16(extport), n.withRateLimit(func() error {
    99  		p, err := n.addAnyPortMapping(protocol, extport, intport, ip, desc, lifetimeS)
   100  		if err == nil {
   101  			extport = int(p)
   102  		}
   103  		return err
   104  	})
   105  }
   106  
   107  func (n *upnp) addAnyPortMapping(protocol string, extport, intport int, ip net.IP, desc string, lifetimeS uint32) (uint16, error) {
   108  	if client, ok := n.client.(*internetgateway2.WANIPConnection2); ok {
   109  		return client.AddAnyPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
   110  	}
   111  	// It will retry with a random port number if the client does
   112  	// not support AddAnyPortMapping.
   113  	extport = n.randomPort()
   114  	err := n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
   115  	if err != nil {
   116  		return 0, err
   117  	}
   118  	return uint16(extport), nil
   119  }
   120  
   121  func (n *upnp) randomPort() int {
   122  	if n.rand == nil {
   123  		n.rand = rand.New(rand.NewSource(time.Now().UnixNano()))
   124  	}
   125  	return n.rand.Intn(math.MaxUint16-10000) + 10000
   126  }
   127  
   128  func (n *upnp) internalAddress() (net.IP, error) {
   129  	devaddr, err := net.ResolveUDPAddr("udp4", n.dev.URLBase.Host)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	ifaces, err := net.Interfaces()
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	for _, iface := range ifaces {
   138  		addrs, err := iface.Addrs()
   139  		if err != nil {
   140  			return nil, err
   141  		}
   142  		for _, addr := range addrs {
   143  			if x, ok := addr.(*net.IPNet); ok && x.Contains(devaddr.IP) {
   144  				return x.IP, nil
   145  			}
   146  		}
   147  	}
   148  	return nil, fmt.Errorf("could not find local address in same net as %v", devaddr)
   149  }
   150  
   151  func (n *upnp) DeleteMapping(protocol string, extport, intport int) error {
   152  	return n.withRateLimit(func() error {
   153  		return n.client.DeletePortMapping("", uint16(extport), strings.ToUpper(protocol))
   154  	})
   155  }
   156  
   157  func (n *upnp) String() string {
   158  	return "UPNP " + n.service
   159  }
   160  
   161  func (n *upnp) withRateLimit(fn func() error) error {
   162  	n.mu.Lock()
   163  	defer n.mu.Unlock()
   164  
   165  	lastreq := time.Since(n.lastReqTime)
   166  	if lastreq < rateLimit {
   167  		time.Sleep(rateLimit - lastreq)
   168  	}
   169  	err := fn()
   170  	n.lastReqTime = time.Now()
   171  	return err
   172  }
   173  
   174  // discoverUPnP searches for Internet Gateway Devices
   175  // and returns the first one it can find on the local network.
   176  func discoverUPnP() Interface {
   177  	found := make(chan *upnp, 2)
   178  	// IGDv1
   179  	go discover(found, internetgateway1.URN_WANConnectionDevice_1, func(sc goupnp.ServiceClient) *upnp {
   180  		switch sc.Service.ServiceType {
   181  		case internetgateway1.URN_WANIPConnection_1:
   182  			return &upnp{service: "IGDv1-IP1", client: &internetgateway1.WANIPConnection1{ServiceClient: sc}}
   183  		case internetgateway1.URN_WANPPPConnection_1:
   184  			return &upnp{service: "IGDv1-PPP1", client: &internetgateway1.WANPPPConnection1{ServiceClient: sc}}
   185  		}
   186  		return nil
   187  	})
   188  	// IGDv2
   189  	go discover(found, internetgateway2.URN_WANConnectionDevice_2, func(sc goupnp.ServiceClient) *upnp {
   190  		switch sc.Service.ServiceType {
   191  		case internetgateway2.URN_WANIPConnection_1:
   192  			return &upnp{service: "IGDv2-IP1", client: &internetgateway2.WANIPConnection1{ServiceClient: sc}}
   193  		case internetgateway2.URN_WANIPConnection_2:
   194  			return &upnp{service: "IGDv2-IP2", client: &internetgateway2.WANIPConnection2{ServiceClient: sc}}
   195  		case internetgateway2.URN_WANPPPConnection_1:
   196  			return &upnp{service: "IGDv2-PPP1", client: &internetgateway2.WANPPPConnection1{ServiceClient: sc}}
   197  		}
   198  		return nil
   199  	})
   200  	for i := 0; i < cap(found); i++ {
   201  		if c := <-found; c != nil {
   202  			return c
   203  		}
   204  	}
   205  	return nil
   206  }
   207  
   208  // finds devices matching the given target and calls matcher for all
   209  // advertised services of each device. The first non-nil service found
   210  // is sent into out. If no service matched, nil is sent.
   211  func discover(out chan<- *upnp, target string, matcher func(goupnp.ServiceClient) *upnp) {
   212  	devs, err := goupnp.DiscoverDevices(target)
   213  	if err != nil {
   214  		out <- nil
   215  		return
   216  	}
   217  	found := false
   218  	for i := 0; i < len(devs) && !found; i++ {
   219  		if devs[i].Root == nil {
   220  			continue
   221  		}
   222  		devs[i].Root.Device.VisitServices(func(service *goupnp.Service) {
   223  			if found {
   224  				return
   225  			}
   226  			// check for a matching IGD service
   227  			sc := goupnp.ServiceClient{
   228  				SOAPClient: service.NewSOAPClient(),
   229  				RootDevice: devs[i].Root,
   230  				Location:   devs[i].Location,
   231  				Service:    service,
   232  			}
   233  			sc.SOAPClient.HTTPClient.Timeout = soapRequestTimeout
   234  			upnp := matcher(sc)
   235  			if upnp == nil {
   236  				return
   237  			}
   238  			upnp.dev = devs[i].Root
   239  
   240  			// check whether port mapping is enabled
   241  			if upnp.natEnabled() {
   242  				out <- upnp
   243  				found = true
   244  			}
   245  		})
   246  	}
   247  	if !found {
   248  		out <- nil
   249  	}
   250  }