123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109 |
- // 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 https://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
- }
- if ip == nil || ip.IsUnspecified() {
- 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
- }
|