lib/pmp: Add NAT-PMP support (ref #698)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2968
This commit is contained in:
committed by
Jakob Borg
parent
52c7804f32
commit
c49453c519
@@ -8,6 +8,8 @@ package nat
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
)
|
||||
|
||||
type DiscoverFunc func(renewal, timeout time.Duration) []Device
|
||||
@@ -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
lib/pmp/debug.go
Normal file
22
lib/pmp/debug.go
Normal file
@@ -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
lib/pmp/pmp.go
Normal file
105
lib/pmp/pmp.go
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user