You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

111 lines
2.7 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. package upnp
  2. import (
  3. "errors"
  4. "fmt"
  5. "net"
  6. "time"
  7. cmn "github.com/tendermint/tmlibs/common"
  8. )
  9. type UPNPCapabilities struct {
  10. PortMapping bool
  11. Hairpin bool
  12. }
  13. func makeUPNPListener(intPort int, extPort int) (NAT, net.Listener, net.IP, error) {
  14. nat, err := Discover()
  15. if err != nil {
  16. return nil, nil, nil, errors.New(fmt.Sprintf("NAT upnp could not be discovered: %v", err))
  17. }
  18. log.Info(cmn.Fmt("ourIP: %v", nat.(*upnpNAT).ourIP))
  19. ext, err := nat.GetExternalAddress()
  20. if err != nil {
  21. return nat, nil, nil, errors.New(fmt.Sprintf("External address error: %v", err))
  22. }
  23. log.Info(cmn.Fmt("External address: %v", ext))
  24. port, err := nat.AddPortMapping("tcp", extPort, intPort, "Tendermint UPnP Probe", 0)
  25. if err != nil {
  26. return nat, nil, ext, errors.New(fmt.Sprintf("Port mapping error: %v", err))
  27. }
  28. log.Info(cmn.Fmt("Port mapping mapped: %v", port))
  29. // also run the listener, open for all remote addresses.
  30. listener, err := net.Listen("tcp", fmt.Sprintf(":%v", intPort))
  31. if err != nil {
  32. return nat, nil, ext, errors.New(fmt.Sprintf("Error establishing listener: %v", err))
  33. }
  34. return nat, listener, ext, nil
  35. }
  36. func testHairpin(listener net.Listener, extAddr string) (supportsHairpin bool) {
  37. // Listener
  38. go func() {
  39. inConn, err := listener.Accept()
  40. if err != nil {
  41. log.Notice(cmn.Fmt("Listener.Accept() error: %v", err))
  42. return
  43. }
  44. log.Info(cmn.Fmt("Accepted incoming connection: %v -> %v", inConn.LocalAddr(), inConn.RemoteAddr()))
  45. buf := make([]byte, 1024)
  46. n, err := inConn.Read(buf)
  47. if err != nil {
  48. log.Notice(cmn.Fmt("Incoming connection read error: %v", err))
  49. return
  50. }
  51. log.Info(cmn.Fmt("Incoming connection read %v bytes: %X", n, buf))
  52. if string(buf) == "test data" {
  53. supportsHairpin = true
  54. return
  55. }
  56. }()
  57. // Establish outgoing
  58. outConn, err := net.Dial("tcp", extAddr)
  59. if err != nil {
  60. log.Notice(cmn.Fmt("Outgoing connection dial error: %v", err))
  61. return
  62. }
  63. n, err := outConn.Write([]byte("test data"))
  64. if err != nil {
  65. log.Notice(cmn.Fmt("Outgoing connection write error: %v", err))
  66. return
  67. }
  68. log.Info(cmn.Fmt("Outgoing connection wrote %v bytes", n))
  69. // Wait for data receipt
  70. time.Sleep(1 * time.Second)
  71. return
  72. }
  73. func Probe() (caps UPNPCapabilities, err error) {
  74. log.Info("Probing for UPnP!")
  75. intPort, extPort := 8001, 8001
  76. nat, listener, ext, err := makeUPNPListener(intPort, extPort)
  77. if err != nil {
  78. return
  79. }
  80. caps.PortMapping = true
  81. // Deferred cleanup
  82. defer func() {
  83. err = nat.DeletePortMapping("tcp", intPort, extPort)
  84. if err != nil {
  85. log.Warn(cmn.Fmt("Port mapping delete error: %v", err))
  86. }
  87. listener.Close()
  88. }()
  89. supportsHairpin := testHairpin(listener, fmt.Sprintf("%v:%v", ext, extPort))
  90. if supportsHairpin {
  91. caps.Hairpin = true
  92. }
  93. return
  94. }