summaryrefslogtreecommitdiffstats
path: root/nselib/eigrp.lua
blob: a4d6b9d461bfd20677c1fd8feb9a648c5227e730 (plain)
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
--- A library supporting parsing and generating a limited subset of the Cisco' EIGRP packets.
--
-- @author Hani Benhabiles
-- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html
-- Version 0.1
--  19/07/2012 - First version.

local table = require "table"
local stdnse = require "stdnse"
local strbuf = require "strbuf"
local string = require "string"
local ipOps = require "ipOps"
local packet = require "packet"
_ENV = stdnse.module("eigrp", stdnse.seeall)


-- TLV Type constants
TLV = {
  PARAM = 0x0001,
  AUTH = 0x0002,
  SEQ = 0x0003,
  SWVER = 0x0004,
  MSEQ = 0x0005,
  STUB = 0x0006,
  TERM = 0x0007,
  TIDLIST = 0x0008,
  REQ = 0x0101,
  INT = 0x0102,
  EXT = 0x0103,
  COM = 0x0104,
  INT6 = 0x0402,
  EXT6 = 0x0403,
  COM6 = 0x0404,
}

-- External protocols constants
EXT_PROTO = {
  NULL      = 0x00,
  IGRP      = 0x01,
  EIGRP     = 0x02,
  Static    = 0x03,
  RIP       = 0x04,
  HELLO     = 0x05,
  OSPF      = 0x06,
  ISIS      = 0x07,
  EGP       = 0x08,
  BGP       = 0x09,
  IDRP      = 0x10,
  Connected = 0x11,
}

-- Packets opcode constants
OPCODE = {
  UPDATE = 0x01,
  RESERVED = 0x02,
  QUERY = 0x03,
  REPLY = 0x04,
  HELLO = 0x05,
}

-- The EIGRP Class
EIGRP = {

  --- Creates a new instance of EIGRP class.
  -- @param opcode integer Opcode. Defaults to 5 (Hello)
  -- @param as integer Autonomous System. Defaults to 0.
  -- @param routerid integer virtual router ID. defaults to 0.
  -- @param flags integer flags field value. Defaults to 0.
  -- @param seq integer sequence value. Defaults to 0.
  -- @param ack integer acknowledge value. Defaults to 0.
  -- @param Checksum integer EIGRP packet checksum. Calculated automatically
  --                 if not manually set.
  -- @param Table TLVs table.
  -- @return o Instance of EIGRP
  new = function(self, opcode, as, routerid, flags, seq, ack, checksum, tlvs)
    local o = {
      ver = 2,
      opcode = opcode or TLV.HELLO,
      as = as or 0,
      routerid = routerid or 0,
      flags = flags or 0,
      seq = seq or 0x00,
      ack = ack or 0x00,
      checksum = checksum,
      tlvs = tlvs or {},
    }
    setmetatable(o, self)
    self.__index = self
    return o
  end,

  --- Parses a raw eigrp packet and returns a structured response.
  -- @param eigrp_raw string EIGRP Raw packet.
  -- @return response table Structured eigrp packet.
  parse = function(eigrp_raw)
    if type(eigrp_raw) ~= 'string' then
      stdnse.debug1("eigrp.lua: parse input should be string.")
      return
    end
    if #eigrp_raw < 20 then
      stdnse.debug1("eigrp.lua: raw packet size lower then 20.")
      return
    end
    local tlv
    local eigrp_packet = {}
    local index = 1
    eigrp_packet.ver,
    eigrp_packet.opcode,
    eigrp_packet.checksum,
    eigrp_packet.flags,
    eigrp_packet.seq,
    eigrp_packet.ack,
    eigrp_packet.routerid,
    eigrp_packet.as, index = string.unpack(">BBI2I4I4I4I2I2", eigrp_raw, index)
    eigrp_packet.tlvs = {}
    while index < #eigrp_raw do
      tlv = {}
      tlv.type, tlv.length, index = string.unpack(">I2I2", eigrp_raw, index)
      if tlv.length == 0x00 then
        -- In case someone wants to DoS us :)
        stdnse.debug1("eigrp.lua: stopped parsing due to null TLV length.")
        break
      end
      -- TODO: These padding calculations seem suspect, especially the ones
      -- that assume a static length for a variable-length field like TLV.SEQ
      if tlv.type == TLV.PARAM then
        -- Parameters
        local k = {}
        k[1], k[2], k[3], k[4], k[5], k[6], tlv.htime, index = string.unpack(">BBBBBBI2", eigrp_raw, index)
        tlv.k = k
        index = index + tlv.length - 12
      elseif tlv.type == TLV.AUTH then
        tlv.authtype,
        tlv.authlen,
        tlv.keyid,
        tlv.keyseq, index = string.unpack(">I2I2I4I4", eigrp_raw, index)
        -- Null pad == tlv.length - What was already parsed - authlen
        tlv.digest, index = string.unpack(">I2", eigrp_raw, index + (tlv.length - tlv.authlen - index + 1))
      elseif tlv.type == TLV.SEQ then
        -- Sequence
        tlv.address, index = string.unpack(">s2", eigrp_raw, index)
        tlv.address = ipOps.str_to_ip(tlv.address)
        index = index + tlv.length - 7
      elseif tlv.type == TLV.SWVER then
        -- Software version
        tlv.majv,
        tlv.minv,
        tlv.majtlv,
        tlv.mintlv, index = string.unpack(">BBBB", eigrp_raw, index)
        index = index + tlv.length - 8
      elseif tlv.type == TLV.MSEQ then
        -- Next Multicast Sequence
        tlv.mseq, index = string.unpack(">I4", eigrp_raw, index)
        index = index + tlv.length - 8
      elseif tlv.type == TLV.STUB then
        -- TODO
        stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        index = index + tlv.length - 4
      elseif tlv.type == TLV.TERM then
        -- TODO
        stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        index = index + tlv.length - 4
      elseif tlv.type == TLV.TIDLIST then
        -- TODO
        stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        index = index + tlv.length - 4
      elseif tlv.type == TLV.REQ then
        -- TODO
        stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        index = index + tlv.length - 4
      elseif tlv.type == TLV.INT then
        -- Internal Route
        tlv.nexth, index = string.unpack(">I4", eigrp_raw, index)
        tlv.nexth = ipOps.fromdword(tlv.nexth)
        tlv.mask, index = string.unpack(">I2", eigrp_raw, index + 15)
        -- Destination varies in length
        -- e.g trailing 0's are omitted
        -- if length = 29 => destination is 4 bytes
        -- if length = 28 => destination is 3 bytes
        -- if length = 27 => destination is 2 bytes
        -- if length = 26 => destination is 1 byte
        local dst = {0,0,0,0}
        for i = 1, (4 + tlv.length - 29) do
          dst[i], index = string.unpack("B", eigrp_raw, index)
        end
        tlv.dst = table.concat(dst, '.')
      elseif tlv.type == TLV.EXT then
        -- External Route
        tlv.nexth,
        tlv.orouterid,
        tlv.oas,
        tlv.tag,
        tlv.emetric,
        -- Skip 2 reserved bytes
        tlv.eproto,
        tlv.eflags,
        tlv.lmetrics,
        tlv.mask, index = string.unpack(">I4I4I4I4I4xxBBc16B", eigrp_raw, index)
        tlv.nexth = ipOps.fromdword(tlv.nexth)
        tlv.orouterid = ipOps.fromdword(tlv.orouterid)
        -- Destination varies in length
        -- if length = 49 => destination is 4 bytes
        -- if length = 48 => destination is 3 bytes
        -- if length = 47 => destination is 2 bytes
        -- if length = 46 => destination is 1 byte
        local dst = {0,0,0,0}
        for i = 1, (4 + tlv.length - 49) do
          dst[i], index = string.unpack("B", eigrp_raw, index)
        end
        tlv.dst = table.concat(dst, '.')
      elseif tlv.type == TLV.COM then
        -- TODO
        stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        index = index + tlv.length - 4
      elseif tlv.type == TLV.INT6 then
        -- TODO
        stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        index = index + tlv.length - 4
      elseif tlv.type == TLV.EXT6 then
        -- TODO
        stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        index = index + tlv.length - 4
      elseif tlv.type == TLV.COM6 then
        -- TODO
        stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        index = index + tlv.length - 4
      else
        stdnse.debug1("eigrp.lua: eigrp.lua: TLV type %d unknown.", tlv.type)
        index = index + tlv.length - 4
      end
      table.insert(eigrp_packet.tlvs, tlv)
    end
    return eigrp_packet
  end,

  --- Adds a TLV table to the table of TLVs.
  -- @param tlv TLV table.
  addTLV = function(self, tlv)
    if type(tlv) == 'table' then
      table.insert(self.tlvs, tlv)
    else
      stdnse.debug1("eigrp.lua: TLV should be a table, not %s", type(tlv))
    end
  end,

  --- Checks if TLV type is one that should contain routing information.
  -- @param tlvtype integer TLV type integer to check.
  -- @return status true if tlvtype is a routing information tlv.
  isRoutingTLV = function(tlvtype)
    if tlvtype == 0x101 or tlvtype == 0x102
      or tlvtype == 0x103 or tlvtype == 0x104
      or tlvtype == 0x402 or tlvtype == 0x403
      or tlvtype == 0x404 then
      return true
    end
  end,

  --- Sets the EIGRP version.
  -- @param ver integer version to set.
  setVersion = function(self, ver)
    self.ver = ver
  end,
  --- Sets the EIGRP Packet opcode
  -- @param opcode integer EIGRP opcode.
  setOpcode = function(self, opcode)
    self.opcode = opcode
  end,
  --- Sets the EIGRP packet checksum
  -- @param integer checksum Checksum to set.
  setChecksum = function(self, checksum)
    self.checksum = checksum
  end,
  --- Sets the EIGRP packet flags field.
  -- @param flags Flags integer value.
  setFlags = function(self, flags)
    self.flags = flags
    end,
    --- Sets the EIGRP packet sequence field.
    -- @param seq EIGRP sequence.
    setSequence = function(self, seq)
      self.seq = seq
    end,
    --- Sets the EIGRP Packet acknowledge field.
    -- @param ack EIGRP acknowledge.
    setAcknowledge = function(self, ack)
      self.ack = ack
    end,
    --- Sets the EIGRP Packet Virtual Router ID.
    -- @param routerid EIGRP Virtual Router ID.
    setRouterID = function(self, routerid)
      self.routerid = routerid
    end,
    --- Sets the EIGRP Packet Autonomous System.
    -- @param as EIGRP A.S.
    setAS = function(self, as)
      self.as = as
    end,
    --- Sets the EIGRP Packet tlvs
    -- @param tlvs table of EIGRP tlvs.
    setTlvs = function(self, tlvs)
      self.tlvs = tlvs
    end,
    --- Converts the request to a string suitable to be sent over a socket.
    -- @return data string containing the complete request to send over the socket
    __tostring = function(self)
      local data = strbuf.new()
      data = data .. string.pack(">BBI2I4I4I4I2I2",
        self.ver, -- Version 2
        self.opcode, -- Opcode: Hello
        self.checksum or 0, -- Calculated later.
        self.flags, -- Flags
        self.seq, -- Sequence 0
        self.ack, -- Acknowledge 0
        self.routerid, -- Virtual Router ID 0
        self.as) -- Autonomous system

      for _, tlv in pairs(self.tlvs) do
        if tlv.type == TLV.PARAM then
          data = data .. string.pack(">I2I2 BBBBBB I2",
            TLV.PARAM,
            12, -- Length
            tlv.k[1], tlv.k[2], tlv.k[3], tlv.k[4], tlv.k[5], tlv.k[6],
            tlv.htime)
        elseif tlv.type == TLV.AUTH then
          -- TODO
          stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        elseif tlv.type == TLV.SEQ then
          -- TODO
          stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        elseif tlv.type == TLV.SWVER then
          data = data .. string.pack(">I2I2 BB BB",
            TLV.SWVER,
            0x0008,
            tonumber(tlv.majv), tonumber(tlv.minv),
            tonumber(tlv.majtlv), tonumber(tlv.mintlv))
        elseif tlv.type == TLV.MSEQ then
          -- TODO
          stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        elseif tlv.type == TLV.STUB then
          -- TODO
          stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        elseif tlv.type == TLV.TERM then
          -- TODO
          stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        elseif tlv.type == TLV.TIDLIST then
          -- TODO
          stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        elseif tlv.type == TLV.REQ then
          -- TODO
          stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        elseif tlv.type == TLV.INT then
          -- TODO
          stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        elseif tlv.type == TLV.EXT then
          -- TODO
          stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        elseif tlv.type == TLV.COM then
          -- TODO
          stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        elseif tlv.type == TLV.INT6 then
          -- TODO
          stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        elseif tlv.type == TLV.EXT6 then
          -- TODO
          stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        elseif tlv.type == TLV.COM6 then
          -- TODO
          stdnse.debug1("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type)
        else
          stdnse.debug1("eigrp.lua: TLV type %d unknown.", tlv.type)
        end
      end
      data = strbuf.dump(data)
      -- In the end, correct the checksum if not manually set
      if not self.checksum then
        data = data:sub(1,2) .. string.pack(">I2", packet.in_cksum(data)) .. data:sub(5)
      end
      return data
    end,
  }

  return _ENV;