From c49453c5195f70e624c00f9c4123b6ec67a0c330 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Wed, 13 Apr 2016 18:50:40 +0000 Subject: [PATCH] lib/pmp: Add NAT-PMP support (ref #698) GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/2968 --- cmd/syncthing/main.go | 5 +- lib/nat/registry.go | 33 ++- lib/pmp/debug.go | 22 ++ lib/pmp/pmp.go | 105 ++++++++++ .../AudriusButkevicius/go-nat-pmp/LICENSE | 13 ++ .../AudriusButkevicius/go-nat-pmp/README.md | 48 +++++ .../AudriusButkevicius/go-nat-pmp/natpmp.go | 189 ++++++++++++++++++ .../go-nat-pmp/natpmp_test.go | 13 ++ vendor/github.com/jackpal/gateway/LICENSE | 27 +++ vendor/github.com/jackpal/gateway/README.md | 7 + .../jackpal/gateway/gateway_darwin.go | 40 ++++ .../jackpal/gateway/gateway_linux.go | 75 +++++++ .../jackpal/gateway/gateway_test.go | 10 + .../jackpal/gateway/gateway_unimplemented.go | 14 ++ .../jackpal/gateway/gateway_windows.go | 43 ++++ vendor/manifest | 12 ++ 16 files changed, 650 insertions(+), 6 deletions(-) create mode 100644 lib/pmp/debug.go create mode 100644 lib/pmp/pmp.go create mode 100644 vendor/github.com/AudriusButkevicius/go-nat-pmp/LICENSE create mode 100644 vendor/github.com/AudriusButkevicius/go-nat-pmp/README.md create mode 100644 vendor/github.com/AudriusButkevicius/go-nat-pmp/natpmp.go create mode 100644 vendor/github.com/AudriusButkevicius/go-nat-pmp/natpmp_test.go create mode 100644 vendor/github.com/jackpal/gateway/LICENSE create mode 100644 vendor/github.com/jackpal/gateway/README.md create mode 100644 vendor/github.com/jackpal/gateway/gateway_darwin.go create mode 100644 vendor/github.com/jackpal/gateway/gateway_linux.go create mode 100644 vendor/github.com/jackpal/gateway/gateway_test.go create mode 100644 vendor/github.com/jackpal/gateway/gateway_unimplemented.go create mode 100644 vendor/github.com/jackpal/gateway/gateway_windows.go diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index cec27f16..27a1099b 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -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" ) diff --git a/lib/nat/registry.go b/lib/nat/registry.go index bab3d3c1..025ded15 100644 --- a/lib/nat/registry.go +++ b/lib/nat/registry.go @@ -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 } diff --git a/lib/pmp/debug.go b/lib/pmp/debug.go new file mode 100644 index 00000000..7de9a16f --- /dev/null +++ b/lib/pmp/debug.go @@ -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") +} diff --git a/lib/pmp/pmp.go b/lib/pmp/pmp.go new file mode 100644 index 00000000..178dd8db --- /dev/null +++ b/lib/pmp/pmp.go @@ -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 +} diff --git a/vendor/github.com/AudriusButkevicius/go-nat-pmp/LICENSE b/vendor/github.com/AudriusButkevicius/go-nat-pmp/LICENSE new file mode 100644 index 00000000..249514b0 --- /dev/null +++ b/vendor/github.com/AudriusButkevicius/go-nat-pmp/LICENSE @@ -0,0 +1,13 @@ + Copyright 2013 John Howard Palevich + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/AudriusButkevicius/go-nat-pmp/README.md b/vendor/github.com/AudriusButkevicius/go-nat-pmp/README.md new file mode 100644 index 00000000..15f72fd8 --- /dev/null +++ b/vendor/github.com/AudriusButkevicius/go-nat-pmp/README.md @@ -0,0 +1,48 @@ +go-nat-pmp +========== + +A Go language client for the NAT-PMP internet protocol for port mapping and discovering the external +IP address of a firewall. + +NAT-PMP is supported by Apple brand routers and open source routers like Tomato and DD-WRT. + +See http://tools.ietf.org/html/draft-cheshire-nat-pmp-03 + +Get the package +--------------- + + go get -u github.com/jackpal/go-nat-pmp + +Usage +----- + + import natpmp "github.com/jackpal/go-nat-pmp" + + client := natpmp.NewClient(gatewayIP) + response, err := client.GetExternalAddress() + if err != nil { + return + } + print("External IP address:", response.ExternalIPAddress) + +Notes +----- + +There doesn't seem to be an easy way to programmatically determine the address of the default gateway. +(Linux and OSX have a "routes" kernel API that can be examined to get this information, but there is +no Go package for getting this information.) + +Clients +------- + +This library is used in the Taipei Torrent BitTorrent client http://github.com/jackpal/Taipei-Torrent + +Complete documentation +---------------------- + + http://godoc.org/github.com/jackpal/go-nat-pmp + +License +------- + +This project is licensed under the Apache License 2.0. diff --git a/vendor/github.com/AudriusButkevicius/go-nat-pmp/natpmp.go b/vendor/github.com/AudriusButkevicius/go-nat-pmp/natpmp.go new file mode 100644 index 00000000..4db7cbe9 --- /dev/null +++ b/vendor/github.com/AudriusButkevicius/go-nat-pmp/natpmp.go @@ -0,0 +1,189 @@ +package natpmp + +import ( + "fmt" + "net" + "time" + + "github.com/jackpal/gateway" +) + +// Implement the NAT-PMP protocol, typically supported by Apple routers and open source +// routers such as DD-WRT and Tomato. +// +// See http://tools.ietf.org/html/draft-cheshire-nat-pmp-03 +// +// Usage: +// +// client := natpmp.NewClient(gatewayIP) +// response, err := client.GetExternalAddress() + +const nAT_PMP_PORT = 5351 + +// The recommended mapping lifetime for AddPortMapping +const RECOMMENDED_MAPPING_LIFETIME_SECONDS = 3600 + +// Client is a NAT-PMP protocol client. +type Client struct { + gateway net.IP + timeout time.Duration +} + +// Create a NAT-PMP client for the NAT-PMP server at the gateway. +func NewClient(gateway net.IP, timeout time.Duration) (nat *Client) { + return &Client{gateway, timeout} +} + +// Create a NAT-PMP client for the NAT-PMP server at the default gateway. +func NewClientForDefaultGateway(timeout time.Duration) (nat *Client, err error) { + var g net.IP + g, err = gateway.DiscoverGateway() + if err != nil { + return + } + nat = NewClient(g, timeout) + return +} + +// Results of the NAT-PMP GetExternalAddress operation +type GetExternalAddressResult struct { + SecondsSinceStartOfEpoc uint32 + ExternalIPAddress [4]byte +} + +// Get the external address of the router. +func (n *Client) GetExternalAddress() (result *GetExternalAddressResult, err error) { + msg := make([]byte, 2) + msg[0] = 0 // Version 0 + msg[1] = 0 // OP Code 0 + response, err := n.rpc(msg, 12) + if err != nil { + return + } + result = &GetExternalAddressResult{} + result.SecondsSinceStartOfEpoc = readNetworkOrderUint32(response[4:8]) + copy(result.ExternalIPAddress[:], response[8:12]) + return +} + +// Results of the NAT-PMP AddPortMapping operation +type AddPortMappingResult struct { + SecondsSinceStartOfEpoc uint32 + InternalPort uint16 + MappedExternalPort uint16 + PortMappingLifetimeInSeconds uint32 +} + +// Add (or delete) a port mapping. To delete a mapping, set the requestedExternalPort and lifetime to 0 +func (n *Client) AddPortMapping(protocol string, internalPort, requestedExternalPort int, lifetime int) (result *AddPortMappingResult, err error) { + var opcode byte + if protocol == "udp" { + opcode = 1 + } else if protocol == "tcp" { + opcode = 2 + } else { + err = fmt.Errorf("unknown protocol %v", protocol) + return + } + msg := make([]byte, 12) + msg[0] = 0 // Version 0 + msg[1] = opcode + writeNetworkOrderUint16(msg[4:6], uint16(internalPort)) + writeNetworkOrderUint16(msg[6:8], uint16(requestedExternalPort)) + writeNetworkOrderUint32(msg[8:12], uint32(lifetime)) + response, err := n.rpc(msg, 16) + if err != nil { + return + } + result = &AddPortMappingResult{} + result.SecondsSinceStartOfEpoc = readNetworkOrderUint32(response[4:8]) + result.InternalPort = readNetworkOrderUint16(response[8:10]) + result.MappedExternalPort = readNetworkOrderUint16(response[10:12]) + result.PortMappingLifetimeInSeconds = readNetworkOrderUint32(response[12:16]) + return +} + +func (n *Client) rpc(msg []byte, resultSize int) (result []byte, err error) { + var server net.UDPAddr + server.IP = n.gateway + server.Port = nAT_PMP_PORT + conn, err := net.DialUDP("udp", nil, &server) + if err != nil { + return + } + defer conn.Close() + + result = make([]byte, resultSize) + + timeout := time.Now().Add(n.timeout) + + err = conn.SetDeadline(timeout) + if err != nil { + return + } + + var bytesRead int + var remoteAddr *net.UDPAddr + for time.Now().Before(timeout) { + _, err = conn.Write(msg) + if err != nil { + return + } + + bytesRead, remoteAddr, err = conn.ReadFromUDP(result) + if err != nil { + if err.(net.Error).Timeout() { + continue + } + return + } + + if !remoteAddr.IP.Equal(n.gateway) { + // Ignore this packet. + continue + } + if bytesRead != resultSize { + err = fmt.Errorf("unexpected result size %d, expected %d", bytesRead, resultSize) + return + } + if result[0] != 0 { + err = fmt.Errorf("unknown protocol version %d", result[0]) + return + } + expectedOp := msg[1] | 0x80 + if result[1] != expectedOp { + err = fmt.Errorf("Unexpected opcode %d. Expected %d", result[1], expectedOp) + return + } + resultCode := readNetworkOrderUint16(result[2:4]) + if resultCode != 0 { + err = fmt.Errorf("Non-zero result code %d", resultCode) + return + } + // If we got here the RPC is good. + return + } + + err = fmt.Errorf("Timed out trying to contact gateway") + return +} + +func writeNetworkOrderUint16(buf []byte, d uint16) { + buf[0] = byte(d >> 8) + buf[1] = byte(d) +} + +func writeNetworkOrderUint32(buf []byte, d uint32) { + buf[0] = byte(d >> 24) + buf[1] = byte(d >> 16) + buf[2] = byte(d >> 8) + buf[3] = byte(d) +} + +func readNetworkOrderUint16(buf []byte) uint16 { + return (uint16(buf[0]) << 8) | uint16(buf[1]) +} + +func readNetworkOrderUint32(buf []byte) uint32 { + return (uint32(buf[0]) << 24) | (uint32(buf[1]) << 16) | (uint32(buf[2]) << 8) | uint32(buf[3]) +} diff --git a/vendor/github.com/AudriusButkevicius/go-nat-pmp/natpmp_test.go b/vendor/github.com/AudriusButkevicius/go-nat-pmp/natpmp_test.go new file mode 100644 index 00000000..28d28a22 --- /dev/null +++ b/vendor/github.com/AudriusButkevicius/go-nat-pmp/natpmp_test.go @@ -0,0 +1,13 @@ +package natpmp + +import ( + "testing" +) + +func TestNatPMP(t *testing.T) { + client, err := NewClientForDefaultGateway() + if err != nil { + t.Errorf("NewClientForDefaultGateway() = %v,%v", client, err) + return + } +} diff --git a/vendor/github.com/jackpal/gateway/LICENSE b/vendor/github.com/jackpal/gateway/LICENSE new file mode 100644 index 00000000..c9efac32 --- /dev/null +++ b/vendor/github.com/jackpal/gateway/LICENSE @@ -0,0 +1,27 @@ +// Copyright (c) 2010 Jack Palevich. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/jackpal/gateway/README.md b/vendor/github.com/jackpal/gateway/README.md new file mode 100644 index 00000000..49cc0b85 --- /dev/null +++ b/vendor/github.com/jackpal/gateway/README.md @@ -0,0 +1,7 @@ +# gateway + +A very simple library for discovering the IP address of the local LAN gateway. + +Provides implementations for Linux, OS X (Darwin) and Windows. + +Pull requests for other OSs happily considered! diff --git a/vendor/github.com/jackpal/gateway/gateway_darwin.go b/vendor/github.com/jackpal/gateway/gateway_darwin.go new file mode 100644 index 00000000..7fe7b519 --- /dev/null +++ b/vendor/github.com/jackpal/gateway/gateway_darwin.go @@ -0,0 +1,40 @@ +package gateway + +import ( + "bytes" + "io/ioutil" + "net" + "os/exec" +) + +func DiscoverGateway() (ip net.IP, err error) { + routeCmd := exec.Command("/sbin/route", "-n", "get", "0.0.0.0") + stdOut, err := routeCmd.StdoutPipe() + if err != nil { + return + } + if err = routeCmd.Start(); err != nil { + return + } + output, err := ioutil.ReadAll(stdOut) + if err != nil { + return + } + + // Darwin route out format is always like this: + // route to: default + // destination: default + // mask: default + // gateway: 192.168.1.1 + outputLines := bytes.Split(output, []byte("\n")) + for _, line := range outputLines { + if bytes.Contains(line, []byte("gateway:")) { + gatewayFields := bytes.Fields(line) + ip = net.ParseIP(string(gatewayFields[1])) + break + } + } + + err = routeCmd.Wait() + return +} diff --git a/vendor/github.com/jackpal/gateway/gateway_linux.go b/vendor/github.com/jackpal/gateway/gateway_linux.go new file mode 100644 index 00000000..333bde1d --- /dev/null +++ b/vendor/github.com/jackpal/gateway/gateway_linux.go @@ -0,0 +1,75 @@ +package gateway + +import ( + "bytes" + "io/ioutil" + "net" + "os/exec" +) + +func discoverGatewayUsingIp() (ip net.IP, err error) { + routeCmd := exec.Command("ip", "route", "show") + stdOut, err := routeCmd.StdoutPipe() + if err != nil { + return + } + if err = routeCmd.Start(); err != nil { + return + } + output, err := ioutil.ReadAll(stdOut) + if err != nil { + return + } + + // Linux 'ip route show' format looks like this: + // default via 192.168.178.1 dev wlp3s0 metric 303 + // 192.168.178.0/24 dev wlp3s0 proto kernel scope link src 192.168.178.76 metric 303 + outputLines := bytes.Split(output, []byte("\n")) + for _, line := range outputLines { + if bytes.Contains(line, []byte("default")) { + ipFields := bytes.Fields(line) + ip = net.ParseIP(string(ipFields[2])) + break + } + } + err = routeCmd.Wait() + return +} + +func discoverGatewayUsingRoute() (ip net.IP, err error) { + routeCmd := exec.Command("route", "-n") + stdOut, err := routeCmd.StdoutPipe() + if err != nil { + return + } + if err = routeCmd.Start(); err != nil { + return + } + output, err := ioutil.ReadAll(stdOut) + if err != nil { + return + } + + // Linux route out format is always like this: + // Kernel IP routing table + // Destination Gateway Genmask Flags Metric Ref Use Iface + // 0.0.0.0 192.168.1.1 0.0.0.0 UG 0 0 0 eth0 + outputLines := bytes.Split(output, []byte("\n")) + for _, line := range outputLines { + if bytes.Contains(line, []byte("0.0.0.0")) { + ipFields := bytes.Fields(line) + ip = net.ParseIP(string(ipFields[1])) + break + } + } + err = routeCmd.Wait() + return +} + +func DiscoverGateway() (ip net.IP, err error) { + ip, err = discoverGatewayUsingRoute() + if err != nil { + ip, err = discoverGatewayUsingIp() + } + return +} diff --git a/vendor/github.com/jackpal/gateway/gateway_test.go b/vendor/github.com/jackpal/gateway/gateway_test.go new file mode 100644 index 00000000..b127b02d --- /dev/null +++ b/vendor/github.com/jackpal/gateway/gateway_test.go @@ -0,0 +1,10 @@ +package gateway + +import "testing" + +func TestGateway(t *testing.T) { + ip, err := DiscoverGateway() + if err != nil { + t.Errorf("DiscoverGateway() = %v,%v", ip, err) + } +} diff --git a/vendor/github.com/jackpal/gateway/gateway_unimplemented.go b/vendor/github.com/jackpal/gateway/gateway_unimplemented.go new file mode 100644 index 00000000..35042b91 --- /dev/null +++ b/vendor/github.com/jackpal/gateway/gateway_unimplemented.go @@ -0,0 +1,14 @@ +// +build !darwin,!linux,!windows + +package gateway + +import ( + "fmt" + "net" + "runtime" +) + +func DiscoverGateway() (ip net.IP, err error) { + err = fmt.Errorf("DiscoverGateway not implemented for OS %s", runtime.GOOS) + return +} diff --git a/vendor/github.com/jackpal/gateway/gateway_windows.go b/vendor/github.com/jackpal/gateway/gateway_windows.go new file mode 100644 index 00000000..282c8f68 --- /dev/null +++ b/vendor/github.com/jackpal/gateway/gateway_windows.go @@ -0,0 +1,43 @@ +package gateway + +import ( + "bytes" + "io/ioutil" + "net" + "os/exec" +) + +func DiscoverGateway() (ip net.IP, err error) { + routeCmd := exec.Command("route", "print", "0.0.0.0") + stdOut, err := routeCmd.StdoutPipe() + if err != nil { + return + } + if err = routeCmd.Start(); err != nil { + return + } + output, err := ioutil.ReadAll(stdOut) + if err != nil { + return + } + + // Windows route output format is always like this: + // =========================================================================== + // Active Routes: + // Network Destination Netmask Gateway Interface Metric + // 0.0.0.0 0.0.0.0 192.168.1.1 192.168.1.100 20 + // =========================================================================== + // I'm trying to pick the active route, + // then jump 2 lines and pick the third IP + // Not using regex because output is quite standard from Windows XP to 8 (NEEDS TESTING) + outputLines := bytes.Split(output, []byte("\n")) + for idx, line := range outputLines { + if bytes.Contains(line, []byte("Active Routes:")) { + ipFields := bytes.Fields(outputLines[idx+2]) + ip = net.ParseIP(string(ipFields[2])) + break + } + } + err = routeCmd.Wait() + return +} diff --git a/vendor/manifest b/vendor/manifest index bde8ec81..449040c6 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -1,6 +1,12 @@ { "version": 0, "dependencies": [ + { + "importpath": "github.com/AudriusButkevicius/go-nat-pmp", + "repository": "https://github.com/AudriusButkevicius/go-nat-pmp", + "revision": "88a8019a0eff7e9db55f458230b867f0d7e5d48f", + "branch": "master" + }, { "importpath": "github.com/bkaradzic/go-lz4", "repository": "https://github.com/bkaradzic/go-lz4", @@ -43,6 +49,12 @@ "revision": "5f1c01d9f64b941dd9582c638279d046eda6ca31", "branch": "master" }, + { + "importpath": "github.com/jackpal/gateway", + "repository": "https://github.com/jackpal/gateway", + "revision": "32194371ec3f370166ee10a5ee079206532fdd74", + "branch": "master" + }, { "importpath": "github.com/juju/ratelimit", "repository": "https://github.com/juju/ratelimit",