pax_global_header00006660000000000000000000000064136431427220014516gustar00rootroot0000000000000052 comment=09bcf27b1f8e62c13ab1b0950f9b5317e67cb9dd golang-github-jackpal-go-nat-pmp-1.0.2/000077500000000000000000000000001364314272200176255ustar00rootroot00000000000000golang-github-jackpal-go-nat-pmp-1.0.2/.travis.yml000066400000000000000000000002421364314272200217340ustar00rootroot00000000000000language: go go: - 1.13.4 - tip allowed_failures: - go: tip install: - go get -d -v ./... && go install -race -v ./... script: go test -race -v ./... golang-github-jackpal-go-nat-pmp-1.0.2/LICENSE000066400000000000000000000011171364314272200206320ustar00rootroot00000000000000 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. golang-github-jackpal-go-nat-pmp-1.0.2/README.md000066400000000000000000000031131364314272200211020ustar00rootroot00000000000000go-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 https://tools.ietf.org/rfc/rfc6886.txt [![Build Status](https://travis-ci.org/jackpal/go-nat-pmp.svg)](https://travis-ci.org/jackpal/go-nat-pmp) Get the package --------------- # Get the go-nat-pmp package. go get -u github.com/jackpal/go-nat-pmp Usage ----- Get one more package, used by the example code: go get -u github.com/jackpal/gateway Create a directory: cd ~/go mkdir -p src/hello cd src/hello Create a file hello.go with these contents: package main import ( "fmt" "github.com/jackpal/gateway" natpmp "github.com/jackpal/go-nat-pmp" ) func main() { gatewayIP, err := gateway.DiscoverGateway() if err != nil { return } client := natpmp.NewClient(gatewayIP) response, err := client.GetExternalAddress() if err != nil { return } fmt.Printf("External IP address: %v\n", response.ExternalIPAddress) } Build the example go build ./hello External IP address: [www xxx yyy zzz] 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. golang-github-jackpal-go-nat-pmp-1.0.2/natpmp.go000066400000000000000000000107401364314272200214550ustar00rootroot00000000000000package natpmp import ( "fmt" "net" "time" ) // Implement the NAT-PMP protocol, typically supported by Apple routers and open source // routers such as DD-WRT and Tomato. // // See https://tools.ietf.org/rfc/rfc6886.txt // // Usage: // // client := natpmp.NewClient(gatewayIP) // response, err := client.GetExternalAddress() // The recommended mapping lifetime for AddPortMapping. const RECOMMENDED_MAPPING_LIFETIME_SECONDS = 3600 // Interface used to make remote procedure calls. type caller interface { call(msg []byte, timeout time.Duration) (result []byte, err error) } // Client is a NAT-PMP protocol client. type Client struct { caller caller timeout time.Duration } // Create a NAT-PMP client for the NAT-PMP server at the gateway. // Uses default timeout which is around 128 seconds. func NewClient(gateway net.IP) (nat *Client) { return &Client{&network{gateway}, 0} } // Create a NAT-PMP client for the NAT-PMP server at the gateway, with a timeout. // Timeout defines the total amount of time we will keep retrying before giving up. func NewClientWithTimeout(gateway net.IP, timeout time.Duration) (nat *Client) { return &Client{&network{gateway}, timeout} } // Results of the NAT-PMP GetExternalAddress operation. type GetExternalAddressResult struct { SecondsSinceStartOfEpoc uint32 ExternalIPAddress [4]byte } // Get the external address of the router. // // Note that this call can take up to 128 seconds to return. 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. // Note that this call can take up to 128 seconds to return. 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 // [2:3] is reserved. 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) { result, err = n.caller.call(msg, n.timeout) if err != nil { return } err = protocolChecks(msg, resultSize, result) return } func protocolChecks(msg []byte, resultSize int, result []byte) (err error) { if len(result) != resultSize { err = fmt.Errorf("unexpected result size %d, expected %d", len(result), 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 } 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]) } golang-github-jackpal-go-nat-pmp-1.0.2/natpmp_test.go000066400000000000000000000160531364314272200225170ustar00rootroot00000000000000package natpmp import ( "bytes" "fmt" "testing" "time" ) type callRecord struct { // The expected msg argument to call. msg []byte result []byte err error } type mockNetwork struct { // test object, used to report errors. t *testing.T cr callRecord } func (n *mockNetwork) call(msg []byte, timeout time.Duration) (result []byte, err error) { if bytes.Compare(msg, n.cr.msg) != 0 { n.t.Errorf("msg=%v, expected %v", msg, n.cr.msg) } return n.cr.result, n.cr.err } type getExternalAddressRecord struct { result *GetExternalAddressResult err error cr callRecord } /* Poor-man's generater code import "net" type callRecorder struct { callRecord callRecord } func (cr *callRecorder) observeCall(msg []byte, result []byte, err error) { cr.callRecord = callRecord{msg, result, err} } func TestRecordGetExternalAddress(t *testing.T) { cr := &callRecorder{} c := Client{&recorder{&network{net.IPv4(192,168,1,1)},cr}} result, err := c.GetExternalAddress() t.Logf("%#v, %#v, %#v", result, err, cr.callRecord) } func TestRecordAddPortMapping(t *testing.T) { cr := &callRecorder{} c := Client{&recorder{&network{net.IPv4(192,168,1,1)},cr}} result, err := c.AddPortMapping("tcp", 123, 0, 0) t.Logf("%#v, %#v, %#v", result, err, cr.callRecord) } */ func TestGetExternalAddress(t *testing.T) { dummyError := fmt.Errorf("dummy error") testCases := []getExternalAddressRecord{ { nil, dummyError, callRecord{[]uint8{0x0, 0x0}, nil, dummyError}, }, { &GetExternalAddressResult{0x13f24f, [4]uint8{0x49, 0x8c, 0x36, 0x9a}}, nil, callRecord{[]uint8{0x0, 0x0}, []uint8{0x0, 0x80, 0x0, 0x0, 0x0, 0x13, 0xf2, 0x4f, 0x49, 0x8c, 0x36, 0x9a}, nil}, }, } for i, testCase := range testCases { t.Logf("case %d", i) c := Client{&mockNetwork{t, testCase.cr}, 0} result, err := c.GetExternalAddress() if err != nil { if err != testCase.err { t.Error(err) } } else { if result.SecondsSinceStartOfEpoc != testCase.result.SecondsSinceStartOfEpoc { t.Errorf("result.SecondsSinceStartOfEpoc=%d != %d", result.SecondsSinceStartOfEpoc, testCase.result.SecondsSinceStartOfEpoc) } if bytes.Compare(result.ExternalIPAddress[:], testCase.result.ExternalIPAddress[:]) != 0 { t.Errorf("result.ExternalIPAddress=%v != %v", result.ExternalIPAddress, testCase.result.ExternalIPAddress) } } } } type addPortMappingRecord struct { protocol string internalPort int requestedExternalPort int lifetime int result *AddPortMappingResult err error cr callRecord } func TestAddPortMapping(t *testing.T) { dummyError := fmt.Errorf("dummy error") testCases := []addPortMappingRecord{ // Propagate error { "udp", 123, 456, 1200, nil, dummyError, callRecord{[]uint8{0x0, 0x1, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0}, nil, dummyError}, }, // Add UDP { "udp", 123, 456, 1200, &AddPortMappingResult{0x13feff, 0x7b, 0x1c8, 0x4b0}, nil, callRecord{ []uint8{0x0, 0x1, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0}, []uint8{0x0, 0x81, 0x0, 0x0, 0x0, 0x13, 0xfe, 0xff, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0}, nil, }, }, // Add TCP { "tcp", 123, 456, 1200, &AddPortMappingResult{0x140321, 0x7b, 0x1c8, 0x4b0}, nil, callRecord{ []uint8{0x0, 0x2, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0}, []uint8{0x0, 0x82, 0x0, 0x0, 0x0, 0x14, 0x3, 0x21, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0}, nil, }, }, // Remove UDP { "udp", 123, 0, 0, &AddPortMappingResult{0x1403d5, 0x7b, 0x0, 0x0}, nil, callRecord{ []uint8{0x0, 0x1, 0x0, 0x0, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, []uint8{0x0, 0x81, 0x0, 0x0, 0x0, 0x14, 0x3, 0xd5, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, nil, }, }, // Remove TCP { "tcp", 123, 0, 0, &AddPortMappingResult{0x140496, 0x7b, 0x0, 0x0}, nil, callRecord{ []uint8{0x0, 0x2, 0x0, 0x0, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, []uint8{0x0, 0x82, 0x0, 0x0, 0x0, 0x14, 0x4, 0x96, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, nil, }, }, } for i, testCase := range testCases { t.Logf("case %d", i) c := Client{&mockNetwork{t, testCase.cr}, 0} result, err := c.AddPortMapping(testCase.protocol, testCase.internalPort, testCase.requestedExternalPort, testCase.lifetime) if err != nil || testCase.err != nil { if err != testCase.err && fmt.Sprintf("%v", err) != fmt.Sprintf("%v", testCase.err) { t.Errorf("err=%v != %v", err, testCase.err) } } else { if result.SecondsSinceStartOfEpoc != testCase.result.SecondsSinceStartOfEpoc { t.Errorf("result.SecondsSinceStartOfEpoc=%d != %d", result.SecondsSinceStartOfEpoc, testCase.result.SecondsSinceStartOfEpoc) } if result.InternalPort != testCase.result.InternalPort { t.Errorf("result.InternalPort=%d != %d", result.InternalPort, testCase.result.InternalPort) } if result.MappedExternalPort != testCase.result.MappedExternalPort { t.Errorf("result.InternalPort=%d != %d", result.MappedExternalPort, testCase.result.MappedExternalPort) } if result.PortMappingLifetimeInSeconds != testCase.result.PortMappingLifetimeInSeconds { t.Errorf("result.InternalPort=%d != %d", result.PortMappingLifetimeInSeconds, testCase.result.PortMappingLifetimeInSeconds) } } } } func TestProtocolChecks(t *testing.T) { testCases := []addPortMappingRecord{ // Unexpected result size. { "tcp", 123, 456, 1200, nil, fmt.Errorf("unexpected result size %d, expected %d", 1, 16), callRecord{ []uint8{0x0, 0x2, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0}, []uint8{0x0}, nil, }, }, // Unknown protocol version. { "tcp", 123, 456, 1200, nil, fmt.Errorf("unknown protocol version %d", 1), callRecord{ []uint8{0x0, 0x2, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0}, []uint8{0x1, 0x82, 0x0, 0x0, 0x0, 0x14, 0x4, 0x96, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, nil, }, }, // Unexpected opcode. { "tcp", 123, 456, 1200, nil, fmt.Errorf("Unexpected opcode %d. Expected %d", 0x88, 0x82), callRecord{ []uint8{0x0, 0x2, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0}, []uint8{0x0, 0x88, 0x0, 0x0, 0x0, 0x14, 0x4, 0x96, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, nil, }, }, // Non-success result code. { "tcp", 123, 456, 1200, nil, fmt.Errorf("Non-zero result code %d", 17), callRecord{ []uint8{0x0, 0x2, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0}, []uint8{0x0, 0x82, 0x0, 0x11, 0x0, 0x14, 0x4, 0x96, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, nil, }, }, } for i, testCase := range testCases { t.Logf("case %d", i) c := Client{&mockNetwork{t, testCase.cr}, 0} result, err := c.AddPortMapping(testCase.protocol, testCase.internalPort, testCase.requestedExternalPort, testCase.lifetime) if err != testCase.err && fmt.Sprintf("%v", err) != fmt.Sprintf("%v", testCase.err) { t.Errorf("err=%v != %v", err, testCase.err) } if result != nil { t.Errorf("result=%v != nil", result) } } } golang-github-jackpal-go-nat-pmp-1.0.2/network.go000066400000000000000000000034041364314272200216460ustar00rootroot00000000000000package natpmp import ( "fmt" "net" "time" ) const nAT_PMP_PORT = 5351 const nAT_TRIES = 9 const nAT_INITIAL_MS = 250 // A caller that implements the NAT-PMP RPC protocol. type network struct { gateway net.IP } func (n *network) call(msg []byte, timeout time.Duration) (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() // 16 bytes is the maximum result size. result = make([]byte, 16) var finalTimeout time.Time if timeout != 0 { finalTimeout = time.Now().Add(timeout) } needNewDeadline := true var tries uint for tries = 0; (tries < nAT_TRIES && finalTimeout.IsZero()) || time.Now().Before(finalTimeout); { if needNewDeadline { nextDeadline := time.Now().Add((nAT_INITIAL_MS << tries) * time.Millisecond) err = conn.SetDeadline(minTime(nextDeadline, finalTimeout)) if err != nil { return } needNewDeadline = false } _, err = conn.Write(msg) if err != nil { return } var bytesRead int var remoteAddr *net.UDPAddr bytesRead, remoteAddr, err = conn.ReadFromUDP(result) if err != nil { if err.(net.Error).Timeout() { tries++ needNewDeadline = true continue } return } if !remoteAddr.IP.Equal(n.gateway) { // Ignore this packet. // Continue without increasing retransmission timeout or deadline. continue } // Trim result to actual number of bytes received if bytesRead < len(result) { result = result[:bytesRead] } return } err = fmt.Errorf("Timed out trying to contact gateway") return } func minTime(a, b time.Time) time.Time { if a.IsZero() { return b } if b.IsZero() { return a } if a.Before(b) { return a } return b } golang-github-jackpal-go-nat-pmp-1.0.2/recorder.go000066400000000000000000000006221364314272200217610ustar00rootroot00000000000000package natpmp import "time" type callObserver interface { observeCall(msg []byte, result []byte, err error) } // A caller that records the RPC call. type recorder struct { child caller observer callObserver } func (n *recorder) call(msg []byte, timeout time.Duration) (result []byte, err error) { result, err = n.child.call(msg, timeout) n.observer.observeCall(msg, result, err) return }