1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
|
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 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])
}
|