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