summaryrefslogtreecommitdiffstats
path: root/nselib/isns.lua
diff options
context:
space:
mode:
Diffstat (limited to 'nselib/isns.lua')
-rw-r--r--nselib/isns.lua556
1 files changed, 556 insertions, 0 deletions
diff --git a/nselib/isns.lua b/nselib/isns.lua
new file mode 100644
index 0000000..4b7531a
--- /dev/null
+++ b/nselib/isns.lua
@@ -0,0 +1,556 @@
+---
+-- A minimal Internet Storage Name Service (iSNS) implementation
+--
+-- @author Patrik Karlsson <patrik@cqure.net>
+--
+
+local ipOps = require('ipOps')
+local match = require('match')
+local nmap = require('nmap')
+local stdnse = require('stdnse')
+local string = require "string"
+local table = require('table')
+_ENV = stdnse.module("isns", stdnse.seeall);
+
+iSCSI = {
+
+ NodeType = {
+ TARGET = 1,
+ INITIATOR = 2,
+ CONTROL = 4,
+ }
+
+}
+
+
+Header = {
+
+ VERSION = 1,
+
+ --
+ -- Creates a header instance
+ --
+ -- @param func_id number containing the function ID of the message
+ -- @param pdu_len number containing the length of the PDU
+ -- @param flags number containing the message flags
+ -- @param trans_id number containing the transaction id
+ -- @param seq_id number containing the sequence id
+ -- @return o new class instance
+ new = function(self, func_id, pdu_len, flags, trans_id, seq_id)
+ local o = {
+ ver = Header.VERSION,
+ func_id = func_id,
+ flags = flags,
+ trans_id = trans_id,
+ seq_id = seq_id,
+ pdu_len = pdu_len,
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ --
+ -- Parses a opaque string and creates a new Header instance
+ --
+ -- @param data opaques string containing the raw data
+ -- @return hdr new instance of Header
+ parse = function(data)
+ local hdr = Header:new()
+
+ hdr.ver, hdr.func_id, hdr.pdu_len, hdr.flags, hdr.trans_id,
+ hdr.seq_id = string.unpack(">I2I2I2I2I2I2", data)
+
+ return hdr
+ end,
+
+ --
+ -- Converts the instance to an opaque string
+ -- @return str containing an opaque string
+ __tostring = function(self)
+ return string.pack(">I2I2I2I2I2I2", self.ver, self.func_id,
+ self.pdu_len, self.flags, self.trans_id, self.seq_id )
+ end
+
+}
+
+Attribute = {
+
+ Tag = {
+ ISNS_TAG_DELIMITER = 0,
+ ISNS_TAG_ENTITY_IDENTIFIER = 1,
+ ISNS_TAG_ENTITY_PROTOCOL = 2,
+ ISNS_TAG_MGMT_IP_ADDRESS = 3,
+ ISNS_TAG_TIMESTAMP = 4,
+ ISNS_TAG_PROTOCOL_VERSION_RANGE = 5,
+ ISNS_TAG_REGISTRATION_PERIOD = 6,
+ ISNS_TAG_ENTITY_INDEX = 7,
+ ISNS_TAG_ENTITY_NEXT_INDEX = 8,
+ ISNS_TAG_ENTITY_ISAKMP_PHASE_1 = 11,
+ ISNS_TAG_ENTITY_CERTIFICATE = 12,
+ ISNS_TAG_PORTAL_IP_ADDRESS = 16,
+ ISNS_TAG_PORTAL_TCP_UDP_PORT = 17,
+ ISNS_TAG_PORTAL_SYMBOLIC_NAME = 18,
+ ISNS_TAG_ESI_INTERVAL = 19,
+ ISNS_TAG_ESI_PORT = 20,
+ ISNS_TAG_PORTAL_INDEX = 22,
+ ISNS_TAG_SCN_PORT = 23,
+ ISNS_TAG_PORTAL_NEXT_INDEX = 24,
+ ISNS_TAG_PORTAL_SECURITY_BITMAP = 27,
+ ISNS_TAG_PORTAL_ISAKMP_PHASE_1 = 28,
+ ISNS_TAG_PORTAL_ISAKMP_PHASE_2 = 29,
+ ISNS_TAG_PORTAL_CERTIFICATE = 31,
+ ISNS_TAG_ISCSI_NAME = 32,
+ ISNS_TAG_ISCSI_NODE_TYPE = 33,
+ ISNS_TAG_ISCSI_ALIAS = 34,
+ ISNS_TAG_ISCSI_SCN_BITMAP = 35,
+ ISNS_TAG_ISCSI_NODE_INDEX = 36,
+ ISNS_TAG_WWNN_TOKEN = 37,
+ ISNS_TAG_ISCSI_NODE_NEXT_INDEX = 38,
+ ISNS_TAG_ISCSI_AUTHMETHOD = 42,
+ ISNS_TAG_PG_ISCSI_NAME = 48,
+ ISNS_TAG_PG_PORTAL_IP_ADDR = 49,
+ ISNS_TAG_PG_PORTAL_TCP_UDP_PORT = 50,
+ ISNS_TAG_PG_TAG = 51,
+ ISNS_TAG_PG_INDEX = 52,
+ ISNS_TAG_PG_NEXT_INDEX = 53,
+ ISNS_TAG_FC_PORT_NAME_WWPN = 64,
+ ISNS_TAG_PORT_ID = 65,
+ ISNS_TAG_FC_PORT_TYPE = 66,
+ ISNS_TAG_SYMBOLIC_PORT_NAME = 67,
+ ISNS_TAG_FABRIC_PORT_NAME = 68,
+ ISNS_TAG_HARD_ADDRESS = 69,
+ ISNS_TAG_PORT_IP_ADDRESS = 70,
+ ISNS_TAG_CLASS_OF_SERVICE = 71,
+ ISNS_TAG_FC4_TYPES = 72,
+ ISNS_TAG_FC4_DESCRIPTOR = 73,
+ ISNS_TAG_FC4_FEATURES = 74,
+ ISNS_TAG_IFCP_SCN_BITMAP = 75,
+ ISNS_TAG_PORT_ROLE = 76,
+ ISNS_TAG_PERMANENT_PORT_NAME = 77,
+ ISNS_TAG_FC4_TYPE_CODE = 95,
+ ISNS_TAG_FC_NODE_NAME_WWNN = 96,
+ ISNS_TAG_SYMBOLIC_NODE_NAME = 97,
+ ISNS_TAG_NODE_IP_ADDRESS = 98,
+ ISNS_TAG_NODE_IPA = 99,
+ ISNS_TAG_PROXY_ISCSI_NAME = 101,
+ ISNS_TAG_SWITCH_NAME = 128,
+ ISNS_TAG_PREFERRED_ID = 129,
+ ISNS_TAG_ASSIGNED_ID = 130,
+ ISNS_TAG_VIRTUAL_FABRIC_ID = 131,
+ ISNS_TAG_SERVER_VENDOR_OUI = 256,
+ ISNS_TAG_DD_SET_ID = 2049,
+ ISNS_TAG_DD_SET_SYMBOLIC_NAME = 2050,
+ ISNS_TAG_DD_SET_STATUS = 2051,
+ ISNS_TAG_DD_SET_NEXT_ID = 2052,
+ ISNS_TAG_DD_ID = 2065,
+ ISNS_TAG_DD_SYMBOLIC_NAME = 2066,
+ ISNS_TAG_DD_MEMBER_ISCSI_INDEX = 2067,
+ ISNS_TAG_DD_MEMBER_ISCSI_NAME = 2068,
+ ISNS_TAG_DD_MEMBER_FC_PORT_NAME = 2069,
+ ISNS_TAG_DD_MEMBER_PORTAL_INDEX = 2070,
+ ISNS_TAG_DD_MEMBER_PORTAL_IP_ADDR = 2071,
+ ISNS_TAG_DD_MEMBER_PORTAL_TCP_UDP_PORT = 2072,
+ ISNS_TAG_DD_FEATURES = 2078,
+ ISNS_TAG_DD_NEXT_ID = 2079,
+ ISNS_VENDOR_SPECIFIC_SERVER_BASE = 257,
+ ISNS_VENDOR_SPECIFIC_ENTITY_BASE = 385,
+ ISNS_VENDOR_SPECIFIC_PORTAL_BASE = 513,
+ ISNS_VENDOR_SPECIFIC_NODE_BASE = 641,
+ ISNS_VENDOR_SPECIFIC_DD_BASE = 1024,
+ ISNS_VENDOR_SPECIFIC_DDSET_BASE = 1281,
+ ISNS_VENDOR_SPECIFIC_OTHER_BASE = 1537,
+ },
+
+ --
+ -- Creates a new Attribute instance
+ --
+ -- @param tag number containing the tag number
+ -- @param val string containing the tag value
+ -- @return o new Attribute instance
+ new = function(self, tag, val)
+ local o = { tag = tag, val = val or "" }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ --
+ -- Creates a new Attribute instance
+ --
+ -- @param data string containing an opaque string of raw data
+ -- @return attr new instance of Attribute
+ parse = function(data)
+ local attr = Attribute:new()
+
+ attr.tag, attr.val = string.unpack(">I4s4", data)
+
+ return attr
+ end,
+
+ --
+ -- Converts the instance to an opaque string
+ -- @return str containing an opaque string
+ __tostring = function(self)
+ return string.pack(">I4s4", self.tag, self.val)
+ end,
+
+}
+
+Attributes = {
+
+ --
+ -- Creates a new Attributes table
+ -- @return o new instance of Attributes
+ new = function(self)
+ local o = { attribs = {} }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ --
+ -- Adds a new Attribute to the table
+ -- @param tag number containing the tag number
+ -- @param val string containing the tag value
+ add = function(self, tag, val)
+ table.insert(self, Attribute:new(tag, val))
+ end,
+
+ --
+ -- Converts the instance to an opaque string
+ -- @return str containing an opaque string
+ __tostring = function(self)
+ local str = ""
+ for _, attr in ipairs(self) do
+ str = str .. tostring(attr)
+ end
+ return str
+ end,
+
+}
+
+Request = {
+
+ FuncId = {
+ DevAttrReg = 0x0001,
+ DevAttrQry = 0x0002,
+ DevGetNext = 0x0003,
+ DevDereg = 0x0004,
+ SCNReg = 0x0005,
+ SCNDereg = 0x0006,
+ SCNEvent = 0x0007,
+ SCN = 0x0008,
+ DDReg = 0x0009,
+ DDDereg = 0x000A,
+ DDSReg = 0x000B,
+ DDSDereg = 0x000C,
+ ESI = 0x000D,
+ Heartbeat = 0x000E,
+ },
+
+ --
+ -- Creates a new Request message
+ -- @param func_id number containing the function ID of the message
+ -- @param flags number containing the message flags
+ -- @param data string containing the opaque raw data
+ -- @param auth string containing the opaque raw auth data
+ -- @param trans_id number containing the transaction id
+ -- @param seq_id number containing the sequence id
+ -- @return o new instance of Request
+ new = function(self, func_id, flags, data, auth, trans_id, seq_id)
+ local o = {
+ header = Header:new(func_id, ( data and #data ) or 0, flags, ( trans_id or -1 ), ( seq_id or -1 )),
+ data = data or ""
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ --
+ -- Converts the instance to an opaque string
+ -- @return str containing an opaque string
+ __tostring = function(self)
+ return tostring(self.header) .. tostring(self.data) ..
+ ( self.auth and self.auth or "" )
+ end,
+
+
+}
+
+Response = {
+
+ Error = {
+ [0] = "Successful",
+ [1] = "Unknown Error",
+ [2] = "Message Format Error",
+ [3] = "Invalid Registration",
+ [4] = "RESERVED",
+ [5] = "Invalid Query",
+ [6] = "Source Unknown",
+ [7] = "Source Absent",
+ [8] = "Source Unauthorized",
+ [9] = "No Such Entry",
+ [10] = "Version Not Supported",
+ [11] = "Internal Error",
+ [12] = "Busy",
+ [13] = "Option Not Understood",
+ [14] = "Invalid Update",
+ [15] = "Message (FUNCTION_ID) Not Supported",
+ [16] = "SCN Event Rejected",
+ [17] = "SCN Registration Rejected",
+ [18] = "Attribute Not Implemented",
+ [19] = "FC_DOMAIN_ID Not Available",
+ [20] = "FC_DOMAIN_ID Not Allocated",
+ [21] = "ESI Not Available",
+ [22] = "Invalid Deregistration",
+ [23] = "Registration Feature Not Supported",
+ },
+
+ --
+ -- Creates a new Response instance
+ -- @return o new instance of Response
+ new = function(self)
+ local o = { attrs = Attributes:new() }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ --
+ -- Creates a new Response instance
+ --
+ -- @param data string containing an opaque string of raw data
+ -- @return attr new instance of Response
+ parse = function(data)
+ local hdr = Header.parse(data)
+ local pos = #(tostring(hdr)) + 1
+ local resp = Response:new()
+
+ resp.error, pos = string.unpack(">I4", data, pos)
+ if ( resp.error ~= 0 ) then
+ return resp
+ end
+
+ while( pos < #data ) do
+ local tag, val
+ tag, val, pos = string.unpack(">I4s4", data, pos)
+ resp.attrs:add(tag, val)
+ end
+ return resp
+ end,
+
+}
+
+
+Session = {
+
+ --
+ -- Creates a new Session instance
+ -- @param host table
+ -- @param port table
+ -- @return o instance of Session
+ new = function(self, host, port)
+ local o = {
+ host = host,
+ port = port,
+ seq_id = 0,
+ trans_id = 0,
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ --
+ -- Connects to the server
+ -- @return status true on success, false on failure
+ connect = function(self)
+ self.socket = nmap.new_socket()
+ self.socket:set_timeout(5000)
+ return self.socket:connect(self.host, self.port)
+ end,
+
+ --
+ -- Sends data to the server
+ -- @return status true on success, false on failure
+ -- @return err string containing the error message on failure
+ send = function(self, req)
+ if ( not(req.header) or not(req.header.seq_id) or not(req.header.trans_id) ) then
+ return false, "Failed to send invalid request"
+ end
+
+ -- update the sequence and transaction ID's
+ req.header.seq_id = self.seq_id
+ req.header.trans_id = self.trans_id
+
+ local status, err = self.socket:send(tostring(req))
+ self.trans_id = self.trans_id + 1
+
+ return status, err
+ end,
+
+ --
+ -- Receives data from the server
+ -- @return status true on success, false on failure
+ -- @return response instance of response
+ receive = function(self)
+ -- receive the 24 byte header
+ local status, buf_hdr = self.socket:receive_buf(match.numbytes(12), true)
+ if ( not(status) ) then
+ return status, buf_hdr
+ end
+
+ local hdr = Header.parse(buf_hdr)
+
+ -- receive the data
+ local buf_data = nil
+ status, buf_data = self.socket:receive_buf(match.numbytes(hdr.pdu_len), true)
+ if ( not(status) ) then
+ return status, buf_data
+ end
+
+ return true, Response.parse(buf_hdr .. buf_data)
+ end,
+
+ close = function(self)
+ return self.close()
+ end
+}
+
+
+Helper = {
+
+ --
+ -- Creates a new Helper instance
+ -- @param host param
+ -- @param port param
+ -- @return o new instance of Helper
+ new = function(self, host, port)
+ local o = { session = Session:new(host, port) }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ --
+ -- Connects to the server
+ -- @return status true on success, false on failure
+ connect = function(self)
+ return self.session:connect()
+ end,
+
+ --
+ -- Lists portals
+ -- @return status true on success, false on failure
+ -- @return results list of iSCSI nodes, err string on failure
+ listPortals = function(self)
+ local attribs, name = Attributes:new(), "iqn.control.node\0por"
+
+ attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NAME, name)
+ attribs:add(Attribute.Tag.ISNS_TAG_PORTAL_IP_ADDRESS)
+ attribs:add(Attribute.Tag.ISNS_TAG_DELIMITER)
+ attribs:add(Attribute.Tag.ISNS_TAG_PORTAL_IP_ADDRESS)
+ attribs:add(Attribute.Tag.ISNS_TAG_PORTAL_TCP_UDP_PORT)
+ attribs:add(Attribute.Tag.ISNS_TAG_ENTITY_IDENTIFIER)
+
+ local flags = 0x8c00 -- Sender is iSNS client, Last PDU, First PDU
+
+ local req = Request:new(Request.FuncId.DevAttrQry, flags, tostring(attribs))
+ if ( not(self.session:send(req)) ) then
+ return false, "Failed to send message to server"
+ end
+
+ local status, resp = self.session:receive()
+ if ( not(status) ) then
+ return false, "Failed to receive message from server"
+ end
+
+ local results = {}
+ local addr, proto, port
+ for _, attr in ipairs(resp.attrs) do
+ if ( attr.tag == Attribute.Tag.ISNS_TAG_PORTAL_IP_ADDRESS ) then
+ addr = attr.val
+ local is_ipv4 = string.unpack("c12", addr)
+ if ( is_ipv4 == "\0\0\0\0\0\0\0\0\0\0\xFF\xFF" ) then
+ addr = ipOps.str_to_ip(addr:sub(13, 16))
+ else
+ addr = ipOps.str_to_ip(addr:sub(1,16))
+ end
+ elseif ( attr.tag == Attribute.Tag.ISNS_TAG_PORTAL_TCP_UDP_PORT ) then
+ local s1
+ s1, port = string.unpack(">I2I2", attr.val)
+
+ if ( s1 == 1 ) then
+ proto = "udp"
+ elseif ( s1 == 0 ) then
+ proto = "tcp"
+ else
+ proto = "UNKNOWN"
+ end
+ elseif ( addr and proto and port ) then
+ table.insert(results, { addr = addr, proto = proto, port = port } )
+ addr, proto, port = nil, nil, nil
+ end
+ end
+ return true, results
+ end,
+
+ --
+ -- Lists iSCSI nodes
+ -- @return status true on success, false on failure
+ -- @return results list of iSCSI nodes, err string on failure
+ listISCINodes = function(self)
+ local attribs = Attributes:new()
+ local name = "iqn.control.node\0por"
+ attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NAME, name)
+ attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NAME)
+ attribs:add(Attribute.Tag.ISNS_TAG_DELIMITER)
+ attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NAME)
+ attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NODE_TYPE)
+
+ local flags = 0x8c00 -- Sender is iSNS client, Last PDU, First PDU
+
+ local req = Request:new(Request.FuncId.DevAttrQry, flags, tostring(attribs))
+ if ( not(self.session:send(req)) ) then
+ return false, "Failed to send message to server"
+ end
+
+ local status, resp = self.session:receive()
+ if ( not(status) ) then
+ return false, "Failed to receive message from server"
+ end
+
+ local name, ntype
+ local results = {}
+ for _, attr in ipairs(resp.attrs) do
+ if ( attr.tag == Attribute.Tag.ISNS_TAG_ISCSI_NAME ) then
+ name = string.unpack("z", attr.val)
+ elseif( attr.tag == Attribute.Tag.ISNS_TAG_ISCSI_NODE_TYPE ) then
+ local val = string.unpack(">I4", attr.val)
+ if ( val == iSCSI.NodeType.CONTROL ) then
+ ntype = "Control"
+ elseif ( val == iSCSI.NodeType.INITIATOR ) then
+ ntype = "Initiator"
+ elseif ( val == iSCSI.NodeType.TARGET ) then
+ ntype = "Target"
+ else
+ ntype = "Unknown"
+ end
+ end
+ if ( name and ntype ) then
+ table.insert(results, { name = name, type = ntype })
+ name, ntype = nil, nil
+ end
+ end
+ return true, results
+ end,
+
+ close = function(self)
+ return self.session:close()
+ end,
+
+}
+
+return _ENV;