123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- package 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 http://tools.ietf.org/html/draft-cheshire-nat-pmp-03
- //
- // 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.
- 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) {
- 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])
- }
|