#! /usr/bin/env python ## PyRT: Python Routeing Toolkit ## ISIS module: provides ISIS listener and ISIS PDU parsers ## Copyright (C) 2001 Richard Mortier , Sprint ATL ## This program is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA ## 02111-1307 USA # # $Id: isis.py,v 1.16 2002/01/17 00:14:13 mort Exp $ # # refs: http://www.rware.demon.co.uk/isis.htm, RFC1195, RFC1142, # This is a good deal grimmer than the BGP module since ISIS, by default on # Ethernet/802.3 links, is encapsulated directly within the frame. As a # consequence we need PF_PACKET and SOCK_RAW to get it -- THESE ARE ONLY # SUPPORTED IN PYTHON >= 2.0. As a result this will not be as portable as I'd # like. Stick to Linux 2.2.x and higher kernels with packet sockets # (CONFIG_PACKET) enabled; I've tested on RH7.1 std. install. Also, it must # run as root :-(( # Explanation of which bits we slurp: we are looking for ISIS packets carried # in IEEE 802.3 frames. This means that we have the following octet layout: # MAC header (IEEE 802.3): # ss-ss-ss-ss-ss-ss :: <6:src MAC> # dd-dd-dd-dd-dd-dd :: <6:dst MAC> # ll-ll :: <2:length> == 0x05dc == 1500 (payload only) # LLC header (IEEE 802.2): # dsap :: <1:DSAP> == 0xfe ...by RFC1340, p53, "IEEE 802 Numbers of interest" # ssap :: <1:SSAP> == 0xfe ...("ISO CLNS IS 8473") # ctrl :: <1 or 2: control> == 0x03 ("unnumbered information") # In fact, from (after some moulinexing :-) # http://cell-relay.indiana.edu/cell-relay/docs/rfc/1483/1483.4.1.html # In LLC Encapsulation the protocol of the routed PDU is identified by # prefixing the PDU by an IEEE 802.2 LLC header, which is possibly followed by # an IEEE 802.1a SubNetwork Attachment Point (SNAP) header. ... The presence # of a SNAP header is indicated by the LLC header value 0xAA-AA-03. # ... # The LLC header value 0xFE-FE-03 identifies that a routed ISO PDU (see [6] # and Appendix B) follows. The Control field value 0x03 specifies Unnumbered # Information Command PDU. ... The routed ISO protocol is identified by a one # octet NLPID field that is part of Protocol Data. NLPID values are # administered by ISO and CCITT. They are defined in ISO/IEC TR 9577 [6] and # some of the currently defined ones are listed in Appendix C. # ... # Appendix C. Partial List of NLPIDs # 0x00 Null Network Layer or Inactive Set (not used with ATM) # 0x80 SNAP # 0x81 ISO CLNP # 0x82 ISO ESIS # 0x83 ISO ISIS # 0xCC Internet IP # ie. we have 14 octets MAC header, 3 octets LLC header, and then we are in # the ISIS packet, starting with the NLPID 0x83. Phew. # Note 1: AFI 49 (pfx on area code) is public CLNS space a la 10.x.x.x in IP # Note 2: Actually, although the intro. above says this is grimmer, it is in # fact quite a lot nicer once adjacency is established. ISIS is a much nicer # protocol than BGP which sucks high vacuum. import sys, getopt, socket, string, os.path, struct, time, select, fcntl, math from mutils import * #------------------------------------------------------------------------------- VERSION = "1.0" INDENT = " " RETX_THRESH = 1 RCV_BUF_SZ = 2048 MAC_PKT_LEN = 1514 MAC_HDR_LEN = 17 ISIS_PKT_LEN = 1500 ISIS_PDU_LEN = ISIS_PKT_LEN-3 ISIS_LLC_HDR = (0xfe, 0xfe, 0x03, 0x83) ISIS_HDR_LEN = 8 ISIS_HELLO_HDR_LEN = 19 ISIS_LSP_HDR_LEN = 19 ISIS_CSN_HDR_LEN = 25 ISIS_PSN_HDR_LEN = 9 AllL1ISs = struct.pack("6B", 0x01, 0x80, 0xc2, 0x00, 0x00, 0x14) AllL2ISs = struct.pack("6B", 0x01, 0x80, 0xc2, 0x00, 0x00, 0x15) ################################################################################ DLIST = [] NLPIDS = { 0x00L: "NULL", 0x80L: "SNAP", 0x81L: "CLNP", 0x82L: "ESIS", 0x83L: "ISIS", 0x8EL: "IPV6", 0xCCL: "IP", } DLIST = DLIST + [NLPIDS] MSG_TYPES = { 0L: "NULL", 2L: "ESH", 4L: "ISH", 6L: "RD", 15L: "L1LANHello", 16L: "L2LANHello", 17L: "PPHello", 18L: "L1LSP", 20L: "L2LSP", 24L: "L1CSN", 25L: "L2CSN", 26L: "L1PSN", 27L: "L2PSN", } DLIST = DLIST + [MSG_TYPES] CIRCUIT_TYPES = { 0L: "reserved", # ignore entire PDU 1L: "L1Circuit", 2L: "L2Circuit", 3L: "L1L2Circuit", } DLIST = DLIST + [CIRCUIT_TYPES] FLAGS = {1L: "SUPPORT_IP", 2L: "SUPPORT_CLNP", } DLIST = DLIST + [FLAGS] VLEN_FIELDS = { 0L: "Null", # null 1L: "AreaAddress", # area address 2L: "LSPIISNeighbor", # ISIS (CLNP) neighbour (in LSP) 3L: "ESNeighbor", # end system (CLNP) neighbour 4L: "PartDIS", # 5L: "PrefixNeighbor", # 6L: "IIHIISNeighbor", # ISIS (CLNP) neighbour (in ISH) 8L: "Padding", # zero padding 9L: "LSPEntries", # LSPs ack'd in this CSNP/PSNP 10L: "Authentication", # 12L: "OptionalChecksum", # 14L: "LSPBufferSize", # 22L: "TEIISNeighbor", # 128L: "IPIntReach", # 'internal' reachable IP subnets 129L: "ProtoSupported", # NLPIDs this IS can relay 130L: "IPExtReach", # 'external' reachable IP subnets 131L: "IPInterDomInfo", # interdomain routeing info 132L: "IPIfAddr", # IP address(es) of the interface 133L: "IPAuthInfo_ILLEGAL", # deprecated 134L: "TERouterID", # TE router ID 135L: "TEIPReach", # 'wide metric TLV' 137L: "DynamicHostname", # dynamic hostname support 180L: "LeafNode", # 222L: "MultipleTopologyISN", # 229L: "MultipleTopologies", # 232L: "IPv6IfAddr", # 235L: "MTIPReach", # 236L: "IPv6IPReach", # 240L: "ThreeWayHello", # 254L: "IPSumReach", # } DLIST = DLIST + [VLEN_FIELDS] STATES = { 0L: "NULL", 1L: "INITIALISING", 2L: "UP", 3L: "DOWN", } DLIST = DLIST + [STATES] for d in DLIST: for k in d.keys(): d[ d[k] ] = k ################################################################################ def padPkt(tgt_len, pkt): pad_len = tgt_len - len(pkt) if pad_len > 0: full, part = divmod(pad_len, 257) pkt = pkt + (full*struct.pack("BB 255s", VLEN_FIELDS["Padding"], 255, 255*'\000')) pkt = pkt + struct.pack("BB %ds" % (part-2, ), VLEN_FIELDS["Padding"], part-2, (part-2)*'\000') return pkt #------------------------------------------------------------------------------- def parseMacHdr(pkt): (dst_mac, src_mac, length, dsap, ssap, ctrl, nlpid) =\ struct.unpack(">6s 6s H B B B B", pkt[0:MAC_HDR_LEN+1]) if (dsap, ssap, ctrl, nlpid) != ISIS_LLC_HDR: raise LLCExc return (src_mac, dst_mac, length, dsap, ssap, ctrl) #------------------------------------------------------------------------------- def parseIsisHdr(pkt): (nlpid, hdr_len, ver_proto_id, resvd, msg_type, ver, eco, user_eco) =\ struct.unpack(">8B", pkt[0:ISIS_HDR_LEN]) return (nlpid, hdr_len, ver_proto_id, resvd, msg_type, ver, eco, user_eco) #------------------------------------------------------------------------------- def parsePsnHdr(pkt): (pdu_len, src_id) = struct.unpack("> H 7s", pkt[:ISIS_PSN_HDR_LEN]) return (pdu_len, src_id) #------------------------------------------------------------------------------- def parseCsnHdr(pkt): (pdu_len, src_id, start_lsp_id, end_lsp_id) =\ struct.unpack("> H 7s 8s 8s", pkt[:ISIS_CSN_HDR_LEN]) return (pdu_len, src_id, start_lsp_id, end_lsp_id) #------------------------------------------------------------------------------- def parseLspHdr(pkt): (pdu_len, lifetime, lsp_id, seq_no, cksm, bits) =\ struct.unpack("> HH 8s LHB", pkt[:ISIS_LSP_HDR_LEN]) lsp_id = struct.unpack("> 6sBB", lsp_id) return (pdu_len, lifetime, lsp_id, seq_no, cksm, bits) ################################################################################ def parseIsisMsg(msg_len, msg, verbose=1, level=0): (src_mac, dst_mac, length, dsap, ssap, ctrl) = parseMacHdr(msg) (nlpid, hdr_len, ver_proto_id, resvd, msg_type, ver, eco, user_eco) =\ parseIsisHdr(msg[MAC_HDR_LEN:MAC_HDR_LEN+ISIS_HDR_LEN]) if verbose > 1: print prtbin(level*INDENT, msg[:MAC_HDR_LEN]) if verbose > 0: print level*INDENT +\ "%s (len=%d):" % (MSG_TYPES[msg_type], length) print (level+1)*INDENT +\ "src mac: %s, dst mac: %s" %\ (str2hex(src_mac), str2hex(dst_mac)) print (level+1)*INDENT +\ "len: %d, LLC: 0x%0.2x.%0.2x.%0.2x" %\ (length, dsap, ssap, ctrl) if verbose > 1: print prtbin((level+1)*INDENT, msg[MAC_HDR_LEN:MAC_HDR_LEN+ISIS_HDR_LEN]) if verbose > 0: print (level+1)*INDENT +\ "hdr_len: %d, protocol id: %d, version: %d, " %\ (hdr_len, ver_proto_id, ver) +\ "eco: %d, user eco: %d" % (eco, user_eco) rv = {"T": msg_type, "L": msg_len, "H": {}, "V": {} } rv["H"]["SRC_MAC"] = src_mac rv["H"]["DST_MAC"] = dst_mac rv["H"]["LENGTH"] = length rv["H"]["DSAP"] = dsap rv["H"]["SSAP"] = ssap rv["H"]["CTRL"] = ctrl rv["H"]["NLPID"] = nlpid rv["H"]["HDR_LEN"] = hdr_len rv["H"]["VER_PROTO_ID"] = ver_proto_id rv["H"]["VER"] = ver rv["H"]["ECO"] = eco rv["H"]["USER_ECO"] = user_eco msg = msg[MAC_HDR_LEN+ISIS_HDR_LEN:] if msg_type in MSG_TYPES.keys(): if msg_type in (MSG_TYPES["L1LANHello"], MSG_TYPES["L2LANHello"]): (rv["V"]["CIRCUIT_TYPE"], rv["V"]["SRC_ID"], rv["V"]["HOLDTIMER"], rv["V"]["PDU_LEN"], rv["V"]["PRIO"], rv["V"]["LAN_ID"], rv["V"]["VFIELDS"]) = parseIsisIsh(msg_len, msg, verbose, level) elif msg_type == MSG_TYPES["PPHello"]: parseIsisPPIsh(msg_len, msg, verbose, level) elif msg_type in (MSG_TYPES["L1LSP"], MSG_TYPES["L2LSP"]): (rv["V"]["PDU_LEN"], rv["V"]["LIFETIME"], rv["V"]["LSP_ID"], rv["V"]["SEQ_NO"], rv["V"]["CKSM"], rv["V"]["BITS"], rv["V"]["VFIELDS"]) = parseIsisLsp(msg_len, msg, verbose, level) elif msg_type in (MSG_TYPES["L1CSN"], MSG_TYPES["L2CSN"]): (rv["V"]["PDU_LEN"], rv["V"]["SRC_ID"], rv["V"]["START_LSP_ID"], rv["V"]["END_LSP_ID"], rv["V"]["VFIELDS"]) = parseIsisCsn(msg_len, msg, verbose, level) elif msg_type in (MSG_TYPES["L1PSN"], MSG_TYPES["L2PSN"]): (rv["V"]["PDU_LEN"], rv["V"]["SRC_ID"], rv["V"]["VFIELDS"]) = parseIsisPsn(msg_len, msg, verbose, level) else: if verbose > 0: print level*INDENT + "[ *** %s *** ]" % MSG_TYPES[msg_type] else: if verbose > 0: print level*INDENT + "[ UNKNOWN ISIS message: ", `msg_type`, " ]" return rv ################################################################################ def parseIsisIsh(msg_len, msg, verbose=1, level=0): (circuit_type, src_id, holdtimer, pdu_len, prio, lan_id) = struct.unpack("> B 6s H H B 7s", msg[:ISIS_HELLO_HDR_LEN]) if verbose > 1: print prtbin(level*INDENT, msg[:ISIS_HELLO_HDR_LEN]) if verbose > 0: print (level+1)*INDENT +\ "circuit type: %s, holdtimer: %d, " %\ (CIRCUIT_TYPES[circuit_type], holdtimer) +\ "PDU len: %d, priority: %d" % (pdu_len, (prio&0x7f)) print (level+1)*INDENT + "src id: %s, LAN id: %s" %\ (str2hex(src_id), str2hex(lan_id)) vfields = parseVLenFields(msg[ISIS_HELLO_HDR_LEN:], verbose, level) return (circuit_type, src_id, holdtimer, pdu_len, prio, lan_id, vfields) #------------------------------------------------------------------------------- def parseIsisPPIsh(msg_len, msg, verbose=1, level=0): print level*INDENT + "[ *** PP ISH NOT PARSED *** ]" #------------------------------------------------------------------------------- def parseIsisLsp(msg_len, msg, verbose=1, level=0): (pdu_len, lifetime, lsp_id, seq_no, cksm, bits) = parseLspHdr(msg) if verbose > 0: if verbose > 1: print prtbin(level*INDENT, msg[:ISIS_LSP_HDR_LEN]) print (level+1)*INDENT +\ "PDU len: %d, lifetime: %d, seq.no: %d, cksm: %s" %\ (pdu_len, lifetime, seq_no, int2hex(cksm)) print (level+1)*INDENT +\ "LSP ID: src: %s, pn: %s, LSP no: %d" %\ (str2hex(lsp_id[0]), int2hex(lsp_id[1]), lsp_id[2]) p = bits & (1<<7) att = (bits & (1<<6)) * "error " + (bits & (1<<5)) * "expense " +\ (bits & (1<<4)) * "delay " + (bits & (1<<3)) * "default" hty = (bits & (1<<2)) >> 2 ist = bits & ((1<<1) | (1<<0)) print (level+1)*INDENT +\ "partition repair: %s, hippity: %s, type: %s" %\ (("no", "yes")[p], ("no", "yes")[hty], ("UNUSED", "L1", "UNUSED", "L1+L2")[ist]) print (level+1)*INDENT + "attached: %s" % att vfields = parseVLenFields(msg[ISIS_LSP_HDR_LEN:], verbose, level) return (pdu_len, lifetime, lsp_id, seq_no, cksm, bits, vfields) #------------------------------------------------------------------------------- def parseIsisCsn(msg_len, msg, verbose=1, level=0): (pdu_len, src_id, start_lsp_id, end_lsp_id) = parseCsnHdr(msg) if verbose > 0: if verbose > 1: print prtbin(level*INDENT, msg[:ISIS_CSN_HDR_LEN]) print (level+1)*INDENT +\ "PDU len: %d, src ID: %s" % (pdu_len, str2hex(src_id)) print (level+1)*INDENT +\ "start LSP ID: %s" % (str2hex(start_lsp_id),) print (level+1)*INDENT +\ "end LSP ID: %s" % (str2hex(end_lsp_id),) vfields = parseVLenFields(msg[ISIS_CSN_HDR_LEN:], verbose, level) return (pdu_len, src_id, start_lsp_id, end_lsp_id, vfields) #------------------------------------------------------------------------------- def parseIsisPsn(msg_len, msg, verbose=1, level=0): (pdu_len, src_id) = parsePsnHdr(msg) if verbose > 0: if verbose > 1: print prtbin(level*INDENT, msg[:ISIS_PSN_HDR_LEN]) print (level+1)*INDENT +\ "PDU len: %d, src ID: %s" % (pdu_len, str2hex(src_id)) vfields = parseVLenFields(msg[ISIS_PSN_HDR_LEN:], verbose, level) return (pdu_len, src_id, vfields) ################################################################################ def parseVLenFields(fields, verbose=1, level=0): vfields = {} while len(fields) > 1: # XXX: strange -- have seen single null byte vfields... (ftype, flen) = struct.unpack(">BB", fields[0:2]) if not vfields.has_key(ftype): vfields[ftype] = [] vfields[ftype].append( parseVLenField(ftype, flen, fields[2:2+flen], verbose, level+1) ) fields = fields[2+flen:] return vfields #------------------------------------------------------------------------------- def parseVLenField(ftype, flen, fval, verbose=1, level=0): rv = { "L" : flen, } if verbose > 1 and ftype not in (VLEN_FIELDS["Padding"], VLEN_FIELDS["Null"]): print prtbin(level*INDENT, `ftype`+`flen`+fval) if ftype in VLEN_FIELDS.keys(): if verbose > 0 and ftype not in (VLEN_FIELDS["Padding"], VLEN_FIELDS["Null"]): print level*INDENT +\ "field: %s, length: %d" % (VLEN_FIELDS[ftype], flen) level = level + 1 if ftype == VLEN_FIELDS["Null"]: pass elif ftype == VLEN_FIELDS["AreaAddress"]: ## 1 rv["V"] = [] areas = "" while len(fval) > 0: (l,) = struct.unpack("> B", fval[0]) rv["V"].append(fval[1:1+l]) areas = areas + '0x' + str2hex(fval[1:1+l]) + ", " fval = fval[1+l:] if verbose > 0: print level*INDENT + "area addresses: " + areas elif ftype == VLEN_FIELDS["LSPIISNeighbor"]: ## 2 rv["V"] = [] vflag = struct.unpack("> B", fval[0]) fval = fval[1:] cnt = 0 while len(fval) > 0: cnt = cnt + 1 default, delay, expense, error, nid =\ struct.unpack("> BBBB 7s", fval[0:11]) is_neighbour = { 'DEFAULT': default, 'DELAY' : delay, 'EXPENSE': expense, 'ERROR' : error, 'NID' : nid, } rv["V"].append(is_neighbour) if verbose > 0: print level*INDENT +\ "IS Neighbour %d: id: %s" % (cnt, str2hex(nid)) print (level+1)*INDENT +\ "default: %d, delay: %d, expense: %d, error: %d" %\ (default, delay, expense, error) fval = fval[11:] elif ftype == VLEN_FIELDS["ESNeighbor"]: ## 3 default, delay, expense, error = struct.unpack("> 4B", fval[0:4]) rv["V"] = { 'DEFAULT' : default, 'DELAY' : delay, 'EXPENSE' : expense, 'ERROR' : error, 'NIDS' : [] } if verbose > 0: print level*INDENT +\ "default: %d, delay: %d, expense: %d, error: %d" %\ (default, delay, expense, error) fval = fval[4:] cnt = 0 while len(fval) > 0: cnt = cnt + 1 (nid,) = struct.unpack("> 6s", fval[0:6]) rv["V"]["NIDS"].append(nid) if verbose > 0: print level*INDENT +\ "ES Neighbour %d: %s" % (cnt, str2hex(nid)) fval = fval[6:] elif ftype == VLEN_FIELDS["IIHIISNeighbor"]: ## 6 rv["V"] = [] cnt = 0 while len(fval) > 0: cnt = cnt + 1 (nid,) = struct.unpack("> 6s", fval[0:6]) rv["V"].append(nid) if verbose > 0: print level*INDENT +\ "IS Neighbour %d: %s" % (cnt, str2hex(nid)) fval = fval[6:] elif ftype == VLEN_FIELDS["Padding"]: ## 8 rv["V"] = None elif ftype == VLEN_FIELDS["LSPEntries"]: ## 9 rv["V"] = [] cnt = 0 while len(fval) > 0: cnt = cnt + 1 lifetime, lsp_id, lsp_seq_no, cksm =\ struct.unpack("> H 8s L H", fval[:16]) lsp_id = struct.unpack("> 6sBB", lsp_id) lsp_entry = { "ID" : lsp_id[0], "PN" : lsp_id[1], "NM" : lsp_id[2], "LIFETIME" : lifetime, "SEQ_NO" : lsp_seq_no, "CKSM" : cksm } rv["V"].append(lsp_entry) if verbose > 0: print level*INDENT +\ "%d: LSP ID: src: %s, pn: %s, LSP no: %d" %\ (cnt, str2hex(lsp_id[0]), int2hex(lsp_id[1]), lsp_id[2]) print (level+1)*INDENT +\ "lifetime: %d, seq.no: %d, cksm: %s" %\ (lifetime, lsp_seq_no, int2hex(cksm)) fval = fval[16:] elif ftype == VLEN_FIELDS["IPIntReach"]: ## 128 rv["V"] = [] cnt = 0 while len(fval) > 0: cnt = cnt + 1 default, delay, expense, error, addr, mask =\ struct.unpack("> 4B LL", fval[0:12]) ipif = { 'DEFAULT': default, 'DELAY' : delay, 'EXPENSE': expense, 'ERROR' : error, 'ADDR' : addr, 'MASK' : mask } rv["V"].append(ipif) if verbose > 0: print level*INDENT +\ "%d: default: %d, delay: %d, expense: %d, error: %d" %\ (cnt, default, delay, expense, error) print (level+1)*INDENT +\ "addr/mask: %s/%s" % (id2str(addr), id2str(mask)) fval = fval[12:] elif ftype == VLEN_FIELDS["ProtoSupported"]: ## 129 prots = struct.unpack("> %dB" % flen, fval) prots_strs = map(lambda x: '%s' % x, map(lambda x: NLPIDS[x], prots)) rv["V"] = prots_strs if verbose > 0: print level*INDENT + "protocols supported: " + `prots_strs` elif ftype == VLEN_FIELDS["IPExtReach"]: ## 130 rv["V"] = [] cnt = 0 while len(fval) > 0: cnt = cnt + 1 default, delay, expense, error, addr, mask =\ struct.unpack("> 4B LL", fval[0:12]) ipif = { 'DEFAULT': default, 'DELAY' : delay, 'EXPENSE': expense, 'ERROR' : error, 'ADDR' : addr, 'MASK' : mask } rv["V"].append(ipif) if verbose > 0: print level*INDENT +\ "%d: default: %d, delay: %d, expense: %d, error: %d" %\ (cnt, default, delay, expense, error) print (level+1)*INDENT +\ "addr/mask: %s/%s" % (id2str(addr), id2str(mask)) fval = fval[12:] elif ftype == VLEN_FIELDS["IPInterDomInfo"]: ## 131 rv["V"] = None if verbose > 0: print level*INDENT + "[ IPInterDomInfo ]" elif ftype == VLEN_FIELDS["IPIfAddr"]: ## 132 addrs = struct.unpack("> %dL" % (flen/4, ), fval) addrs_strs = map(lambda x: id2str(x), addrs) rv["V"] = addrs_strs if verbose > 0: print level*INDENT + "interface IP addresses: " + `addrs_strs` elif ftype == VLEN_FIELDS["DynamicHostname"]: ## 137 name = struct.unpack("> %ds" % flen, fval) rv["V"] = name if verbose > 0: print level*INDENT + "dynamic hostname: '%s'" % name else: if verbose > 0: print level*INDENT + "[ *** %s *** ]" % VLEN_FIELDS[ftype] else: if verbose > 0: print level*INDENT + \ "[ UNKNOWN ISIS variable length field: ", `ftype`, " ]" return rv ################################################################################ class LLCExc(Exception): pass class VLenFieldExc(Exception): pass #------------------------------------------------------------------------------- class Isis: _eth_p_802_2 = socket.htons(0x0004) _dev_str = "eth0" _version = 1 _version_proto_id = 1 _hold_multiplier = 3 _holdtimer = 10 #--------------------------------------------------------------------------- class Adj: def __init__(self, atype, rx_ish, tx_ish): self._state = STATES["INITIALISING"] self._type = atype self._tx_ish = tx_ish self._rx_ish = rx_ish self._rtx_at = 0 (src_mac, None, None, None, None, None) = parseMacHdr(rx_ish) self._nbr_mac_addr = src_mac hdr_start = MAC_HDR_LEN + ISIS_HDR_LEN hdr_end = hdr_start + ISIS_HELLO_HDR_LEN (None, src_id, ht, None, prio, lan_id) =\ struct.unpack(">B 6s H H B 7s", rx_ish[hdr_start:hdr_end]) self._holdtimer = ht self._nbr_src_id = src_id self._nbr_lan_id = lan_id self._nbr_areas = [] fields = rx_ish[MAC_HDR_LEN+ISIS_HDR_LEN+ISIS_HELLO_HDR_LEN:] while len(fields) > 0: (ftype, flen) = struct.unpack(">BB", fields[0:2]) fval = fields[2:2+flen] if ftype == VLEN_FIELDS["AreaAddress"]: while len(fval) > 0: (l,) = struct.unpack("B", fval[0]) self._nbr_areas.append(fval[1:1+l]) fval = fval[1+l:] fields = fields[2+flen:] def __repr__(self): ret = """st: %s, ht: %d, retx: %d, neighbour areas: %s, nbr src id: %s, lan id: %s""" %\ (STATES[self._state], self._holdtimer, self._rtx_at, `map(str2hex, self._nbr_areas)`, str2hex(self._nbr_src_id), str2hex(self._nbr_lan_id)) return ret #--------------------------------------------------------------------------- def __init__(self, dev, area_addr, src_id=None, lan_id=None, src_ip=None): self._sock = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, Isis._eth_p_802_2) self._sockaddr = (dev, 0x0000) self._sock.bind(self._sockaddr) self._sockname = self._sock.getsockname() # XXX HACK: want to query _sock for IP addr; can't figure out # how at the moment if src_ip: self._src_ip = src_ip else: self._src_ip = str2id(socket.gethostbyname(socket.gethostname())) self._src_mac = self._sockname[-1] self._area_addr = area_addr if src_id: self._src_id = src_id else: self._src_id = self._src_mac if lan_id: self._lan_id = lan_id else: self._lan_id = self._src_id + '\001' self._adjs = { } self._rcvd = "" self._mrtd = None def __repr__(self): ret = """Passive ISIS speaker, version %s: Src IP: %s, Src MAC: %s Area address: %s Src ID: %s LAN ID: %s Adjs: %s\n""" %\ (VERSION, id2str(self._src_ip), str2hex(self._src_mac), str2hex(self._area_addr), str2hex(self._src_id), str2hex(self._lan_id), `self._adjs`) return ret def close(self): self._sock.close() self._mrtd.close() #--------------------------------------------------------------------------- def recvMsg(self, verbose=1, level=0): self._rcvd = self._sock.recv(RCV_BUF_SZ) (src_mac, dst_mac, length, dsap, ssap, ctrl) = parseMacHdr(self._rcvd) if verbose > 2: print "%srecvMsg: recv: len=%d%s" %\ (level*INDENT, len(self._rcvd), prthex((level+1)*INDENT, self._rcvd)) if verbose > 1: print "%srecvMsg: src: %s\n dst: %s" %\ (level*INDENT, str2hex(src_mac), str2hex(dst_mac)) print " len: %d" % (length, ) print " dsap: %#0.2x, ssap: %#0.2x, ctl: %#0.2x" %\ (dsap, ssap, ctrl) return (len(self._rcvd), self._rcvd) def sendMsg(self, pkt, verbose=1, level=0): (src_mac, dst_mac, length, dsap, ssap, ctrl) = parseMacHdr(pkt) (nlpid, hdr_len, ver_proto_id, resvd, msg_type, ver, eco, user_eco) = parseIsisHdr(pkt) if DUMP_MRTD == 1: self._mrtd.writeIsisMsg(msg_type, len(pkt), pkt) elif DUMP_MRTD == 2: self._mrtd.writeIsis2Msg(msg_type, len(pkt), pkt) if verbose > 2: print "%ssendMsg: send: len=%d%s" %\ (level*INDENT, len(pkt), prthex((level+1)*INDENT, pkt)) if verbose > 1: print "%ssendMsg: src: %s\n dst: %s" %\ (level*INDENT, str2hex(src_mac), str2hex(dst_mac)) print " len: %d" % (length, ) print " dsap: %#0.2x, ssap: %#0.2x, ctl: %#0.2x" %\ (dsap, ssap, ctrl) if verbose > 0: parseIsisMsg(len(pkt), pkt, verbose, level) if len(pkt) <= MAC_PKT_LEN: self._sock.send(pkt) def parseMsg(self, verbose=1, level=0): try: (msg_len, msg) = self.recvMsg(verbose, level) except (LLCExc): if verbose > 1: print "[ *** Non ISIS frame received *** ]" return (nlpid, hdr_len, ver_proto_id, resvd, msg_type, ver, eco, user_eco) = parseIsisHdr(msg) if DUMP_MRTD == 1: self._mrtd.writeIsisMsg(msg_type, msg_len, msg) elif DUMP_MRTD == 2: self._mrtd.writeIsis2Msg(msg_type, msg_len, msg) if verbose > 2: print "%sparseMsg: len=%d%s" %\ (level*INDENT, msg_len, prthex((level+1)*INDENT, msg)) rv = parseIsisMsg(msg_len, msg, verbose, level) self.processFsm(msg, verbose, level) return rv #--------------------------------------------------------------------------- def mkMacHdr(self, dst_mac, src_mac): hdr = struct.pack(">6s 6s H 3B ", dst_mac, src_mac, ISIS_PKT_LEN, ISIS_LLC_HDR[0], ISIS_LLC_HDR[1], ISIS_LLC_HDR[2]) return hdr def mkIsisHdr(self, msg_type, hdr_len): nlpid = NLPIDS["ISIS"] ret = struct.pack("8B", nlpid, hdr_len, Isis._version_proto_id, 0, msg_type, Isis._version, 0, 0) return ret def mkIshHdr(self, circuit, src_id, holdtimer, pdu_len, prio, lan_id): ret = struct.pack(">B 6s H H B 7s", circuit, src_id, holdtimer, pdu_len, prio, lan_id) return ret def mkVLenField(self, ftype_str, flen, fval=None): ftype = VLEN_FIELDS[ftype_str] ret = struct.pack("2B", ftype, flen) if ftype == VLEN_FIELDS["AreaAddress"]: for i in range(len(fval)): ret = ret +\ struct.pack("B %ds" % fval[i][0], fval[i][0], fval[i][1]) elif ftype == VLEN_FIELDS["Padding"]: return padPkt(flen+2, "") elif ftype == VLEN_FIELDS["ProtoSupported"]: for i in range(flen): ret = ret + struct.pack("B", fval[i]) elif ftype == VLEN_FIELDS["IPIfAddr"]: for i in range(flen/4): ret = ret + struct.pack(">L", fval[i]) elif ftype == VLEN_FIELDS["IIHIISNeighbor"]: for i in range(flen/6): ret = ret + struct.pack("6s", fval[i]) else: raise VLenFieldExc return ret def mkIsh(self, ln, lan_id, holdtimer): isns = [] if ln == 1: dst_mac = AllL1ISs for adj in self._adjs.keys(): if self._adjs[adj].has_key(1): isns.append(str2mac(adj)) msg_type = MSG_TYPES["L1LANHello"] elif ln == 2: dst_mac = AllL2ISs for adj in self._adjs.keys(): if self._adjs[adj].has_key(2): isns.append(str2mac(adj)) msg_type = MSG_TYPES["L2LANHello"] ish = self.mkMacHdr(dst_mac, self._src_mac) ish = ish + self.mkIsisHdr(msg_type, ISIS_HDR_LEN + ISIS_HELLO_HDR_LEN) prio = 0 # we don't ever want to be elected Designated System ish = ish + self.mkIshHdr(CIRCUIT_TYPES["L1L2Circuit"], self._src_id, holdtimer, ISIS_PDU_LEN, prio, lan_id) ish = ish + self.mkVLenField("ProtoSupported", 1, (NLPIDS["IP"],)) ish = ish + self.mkVLenField("AreaAddress", 1+len(self._area_addr), ((len(self._area_addr), self._area_addr),)) ish = ish + self.mkVLenField("IPIfAddr", 4, (self._src_ip,)) if len(isns) > 0: ish = ish + self.mkVLenField("IIHIISNeighbor", len(isns)*6, isns) ish = padPkt(MAC_PKT_LEN, ish) return ish ############################################################################ def processFsm(self, msg, verbose=1, level=0): (src_mac, None, None, None, None, None) = parseMacHdr(msg) (None, None, None, None, msg_type, None, None, None) = parseIsisHdr(msg[MAC_HDR_LEN:]) hdr_start = MAC_HDR_LEN + ISIS_HDR_LEN hdr_end = hdr_start + ISIS_HELLO_HDR_LEN (None, src_id, None, None, None, lan_id) =\ struct.unpack("> B 6s H H B 7s", msg[hdr_start:hdr_end]) smac = str2hex(src_mac) if not self._adjs.has_key(smac): self._adjs[smac] = { } if msg_type in (MSG_TYPES["L1LANHello"], MSG_TYPES["L2LANHello"]): k = msg_type - 14 # L1 or L2? if not self._adjs[smac].has_key(k): # new adjacency adj = Isis.Adj(k, msg, self.mkIsh(k, self._lan_id, Isis._holdtimer)) self._adjs[smac][k] = adj else: # existing adjacency adj = self._adjs[smac][k] adj._state = STATES["UP"] adj._rx_ish = msg adj._tx_ish = self.mkIsh(k, lan_id, Isis._holdtimer*Isis._hold_multiplier) if adj._rtx_at <= RETX_THRESH: self.sendMsg(adj._tx_ish, verbose, level) else: pass #--------------------------------------------------------------------------- ################################################################################ if __name__ == "__main__": import mrtd #--------------------------------------------------------------------------- global VERBOSE, DUMP_MRTD VERBOSE = 1 DUMP_MRTD = 0 file_pfx = mrtd.DEFAULT_FILE file_sz = mrtd.DEFAULT_SIZE mrtd_type = None area_addr = None src_id = None lan_id = None #--------------------------------------------------------------------------- def usage(): print """Usage: %s [ options ] where options are ([*] required): -h|--help : Help -v|--verbose : Be verbose -q|--quiet : Be quiet -a|--area-addr : set the area address to which this IS belongs -i|--ip-addr : *** HACK *** set the IP address to advertise -s|--src-id : set the source ID of this IS -l|--lan-id : set the LAN ID of this IS (def: ":01") --device : Set the device to receive on (def: %s) -d|--dump : Dump MRTd::PROTOCOL_ISIS format -y|--dump-isis2 : Dump MRTd::PROTOCOL_ISIS2 format -f|--file : Set file prefix for MRTd dump (def: %s) -z|--size : Size of output file(s) (min: %d)""" %\ (os.path.basename(sys.argv[0]), Isis._dev_str, mrtd.DEFAULT_FILE, mrtd.MIN_FILE_SZ) sys.exit(0) #--------------------------------------------------------------------------- if len(sys.argv) < 2: usage() try: opts, args = getopt.getopt(sys.argv[1:], "hqvVdyf:s:l:a:z:i:", ("help", "quiet", "verbose", "VERBOSE", "dump", "dump-isis2", "file-pfx=", "file-size=", "device=", "src-id=", "lan-id=", "area-addr=", "ip-addr=" )) except (getopt.error): usage() for (x, y) in opts: if x in ('-h', '--help'): usage() elif x in ('-q', '--quiet'): VERBOSE = 0 elif x in ('-v', '--verbose'): VERBOSE = 2 elif x in ('-V', '--VERBOSE'): VERBOSE = 3 elif x in ('-d', '--dump'): DUMP_MRTD = 1 mrtd_type = mrtd.MSG_TYPES["PROTOCOL_ISIS"] elif x in ('-y', '--dump-isis2'): DUMP_MRTD = 2 mrtd_type = mrtd.MSG_TYPES["PROTOCOL_ISIS2"] elif x in ('-f', '--file-pfx'): file_pfx = y elif x in ('--device', ): Isis._dev_str = y elif x in ('-s', '--src-id'): src_id = map(lambda x: int(x, 16), string.split(y, '.')) src_id = struct.pack("6B", src_id[0], src_id[1], src_id[2], src_id[3], src_id[4], src_id[5]) elif x in ('-l', '--lan-id'): lan_id = map(lambda x: int(x, 16), string.split(y, '.')) lan_id = struct.pack("7B", lan_id[0], lan_id[1], lan_id[2], lan_id[3], lan_id[4], lan_id[5], lan_id[6]) elif x in ('-a', '--area-addr'): area_addr = map(lambda x: int(x, 16), string.split(y, '.')) # this is grim, but that's not important right now... area_addr_str = "" for i in range(len(area_addr)): area_addr_str = struct.pack("%ds B" % len(area_addr_str), area_addr_str, area_addr[i]) area_addr = area_addr_str elif x in ('-z', '--file-size'): file_sz = max(string.atof(y), mrtd.MIN_FILE_SZ) elif x in ('-i', '--ip-addr'): src_ip = str2id(y) else: usage() #--------------------------------------------------------------------------- if not area_addr: usage() isis = Isis(Isis._dev_str, area_addr, src_id, lan_id, src_ip) isis._mrtd = mrtd.Mrtd(file_pfx, "w+b", file_sz, mrtd_type, isis) if VERBOSE > 1: print `isis` try: timeout = Isis._holdtimer while 1: # main loop before = time.time() rfds, None, None = select.select([isis._sock], [], [], timeout) after = time.time() elapsed = after - before if rfds != []: # need to rx pkt(s) rv = isis.parseMsg(VERBOSE, 0) else: # need to tx pkt(s) of some sort timeout = Isis._holdtimer for mac in isis._adjs.keys(): for a in isis._adjs[mac].keys(): adj = isis._adjs[mac][a] adj._rtx_at = adj._rtx_at - elapsed if adj._rtx_at <= RETX_THRESH: isis.sendMsg(adj._tx_ish, VERBOSE, 0) adj._rtx_at = adj._holdtimer timeout = min(timeout, adj._rtx_at-RETX_THRESH) except (KeyboardInterrupt): isis.close() sys.exit(1) ################################################################################ ################################################################################