From f86d061e005d419639b98ec6c6c28e551e2af021 Mon Sep 17 00:00:00 2001 From: jchauhan Date: Tue, 14 Jul 2020 14:28:24 -0400 Subject: [PATCH 1/4] Changes in frr_config.py to support running local docker image. Support for scapy arguments added. Some additional scapy scripts to run inside routers. --- frrouting_automation/frr_config.py | 30 +- frrouting_automation/router_id_name.txt | 3 + .../scapy_scripts/arp_scan.py | 15 + frrouting_automation/scapy_scripts/hello.py | 17 + .../scapy_scripts/scapy_ospf.py | 466 ++++++++++++++++++ scapyintegration/Dockerfile | 2 +- 6 files changed, 528 insertions(+), 5 deletions(-) create mode 100644 frrouting_automation/router_id_name.txt create mode 100644 frrouting_automation/scapy_scripts/arp_scan.py create mode 100644 frrouting_automation/scapy_scripts/hello.py create mode 100644 frrouting_automation/scapy_scripts/scapy_ospf.py diff --git a/frrouting_automation/frr_config.py b/frrouting_automation/frr_config.py index 77b314b..59ed10e 100755 --- a/frrouting_automation/frr_config.py +++ b/frrouting_automation/frr_config.py @@ -7,7 +7,7 @@ import json import os import fileinput -import pyshark +#import pyshark def load_config(filepath): @@ -43,14 +43,14 @@ def create_networks(links, client): def create_containers(routers, client, topology): '''Create a container for each router''' - client.images.pull('colgatenetresearch/frr:version-6.0') + #client.images.pull('frrouting/frr') images = set() for router_name in sorted(routers.keys()): router = routers[router_name] print("Creating %s" % router.name) if router.image not in images: - client.images.pull(router.image) + #client.images.pull(router.image) images.add(router.image) client.containers.create(router.image, detach=True, name=router.name, labels=[topology], cap_add=["NET_ADMIN", "SYS_ADMIN"]) @@ -60,6 +60,7 @@ def config_routers(routers, client): router = routers[router_name] config_daemons(router) config_vtysh(router) + #scapyintegration(router,filename) if "bgp" in router.protocols: config_bgp(router) @@ -186,7 +187,7 @@ def __str__(self): class Router: def __init__(self, name, as_num): self.name = name - self.image = 'frrouting/frr' + self.image = 'scapyintergration:latest' self.links = [] self.protocols = set() self.as_num = as_num @@ -301,12 +302,25 @@ def shark(protocol): print('~', file=p) s.close() +def scapyintegration(routers,filename): + + + if (filename.lower()=="all"): + for router_name in sorted(routers.keys()): + os.system("docker cp ~/protocol-verification/frrouting_automation/scapy_scripts/. " + router_name +":/tmp/") + + else: + for router_name in sorted(routers.keys()): + os.system("docker cp ~/protocol-verification/frrouting_automation/scapy_scripts/"+filename+" " + router_name +":/tmp/"+filename) + + def main(): parser = argparse.ArgumentParser() parser.add_argument("-c", "--config", help="Path to JSON config file", required=True) parser.add_argument("-a", "--action", choices=['start', 'stop', 'restart'], help="Operation to perform", required=True) parser.add_argument("-t", "--tcp", choices=['tcpon', 'tcpoff'], help="Option to have tcpdump on or off", required=False) parser.add_argument("-s", "--shark", choices=['ospf', 'bgp'], help="Option to look at ospf or bgp packets with tshark", required=False) + parser.add_argument("-sc", "--scapy", help="Transfer all files using 'all' or a specific file using its filename", required=False) settings = parser.parse_args() @@ -339,6 +353,14 @@ def main(): os.system('rm tcpdump.pcap') elif (settings.tcp in ['tcpon']): os.system('docker run --rm --net=host -v $PWD:/tcpdump kaazing/tcpdump') + + if (settings.scapy in ['all']): + scapyintegration(routers,'all') + + else: + scapyintegration(routers,settings.scapy) + + client.close() if __name__ == "__main__": diff --git a/frrouting_automation/router_id_name.txt b/frrouting_automation/router_id_name.txt new file mode 100644 index 0000000..d677aca --- /dev/null +++ b/frrouting_automation/router_id_name.txt @@ -0,0 +1,3 @@ +router1 +router2 +router3 diff --git a/frrouting_automation/scapy_scripts/arp_scan.py b/frrouting_automation/scapy_scripts/arp_scan.py new file mode 100644 index 0000000..e4e5ca2 --- /dev/null +++ b/frrouting_automation/scapy_scripts/arp_scan.py @@ -0,0 +1,15 @@ +import scapy.all as scapy + +def Arp(ip): + print(ip) + arp_r = scapy.ARP(pdst=ip) + br = scapy.Ether(dst='ff:ff:ff:ff:ff:ff') + request = br/arp_r + answered, unanswered = scapy.srp(request, timeout=1) + print('\tIP \t\t\t MAC') + for i in answered: + ip, mac = i[1].psrc, i[1].hwsrc + print(ip, '\t\t' + mac) + + +Arp('172.17.0.0/24') # call the method \ No newline at end of file diff --git a/frrouting_automation/scapy_scripts/hello.py b/frrouting_automation/scapy_scripts/hello.py new file mode 100644 index 0000000..a22fa0c --- /dev/null +++ b/frrouting_automation/scapy_scripts/hello.py @@ -0,0 +1,17 @@ +import scapy.all as scapy + +packet = scapy.Ether(src='00:06:28:b9:85:31',dst='01:00:5e:00:00:05') + + +#packet.show() + + + +packet = packet/scapy.Dot1Q(vlan=33) + +#packet.show() +packet = packet/scapy.IP(src='192.16.2.2',dst='172.17.0.5') +packet = packet/scapy.OSPF_Hdr(src='192.16.2.2') +packet = packet/scapy.OSPF_Hello(router='172.17.2.2',backup='172.17.2.1',neighbor='172.17.2.1') +packet.show() +scapy.sendp(packet,loop=True,inter=0.1) \ No newline at end of file diff --git a/frrouting_automation/scapy_scripts/scapy_ospf.py b/frrouting_automation/scapy_scripts/scapy_ospf.py new file mode 100644 index 0000000..62a224a --- /dev/null +++ b/frrouting_automation/scapy_scripts/scapy_ospf.py @@ -0,0 +1,466 @@ +#!/usr/bin/env python +""" +OSPF extension for Scapy + +This module provides Scapy layers for the Open Shortest Path First +routing protocol as defined in RFC 2328. + +Copyright (c) 2008 Dirk Loss : mail dirk-loss de + +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. +""" + +# Contributors: +# Jochen Bartl : lobo c3a de + +from scapy.all import * + +EXT_VERSION = "v0.9.1" + + +class OSPFOptionsField(FlagsField): + def __init__(self, name="options", default=0, size=8, + names=["8", "E", "MC", "NP", "L", "DC", "O", "DN"]): + FlagsField.__init__(self, name, default, size, names) + +_OSPF_types = { 1: "Hello", + 2: "DBDesc", + 3: "LSReq", + 4: "LSUpd", + 5: "LSAck" } + +class OSPF_Hdr(Packet): + name = "OSPF Header" + fields_desc = [ + ByteField("version", 2), + ByteEnumField("type", 1, _OSPF_types), + ShortField("len", None), + IPField("src", "127.0.0.1"), + IPField("area", "0.0.0.0"), # default: backbone + XShortField("chksum", None), + ShortEnumField("authtype", 0, {0:"Null", 1:"Simple", 2:"Crypto"}), + # Null or Simple Authentication + ConditionalField(XLongField("authdata", 0), lambda pkt:pkt.authtype != 2), + # Crypto Authentication + ConditionalField(XShortField("reserved", 0), lambda pkt:pkt.authtype == 2), + ConditionalField(ByteField("keyid", 1), lambda pkt:pkt.authtype == 2), + ConditionalField(ByteField("authdatalen", 0), lambda pkt:pkt.authtype == 2), + ConditionalField(XIntField("seq", 0), lambda pkt:pkt.authtype == 2), + # TODO: Support authdata (which is appended to the packets as if it were padding) + ] + + def post_build(self, p, pay): + # TODO: Remove LLS data from pay + # LLS data blocks may be attached to OSPF Hello and DD packets + # The length of the LLS block shall not be included into the length of OSPF packet + # See + p += pay + l = self.len + if l is None: + l = len(p) + p = p[:2]+struct.pack("!H",l)+p[4:] + if self.chksum is None: + if self.authtype == 2: + ck = 0 # Crypto, see RFC 2328, D.4.3 + else: + # Checksum is calculated without authentication data + # Algorithm is the same as in IP() + ck = checksum(p[:16]+p[24:]) + p = p[:12]+chr(ck>>8)+chr(ck&0xff)+p[14:] + # TODO: Handle Crypto: Add message digest (RFC 2328, D.4.3) + return p + + def hashret(self): + return struct.pack("H",self.area)+self.payload.hashret() + + def answers(self, other): + if (isinstance(other,OSPF_Hdr) and + self.area == other.area and + self.type == 5): # Only acknowledgements answer other packets + return self.payload.answers(other.payload) + return 0 + + +class OSPF_Hello(Packet): + name = "OSPF Hello" + fields_desc = [ IPField("mask", "255.255.255.0"), + ShortField("hellointerval", 10), + OSPFOptionsField(), + ByteField("prio", 1), + IntField("deadinterval", 40), + IPField("router", "0.0.0.0"), + IPField("backup", "0.0.0.0"), + FieldListField ("neighbors", [], IPField("", "0.0.0.0"), length_from=lambda pkt: (pkt.underlayer.len - 44)) + ] + + def guess_payload_class(self, payload): + # check presence of LLS data block flag + if self.options & 0x10 == 0x10: + return OSPF_LLS_Hdr + else: + return Packet.guess_payload_class(self, payload) + +class RepeatedTlvListField(PacketListField): + # Code borrowed from scapy-cdp.py (Nicolas Bareil, Arnaud Ebalard) + def __init__(self, name, default, cls): + PacketField.__init__(self, name, default, cls) + + def getfield(self, pkt, s): + lst = [] + remain = s + while len(remain) > 0: + p = self.m2i(pkt,remain) + if Padding in p: + pad = p[Padding] + remain = pad.load + del(pad.underlayer.payload) + else: + remain = "" + lst.append(p) + return remain,lst + + def addfield(self, pkt, s, val): + return s+reduce(str.__add__, map(str, val),"") + +class LLS_ExtendedOptionsField(FlagsField): + def __init__(self, name="options", default=0, size=32, + names=["LR", "RS"]): + FlagsField.__init__(self, name, default, size, names) + +class LLS_Extended_Options(Packet): + name = "LLS Extended Options" + fields_desc = [ ShortField("type", 1), + ShortField("len", 4), + LLS_ExtendedOptionsField() + ] + +class LLS_Crypto_Auth(Packet): + name = "LLS Cryptographic Authentication" + fields_desc = [ ShortField("type", 2), + FieldLenField("length", 20, fmt="B", length_of=lambda x: x.authdata), + XIntField("sequence", "\x00\x00\x00\x00"), + StrLenField("authdata", "\x00" * 16, length_from=lambda x: x.length), + ] + + def post_build(self, p, pay): + p += pay + l = self.len + if l is None: + # length = len(sequence) + len(authdata) + len(payload) + l = len(p[3:]) + p = p[:2]+struct.pack("!H",l)+p[3:] + return p + +_OSPF_LLSclasses = { 1: "LLS_Extended_Options", + 2: "LLS_Crypto_Auth", + } + +def _LLSGuessPayloadClass(p, **kargs): + """ Guess the correct LLS class for a given payload """ + # This is heavily based on scapy-cdp.py by Nicolas Bareil and Arnaud Ebalard + cls = Raw + if len(p) >= 4: + typ = struct.unpack("!H", p[0:2])[0] + clsname = _OSPF_LLSclasses.get(typ, "Raw") + cls = globals()[clsname] + return cls(p, **kargs) + +class OSPF_LLS_Hdr(Packet): + name = "OSPF Link-local signaling" + fields_desc = [ XShortField("chksum", None), + ShortField("len", None), + RepeatedTlvListField("llstlv", [], _LLSGuessPayloadClass) + ] + + def post_build(self, p, pay): + p += pay + l = self.len + if l is None: + # Length in 32-bit words + l = len(p) / 4 + p = p[:2]+struct.pack("!H",l)+p[4:] + if self.chksum is None: + c = checksum(p) + p = chr((c>>8)&0xff)+chr(c&0xff) + p[2:] + return p + +_OSPF_LStypes = { 1: "router", + 2: "network", + 3: "summaryIP", + 4: "summaryASBR", + 5: "external"} + +_OSPF_LSclasses = { 1: "OSPF_Router_LSA", + 2: "OSPF_Network_LSA", + 3: "OSPF_SummaryIP_LSA", + 4: "OSPF_SummaryASBR_LSA", + 5: "OSPF_External_LSA" } + +def ospf_lsa_checksum(lsa): + """ Fletcher checksum for OSPF LSAs, returned as a 2 byte string. + + Give the whole LSA packet as argument. + For details on the algorithm, see RFC 2328 chapter 12.1.7 and RFC 905 Annex B. + """ + # This is based on the GPLed C implementation in Zebra + + CHKSUM_OFFSET = 16 + if len(lsa) < CHKSUM_OFFSET: + raise Exception("LSA Packet too short (%s bytes)" % len(lsa)) + + c0 = c1 = 0 + # Calculation is done with checksum set to zero + lsa = lsa[:CHKSUM_OFFSET] + "\x00\x00" + lsa[CHKSUM_OFFSET+2:] + for char in lsa[2:]: # leave out age + c0 += ord(char) + c1 += c0 + c0 %= 255 + c1 %= 255 + + x = ((len(lsa) - CHKSUM_OFFSET - 1) * c0 - c1) % 255 + if (x <= 0): + x += 255 + y = 510 - c0 - x + if (y > 255): + y -= 255 + #checksum = (x << 8) + y + return chr(x) + chr(y) + + +class OSPF_LSA_Hdr(Packet): + name = "OSPF LSA Header" + fields_desc = [ ShortField("age", 1), + OSPFOptionsField(), + ByteEnumField("type", 1, _OSPF_LStypes), + IPField("id", "0.0.0.0"), + IPField("adrouter", "0.0.0.0"), + XIntField("seq", 1), + XShortField("chksum", 0), + ShortField("len", 36)] + + def extract_padding(self, s): + return "", s + + +_OSPF_Router_LSA_types = { 1: "p2p", + 2: "transit", + 3: "stub", + 4: "virtual" } + +class OSPF_Link(Packet): + name = "OSPF Link" + fields_desc = [ IPField("id", "0.0.0.0"), + IPField("data", "2.2.2.2"), + ByteEnumField("type", 1, _OSPF_Router_LSA_types), + ByteField("toscount", 0), + ShortField("metric", 1), + # TODO: define correct conditions + ConditionalField(ByteField("tos", 0), lambda pkt: False), + ConditionalField(ByteField("reserved", 0), lambda pkt: False), + ConditionalField(ShortField("tosmetric", 0), lambda pkt: False) + ] + + def extract_padding(self,s): + return "", s + + +def _LSAGuessPayloadClass(p, **kargs): + """ Guess the correct LSA class for a given payload """ + # This is heavily based on scapy-cdp.py by Nicolas Bareil and Arnaud Ebalard + # XXX: This only works if all payload + cls = Raw + if len(p) >= 4: + typ = struct.unpack("!B", p[3])[0] + clsname = _OSPF_LSclasses.get(typ, "Raw") + cls = globals()[clsname] + return cls(p, **kargs) + + +class OSPF_BaseLSA(Packet): + """ An abstract base class for Link State Advertisements """ + + def post_build(self, p, pay): + length = self.len + if length is None: + length = len(p) + p = p[:18] + struct.pack("!H",length) + p[20:] + if self.chksum is None: + chksum = ospf_lsa_checksum(p) + p = p[:16] + chksum + p[18:] + return p # p+pay? + + def extract_padding(self, s): + length = self.len + return "", s + + +class OSPF_Router_LSA(OSPF_BaseLSA): + name = "OSPF Router LSA" + + fields_desc = [ ShortField("age", 1), + OSPFOptionsField(), + ByteField("type", 1), + IPField("id", "0.0.0.0"), + IPField("adrouter", "0.0.0.0"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + FlagsField("flags", 0, 16, ["16", "15", "14", "13", "12", "V", "E", "B", "8", "7", "6", "5", "4", "3", "2", "1"]), + FieldLenField("linkcount", None, count_of="linklist"), + PacketListField("linklist", [], OSPF_Link, + count_from=lambda pkt:pkt.linkcount, + length_from=lambda pkt:pkt.linkcount*12)] + + +class OSPF_Network_LSA(OSPF_BaseLSA): + name = "OSPF Network LSA" + fields_desc = [ ShortField("age", 1), + OSPFOptionsField(), + ByteField("type", 2), + IPField("id", "0.0.0.0"), + IPField("adrouter", "0.0.0.0"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + IPField("mask", "255.255.255.0"), + FieldListField("routerlist", [], IPField("", "0.0.0.1"), + length_from=lambda pkt: pkt.len - 24)] + + +class OSPF_SummaryIP_LSA(OSPF_BaseLSA): + name = "OSPF Summary LSA (IP Network)" + fields_desc = [ ShortField("age", 1), + OSPFOptionsField(), + ByteField("type", 3), + IPField("id", "0.0.0.0"), + IPField("advertrouter", "0.0.0.0"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + IPField("mask", "255.255.255.0"), + ByteField("reserved", 0), + X3BytesField("metric", 0), + # TODO: Define correct conditions + ConditionalField(ByteField("tos", 0), lambda pkt:False), + ConditionalField(X3BytesField("tosmetric", 0), lambda pkt:False) + ] + +class OSPF_SummaryASBR_LSA(OSPF_BaseLSA): + name = "OSPF Summary LSA (AS Boundary Router)" + fields_desc = [ ShortField("age", 1), + OSPFOptionsField(), + ByteField("type", 4), + IPField("id", "0.0.0.0"), + IPField("adrouter", "0.0.0.0"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + IPField("mask", "255.255.255.0"), + ByteField("reserved", 0), + X3BytesField("metric", 0), + # TODO: Define correct conditions + ConditionalField(ByteField("tos", 0), lambda pkt:False), + ConditionalField(X3BytesField("tosmetric", 0), lambda pkt:False) + ] + + +class OSPF_External_LSA(OSPF_BaseLSA): + name = "OSPF External LSA (ASBR)" + fields_desc = [ + ShortField("age", 1), + OSPFOptionsField(), + ByteField("type", 5), + IPField("id", "0.0.0.0"), + IPField("adrouter", "0.0.0.0"), + XIntField("seq", 1), + XShortField("chksum", None), + ShortField("len", None), + IPField("mask", "255.255.255.0"), + FlagsField("ebit", 0, 1, ["E"]), + BitField("reserved", 0, 7), + X3BytesField("metric", 1), # XXX + IPField("fwdaddr", "0.0.0.0"), + XIntField("tag", 0), + # TODO: Define correct conditions + ConditionalField(ByteField("tos", 0), lambda pkt:False), + ConditionalField(X3BytesField("tosmetric", 0), lambda pkt:False) + ] + +class OSPF_DBDesc(Packet): + name = "OSPF Database Description" + fields_desc = [ ShortField("mtu", 1500), + OSPFOptionsField(), + FlagsField("dbdescr", 0, 8, ["MS", "M", "I", "R", "4", "3", "2", "1"]), + IntField("ddseq", 1), + PacketListField("lsaheaders", None, OSPF_LSA_Hdr, + count_from = lambda pkt:None, + length_from = lambda pkt:pkt.underlayer.len - 24 - 8)] + + def guess_payload_class(self, payload): + # check presence of LLS data block flag + if self.options & 0x10 == 0x10: + return OSPF_LLS_Hdr + else: + return Packet.guess_payload_class(self, payload) + +class OSPF_LSReq_Item(Packet): + name = "OSPF Link State Request (item)" + fields_desc = [ IntEnumField("type", 1, _OSPF_LStypes), + IPField("id", "0.0.0.0"), + IPField("adrouter", "0.0.0.0")] + + def extract_padding(self, s): + return "", s + + +class OSPF_LSReq(Packet): + name = "OSPF Link State Request (container)" + fields_desc = [ PacketListField("requests", None, OSPF_LSReq_Item, + count_from = lambda pkt:None, + length_from = lambda pkt:pkt.underlayer.len - 24)] + +class OSPF_LSUpd(Packet): + name = "OSPF Link State Update" + fields_desc = [ FieldLenField("lsacount", None, fmt="!I", count_of="lsalist"), + PacketListField("lsalist", [], _LSAGuessPayloadClass, + count_from = lambda pkt:pkt.lsacount, + length_from = lambda pkt:pkt.underlayer.len - 24) + ] + + + +class OSPF_LSAck(Packet): + name = "OSPF Link State Acknowledgement" + fields_desc = [ PacketListField("lsaheaders", None, OSPF_LSA_Hdr, + count_from = lambda pkt:None, + length_from = lambda pkt:pkt.underlayer.len - 24)] + + def answers(self, other): + if isinstance(other,OSPF_LSUpd): + for reqLSA in other.lsalist: + for ackLSA in self.lsaheaders: + if (reqLSA.type == ackLSA.type and + reqLSA.seq == ackLSA.seq): + return 1 + return 0 + + +bind_layers(IP, OSPF_Hdr, proto=89) +bind_layers(OSPF_Hdr, OSPF_Hello, type=1) +bind_layers(OSPF_Hdr, OSPF_DBDesc, type=2) +bind_layers(OSPF_Hdr, OSPF_LSReq, type=3) +bind_layers(OSPF_Hdr, OSPF_LSUpd, type=4) +bind_layers(OSPF_Hdr, OSPF_LSAck, type=5) + + +if __name__ == "__main__": + interact(mydict=globals(), mybanner="OSPF extension %s" % EXT_VERSION) + \ No newline at end of file diff --git a/scapyintegration/Dockerfile b/scapyintegration/Dockerfile index 12b33b6..e700fb9 100644 --- a/scapyintegration/Dockerfile +++ b/scapyintegration/Dockerfile @@ -2,4 +2,4 @@ FROM frrouting/frr as source-builder USER root RUN apk add --update py-pip RUN pip install --upgrade pip \ - pip install scapy \ \ No newline at end of file + pip3 install --pre scapy[basic] \ \ No newline at end of file From 1d2224cdb2e38297319b1814e4085155cbd44b62 Mon Sep 17 00:00:00 2001 From: Aaron Gember-Jacobson Date: Wed, 15 Jul 2020 14:35:51 -0400 Subject: [PATCH 2/4] Base scapy Docker image on alpine instead of frr --- scapyintegration/Dockerfile | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/scapyintegration/Dockerfile b/scapyintegration/Dockerfile index e700fb9..119c70c 100644 --- a/scapyintegration/Dockerfile +++ b/scapyintegration/Dockerfile @@ -1,5 +1,2 @@ -FROM frrouting/frr as source-builder -USER root -RUN apk add --update py-pip -RUN pip install --upgrade pip \ - pip3 install --pre scapy[basic] \ \ No newline at end of file +FROM alpine:edge +RUN apk add --update scapy tcpdump From ad3b7289bf6207b572fdf6261f63e8c954451ccb Mon Sep 17 00:00:00 2001 From: jchauhan Date: Thu, 16 Jul 2020 10:37:09 -0400 Subject: [PATCH 3/4] Updated scapy_ospf file --- .../scapy_scripts/scapy_ospf.py | 914 ++++++++++++------ 1 file changed, 618 insertions(+), 296 deletions(-) diff --git a/frrouting_automation/scapy_scripts/scapy_ospf.py b/frrouting_automation/scapy_scripts/scapy_ospf.py index 62a224a..0dd8fd5 100644 --- a/frrouting_automation/scapy_scripts/scapy_ospf.py +++ b/frrouting_automation/scapy_scripts/scapy_ospf.py @@ -1,105 +1,131 @@ -#!/usr/bin/env python +# scapy.contrib.description = Open Shortest Path First (OSPF) +# scapy.contrib.status = loads + +# This file is part of Scapy +# Scapy 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 +# any later version. +# +# Scapy 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 Scapy. If not, see . + """ OSPF extension for Scapy - This module provides Scapy layers for the Open Shortest Path First -routing protocol as defined in RFC 2328. - +routing protocol as defined in RFC 2328 and RFC 5340. Copyright (c) 2008 Dirk Loss : mail dirk-loss de - -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. +Copyright (c) 2010 Jochen Bartl : jochen.bartl gmail com """ -# Contributors: -# Jochen Bartl : lobo c3a de -from scapy.all import * +import struct + +from scapy.packet import bind_layers, Packet +from scapy.fields import BitField, ByteEnumField, ByteField, \ + ConditionalField, DestIP6Field, FieldLenField, \ + FieldListField, FlagsField, IP6Field, IP6PrefixField, IPField, \ + IntEnumField, IntField, LenField, PacketListField, ShortEnumField, \ + ShortField, StrLenField, X3BytesField, XIntField, XLongField, XShortField +from scapy.layers.inet import IP, DestIPField +from scapy.layers.inet6 import IPv6, in6_chksum +from scapy.utils import fletcher16_checkbytes, checksum, inet_aton +from scapy.compat import orb +from scapy.config import conf -EXT_VERSION = "v0.9.1" +EXT_VERSION = "v0.9.2" class OSPFOptionsField(FlagsField): + def __init__(self, name="options", default=0, size=8, - names=["8", "E", "MC", "NP", "L", "DC", "O", "DN"]): + names=None): + if names is None: + names = ["MT", "E", "MC", "NP", "L", "DC", "O", "DN"] FlagsField.__init__(self, name, default, size, names) -_OSPF_types = { 1: "Hello", - 2: "DBDesc", - 3: "LSReq", - 4: "LSUpd", - 5: "LSAck" } + +_OSPF_types = {1: "Hello", + 2: "DBDesc", + 3: "LSReq", + 4: "LSUpd", + 5: "LSAck"} + + +class _NoLLSLenField(LenField): + """ + LenField that will ignore the size of OSPF_LLS_Hdr if it exists + in the payload + """ + + def i2m(self, pkt, x): + if x is None: + x = self.adjust(len(pkt.payload)) + if OSPF_LLS_Hdr in pkt: + x -= len(pkt[OSPF_LLS_Hdr]) + return x + class OSPF_Hdr(Packet): name = "OSPF Header" - fields_desc = [ - ByteField("version", 2), - ByteEnumField("type", 1, _OSPF_types), - ShortField("len", None), - IPField("src", "127.0.0.1"), - IPField("area", "0.0.0.0"), # default: backbone - XShortField("chksum", None), - ShortEnumField("authtype", 0, {0:"Null", 1:"Simple", 2:"Crypto"}), - # Null or Simple Authentication - ConditionalField(XLongField("authdata", 0), lambda pkt:pkt.authtype != 2), - # Crypto Authentication - ConditionalField(XShortField("reserved", 0), lambda pkt:pkt.authtype == 2), - ConditionalField(ByteField("keyid", 1), lambda pkt:pkt.authtype == 2), - ConditionalField(ByteField("authdatalen", 0), lambda pkt:pkt.authtype == 2), - ConditionalField(XIntField("seq", 0), lambda pkt:pkt.authtype == 2), - # TODO: Support authdata (which is appended to the packets as if it were padding) - ] + fields_desc = [ + ByteField("version", 2), + ByteEnumField("type", 1, _OSPF_types), + _NoLLSLenField("len", None, adjust=lambda x: x + 24), + IPField("src", "1.1.1.1"), + IPField("area", "0.0.0.0"), # default: backbone + XShortField("chksum", None), + ShortEnumField("authtype", 0, {0: "Null", 1: "Simple", 2: "Crypto"}), + # Null or Simple Authentication + ConditionalField(XLongField("authdata", 0), lambda pkt: pkt.authtype != 2), # noqa: E501 + # Crypto Authentication + ConditionalField(XShortField("reserved", 0), lambda pkt: pkt.authtype == 2), # noqa: E501 + ConditionalField(ByteField("keyid", 1), lambda pkt: pkt.authtype == 2), + ConditionalField(ByteField("authdatalen", 0), lambda pkt: pkt.authtype == 2), # noqa: E501 + ConditionalField(XIntField("seq", 0), lambda pkt: pkt.authtype == 2), + # TODO: Support authdata (which is appended to the packets as if it were padding) # noqa: E501 + ] def post_build(self, p, pay): - # TODO: Remove LLS data from pay - # LLS data blocks may be attached to OSPF Hello and DD packets - # The length of the LLS block shall not be included into the length of OSPF packet - # See + # See p += pay - l = self.len - if l is None: - l = len(p) - p = p[:2]+struct.pack("!H",l)+p[4:] if self.chksum is None: if self.authtype == 2: ck = 0 # Crypto, see RFC 2328, D.4.3 else: # Checksum is calculated without authentication data # Algorithm is the same as in IP() - ck = checksum(p[:16]+p[24:]) - p = p[:12]+chr(ck>>8)+chr(ck&0xff)+p[14:] - # TODO: Handle Crypto: Add message digest (RFC 2328, D.4.3) + ck = checksum(p[:16] + p[24:]) + p = p[:12] + struct.pack("!H", ck) + p[14:] + # TODO: Handle Crypto: Add message digest (RFC 2328, D.4.3) return p def hashret(self): - return struct.pack("H",self.area)+self.payload.hashret() + return struct.pack("H", self.area) + self.payload.hashret() def answers(self, other): - if (isinstance(other,OSPF_Hdr) and + if (isinstance(other, OSPF_Hdr) and self.area == other.area and - self.type == 5): # Only acknowledgements answer other packets - return self.payload.answers(other.payload) + self.type == 5): # Only acknowledgements answer other packets + return self.payload.answers(other.payload) return 0 class OSPF_Hello(Packet): name = "OSPF Hello" - fields_desc = [ IPField("mask", "255.255.255.0"), - ShortField("hellointerval", 10), - OSPFOptionsField(), - ByteField("prio", 1), - IntField("deadinterval", 40), - IPField("router", "0.0.0.0"), - IPField("backup", "0.0.0.0"), - FieldListField ("neighbors", [], IPField("", "0.0.0.0"), length_from=lambda pkt: (pkt.underlayer.len - 44)) - ] + fields_desc = [IPField("mask", "255.255.255.0"), + ShortField("hellointerval", 10), + OSPFOptionsField(), + ByteField("prio", 1), + IntField("deadinterval", 40), + IPField("router", "0.0.0.0"), + IPField("backup", "0.0.0.0"), + FieldListField("neighbors", [], IPField("", "0.0.0.0"), length_from=lambda pkt: (pkt.underlayer.len - 44) if pkt.underlayer else None)] # noqa: E501 def guess_payload_class(self, payload): # check presence of LLS data block flag @@ -108,177 +134,136 @@ def guess_payload_class(self, payload): else: return Packet.guess_payload_class(self, payload) -class RepeatedTlvListField(PacketListField): - # Code borrowed from scapy-cdp.py (Nicolas Bareil, Arnaud Ebalard) - def __init__(self, name, default, cls): - PacketField.__init__(self, name, default, cls) - - def getfield(self, pkt, s): - lst = [] - remain = s - while len(remain) > 0: - p = self.m2i(pkt,remain) - if Padding in p: - pad = p[Padding] - remain = pad.load - del(pad.underlayer.payload) - else: - remain = "" - lst.append(p) - return remain,lst - def addfield(self, pkt, s, val): - return s+reduce(str.__add__, map(str, val),"") +class LLS_Generic_TLV(Packet): + name = "LLS Generic" + fields_desc = [ShortField("type", 0), + FieldLenField("len", None, length_of="val"), + StrLenField("val", "", length_from=lambda x: x.len)] + + def guess_payload_class(self, p): + return conf.padding_layer -class LLS_ExtendedOptionsField(FlagsField): - def __init__(self, name="options", default=0, size=32, - names=["LR", "RS"]): - FlagsField.__init__(self, name, default, size, names) -class LLS_Extended_Options(Packet): - name = "LLS Extended Options" - fields_desc = [ ShortField("type", 1), - ShortField("len", 4), - LLS_ExtendedOptionsField() - ] +class LLS_Extended_Options(LLS_Generic_TLV): + name = "LLS Extended Options and Flags" + fields_desc = [ShortField("type", 1), + FieldLenField("len", None, fmt="!H", length_of="options"), + StrLenField("options", "", length_from=lambda x: x.len)] + # TODO: FlagsField("options", 0, names=["LR", "RS"], size) with dynamic size # noqa: E501 -class LLS_Crypto_Auth(Packet): + +class LLS_Crypto_Auth(LLS_Generic_TLV): name = "LLS Cryptographic Authentication" - fields_desc = [ ShortField("type", 2), - FieldLenField("length", 20, fmt="B", length_of=lambda x: x.authdata), - XIntField("sequence", "\x00\x00\x00\x00"), - StrLenField("authdata", "\x00" * 16, length_from=lambda x: x.length), - ] + fields_desc = [ShortField("type", 2), + FieldLenField("len", 20, fmt="B", length_of=lambda x: x.authdata + 4), # noqa: E501 + XIntField("sequence", 0), + StrLenField("authdata", b"\x00" * 16, length_from=lambda x: x.len - 4)] # noqa: E501 - def post_build(self, p, pay): - p += pay - l = self.len - if l is None: - # length = len(sequence) + len(authdata) + len(payload) - l = len(p[3:]) - p = p[:2]+struct.pack("!H",l)+p[3:] - return p -_OSPF_LLSclasses = { 1: "LLS_Extended_Options", - 2: "LLS_Crypto_Auth", - } +_OSPF_LLSclasses = {1: "LLS_Extended_Options", + 2: "LLS_Crypto_Auth"} + def _LLSGuessPayloadClass(p, **kargs): """ Guess the correct LLS class for a given payload """ - # This is heavily based on scapy-cdp.py by Nicolas Bareil and Arnaud Ebalard - cls = Raw - if len(p) >= 4: + + cls = conf.raw_layer + if len(p) >= 3: typ = struct.unpack("!H", p[0:2])[0] - clsname = _OSPF_LLSclasses.get(typ, "Raw") + clsname = _OSPF_LLSclasses.get(typ, "LLS_Generic_TLV") cls = globals()[clsname] return cls(p, **kargs) + +class FieldLenField32Bits(FieldLenField): + def i2repr(self, pkt, x): + return repr(x) if not x else str(FieldLenField.i2h(self, pkt, x) << 2) + " bytes" # noqa: E501 + + class OSPF_LLS_Hdr(Packet): name = "OSPF Link-local signaling" - fields_desc = [ XShortField("chksum", None), - ShortField("len", None), - RepeatedTlvListField("llstlv", [], _LLSGuessPayloadClass) - ] + fields_desc = [XShortField("chksum", None), + FieldLenField32Bits("len", None, length_of="llstlv", adjust=lambda pkt, x: (x + 4) >> 2), # noqa: E501 + PacketListField("llstlv", [], _LLSGuessPayloadClass, length_from=lambda x: (x.len << 2) - 4)] # noqa: E501 def post_build(self, p, pay): p += pay - l = self.len - if l is None: - # Length in 32-bit words - l = len(p) / 4 - p = p[:2]+struct.pack("!H",l)+p[4:] if self.chksum is None: c = checksum(p) - p = chr((c>>8)&0xff)+chr(c&0xff) + p[2:] + p = struct.pack("!H", c) + p[2:] return p -_OSPF_LStypes = { 1: "router", - 2: "network", - 3: "summaryIP", - 4: "summaryASBR", - 5: "external"} -_OSPF_LSclasses = { 1: "OSPF_Router_LSA", - 2: "OSPF_Network_LSA", - 3: "OSPF_SummaryIP_LSA", - 4: "OSPF_SummaryASBR_LSA", - 5: "OSPF_External_LSA" } +_OSPF_LStypes = {1: "router", + 2: "network", + 3: "summaryIP", + 4: "summaryASBR", + 5: "external", + 7: "NSSAexternal", + 9: "linkScopeOpaque", + 10: "areaScopeOpaque", + 11: "asScopeOpaque"} -def ospf_lsa_checksum(lsa): - """ Fletcher checksum for OSPF LSAs, returned as a 2 byte string. +_OSPF_LSclasses = {1: "OSPF_Router_LSA", + 2: "OSPF_Network_LSA", + 3: "OSPF_SummaryIP_LSA", + 4: "OSPF_SummaryASBR_LSA", + 5: "OSPF_External_LSA", + 7: "OSPF_NSSA_External_LSA", + 9: "OSPF_Link_Scope_Opaque_LSA", + 10: "OSPF_Area_Scope_Opaque_LSA", + 11: "OSPF_AS_Scope_Opaque_LSA"} - Give the whole LSA packet as argument. - For details on the algorithm, see RFC 2328 chapter 12.1.7 and RFC 905 Annex B. - """ - # This is based on the GPLed C implementation in Zebra - - CHKSUM_OFFSET = 16 - if len(lsa) < CHKSUM_OFFSET: - raise Exception("LSA Packet too short (%s bytes)" % len(lsa)) - - c0 = c1 = 0 - # Calculation is done with checksum set to zero - lsa = lsa[:CHKSUM_OFFSET] + "\x00\x00" + lsa[CHKSUM_OFFSET+2:] - for char in lsa[2:]: # leave out age - c0 += ord(char) - c1 += c0 - c0 %= 255 - c1 %= 255 - - x = ((len(lsa) - CHKSUM_OFFSET - 1) * c0 - c1) % 255 - if (x <= 0): - x += 255 - y = 510 - c0 - x - if (y > 255): - y -= 255 - #checksum = (x << 8) + y - return chr(x) + chr(y) + +def ospf_lsa_checksum(lsa): + return fletcher16_checkbytes(b"\x00\x00" + lsa[2:], 16) # leave out age class OSPF_LSA_Hdr(Packet): name = "OSPF LSA Header" - fields_desc = [ ShortField("age", 1), - OSPFOptionsField(), - ByteEnumField("type", 1, _OSPF_LStypes), - IPField("id", "0.0.0.0"), - IPField("adrouter", "0.0.0.0"), - XIntField("seq", 1), - XShortField("chksum", 0), - ShortField("len", 36)] + fields_desc = [ShortField("age", 1), + OSPFOptionsField(), + ByteEnumField("type", 1, _OSPF_LStypes), + IPField("id", "192.168.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", 0), + ShortField("len", 36)] def extract_padding(self, s): return "", s -_OSPF_Router_LSA_types = { 1: "p2p", - 2: "transit", - 3: "stub", - 4: "virtual" } +_OSPF_Router_LSA_types = {1: "p2p", + 2: "transit", + 3: "stub", + 4: "virtual"} + class OSPF_Link(Packet): name = "OSPF Link" - fields_desc = [ IPField("id", "0.0.0.0"), - IPField("data", "2.2.2.2"), - ByteEnumField("type", 1, _OSPF_Router_LSA_types), - ByteField("toscount", 0), - ShortField("metric", 1), - # TODO: define correct conditions - ConditionalField(ByteField("tos", 0), lambda pkt: False), - ConditionalField(ByteField("reserved", 0), lambda pkt: False), - ConditionalField(ShortField("tosmetric", 0), lambda pkt: False) - ] - - def extract_padding(self,s): + fields_desc = [IPField("id", "192.168.0.0"), + IPField("data", "255.255.255.0"), + ByteEnumField("type", 3, _OSPF_Router_LSA_types), + ByteField("toscount", 0), + ShortField("metric", 10), + # TODO: define correct conditions + ConditionalField(ByteField("tos", 0), lambda pkt: False), + ConditionalField(ByteField("reserved", 0), lambda pkt: False), # noqa: E501 + ConditionalField(ShortField("tosmetric", 0), lambda pkt: False)] # noqa: E501 + + def extract_padding(self, s): return "", s def _LSAGuessPayloadClass(p, **kargs): """ Guess the correct LSA class for a given payload """ - # This is heavily based on scapy-cdp.py by Nicolas Bareil and Arnaud Ebalard - # XXX: This only works if all payload - cls = Raw + # This is heavily based on scapy-cdp.py by Nicolas Bareil and Arnaud Ebalard # noqa: E501 + + cls = conf.raw_layer if len(p) >= 4: - typ = struct.unpack("!B", p[3])[0] + typ = orb(p[3]) clsname = _OSPF_LSclasses.get(typ, "Raw") cls = globals()[clsname] return cls(p, **kargs) @@ -291,118 +276,142 @@ def post_build(self, p, pay): length = self.len if length is None: length = len(p) - p = p[:18] + struct.pack("!H",length) + p[20:] + p = p[:18] + struct.pack("!H", length) + p[20:] if self.chksum is None: chksum = ospf_lsa_checksum(p) p = p[:16] + chksum + p[18:] return p # p+pay? def extract_padding(self, s): - length = self.len return "", s class OSPF_Router_LSA(OSPF_BaseLSA): name = "OSPF Router LSA" - - fields_desc = [ ShortField("age", 1), - OSPFOptionsField(), - ByteField("type", 1), - IPField("id", "0.0.0.0"), - IPField("adrouter", "0.0.0.0"), - XIntField("seq", 0x80000001), - XShortField("chksum", None), - ShortField("len", None), - FlagsField("flags", 0, 16, ["16", "15", "14", "13", "12", "V", "E", "B", "8", "7", "6", "5", "4", "3", "2", "1"]), - FieldLenField("linkcount", None, count_of="linklist"), - PacketListField("linklist", [], OSPF_Link, - count_from=lambda pkt:pkt.linkcount, - length_from=lambda pkt:pkt.linkcount*12)] + fields_desc = [ShortField("age", 1), + OSPFOptionsField(), + ByteField("type", 1), + IPField("id", "1.1.1.1"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + FlagsField("flags", 0, 8, ["B", "E", "V", "W", "Nt"]), + ByteField("reserved", 0), + FieldLenField("linkcount", None, count_of="linklist"), + PacketListField("linklist", [], OSPF_Link, + count_from=lambda pkt: pkt.linkcount, + length_from=lambda pkt: pkt.linkcount * 12)] class OSPF_Network_LSA(OSPF_BaseLSA): name = "OSPF Network LSA" - fields_desc = [ ShortField("age", 1), - OSPFOptionsField(), - ByteField("type", 2), - IPField("id", "0.0.0.0"), - IPField("adrouter", "0.0.0.0"), - XIntField("seq", 0x80000001), - XShortField("chksum", None), - ShortField("len", None), - IPField("mask", "255.255.255.0"), - FieldListField("routerlist", [], IPField("", "0.0.0.1"), - length_from=lambda pkt: pkt.len - 24)] + fields_desc = [ShortField("age", 1), + OSPFOptionsField(), + ByteField("type", 2), + IPField("id", "192.168.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + IPField("mask", "255.255.255.0"), + FieldListField("routerlist", [], IPField("", "1.1.1.1"), + length_from=lambda pkt: pkt.len - 24)] class OSPF_SummaryIP_LSA(OSPF_BaseLSA): name = "OSPF Summary LSA (IP Network)" - fields_desc = [ ShortField("age", 1), - OSPFOptionsField(), - ByteField("type", 3), - IPField("id", "0.0.0.0"), - IPField("advertrouter", "0.0.0.0"), - XIntField("seq", 0x80000001), - XShortField("chksum", None), - ShortField("len", None), - IPField("mask", "255.255.255.0"), - ByteField("reserved", 0), - X3BytesField("metric", 0), - # TODO: Define correct conditions - ConditionalField(ByteField("tos", 0), lambda pkt:False), - ConditionalField(X3BytesField("tosmetric", 0), lambda pkt:False) - ] - -class OSPF_SummaryASBR_LSA(OSPF_BaseLSA): + fields_desc = [ShortField("age", 1), + OSPFOptionsField(), + ByteField("type", 3), + IPField("id", "192.168.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + IPField("mask", "255.255.255.0"), + ByteField("reserved", 0), + X3BytesField("metric", 10), + # TODO: Define correct conditions + ConditionalField(ByteField("tos", 0), lambda pkt:False), + ConditionalField(X3BytesField("tosmetric", 0), lambda pkt:False)] # noqa: E501 + + +class OSPF_SummaryASBR_LSA(OSPF_SummaryIP_LSA): name = "OSPF Summary LSA (AS Boundary Router)" - fields_desc = [ ShortField("age", 1), - OSPFOptionsField(), - ByteField("type", 4), - IPField("id", "0.0.0.0"), - IPField("adrouter", "0.0.0.0"), - XIntField("seq", 0x80000001), - XShortField("chksum", None), - ShortField("len", None), - IPField("mask", "255.255.255.0"), - ByteField("reserved", 0), - X3BytesField("metric", 0), - # TODO: Define correct conditions - ConditionalField(ByteField("tos", 0), lambda pkt:False), - ConditionalField(X3BytesField("tosmetric", 0), lambda pkt:False) - ] + type = 4 + id = "2.2.2.2" + mask = "0.0.0.0" + metric = 20 class OSPF_External_LSA(OSPF_BaseLSA): name = "OSPF External LSA (ASBR)" - fields_desc = [ - ShortField("age", 1), - OSPFOptionsField(), - ByteField("type", 5), - IPField("id", "0.0.0.0"), - IPField("adrouter", "0.0.0.0"), - XIntField("seq", 1), - XShortField("chksum", None), - ShortField("len", None), - IPField("mask", "255.255.255.0"), - FlagsField("ebit", 0, 1, ["E"]), - BitField("reserved", 0, 7), - X3BytesField("metric", 1), # XXX - IPField("fwdaddr", "0.0.0.0"), - XIntField("tag", 0), - # TODO: Define correct conditions - ConditionalField(ByteField("tos", 0), lambda pkt:False), - ConditionalField(X3BytesField("tosmetric", 0), lambda pkt:False) - ] + fields_desc = [ShortField("age", 1), + OSPFOptionsField(), + ByteField("type", 5), + IPField("id", "192.168.0.0"), + IPField("adrouter", "2.2.2.2"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + IPField("mask", "255.255.255.0"), + FlagsField("ebit", 0, 1, ["E"]), + BitField("reserved", 0, 7), + X3BytesField("metric", 20), + IPField("fwdaddr", "0.0.0.0"), + XIntField("tag", 0), + # TODO: Define correct conditions + ConditionalField(ByteField("tos", 0), lambda pkt:False), + ConditionalField(X3BytesField("tosmetric", 0), lambda pkt:False)] # noqa: E501 + + +class OSPF_NSSA_External_LSA(OSPF_External_LSA): + name = "OSPF NSSA External LSA" + type = 7 + + +class OSPF_Link_Scope_Opaque_LSA(OSPF_BaseLSA): + name = "OSPF Link Scope External LSA" + type = 9 + fields_desc = [ShortField("age", 1), + OSPFOptionsField(), + ByteField("type", 9), + IPField("id", "192.0.2.1"), + IPField("adrouter", "198.51.100.100"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + StrLenField("data", "data", + length_from=lambda pkt: pkt.len - 20) + ] + + def opaqueid(self): + return struct.unpack('>I', inet_aton(self.id))[0] & 0xFFFFFF + + def opaquetype(self): + return (struct.unpack('>I', inet_aton(self.id))[0] >> 24) & 0xFF + + +class OSPF_Area_Scope_Opaque_LSA(OSPF_Link_Scope_Opaque_LSA): + name = "OSPF Area Scope External LSA" + type = 10 + + +class OSPF_AS_Scope_Opaque_LSA(OSPF_Link_Scope_Opaque_LSA): + name = "OSPF AS Scope External LSA" + type = 11 + class OSPF_DBDesc(Packet): name = "OSPF Database Description" - fields_desc = [ ShortField("mtu", 1500), - OSPFOptionsField(), - FlagsField("dbdescr", 0, 8, ["MS", "M", "I", "R", "4", "3", "2", "1"]), - IntField("ddseq", 1), - PacketListField("lsaheaders", None, OSPF_LSA_Hdr, - count_from = lambda pkt:None, - length_from = lambda pkt:pkt.underlayer.len - 24 - 8)] + fields_desc = [ShortField("mtu", 1500), + OSPFOptionsField(), + FlagsField("dbdescr", 0, 8, ["MS", "M", "I", "R", "4", "3", "2", "1"]), # noqa: E501 + IntField("ddseq", 1), + PacketListField("lsaheaders", None, OSPF_LSA_Hdr, + count_from=lambda pkt: None, + length_from=lambda pkt: pkt.underlayer.len - 24 - 8)] # noqa: E501 def guess_payload_class(self, payload): # check presence of LLS data block flag @@ -411,11 +420,12 @@ def guess_payload_class(self, payload): else: return Packet.guess_payload_class(self, payload) + class OSPF_LSReq_Item(Packet): name = "OSPF Link State Request (item)" - fields_desc = [ IntEnumField("type", 1, _OSPF_LStypes), - IPField("id", "0.0.0.0"), - IPField("adrouter", "0.0.0.0")] + fields_desc = [IntEnumField("type", 1, _OSPF_LStypes), + IPField("id", "1.1.1.1"), + IPField("adrouter", "1.1.1.1")] def extract_padding(self, s): return "", s @@ -423,44 +433,356 @@ def extract_padding(self, s): class OSPF_LSReq(Packet): name = "OSPF Link State Request (container)" - fields_desc = [ PacketListField("requests", None, OSPF_LSReq_Item, - count_from = lambda pkt:None, - length_from = lambda pkt:pkt.underlayer.len - 24)] + fields_desc = [PacketListField("requests", None, OSPF_LSReq_Item, + count_from=lambda pkt:None, + length_from=lambda pkt:pkt.underlayer.len - 24)] # noqa: E501 + class OSPF_LSUpd(Packet): name = "OSPF Link State Update" - fields_desc = [ FieldLenField("lsacount", None, fmt="!I", count_of="lsalist"), - PacketListField("lsalist", [], _LSAGuessPayloadClass, - count_from = lambda pkt:pkt.lsacount, - length_from = lambda pkt:pkt.underlayer.len - 24) - ] - + fields_desc = [FieldLenField("lsacount", None, fmt="!I", count_of="lsalist"), # noqa: E501 + PacketListField("lsalist", None, _LSAGuessPayloadClass, + count_from=lambda pkt: pkt.lsacount, + length_from=lambda pkt: pkt.underlayer.len - 24)] # noqa: E501 class OSPF_LSAck(Packet): name = "OSPF Link State Acknowledgement" - fields_desc = [ PacketListField("lsaheaders", None, OSPF_LSA_Hdr, - count_from = lambda pkt:None, - length_from = lambda pkt:pkt.underlayer.len - 24)] + fields_desc = [PacketListField("lsaheaders", None, OSPF_LSA_Hdr, + count_from=lambda pkt: None, + length_from=lambda pkt: pkt.underlayer.len - 24)] # noqa: E501 def answers(self, other): - if isinstance(other,OSPF_LSUpd): + if isinstance(other, OSPF_LSUpd): for reqLSA in other.lsalist: for ackLSA in self.lsaheaders: if (reqLSA.type == ackLSA.type and - reqLSA.seq == ackLSA.seq): + reqLSA.seq == ackLSA.seq): return 1 return 0 +############################################################################### +# OSPFv3 +############################################################################### +class OSPFv3_Hdr(Packet): + name = "OSPFv3 Header" + fields_desc = [ByteField("version", 3), + ByteEnumField("type", 1, _OSPF_types), + ShortField("len", None), + IPField("src", "1.1.1.1"), + IPField("area", "0.0.0.0"), + XShortField("chksum", None), + ByteField("instance", 0), + ByteField("reserved", 0)] + + def post_build(self, p, pay): + p += pay + tmp_len = self.len + + if tmp_len is None: + tmp_len = len(p) + p = p[:2] + struct.pack("!H", tmp_len) + p[4:] + + if self.chksum is None: + chksum = in6_chksum(89, self.underlayer, p) + p = p[:12] + struct.pack("!H", chksum) + p[14:] + + return p + + +class OSPFv3OptionsField(FlagsField): + + def __init__(self, name="options", default=0, size=24, + names=None): + if names is None: + names = ["V6", "E", "MC", "N", "R", "DC", "AF", "L", "I", "F"] + FlagsField.__init__(self, name, default, size, names) + + +class OSPFv3_Hello(Packet): + name = "OSPFv3 Hello" + fields_desc = [IntField("intid", 0), + ByteField("prio", 1), + OSPFv3OptionsField(), + ShortField("hellointerval", 10), + ShortField("deadinterval", 40), + IPField("router", "0.0.0.0"), + IPField("backup", "0.0.0.0"), + FieldListField("neighbors", [], IPField("", "0.0.0.0"), + length_from=lambda pkt: (pkt.underlayer.len - 36))] # noqa: E501 + + +_OSPFv3_LStypes = {0x2001: "router", + 0x2002: "network", + 0x2003: "interAreaPrefix", + 0x2004: "interAreaRouter", + 0x4005: "asExternal", + 0x2007: "type7", + 0x0008: "link", + 0x2009: "intraAreaPrefix"} + +_OSPFv3_LSclasses = {0x2001: "OSPFv3_Router_LSA", + 0x2002: "OSPFv3_Network_LSA", + 0x2003: "OSPFv3_Inter_Area_Prefix_LSA", + 0x2004: "OSPFv3_Inter_Area_Router_LSA", + 0x4005: "OSPFv3_AS_External_LSA", + 0x2007: "OSPFv3_Type_7_LSA", + 0x0008: "OSPFv3_Link_LSA", + 0x2009: "OSPFv3_Intra_Area_Prefix_LSA"} + + +class OSPFv3_LSA_Hdr(Packet): + name = "OSPFv3 LSA Header" + fields_desc = [ShortField("age", 1), + ShortEnumField("type", 0x2001, _OSPFv3_LStypes), + IPField("id", "0.0.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", 0), + ShortField("len", 36)] + + def extract_padding(self, s): + return "", s + + +def _OSPFv3_LSAGuessPayloadClass(p, **kargs): + """ Guess the correct OSPFv3 LSA class for a given payload """ + + cls = conf.raw_layer + + if len(p) >= 6: + typ = struct.unpack("!H", p[2:4])[0] + clsname = _OSPFv3_LSclasses.get(typ, "Raw") + cls = globals()[clsname] + + return cls(p, **kargs) + + +_OSPFv3_Router_LSA_types = {1: "p2p", + 2: "transit", + 3: "reserved", + 4: "virtual"} + + +class OSPFv3_Link(Packet): + name = "OSPFv3 Link" + fields_desc = [ByteEnumField("type", 1, _OSPFv3_Router_LSA_types), + ByteField("reserved", 0), + ShortField("metric", 10), + IntField("intid", 0), + IntField("neighintid", 0), + IPField("neighbor", "2.2.2.2")] + + def extract_padding(self, s): + return "", s + + +class OSPFv3_Router_LSA(OSPF_BaseLSA): + name = "OSPFv3 Router LSA" + fields_desc = [ShortField("age", 1), + ShortEnumField("type", 0x2001, _OSPFv3_LStypes), + IPField("id", "0.0.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + FlagsField("flags", 0, 8, ["B", "E", "V", "W"]), + OSPFv3OptionsField(), + PacketListField("linklist", [], OSPFv3_Link, + length_from=lambda pkt:pkt.len - 24)] + + +class OSPFv3_Network_LSA(OSPF_BaseLSA): + name = "OSPFv3 Network LSA" + fields_desc = [ShortField("age", 1), + ShortEnumField("type", 0x2002, _OSPFv3_LStypes), + IPField("id", "0.0.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + ByteField("reserved", 0), + OSPFv3OptionsField(), + FieldListField("routerlist", [], IPField("", "0.0.0.1"), + length_from=lambda pkt: pkt.len - 24)] + + +class OSPFv3PrefixOptionsField(FlagsField): + + def __init__(self, name="prefixoptions", default=0, size=8, + names=None): + if names is None: + names = ["NU", "LA", "MC", "P"] + FlagsField.__init__(self, name, default, size, names) + + +class OSPFv3_Inter_Area_Prefix_LSA(OSPF_BaseLSA): + name = "OSPFv3 Inter Area Prefix LSA" + fields_desc = [ShortField("age", 1), + ShortEnumField("type", 0x2003, _OSPFv3_LStypes), + IPField("id", "0.0.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + ByteField("reserved", 0), + X3BytesField("metric", 10), + FieldLenField("prefixlen", None, length_of="prefix", fmt="B"), # noqa: E501 + OSPFv3PrefixOptionsField(), + ShortField("reserved2", 0), + IP6PrefixField("prefix", "2001:db8:0:42::/64", wordbytes=4, length_from=lambda pkt: pkt.prefixlen)] # noqa: E501 + + +class OSPFv3_Inter_Area_Router_LSA(OSPF_BaseLSA): + name = "OSPFv3 Inter Area Router LSA" + fields_desc = [ShortField("age", 1), + ShortEnumField("type", 0x2004, _OSPFv3_LStypes), + IPField("id", "0.0.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + ByteField("reserved", 0), + OSPFv3OptionsField(), + ByteField("reserved2", 0), + X3BytesField("metric", 1), + IPField("router", "2.2.2.2")] + + +class OSPFv3_AS_External_LSA(OSPF_BaseLSA): + name = "OSPFv3 AS External LSA" + fields_desc = [ShortField("age", 1), + ShortEnumField("type", 0x4005, _OSPFv3_LStypes), + IPField("id", "0.0.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + FlagsField("flags", 0, 8, ["T", "F", "E"]), + X3BytesField("metric", 20), + FieldLenField("prefixlen", None, length_of="prefix", fmt="B"), # noqa: E501 + OSPFv3PrefixOptionsField(), + ShortEnumField("reflstype", 0, _OSPFv3_LStypes), + IP6PrefixField("prefix", "2001:db8:0:42::/64", wordbytes=4, length_from=lambda pkt: pkt.prefixlen), # noqa: E501 + ConditionalField(IP6Field("fwaddr", "::"), lambda pkt: pkt.flags & 0x02 == 0x02), # noqa: E501 + ConditionalField(IntField("tag", 0), lambda pkt: pkt.flags & 0x01 == 0x01), # noqa: E501 + ConditionalField(IPField("reflsid", 0), lambda pkt: pkt.reflstype != 0)] # noqa: E501 + + +class OSPFv3_Type_7_LSA(OSPFv3_AS_External_LSA): + name = "OSPFv3 Type 7 LSA" + type = 0x2007 + + +class OSPFv3_Prefix_Item(Packet): + name = "OSPFv3 Link Prefix Item" + fields_desc = [FieldLenField("prefixlen", None, length_of="prefix", fmt="B"), # noqa: E501 + OSPFv3PrefixOptionsField(), + ShortField("metric", 10), + IP6PrefixField("prefix", "2001:db8:0:42::/64", wordbytes=4, length_from=lambda pkt: pkt.prefixlen)] # noqa: E501 + + def extract_padding(self, s): + return "", s + + +class OSPFv3_Link_LSA(OSPF_BaseLSA): + name = "OSPFv3 Link LSA" + fields_desc = [ShortField("age", 1), + ShortEnumField("type", 0x0008, _OSPFv3_LStypes), + IPField("id", "0.0.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + ByteField("prio", 1), + OSPFv3OptionsField(), + IP6Field("lladdr", "fe80::"), + FieldLenField("prefixes", None, count_of="prefixlist", fmt="I"), # noqa: E501 + PacketListField("prefixlist", None, OSPFv3_Prefix_Item, + count_from=lambda pkt: pkt.prefixes)] + + +class OSPFv3_Intra_Area_Prefix_LSA(OSPF_BaseLSA): + name = "OSPFv3 Intra Area Prefix LSA" + fields_desc = [ShortField("age", 1), + ShortEnumField("type", 0x2009, _OSPFv3_LStypes), + IPField("id", "0.0.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + FieldLenField("prefixes", None, count_of="prefixlist", fmt="H"), # noqa: E501 + ShortEnumField("reflstype", 0, _OSPFv3_LStypes), + IPField("reflsid", "0.0.0.0"), + IPField("refadrouter", "0.0.0.0"), + PacketListField("prefixlist", None, OSPFv3_Prefix_Item, + count_from=lambda pkt: pkt.prefixes)] + + +class OSPFv3_DBDesc(Packet): + name = "OSPFv3 Database Description" + fields_desc = [ByteField("reserved", 0), + OSPFv3OptionsField(), + ShortField("mtu", 1500), + ByteField("reserved2", 0), + FlagsField("dbdescr", 0, 8, ["MS", "M", "I", "R"]), + IntField("ddseq", 1), + PacketListField("lsaheaders", None, OSPFv3_LSA_Hdr, + count_from=lambda pkt:None, + length_from=lambda pkt:pkt.underlayer.len - 28)] # noqa: E501 + + +class OSPFv3_LSReq_Item(Packet): + name = "OSPFv3 Link State Request (item)" + fields_desc = [ShortField("reserved", 0), + ShortEnumField("type", 0x2001, _OSPFv3_LStypes), + IPField("id", "1.1.1.1"), + IPField("adrouter", "1.1.1.1")] + + def extract_padding(self, s): + return "", s + + +class OSPFv3_LSReq(Packet): + name = "OSPFv3 Link State Request (container)" + fields_desc = [PacketListField("requests", None, OSPFv3_LSReq_Item, + count_from=lambda pkt:None, + length_from=lambda pkt:pkt.underlayer.len - 16)] # noqa: E501 + + +class OSPFv3_LSUpd(Packet): + name = "OSPFv3 Link State Update" + fields_desc = [FieldLenField("lsacount", None, fmt="!I", count_of="lsalist"), # noqa: E501 + PacketListField("lsalist", [], _OSPFv3_LSAGuessPayloadClass, + count_from=lambda pkt:pkt.lsacount, + length_from=lambda pkt:pkt.underlayer.len - 16)] # noqa: E501 + + +class OSPFv3_LSAck(Packet): + name = "OSPFv3 Link State Acknowledgement" + fields_desc = [PacketListField("lsaheaders", None, OSPFv3_LSA_Hdr, + count_from=lambda pkt:None, + length_from=lambda pkt:pkt.underlayer.len - 16)] # noqa: E501 + + bind_layers(IP, OSPF_Hdr, proto=89) bind_layers(OSPF_Hdr, OSPF_Hello, type=1) bind_layers(OSPF_Hdr, OSPF_DBDesc, type=2) bind_layers(OSPF_Hdr, OSPF_LSReq, type=3) bind_layers(OSPF_Hdr, OSPF_LSUpd, type=4) bind_layers(OSPF_Hdr, OSPF_LSAck, type=5) +DestIPField.bind_addr(OSPF_Hdr, "224.0.0.5") + +bind_layers(IPv6, OSPFv3_Hdr, nh=89) +bind_layers(OSPFv3_Hdr, OSPFv3_Hello, type=1) +bind_layers(OSPFv3_Hdr, OSPFv3_DBDesc, type=2) +bind_layers(OSPFv3_Hdr, OSPFv3_LSReq, type=3) +bind_layers(OSPFv3_Hdr, OSPFv3_LSUpd, type=4) +bind_layers(OSPFv3_Hdr, OSPFv3_LSAck, type=5) +DestIP6Field.bind_addr(OSPFv3_Hdr, "ff02::5") if __name__ == "__main__": - interact(mydict=globals(), mybanner="OSPF extension %s" % EXT_VERSION) - \ No newline at end of file + from scapy.main import interact + interact(mydict=globals(), mybanner="OSPF extension %s" % EXT_VERSION) \ No newline at end of file From 51e92490e84d79282647ab0516518aa8e64f6527 Mon Sep 17 00:00:00 2001 From: Aaron Gember-Jacobson Date: Thu, 16 Jul 2020 10:51:24 -0400 Subject: [PATCH 4/4] Fix import and usage of scapy_ospf --- frrouting_automation/scapy_scripts/hello.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frrouting_automation/scapy_scripts/hello.py b/frrouting_automation/scapy_scripts/hello.py index a22fa0c..612a70c 100644 --- a/frrouting_automation/scapy_scripts/hello.py +++ b/frrouting_automation/scapy_scripts/hello.py @@ -1,4 +1,5 @@ import scapy.all as scapy +from scapy_ospf import OSPF_Hdr, OSPF_Hello packet = scapy.Ether(src='00:06:28:b9:85:31',dst='01:00:5e:00:00:05') @@ -11,7 +12,7 @@ #packet.show() packet = packet/scapy.IP(src='192.16.2.2',dst='172.17.0.5') -packet = packet/scapy.OSPF_Hdr(src='192.16.2.2') -packet = packet/scapy.OSPF_Hello(router='172.17.2.2',backup='172.17.2.1',neighbor='172.17.2.1') +packet = packet/OSPF_Hdr(src='192.16.2.2') +packet = packet/OSPF_Hello(router='172.17.2.2',backup='172.17.2.1',neighbor='172.17.2.1') packet.show() scapy.sendp(packet,loop=True,inter=0.1) \ No newline at end of file