github.com/benorgera/go-ethereum@v1.10.18-0.20220401011646-b3f57b1a73ba/p2p/nat/natpmp.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  	"fmt"
    21  	"net"
    22  	"strings"
    23  	"time"
    24  
    25  	natpmp "github.com/jackpal/go-nat-pmp"
    26  )
    27  
    28  // natPMPClient adapts the NAT-PMP protocol implementation so it conforms to
    29  // the common interface.
    30  type pmp struct {
    31  	gw net.IP
    32  	c  *natpmp.Client
    33  }
    34  
    35  func (n *pmp) String() string {
    36  	return fmt.Sprintf("NAT-PMP(%v)", n.gw)
    37  }
    38  
    39  func (n *pmp) ExternalIP() (net.IP, error) {
    40  	response, err := n.c.GetExternalAddress()
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  	return response.ExternalIPAddress[:], nil
    45  }
    46  
    47  func (n *pmp) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error {
    48  	if lifetime <= 0 {
    49  		return fmt.Errorf("lifetime must not be <= 0")
    50  	}
    51  	// Note order of port arguments is switched between our
    52  	// AddMapping and the client's AddPortMapping.
    53  	_, err := n.c.AddPortMapping(strings.ToLower(protocol), intport, extport, int(lifetime/time.Second))
    54  	return err
    55  }
    56  
    57  func (n *pmp) DeleteMapping(protocol string, extport, intport int) (err error) {
    58  	// To destroy a mapping, send an add-port with an internalPort of
    59  	// the internal port to destroy, an external port of zero and a
    60  	// time of zero.
    61  	_, err = n.c.AddPortMapping(strings.ToLower(protocol), intport, 0, 0)
    62  	return err
    63  }
    64  
    65  func discoverPMP() Interface {
    66  	// run external address lookups on all potential gateways
    67  	gws := potentialGateways()
    68  	found := make(chan *pmp, len(gws))
    69  	for i := range gws {
    70  		gw := gws[i]
    71  		go func() {
    72  			c := natpmp.NewClient(gw)
    73  			if _, err := c.GetExternalAddress(); err != nil {
    74  				found <- nil
    75  			} else {
    76  				found <- &pmp{gw, c}
    77  			}
    78  		}()
    79  	}
    80  	// return the one that responds first.
    81  	// discovery needs to be quick, so we stop caring about
    82  	// any responses after a very short timeout.
    83  	timeout := time.NewTimer(1 * time.Second)
    84  	defer timeout.Stop()
    85  	for range gws {
    86  		select {
    87  		case c := <-found:
    88  			if c != nil {
    89  				return c
    90  			}
    91  		case <-timeout.C:
    92  			return nil
    93  		}
    94  	}
    95  	return nil
    96  }
    97  
    98  var (
    99  	// LAN IP ranges
   100  	_, lan10, _  = net.ParseCIDR("10.0.0.0/8")
   101  	_, lan176, _ = net.ParseCIDR("172.16.0.0/12")
   102  	_, lan192, _ = net.ParseCIDR("192.168.0.0/16")
   103  )
   104  
   105  // TODO: improve this. We currently assume that (on most networks)
   106  // the router is X.X.X.1 in a local LAN range.
   107  func potentialGateways() (gws []net.IP) {
   108  	ifaces, err := net.Interfaces()
   109  	if err != nil {
   110  		return nil
   111  	}
   112  	for _, iface := range ifaces {
   113  		ifaddrs, err := iface.Addrs()
   114  		if err != nil {
   115  			return gws
   116  		}
   117  		for _, addr := range ifaddrs {
   118  			if x, ok := addr.(*net.IPNet); ok {
   119  				if lan10.Contains(x.IP) || lan176.Contains(x.IP) || lan192.Contains(x.IP) {
   120  					ip := x.IP.Mask(x.Mask).To4()
   121  					if ip != nil {
   122  						ip[3] = ip[3] | 0x01
   123  						gws = append(gws, ip)
   124  					}
   125  				}
   126  			}
   127  		}
   128  	}
   129  	return gws
   130  }