| 
									
										
										
										
											2015-07-07 02:54:22 +02:00
										 |  |  | // Copyright 2015 The go-ethereum Authors | 
					
						
							| 
									
										
										
										
											2015-07-22 18:48:40 +02:00
										 |  |  | // This file is part of the go-ethereum library. | 
					
						
							| 
									
										
										
										
											2015-07-07 02:54:22 +02:00
										 |  |  | // | 
					
						
							| 
									
										
										
										
											2015-07-23 18:35:11 +02:00
										 |  |  | // The go-ethereum library is free software: you can redistribute it and/or modify | 
					
						
							| 
									
										
										
										
											2015-07-07 02:54:22 +02:00
										 |  |  | // it under the terms of the GNU Lesser General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							| 
									
										
										
										
											2015-07-22 18:48:40 +02:00
										 |  |  | // The go-ethereum library is distributed in the hope that it will be useful, | 
					
						
							| 
									
										
										
										
											2015-07-07 02:54:22 +02:00
										 |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							| 
									
										
										
										
											2015-07-22 18:48:40 +02:00
										 |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 
					
						
							| 
									
										
										
										
											2015-07-07 02:54:22 +02:00
										 |  |  | // GNU Lesser General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Lesser General Public License | 
					
						
							| 
									
										
										
										
											2015-07-22 18:48:40 +02:00
										 |  |  | // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | 
					
						
							| 
									
										
										
										
											2015-07-07 02:54:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-07-07 05:08:16 +02:00
										 |  |  | // Package nat provides access to common network port mapping protocols. | 
					
						
							| 
									
										
										
										
											2015-02-10 23:49:45 +01:00
										 |  |  | package nat | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"net" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-22 14:10:07 +02:00
										 |  |  | 	"github.com/ethereum/go-ethereum/log" | 
					
						
							| 
									
										
										
										
											2019-11-18 09:49:18 +01:00
										 |  |  | 	natpmp "github.com/jackpal/go-nat-pmp" | 
					
						
							| 
									
										
										
										
											2015-02-10 23:49:45 +01:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // An implementation of nat.Interface can map local ports to ports | 
					
						
							|  |  |  | // accessible from the Internet. | 
					
						
							|  |  |  | type Interface interface { | 
					
						
							|  |  |  | 	// These methods manage a mapping between a port on the local | 
					
						
							|  |  |  | 	// machine to a port that can be connected to from the internet. | 
					
						
							|  |  |  | 	// | 
					
						
							|  |  |  | 	// protocol is "UDP" or "TCP". Some implementations allow setting | 
					
						
							|  |  |  | 	// a display name for the mapping. The mapping may be removed by | 
					
						
							|  |  |  | 	// the gateway when its lifetime ends. | 
					
						
							|  |  |  | 	AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error | 
					
						
							|  |  |  | 	DeleteMapping(protocol string, extport, intport int) error | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// This method should return the external (Internet-facing) | 
					
						
							|  |  |  | 	// address of the gateway device. | 
					
						
							|  |  |  | 	ExternalIP() (net.IP, error) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Should return name of the method. This is used for logging. | 
					
						
							|  |  |  | 	String() string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Parse parses a NAT interface description. | 
					
						
							|  |  |  | // The following formats are currently accepted. | 
					
						
							|  |  |  | // Note that mechanism names are not case-sensitive. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | //     "" or "none"         return nil | 
					
						
							|  |  |  | //     "extip:77.12.33.4"   will assume the local machine is reachable on the given IP | 
					
						
							|  |  |  | //     "any"                uses the first auto-detected mechanism | 
					
						
							|  |  |  | //     "upnp"               uses the Universal Plug and Play protocol | 
					
						
							|  |  |  | //     "pmp"                uses NAT-PMP with an auto-detected gateway address | 
					
						
							|  |  |  | //     "pmp:192.168.0.1"    uses NAT-PMP with the given gateway address | 
					
						
							|  |  |  | func Parse(spec string) (Interface, error) { | 
					
						
							|  |  |  | 	var ( | 
					
						
							|  |  |  | 		parts = strings.SplitN(spec, ":", 2) | 
					
						
							|  |  |  | 		mech  = strings.ToLower(parts[0]) | 
					
						
							|  |  |  | 		ip    net.IP | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if len(parts) > 1 { | 
					
						
							|  |  |  | 		ip = net.ParseIP(parts[1]) | 
					
						
							|  |  |  | 		if ip == nil { | 
					
						
							|  |  |  | 			return nil, errors.New("invalid IP address") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	switch mech { | 
					
						
							|  |  |  | 	case "", "none", "off": | 
					
						
							|  |  |  | 		return nil, nil | 
					
						
							|  |  |  | 	case "any", "auto", "on": | 
					
						
							|  |  |  | 		return Any(), nil | 
					
						
							|  |  |  | 	case "extip", "ip": | 
					
						
							|  |  |  | 		if ip == nil { | 
					
						
							|  |  |  | 			return nil, errors.New("missing IP address") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return ExtIP(ip), nil | 
					
						
							|  |  |  | 	case "upnp": | 
					
						
							|  |  |  | 		return UPnP(), nil | 
					
						
							|  |  |  | 	case "pmp", "natpmp", "nat-pmp": | 
					
						
							|  |  |  | 		return PMP(ip), nil | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("unknown mechanism %q", parts[0]) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							| 
									
										
										
										
											2020-08-05 09:51:37 +02:00
										 |  |  | 	mapTimeout = 10 * time.Minute | 
					
						
							| 
									
										
										
										
											2015-02-10 23:49:45 +01:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Map adds a port mapping on m and keeps it alive until c is closed. | 
					
						
							|  |  |  | // This function is typically invoked in its own goroutine. | 
					
						
							| 
									
										
										
										
											2020-08-05 09:51:37 +02:00
										 |  |  | func Map(m Interface, c <-chan struct{}, protocol string, extport, intport int, name string) { | 
					
						
							| 
									
										
										
										
											2017-02-24 09:58:04 +01:00
										 |  |  | 	log := log.New("proto", protocol, "extport", extport, "intport", intport, "interface", m) | 
					
						
							| 
									
										
										
										
											2020-08-05 09:51:37 +02:00
										 |  |  | 	refresh := time.NewTimer(mapTimeout) | 
					
						
							| 
									
										
										
										
											2015-02-10 23:49:45 +01:00
										 |  |  | 	defer func() { | 
					
						
							|  |  |  | 		refresh.Stop() | 
					
						
							| 
									
										
										
										
											2017-02-24 09:58:04 +01:00
										 |  |  | 		log.Debug("Deleting port mapping") | 
					
						
							| 
									
										
										
										
											2015-02-10 23:49:45 +01:00
										 |  |  | 		m.DeleteMapping(protocol, extport, intport) | 
					
						
							|  |  |  | 	}() | 
					
						
							| 
									
										
										
										
											2016-09-28 18:11:58 +08:00
										 |  |  | 	if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil { | 
					
						
							| 
									
										
										
										
											2017-02-24 09:58:04 +01:00
										 |  |  | 		log.Debug("Couldn't add port mapping", "err", err) | 
					
						
							| 
									
										
										
										
											2015-05-14 12:54:59 +02:00
										 |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2017-02-24 09:58:04 +01:00
										 |  |  | 		log.Info("Mapped network port") | 
					
						
							| 
									
										
										
										
											2015-02-10 23:49:45 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		select { | 
					
						
							|  |  |  | 		case _, ok := <-c: | 
					
						
							|  |  |  | 			if !ok { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case <-refresh.C: | 
					
						
							| 
									
										
										
										
											2017-02-24 09:58:04 +01:00
										 |  |  | 			log.Trace("Refreshing port mapping") | 
					
						
							| 
									
										
										
										
											2016-09-28 18:11:58 +08:00
										 |  |  | 			if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil { | 
					
						
							| 
									
										
										
										
											2017-02-24 09:58:04 +01:00
										 |  |  | 				log.Debug("Couldn't add port mapping", "err", err) | 
					
						
							| 
									
										
										
										
											2015-02-10 23:49:45 +01:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-08-05 09:51:37 +02:00
										 |  |  | 			refresh.Reset(mapTimeout) | 
					
						
							| 
									
										
										
										
											2015-02-10 23:49:45 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ExtIP assumes that the local machine is reachable on the given | 
					
						
							|  |  |  | // external IP address, and that any required ports were mapped manually. | 
					
						
							|  |  |  | // Mapping operations will not return an error but won't actually do anything. | 
					
						
							| 
									
										
										
										
											2018-10-12 11:47:24 +02:00
										 |  |  | type ExtIP net.IP | 
					
						
							| 
									
										
										
										
											2015-02-10 23:49:45 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-12 11:47:24 +02:00
										 |  |  | func (n ExtIP) ExternalIP() (net.IP, error) { return net.IP(n), nil } | 
					
						
							|  |  |  | func (n ExtIP) String() string              { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) } | 
					
						
							| 
									
										
										
										
											2015-02-10 23:49:45 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | // These do nothing. | 
					
						
							| 
									
										
										
										
											2018-10-12 11:47:24 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | func (ExtIP) AddMapping(string, int, int, string, time.Duration) error { return nil } | 
					
						
							|  |  |  | func (ExtIP) DeleteMapping(string, int, int) error                     { return nil } | 
					
						
							| 
									
										
										
										
											2015-02-10 23:49:45 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Any returns a port mapper that tries to discover any supported | 
					
						
							|  |  |  | // mechanism on the local network. | 
					
						
							|  |  |  | func Any() Interface { | 
					
						
							|  |  |  | 	// TODO: attempt to discover whether the local machine has an | 
					
						
							|  |  |  | 	// Internet-class address. Return ExtIP in this case. | 
					
						
							|  |  |  | 	return startautodisc("UPnP or NAT-PMP", func() Interface { | 
					
						
							|  |  |  | 		found := make(chan Interface, 2) | 
					
						
							|  |  |  | 		go func() { found <- discoverUPnP() }() | 
					
						
							|  |  |  | 		go func() { found <- discoverPMP() }() | 
					
						
							|  |  |  | 		for i := 0; i < cap(found); i++ { | 
					
						
							|  |  |  | 			if c := <-found; c != nil { | 
					
						
							|  |  |  | 				return c | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // UPnP returns a port mapper that uses UPnP. It will attempt to | 
					
						
							|  |  |  | // discover the address of your router using UDP broadcasts. | 
					
						
							|  |  |  | func UPnP() Interface { | 
					
						
							|  |  |  | 	return startautodisc("UPnP", discoverUPnP) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // PMP returns a port mapper that uses NAT-PMP. The provided gateway | 
					
						
							|  |  |  | // address should be the IP of your router. If the given gateway | 
					
						
							|  |  |  | // address is nil, PMP will attempt to auto-discover the router. | 
					
						
							|  |  |  | func PMP(gateway net.IP) Interface { | 
					
						
							|  |  |  | 	if gateway != nil { | 
					
						
							|  |  |  | 		return &pmp{gw: gateway, c: natpmp.NewClient(gateway)} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return startautodisc("NAT-PMP", discoverPMP) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // autodisc represents a port mapping mechanism that is still being | 
					
						
							|  |  |  | // auto-discovered. Calls to the Interface methods on this type will | 
					
						
							|  |  |  | // wait until the discovery is done and then call the method on the | 
					
						
							|  |  |  | // discovered mechanism. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This type is useful because discovery can take a while but we | 
					
						
							|  |  |  | // want return an Interface value from UPnP, PMP and Auto immediately. | 
					
						
							|  |  |  | type autodisc struct { | 
					
						
							| 
									
										
										
										
											2015-05-13 16:04:06 +02:00
										 |  |  | 	what string // type of interface being autodiscovered | 
					
						
							|  |  |  | 	once sync.Once | 
					
						
							|  |  |  | 	doit func() Interface | 
					
						
							| 
									
										
										
										
											2015-02-10 23:49:45 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	mu    sync.Mutex | 
					
						
							|  |  |  | 	found Interface | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func startautodisc(what string, doit func() Interface) Interface { | 
					
						
							|  |  |  | 	// TODO: monitor network configuration and rerun doit when it changes. | 
					
						
							| 
									
										
										
										
											2016-09-06 00:26:54 +02:00
										 |  |  | 	return &autodisc{what: what, doit: doit} | 
					
						
							| 
									
										
										
										
											2015-02-10 23:49:45 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (n *autodisc) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error { | 
					
						
							|  |  |  | 	if err := n.wait(); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return n.found.AddMapping(protocol, extport, intport, name, lifetime) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (n *autodisc) DeleteMapping(protocol string, extport, intport int) error { | 
					
						
							|  |  |  | 	if err := n.wait(); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return n.found.DeleteMapping(protocol, extport, intport) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (n *autodisc) ExternalIP() (net.IP, error) { | 
					
						
							|  |  |  | 	if err := n.wait(); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return n.found.ExternalIP() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (n *autodisc) String() string { | 
					
						
							|  |  |  | 	n.mu.Lock() | 
					
						
							|  |  |  | 	defer n.mu.Unlock() | 
					
						
							|  |  |  | 	if n.found == nil { | 
					
						
							|  |  |  | 		return n.what | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-11-25 09:24:50 +01:00
										 |  |  | 	return n.found.String() | 
					
						
							| 
									
										
										
										
											2015-02-10 23:49:45 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-13 16:04:06 +02:00
										 |  |  | // wait blocks until auto-discovery has been performed. | 
					
						
							| 
									
										
										
										
											2015-02-10 23:49:45 +01:00
										 |  |  | func (n *autodisc) wait() error { | 
					
						
							| 
									
										
										
										
											2015-05-13 16:04:06 +02:00
										 |  |  | 	n.once.Do(func() { | 
					
						
							|  |  |  | 		n.mu.Lock() | 
					
						
							|  |  |  | 		n.found = n.doit() | 
					
						
							|  |  |  | 		n.mu.Unlock() | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	if n.found == nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("no %s router discovered", n.what) | 
					
						
							| 
									
										
										
										
											2015-02-10 23:49:45 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } |