summaryrefslogtreecommitdiffstats
path: root/nselib/natpmp.lua
diff options
context:
space:
mode:
Diffstat (limited to 'nselib/natpmp.lua')
-rw-r--r--nselib/natpmp.lua222
1 files changed, 222 insertions, 0 deletions
diff --git a/nselib/natpmp.lua b/nselib/natpmp.lua
new file mode 100644
index 0000000..93c4647
--- /dev/null
+++ b/nselib/natpmp.lua
@@ -0,0 +1,222 @@
+---
+-- 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;