223 lines
5.1 KiB
Lua
Raw Permalink Normal View History

---
-- This library implements the basics of NAT-PMP as described in the
-- NAT Port Mapping Protocol (NAT-PMP) draft:
-- o http://tools.ietf.org/html/draft-cheshire-nat-pmp-03
--
--
-- @author Patrik Karlsson <patrik@cqure.net>
--
local datetime = require "datetime"
local ipOps = require "ipOps"
local nmap = require "nmap"
local stdnse = require "stdnse"
local string = require "string"
_ENV = stdnse.module("natpmp", stdnse.seeall)
local ResultCode = {
SUCCESS = 0,
UNSUPPORTED_VERSION = 1,
NOT_AUTHORIZED = 2,
NETWORK_FAILURE = 3,
OUT_OF_RESOURCES = 4,
UNSUPPORTED_OPCODE = 5,
}
local ErrorMessage = {
[ResultCode.UNSUPPORTED_VERSION] = "The device did not support the protocol version",
[ResultCode.NOT_AUTHORIZED] = "The operation was not authorized",
[ResultCode.NETWORK_FAILURE] = "Network failure",
[ResultCode.OUT_OF_RESOURCES] = "The device is out of resources",
[ResultCode.UNSUPPORTED_OPCODE] = "The requested operation was not supported",
}
Request = {
GetWANIP = {
new = function(self)
local o = { version = 0, op = 0 }
setmetatable(o, self)
self.__index = self
return o
end,
__tostring = function(self)
return string.pack(">BB", self.version, self.op)
end,
},
MapPort = {
new = function(self, pubport, privport, proto, lifetime)
assert(proto == "udp" or proto == "tcp", "Unsupported protocol")
local o = {
version = 0,
pubport = pubport,
privport = privport,
proto = proto,
lifetime = lifetime or 3600
}
setmetatable(o, self)
self.__index = self
return o
end,
__tostring = function(self)
return string.pack(">BBI2I2I2I4",
self.version,
(self.proto=="udp" and 1 or 2),
0, -- reserved
self.privport, self.pubport,
self.lifetime)
end,
}
}
Response = {
GetWANIP = {
new = function(self, data)
local o = { data = data }
setmetatable(o, self)
self.__index = self
if ( o:parse() ) then
return o
end
end,
parse = function(self)
if ( #self.data ~= 12 ) then
return
end
local pos
self.version, self.op, self.rescode, pos = string.unpack(">BBI2", self.data)
if ( self.rescode ~= ResultCode.SUCCESS or self.op ~= 128 ) then
return
end
self.time, self.ip, pos = string.unpack(">I4I4", self.data, pos)
self.ip = ipOps.fromdword(self.ip)
self.time = datetime.format_timestamp(self.time)
return true
end,
},
MapPort = {
new = function(self, data)
local o = { data = data }
setmetatable(o, self)
self.__index = self
if ( o:parse() ) then
return o
end
end,
parse = function(self)
if ( #self.data ~= 16 ) then
return
end
local pos
self.version, self.op, self.rescode, pos = string.unpack(">BBI2", self.data)
if ( self.rescode ~= ResultCode.SUCCESS ) then
return
end
self.time, self.privport, self.pubport, self.lifetime, pos = string.unpack(">I4I2I2I4", self.data, pos)
return true
end,
}
}
Helper = {
new = function(self, host, port)
local o = { host = host, port = port }
setmetatable(o, self)
self.__index = self
return o
end,
exchPacket = function(self, data)
local socket = nmap.new_socket("udp")
socket:set_timeout(5000)
local status = socket:sendto(self.host, self.port, data)
if ( not(status) ) then
socket:close()
return false, "Failed to send request to device"
end
local response
status, response = socket:receive()
socket:close()
if ( not(status) ) then
return false, "Failed to receive response from router"
end
return true, response
end,
--- Gets the WAN ip of the router
getWANIP = function(self)
local packet = Request.GetWANIP:new()
local status, response = self:exchPacket(tostring(packet))
if ( not(status) ) then
return status, response
end
response = Response.GetWANIP:new(response)
if ( not(response) ) then
return false, "Failed to parse response from router"
end
return true, response
end,
--- Maps a public port to a private port
-- @param pubport number containing the public external port to map
-- @param privport number containing the private internal port to map
-- @param protocol string containing the protocol to map (udp|tcp)
-- @param lifetime [optional] number containing the lifetime in seconds
mapPort = function(self, pubport, privport, protocol, lifetime)
local packet = Request.MapPort:new(pubport, privport, protocol, lifetime)
local status, response = self:exchPacket(tostring(packet))
if ( not(status) ) then
return status, response
end
response = Response.MapPort:new(response)
if ( not(response) ) then
return false, "Failed to parse response from router"
end
return true, response
end,
unmapPort = function(self, pubport, privport)
return self:mapPort(pubport, privport, 0)
end,
unmapAllPorts = function(self)
return self.mapPort(0, 0, 0)
end,
}
return _ENV;