github.com/jonasnick/go-ethereum@v0.7.12-0.20150216215225-22176f05d387/p2p/nat/natpmp.go (about)

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