nat.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  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. // Package nat provides access to common network port mapping protocols.
  17. package nat
  18. import (
  19. "errors"
  20. "fmt"
  21. "net"
  22. "strings"
  23. "sync"
  24. "time"
  25. "github.com/ethereum/go-ethereum/log"
  26. "github.com/jackpal/go-nat-pmp"
  27. )
  28. // An implementation of nat.Interface can map local ports to ports
  29. // accessible from the Internet.
  30. type Interface interface {
  31. // These methods manage a mapping between a port on the local
  32. // machine to a port that can be connected to from the internet.
  33. //
  34. // protocol is "UDP" or "TCP". Some implementations allow setting
  35. // a display name for the mapping. The mapping may be removed by
  36. // the gateway when its lifetime ends.
  37. AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error
  38. DeleteMapping(protocol string, extport, intport int) error
  39. // This method should return the external (Internet-facing)
  40. // address of the gateway device.
  41. ExternalIP() (net.IP, error)
  42. // Should return name of the method. This is used for logging.
  43. String() string
  44. }
  45. // Parse parses a NAT interface description.
  46. // The following formats are currently accepted.
  47. // Note that mechanism names are not case-sensitive.
  48. //
  49. // "" or "none" return nil
  50. // "extip:77.12.33.4" will assume the local machine is reachable on the given IP
  51. // "any" uses the first auto-detected mechanism
  52. // "upnp" uses the Universal Plug and Play protocol
  53. // "pmp" uses NAT-PMP with an auto-detected gateway address
  54. // "pmp:192.168.0.1" uses NAT-PMP with the given gateway address
  55. func Parse(spec string) (Interface, error) {
  56. var (
  57. parts = strings.SplitN(spec, ":", 2)
  58. mech = strings.ToLower(parts[0])
  59. ip net.IP
  60. )
  61. if len(parts) > 1 {
  62. ip = net.ParseIP(parts[1])
  63. if ip == nil {
  64. return nil, errors.New("invalid IP address")
  65. }
  66. }
  67. switch mech {
  68. case "", "none", "off":
  69. return nil, nil
  70. case "any", "auto", "on":
  71. return Any(), nil
  72. case "extip", "ip":
  73. if ip == nil {
  74. return nil, errors.New("missing IP address")
  75. }
  76. return ExtIP(ip), nil
  77. case "upnp":
  78. return UPnP(), nil
  79. case "pmp", "natpmp", "nat-pmp":
  80. return PMP(ip), nil
  81. default:
  82. return nil, fmt.Errorf("unknown mechanism %q", parts[0])
  83. }
  84. }
  85. const (
  86. mapTimeout = 20 * time.Minute
  87. mapUpdateInterval = 15 * time.Minute
  88. )
  89. // Map adds a port mapping on m and keeps it alive until c is closed.
  90. // This function is typically invoked in its own goroutine.
  91. func Map(m Interface, c chan struct{}, protocol string, extport, intport int, name string) {
  92. log := log.New("proto", protocol, "extport", extport, "intport", intport, "interface", m)
  93. refresh := time.NewTimer(mapUpdateInterval)
  94. defer func() {
  95. refresh.Stop()
  96. log.Debug("Deleting port mapping")
  97. m.DeleteMapping(protocol, extport, intport)
  98. }()
  99. if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil {
  100. log.Debug("Couldn't add port mapping", "err", err)
  101. } else {
  102. log.Info("Mapped network port")
  103. }
  104. for {
  105. select {
  106. case _, ok := <-c:
  107. if !ok {
  108. return
  109. }
  110. case <-refresh.C:
  111. log.Trace("Refreshing port mapping")
  112. if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil {
  113. log.Debug("Couldn't add port mapping", "err", err)
  114. }
  115. refresh.Reset(mapUpdateInterval)
  116. }
  117. }
  118. }
  119. // ExtIP assumes that the local machine is reachable on the given
  120. // external IP address, and that any required ports were mapped manually.
  121. // Mapping operations will not return an error but won't actually do anything.
  122. func ExtIP(ip net.IP) Interface {
  123. if ip == nil {
  124. panic("IP must not be nil")
  125. }
  126. return extIP(ip)
  127. }
  128. type extIP net.IP
  129. func (n extIP) ExternalIP() (net.IP, error) { return net.IP(n), nil }
  130. func (n extIP) String() string { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) }
  131. // These do nothing.
  132. func (extIP) AddMapping(string, int, int, string, time.Duration) error { return nil }
  133. func (extIP) DeleteMapping(string, int, int) error { return nil }
  134. // Any returns a port mapper that tries to discover any supported
  135. // mechanism on the local network.
  136. func Any() Interface {
  137. // TODO: attempt to discover whether the local machine has an
  138. // Internet-class address. Return ExtIP in this case.
  139. return startautodisc("UPnP or NAT-PMP", func() Interface {
  140. found := make(chan Interface, 2)
  141. go func() { found <- discoverUPnP() }()
  142. go func() { found <- discoverPMP() }()
  143. for i := 0; i < cap(found); i++ {
  144. if c := <-found; c != nil {
  145. return c
  146. }
  147. }
  148. return nil
  149. })
  150. }
  151. // UPnP returns a port mapper that uses UPnP. It will attempt to
  152. // discover the address of your router using UDP broadcasts.
  153. func UPnP() Interface {
  154. return startautodisc("UPnP", discoverUPnP)
  155. }
  156. // PMP returns a port mapper that uses NAT-PMP. The provided gateway
  157. // address should be the IP of your router. If the given gateway
  158. // address is nil, PMP will attempt to auto-discover the router.
  159. func PMP(gateway net.IP) Interface {
  160. if gateway != nil {
  161. return &pmp{gw: gateway, c: natpmp.NewClient(gateway)}
  162. }
  163. return startautodisc("NAT-PMP", discoverPMP)
  164. }
  165. // autodisc represents a port mapping mechanism that is still being
  166. // auto-discovered. Calls to the Interface methods on this type will
  167. // wait until the discovery is done and then call the method on the
  168. // discovered mechanism.
  169. //
  170. // This type is useful because discovery can take a while but we
  171. // want return an Interface value from UPnP, PMP and Auto immediately.
  172. type autodisc struct {
  173. what string // type of interface being autodiscovered
  174. once sync.Once
  175. doit func() Interface
  176. mu sync.Mutex
  177. found Interface
  178. }
  179. func startautodisc(what string, doit func() Interface) Interface {
  180. // TODO: monitor network configuration and rerun doit when it changes.
  181. return &autodisc{what: what, doit: doit}
  182. }
  183. func (n *autodisc) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error {
  184. if err := n.wait(); err != nil {
  185. return err
  186. }
  187. return n.found.AddMapping(protocol, extport, intport, name, lifetime)
  188. }
  189. func (n *autodisc) DeleteMapping(protocol string, extport, intport int) error {
  190. if err := n.wait(); err != nil {
  191. return err
  192. }
  193. return n.found.DeleteMapping(protocol, extport, intport)
  194. }
  195. func (n *autodisc) ExternalIP() (net.IP, error) {
  196. if err := n.wait(); err != nil {
  197. return nil, err
  198. }
  199. return n.found.ExternalIP()
  200. }
  201. func (n *autodisc) String() string {
  202. n.mu.Lock()
  203. defer n.mu.Unlock()
  204. if n.found == nil {
  205. return n.what
  206. } else {
  207. return n.found.String()
  208. }
  209. }
  210. // wait blocks until auto-discovery has been performed.
  211. func (n *autodisc) wait() error {
  212. n.once.Do(func() {
  213. n.mu.Lock()
  214. n.found = n.doit()
  215. n.mu.Unlock()
  216. })
  217. if n.found == nil {
  218. return fmt.Errorf("no %s router discovered", n.what)
  219. }
  220. return nil
  221. }