Skip to content

Commit

Permalink
lib/pmp: Add NAT-PMP support (ref #698)
Browse files Browse the repository at this point in the history
GitHub-Pull-Request: #2968
  • Loading branch information
AudriusButkevicius authored and calmh committed Apr 13, 2016
1 parent 52c7804 commit c49453c
Show file tree
Hide file tree
Showing 16 changed files with 650 additions and 6 deletions.
5 changes: 4 additions & 1 deletion cmd/syncthing/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,12 @@ import (
"github.com/syncthing/syncthing/lib/symlinks"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/upgrade"
_ "github.com/syncthing/syncthing/lib/upnp"
"github.com/syncthing/syncthing/lib/util"

// Registers NAT service providers
_ "github.com/syncthing/syncthing/lib/pmp"
_ "github.com/syncthing/syncthing/lib/upnp"

"github.com/thejerf/suture"
)

Expand Down
33 changes: 28 additions & 5 deletions lib/nat/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ package nat

import (
"time"

"github.com/syncthing/syncthing/lib/sync"
)

type DiscoverFunc func(renewal, timeout time.Duration) []Device
Expand All @@ -19,12 +21,33 @@ func Register(provider DiscoverFunc) {
}

func discoverAll(renewal, timeout time.Duration) map[string]Device {
nats := make(map[string]Device)
wg := sync.NewWaitGroup()
wg.Add(len(providers))

c := make(chan Device)
done := make(chan struct{})

for _, discoverFunc := range providers {
discoveredNATs := discoverFunc(renewal, timeout)
for _, discoveredNAT := range discoveredNATs {
nats[discoveredNAT.ID()] = discoveredNAT
}
go func(f DiscoverFunc) {
for _, dev := range f(renewal, timeout) {
c <- dev
}
wg.Done()
}(discoverFunc)
}

nats := make(map[string]Device)

go func() {
for dev := range c {
nats[dev.ID()] = dev
}
close(done)
}()

wg.Wait()
close(c)
<-done

return nats
}
22 changes: 22 additions & 0 deletions lib/pmp/debug.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

package pmp

import (
"os"
"strings"

"github.com/syncthing/syncthing/lib/logger"
)

var (
l = logger.DefaultLogger.NewFacility("pmp", "NAT-PMP discovery and port mapping")
)

func init() {
l.SetDebug("pmp", strings.Contains(os.Getenv("STTRACE"), "pmp") || os.Getenv("STTRACE") == "all")
}
105 changes: 105 additions & 0 deletions lib/pmp/pmp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

package pmp

import (
"fmt"
"net"
"strings"
"time"

"github.com/AudriusButkevicius/go-nat-pmp"
"github.com/jackpal/gateway"
"github.com/syncthing/syncthing/lib/nat"
)

func init() {
nat.Register(Discover)
}

func Discover(renewal, timeout time.Duration) []nat.Device {
ip, err := gateway.DiscoverGateway()
if err != nil {
l.Debugln("Failed to discover gateway", err)
return nil
}

l.Debugln("Discovered gateway at", ip)

c := natpmp.NewClient(ip, timeout)
// Try contacting the gateway, if it does not respond, assume it does not
// speak NAT-PMP.
_, err = c.GetExternalAddress()
if err != nil && strings.Contains(err.Error(), "Timed out") {
l.Debugln("Timeout trying to get external address, assume no NAT-PMP available")
return nil
}

var localIP net.IP
// Port comes from the natpmp package
conn, err := net.DialTimeout("udp", net.JoinHostPort(ip.String(), "5351"), timeout)
if err == nil {
conn.Close()
localIPAddress, _, err := net.SplitHostPort(conn.LocalAddr().String())
if err == nil {
localIP = net.ParseIP(localIPAddress)
} else {
l.Debugln("Failed to lookup local IP", err)
}
}

return []nat.Device{&wrapper{
renewal: renewal,
localIP: localIP,
gatewayIP: ip,
client: c,
}}
}

type wrapper struct {
renewal time.Duration
localIP net.IP
gatewayIP net.IP
client *natpmp.Client
}

func (w *wrapper) ID() string {
return fmt.Sprintf("NAT-PMP@%s", w.gatewayIP.String())
}

func (w *wrapper) GetLocalIPAddress() net.IP {
return w.localIP
}

func (w *wrapper) AddPortMapping(protocol nat.Protocol, internalPort, externalPort int, description string, duration time.Duration) (int, error) {
// NAT-PMP says that if duration is 0, the mapping is actually removed
// Swap the zero with the renewal value, which should make the lease for the
// exact amount of time between the calls.
if duration == 0 {
duration = w.renewal
}
result, err := w.client.AddPortMapping(strings.ToLower(string(protocol)), internalPort, externalPort, int(duration/time.Second))
port := 0
if result != nil {
port = int(result.MappedExternalPort)
}
return port, err
}

func (w *wrapper) GetExternalIPAddress() (net.IP, error) {
result, err := w.client.GetExternalAddress()
ip := net.IPv4zero
if result != nil {
ip = net.IPv4(
result.ExternalIPAddress[0],
result.ExternalIPAddress[1],
result.ExternalIPAddress[2],
result.ExternalIPAddress[3],
)
}
return ip, err
}
13 changes: 13 additions & 0 deletions vendor/github.com/AudriusButkevicius/go-nat-pmp/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 48 additions & 0 deletions vendor/github.com/AudriusButkevicius/go-nat-pmp/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit c49453c

Please sign in to comment.