pmp.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. // Copyright (C) 2016 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package pmp
  7. import (
  8. "fmt"
  9. "net"
  10. "strings"
  11. "time"
  12. "github.com/AudriusButkevicius/go-nat-pmp"
  13. "github.com/jackpal/gateway"
  14. "github.com/syncthing/syncthing/lib/nat"
  15. )
  16. func init() {
  17. nat.Register(Discover)
  18. }
  19. func Discover(renewal, timeout time.Duration) []nat.Device {
  20. ip, err := gateway.DiscoverGateway()
  21. if err != nil {
  22. l.Debugln("Failed to discover gateway", err)
  23. return nil
  24. }
  25. if ip == nil || ip.IsUnspecified() {
  26. return nil
  27. }
  28. l.Debugln("Discovered gateway at", ip)
  29. c := natpmp.NewClient(ip, timeout)
  30. // Try contacting the gateway, if it does not respond, assume it does not
  31. // speak NAT-PMP.
  32. _, err = c.GetExternalAddress()
  33. if err != nil && strings.Contains(err.Error(), "Timed out") {
  34. l.Debugln("Timeout trying to get external address, assume no NAT-PMP available")
  35. return nil
  36. }
  37. var localIP net.IP
  38. // Port comes from the natpmp package
  39. conn, err := net.DialTimeout("udp", net.JoinHostPort(ip.String(), "5351"), timeout)
  40. if err == nil {
  41. conn.Close()
  42. localIPAddress, _, err := net.SplitHostPort(conn.LocalAddr().String())
  43. if err == nil {
  44. localIP = net.ParseIP(localIPAddress)
  45. } else {
  46. l.Debugln("Failed to lookup local IP", err)
  47. }
  48. }
  49. return []nat.Device{&wrapper{
  50. renewal: renewal,
  51. localIP: localIP,
  52. gatewayIP: ip,
  53. client: c,
  54. }}
  55. }
  56. type wrapper struct {
  57. renewal time.Duration
  58. localIP net.IP
  59. gatewayIP net.IP
  60. client *natpmp.Client
  61. }
  62. func (w *wrapper) ID() string {
  63. return fmt.Sprintf("NAT-PMP@%s", w.gatewayIP.String())
  64. }
  65. func (w *wrapper) GetLocalIPAddress() net.IP {
  66. return w.localIP
  67. }
  68. func (w *wrapper) AddPortMapping(protocol nat.Protocol, internalPort, externalPort int, description string, duration time.Duration) (int, error) {
  69. // NAT-PMP says that if duration is 0, the mapping is actually removed
  70. // Swap the zero with the renewal value, which should make the lease for the
  71. // exact amount of time between the calls.
  72. if duration == 0 {
  73. duration = w.renewal
  74. }
  75. result, err := w.client.AddPortMapping(strings.ToLower(string(protocol)), internalPort, externalPort, int(duration/time.Second))
  76. port := 0
  77. if result != nil {
  78. port = int(result.MappedExternalPort)
  79. }
  80. return port, err
  81. }
  82. func (w *wrapper) GetExternalIPAddress() (net.IP, error) {
  83. result, err := w.client.GetExternalAddress()
  84. ip := net.IPv4zero
  85. if result != nil {
  86. ip = net.IPv4(
  87. result.ExternalIPAddress[0],
  88. result.ExternalIPAddress[1],
  89. result.ExternalIPAddress[2],
  90. result.ExternalIPAddress[3],
  91. )
  92. }
  93. return ip, err
  94. }