diff --git a/.idea/webServers.xml b/.idea/webServers.xml new file mode 100644 index 0000000..35f250c --- /dev/null +++ b/.idea/webServers.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 4982f1d..635ece6 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,53 @@ + OFP_Sniffer is an OpenFlow sniffer to be used for troubleshooting and learning purposes. -Currently on version 0.2, it dissects all OpenFlow 1.0 messages. OpenFlow version 1.3 will -be available on version 0.3 (to be released soon). +Currently on version 1.0, it dissects all OpenFlow 1.0 messages. +OpenFlow version 1.3 will be available on version 1.1 (to be released soon). It works directly on Linux shell and dissects all OpenFlow messages on the wire. Using OFP_Sniffer, you can easily track OpenFlow messages and errors -associated (if any), without openning X11 or Wireshark. OFP_Sniffer was -written in Python to support the AmLight SDN deployment (www.sdn.amlight.net). +associated (if any), without opening X11 or Wireshark. OFP_Sniffer was +written in Python 3.6 to support the AmLight SDN deployment (www.sdn.amlight.net). AmLight SDN uses Internet2 FlowSpace Firewall, OESS and On.Lab ONOS, and these -apps were tested and are fully supported (well, they should be ;)). +apps were tested and are fully supported. This tool started to be developed after a conversation with Andrew Ragusa (a.k.a. A.J) from Indiana University along the NITRD - Roadmap to Operating SDN-based Networks Workshop hosted by ESNET and Internet2. Thanks A.J. for your -constant support and mentoring!! (Link to NITRD workshop: https://www.nitrd.gov/nitrdgroups/index.php?title=SDN_Operational_Issues_WS) +constant support! (Link to NITRD workshop: +https://www.nitrd.gov/nitrdgroups/index.php?title=SDN_Operational_Issues_WS) As a command line interface tool, it has a few input parameters: ``` # ./ofp_sniffer.py -h Usage: ./ofp_sniffer.py [-p min|full] [-f pcap_filter] [-F filter_file] [-i dev] [-r pcap_file] - -p [min|full] or --print=[min|full]: print min or full packet headers. Default: min - -f pcap_filter or --pcap-filter=pcap_filter : add a libpcap filter - -F sanitizer_file.json or --sanitizer-file=sanitizerfile.json - -i interface or --interface=interface. Default: eth0 - -r captured.pcap or --src-file=captured.pcap - -o or --print-ovs : print using ovs-ofctl format - -h or --help : prints this guidance - -c or --no-colors: removes colors - -v or --version : prints version - --p [min|full] gives you the option of printing minimal or full TCP/IP headers --f pcap_filter gives you the possibility of adding libpcap filters. - Filter "port 6633 " is already applied. If you want to add more options, just - add -f and the filter, for example: - -f " or port 6634 or host 192.168.0.2" --F sanitizer_file.json gives you the possibility of using specific OpenFlow - filters, for example, ignore some OpenFlow types (PacketIn, PacketOut, - etc). An example is shipped with the source code --i interface gives you the possibility of choosing the interface to sniffer. - Remember that you will need root powers. --r capture.pcap gives you the possibility of working on a previously captured libpcap file. --o gives you the possibility of printing the ovs-ofctl command that generated a - specific flow-mod message. + -p : print full headers packet headers. Default: min + -f pcap_filter or --pcap-filter=pcap_filter: add a libpcap filter + -F filters_file.json or --filters-file=filters.json + -i interface or --interface=interface. Default: eth0 + -r captured.pcap or --src-file=captured.pcap + -P topology.json or --topology-file=topology.json + -h or --help : prints this guidance + -c or --no-colors: removes colors + -v or --version : prints version + -O or --oess-fvd: monitor OESS FVD status + -S or --enable-statistics: creates statistics ``` + +Starting on version 1.0, apps are supported to handle specific needs, such as track OESS FVD +messages, or to creates statistics via REST and be integrated to NMSes (f.i., Zabbix). New apps +are coming soon to discover the network topology and verify link integrity. + +More info: https://amlight.net/wp-content/uploads/2015/03/wpeif-2016-ofpsniffer.pdf + ##################### Instalation ###################### ``` -Required Python 2.7 (2.6 works but with issues) -apt-get install python-pcapy or yum install pcapy -git clone https://github.com/jab1982/ofp_sniffer.git +Requires Python 3.6 +git clone https://github.com/amlight/ofp_sniffer.git cd ofp_sniffer -git checkout 0.2 +pip3.6 install docs/requirements.txt sudo ./ofp_sniffer.py ``` ##################### Examples ######################### @@ -104,39 +100,69 @@ OpenFlow Version: 1.0(1) Type: FlowMod(14) Length: 88 XID: 2 # ovs-ofctl del-flows tcp:192.168.56.101:6634 "dl_type=0x88bc,dl_dst=10:00:00:01:20:00, " -# ofp_sniffer with option -o (to print ovs-ofctl command) - 2015-09-13 11:50:43.636925 192.168.56.102:37454 -> 192.168.56.101:6634 Size: 138 OpenFlow Version: 1.0(1) Type: FlowMod(14) Length: 72 XID: 2 2 OpenFlow Match - wildcards: 3678439 dl_type: 0x88bc dl_dst: 10:00:00:01:20:00 2 OpenFlow Body - Cookie: 0x00 Command: Delete(3) Idle/Hard Timeouts: 0/0 Priority: 32768 Buffer ID: 0xffffffff Out Port: 65535 Flags: Unknown Flag(0) -ovs-ofctl del-flows tcp:192.168.56.101:6634 "dl_type=0x88bc,dl_dst=10:00:00:01:20:00, " # ovs-ofctl add-flow tcp:192.168.56.101:6634 "dl_dst=10:00:00:01:20:00,dl_type=0x88bc actions=mod_vlan_vid:14,output:2" -# ofp_sniffer with option -o (to print ovs-ofctl command) - 2015-09-13 11:52:58.563737 192.168.56.102:37455 -> 192.168.56.101:6634 Size: 154 OpenFlow Version: 1.0(1) Type: FlowMod(14) Length: 88 XID: 2 2 OpenFlow Match - wildcards: 3678439 dl_type: 0x88bc dl_dst: 10:00:00:01:20:00 2 OpenFlow Body - Cookie: 0x00 Command: Add(0) Idle/Hard Timeouts: 0/0 Priority: 32768 Buffer ID: 0xffffffff Out Port: 65535 Flags: Unknown Flag(0) 2 OpenFlow Action - Type: SetVLANID Length: 8 VLAN ID: 14 Pad: 0 2 OpenFlow Action - Type: OUTPUT Length: 8 Port: 2 Max Length: 0 -ovs-ofctl add-flow tcp:192.168.56.101:6634 "dl_type=0x88bc,dl_dst=10:00:00:01:20:00, action=mod_vlan_vid:14,output:2," ``` Using Filters: -When using option -F ./example_filter.json you will have a few options: +When using option -F ./filters.json you will have a few options: -"allowed_of_versions" : used to select what OpenFlow messages you DON'T want to see. You can define different filters +"rejected_of_types" : used to select what OpenFlow message types you DON'T want to see. You can define different filters depending of the OpenFlow version + +Filters by Ethertype: + +If you are looking for a specific Ethertype being transported by PacketOut or PacketIn messages, you can reject all +others, giving you easy visualization. + +Example: + +``` + "filters":{ + "ethertypes": { + "lldp" : 0, + "fvd" : 0, + "arp" : 1, + "others": [ "88b5" ] + }, + "packetIn_filter": { + "switch_dpid": "any", + "in_port": "any" + }, + "packetOut_filter": { + "switch_dpid": "any", + "out_port": "any" + } + } +} +``` + +In the ethertype section, 1 means filter, 0 means print it. In the example provided, ARP messages won't be seen, while +OESS FVD and LLDP will. You can add the Ethertype hex number (without the 0x) in the "others" section, just adding +commas (","). + "packetIn_filter": used to define what PacketIn + LLDP messages you WANT to see. You can define per switch and/or + per port. For switch, you need to use the datapath_id as seen by the application you are using. For example, + some apps fill in the field c_id with of:dpid_id, other with dpid:dpid_id. For ports, using the OpenFlow port_id, + not the port name. For example, on Brocade, eth1/1 == 1. So use 1 instead of eth1/1. + +"packetOut_filter": used to define what PacketOut + LLDP messages you WANT to see. You can define per switch and/or per port. For switch, you need to use the datapath_id as seen by the application you are using. For example, some apps fill in the field c_id with of:dpid_id, other with dpid:dpid_id. For ports, using the OpenFlow port_id, not the name of the port. For example, on Brocade, eth1/1 == 1. So use 1 instead of eth1/1. - -Options PacketOut_filter and flowMod_logs are not deployed yet (future use). + Support for OpenFlow proxies: @@ -147,10 +173,9 @@ When using an OpenFlow proxy, depending of the interface you select to sniffer, IP_Proxy <-> IP_Switch It is hard to associate which controller is talking to which switch. To ease this troubleshooting, the OpenFlow - sniffer automatically monitors all PacketOut + LLDP messages to create a dictionary of {IP:port, name_switch}. - If this is your case, change the file ofp_fsfw_v10.py, under the variable "name" with the DPID and name of - each switch. Next time you run the sniffer, you are going to see the IP and between paranthesys the device behind - the proxy. Example: + sniffer automatically monitors all PacketOut + LLDP messages to create a dictionary of {(IP, port): name_switch}. + If this is your case, change the file docs/topology.json. Next time you run the sniffer, you are going to see + the IP and between parentheses the device behind the proxy. Example: ``` 2015-12-16 15:37:41.563621 200.0.207.79(andes1):7801 -> 190.103.184.135:6633 Size: 157 Bytes @@ -173,11 +198,11 @@ OpenFlow Version: 1.0(1) Type: PacketIn(10) Length: 99 XID: 0 0 LLDP: END(0) Length: 0 ``` -The name (andes1) represents a switch called "andes1" with DPID cc4e249126000000. Not that the DPID showed in the +The name (andes1) represents a switch called "andes1" with DPID cc4e249126000000. Note that the DPID showed in the example is not the same, because a PacketIn message is being used as an example. PacketIn shows the DPID of the neighbors of "andes1". -I hope this code helps you. This is the second version, a few changes are already planned for 0.3. Coming soon! +I hope this code helps you. This is the first stable version, a few changes are already planned for 1.1. Coming soon! -Questions/Suggestions: Jeronimo Bezerra +Questions/Suggestions: AmLight Dev Team diff --git a/TODO b/TODO deleted file mode 100644 index 9e97cb7..0000000 --- a/TODO +++ /dev/null @@ -1,23 +0,0 @@ -TODO List: - -Version 0.2: - -- Dissects all OpenFlow 1.0 message types -- Reads from Libpcap files -- PacketIn filters -- Integrate with FlowSpace Firewall - -Version 0.3: - -- Dissects all OpenFlow 1.3 message types - -Version 0.4: - -- Topology Validator -- Labels outputs depending of Match and Action -- Full OpenVSwitch/NICIRA dissector -- Outputs as text and libcap to different files -- Reads SSL/TLS sessions -- Make TAR.GZ available (with Makefile) -- Create RPM and DEB packages - diff --git a/__init__.py b/__init__.py index 080b343..e69de29 100644 --- a/__init__.py +++ b/__init__.py @@ -1,5 +0,0 @@ -import ofp_dissector_v10.py -import ofp_parser_v10.py -import ofp_prints_v10.py -import ofp_fsfw_v10.py -import termcolor diff --git a/apps/__init__.py b/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/oess_fvd.py b/apps/oess_fvd.py new file mode 100644 index 0000000..2fb98c1 --- /dev/null +++ b/apps/oess_fvd.py @@ -0,0 +1,148 @@ +""" + This app was created to specifically monitor the + OESS-FVD communication. It could be used to generate alarms + when a packetIn is received with current time sent by the FVD + too high compared with the time when the packet was + captured. +""" + +from datetime import datetime, timedelta +from libs.core.topo_reader import TopoReader +from libs.tcpiplib.process_data import is_protocol + + +OFP_PACKET_IN = 10 +OFP_PACKET_OUT = 13 +WARN = 8 +CRITICAL = 30 + + +class OessFvdTracer: + """ + OessFvdTracer is an app to evaluate the OESS FVD app. + """ + + def __init__(self): + self.links = dict() + self.layout = '%-20s %-14s %-30s %-30s %s' + self.starting() + self.last_printed = None + + @staticmethod + def starting(): + """ + Just print the app name + """ + print('OESS Forwarding Verification Monitoring') + + def process_packet(self, pkt): + """ + Method called by ofp_sniffer to process the IP+OF packet + We are only interested in Packet_Ins because these are + messages coming from the switch, which means, the end of + the OESS FV cycle: + (OESS -> packetOut -> dpid -> packetIn -> OESS) + + Args: + pkt: Packet class + """ + for msg in pkt.ofmsgs: + if msg.ofp.header.message_type in [OFP_PACKET_IN]: + fvd = is_protocol(msg.ofp.data, oess=True) + if fvd is not False: + self.add_link(fvd, pkt.l1.time) + + def add_link(self, fvd, capture_time): + """ + Add detected OESS link to self.links dictionary + + Args: + fvd: OESS class + capture_time: time when the packet was capture + by ofp_sniffer + """ + + if fvd.side_a not in self.links: + self.links[fvd.side_a] = dict() + + capture_time = datetime.strptime(capture_time, '%Y-%m-%d %H:%M:%S.%f') + + time_diff = self.calculate_time_diff(capture_time, fvd.timestamp) + + self.links[fvd.side_a][fvd.port_a] = {'remote': fvd.side_z, + 'port': fvd.port_z, + 'timestamp': fvd.timestamp, + 'last_seen': capture_time, + 'diff': time_diff} + + self.print_link_status(fvd.side_a, fvd.port_a) + + @staticmethod + def calculate_time_diff(capture_time, oess_time): + """ + Calculate the time difference between packet sent via PacketOut + and the packet received via PacketIn. + + Args: + capture_time: PacketIn time + oess_time: PacketOut time + Returns: + difference + """ + return capture_time - datetime.fromtimestamp(oess_time) + + def print_link_status(self, dpid, port, alert=False): + """ + Now, just print the OESS link detected. The idea of this method + is to generate alarms when time different from the moment packet + is seen by ofp_sniffer with the time packet was sent is over + many seconds. + + Args: + dpid: source DPID in the OESS message + port: source port in the OESS message + alert: print only warning and critical + """ + + link = self.links[dpid][port] + + timestamp = str(datetime.fromtimestamp(link['timestamp'])) + topo_link = TopoReader().get_link_aliases(dpid, port, link['remote'], + link['port'], option="Full") + source_dpid = TopoReader().get_datapath_name(dpid) + + if timedelta(seconds=CRITICAL) > link['diff'] > timedelta(seconds=WARN): + link['diff'] = str(link['diff']) + ' <-- Warning!' + alert = True + + elif link['diff'] > timedelta(seconds=CRITICAL): + link['diff'] = str(link['diff']) + ' <-- Critical!' + alert = True + + if alert: + if len(topo_link) > 0: + self.print_header(True) + print(self.layout % (topo_link, source_dpid, timestamp, + link['last_seen'], link['diff'])) + else: + self.print_header() + print('%-24s %-4s %-24s %-4s %s\t %s\t %s' % + (dpid, port, link['remote'], link['port'], timestamp, + link['last_seen'], link['diff'])) + + def print_header(self, topo_link=False): + """ + Print headers just once. In case it keeps changing (because link + was not found in the topology.json), prints the header again. + + Args: + topo_link: indicates if link was found in the topology.json + """ + if topo_link and self.last_printed in [None, 'not_topo_link']: + print(self.layout % ('Link', 'Source DPID', 'Sent by OESS-FVD', + 'Received by OFP_Sniffer', 'Delay')) + self.last_printed = 'topo_link' + elif not topo_link and self.last_printed in [None, 'topo_link']: + print('%-24s %-4s %-24s %-4s %s\t\t\t\t\t %s\t\t\t\t\t\t %s' % + ('DPID', 'Port', 'Neighbor', 'Port', 'Sent', 'Seen', 'Delay')) + self.last_printed = 'not_topo_link' diff --git a/apps/ofp_proxies.py b/apps/ofp_proxies.py new file mode 100644 index 0000000..485caaa --- /dev/null +++ b/apps/ofp_proxies.py @@ -0,0 +1,123 @@ +""" + This code is used to associate IP address seen to a switch when + network slicing is in place. + If FlowSpace Firewall or FlowVisor is not used, this module + is not useful. +""" + + +from libs.core.singleton import Singleton +from libs.core.debugging import debugclass +from libs.core.topo_reader import TopoReader +from libs.tcpiplib.packet import LLDP +from libs.tcpiplib.process_data import is_protocol +from libs.gen.dpid_handling import clear_dpid + + +@debugclass +class OFProxy(metaclass=Singleton): + """ + This app is used to help identifying switches when openflow + proxies are in the middle, such as flowvisor and fsfw. It is + not possible to deactivate it. + """ + def __init__(self): + self.dpid_dict = dict() # dpid to alias dict + self.proxy_db = dict() # [ip, port] to alias dict + self.active = False + self.load_topology_dpids() + + def load_topology_dpids(self): + """ + Gets all DPIDs and datapath names from the + topology. + """ + topo = TopoReader().get_topology() + + try: + for switch in topo['switches']: + for dpid in topo['switches'][switch]['dpids']: + self.add_dpid(dpid, TopoReader().get_datapath_name(dpid)) + self.active = True + except KeyError: + pass + + + def add_dpid(self, dpid, name): + """ + Add dpid found in the topology to a dict + + Args: + dpid: datapath_id + name: datapath name + """ + self.dpid_dict[dpid] = name + + def get_datapath_name(self, dpid): + """ + Get the switch name using dpid + + Args: + dpid: datapath_id + Returns: + name: datapath name + """ + try: + return self.dpid_dict[dpid] + except: + return 'DPID_' + str(dpid) + + def add_dpid_to_proxy_db(self, ip_addr, port, dpid): + """ + Receives the IP, TCP port and DPID and save them to the + proxy_db. IP and TCP port are the indexes. + + Args: + ip_addr: IP address + port: TCP port + dpid: switch datapath id + """ + dpid = clear_dpid(dpid) + self.proxy_db[ip_addr, port] = self.get_datapath_name(dpid) + + def process_packet(self, pkt): + """ + Go through all OFMessages in Pkt. + IF FeaturesReply or PacketOut, get the DPID + + pkt: packet class + """ + + if not self.active: + return + + for msg in pkt.ofmsgs: + if msg.ofp.header.message_type.value == 6: + ip_addr = pkt.l3.s_addr + port = pkt.l4.source_port + self.add_dpid_to_proxy_db(ip_addr, port, msg.ofp.datapath_id.value) + + elif msg.ofp.header.message_type.value == 13: + ip_addr = pkt.l3.d_addr + port = pkt.l4.dest_port + lldp = is_protocol(msg.ofp.data, lldp=True) + if isinstance(lldp, LLDP): + self.add_dpid_to_proxy_db(ip_addr, port, lldp.c_id) + + def get_name(self, ip_addr, port): + """ + Method used by the tcpiplib printing to associate + ip:port to a switch name + + Args: + ip_addr: IP address + port: TCP port + dpid: switch datapath id + """ + if not self.active: + return ip_addr + + for ip_port, name in self.proxy_db.items(): + if ip_port == (ip_addr, port): + return '%s(%s)' % (ip_addr, name) + return ip_addr diff --git a/apps/ofp_stats.py b/apps/ofp_stats.py new file mode 100644 index 0000000..4314827 --- /dev/null +++ b/apps/ofp_stats.py @@ -0,0 +1,147 @@ +""" + This app process all OF messages to create statistics + export via REST. Main user is Zabbix and SDN-LG +""" + + +import json +import time +from datetime import datetime +from _thread import start_new_thread as new_thread +from apps.rest import CreateRest +from libs.core.singleton import Singleton + + +class OFStats(metaclass=Singleton): + """ + This class processes the OF messages for statistics. + """ + + def __init__(self): + self.start_time = str(datetime.now()) + self.last_msgs = CircularList() + self.num_packets = 0 + self.type_packets = self.init_type_packets() + new_thread(self._run_rest, tuple()) + + @staticmethod + def init_type_packets(): + """ + Initialize all dictionaries + """ + types = dict() + types['1'] = dict() + types['4'] = dict() + return types + + @staticmethod + def _run_rest(): + """ + This app only exports data via REST. + So, load up the REST interface + """ + CreateRest() + + @staticmethod + def to_json(msg): + """ + Convert dictionaries to JSON to export + via REST + Args: + msg: message to be converted + Returns: + json.dumps + """ + result = dict() + result['result'] = msg + return json.dumps(result) + + @staticmethod + def get_unix_time(): + """ + Returns datetime.now() in unixstamp format. + """ + date = datetime.now() + return time.mktime(date.timetuple()) + + @staticmethod + def get_time(): + """ + Returns datetime.now() in string format. + """ + return str(datetime.now()) + + # REST Methods + def get_start_time(self): + """ + Just return when the ofp_sniffer started + """ + msg = {'start_time': self.start_time} + return self.to_json(msg) + + def get_counter(self): + """ + Get counters via REST + """ + msg = dict() + msg['current_time'] = self.get_time() + msg['total_packets'] = self.num_packets + msg['per_types'] = self.type_packets + return self.to_json(msg) + + def get_last_msgs(self): + """ + Get the last messages seen + """ + return self.to_json(self.last_msgs.items) + + # Main Methods + + def process_packet(self, pkt): + """ + Method called by ofp_sniffer.py to process the OF message + """ + self.num_packets += 1 + + for of_msg in pkt.ofmsgs: + version = str(of_msg.ofp.header.version.value) + message_type = str(of_msg.ofp.header.message_type) + message_type = message_type.split('.')[1] + try: + self.type_packets[version][message_type] += 1 + except KeyError: + self.type_packets[version][message_type] = 1 + + self.last_msgs.add(of_msg.ofp) + + +class CircularList(object): + """ + This class only creates a new type: a CircularList. + The idea is to export the last LIMIT messages via REST. + """ + LIMIT = 500 + + def __init__(self): + self._queue = dict() + self._num_items = 0 + + @property + def items(self): + """ + Return all items + """ + return self._queue + + def add(self, msg): + """ + Add an OF message to the CircularList + """ + + if self._num_items < self.LIMIT - 1: + self._queue[self._num_items] = {'time': str(datetime.now()), + 'type': msg.header.message_type} + self._num_items += 1 + elif self._num_items == self.LIMIT - 1: + self._queue[self._num_items] = msg + self._num_items = 0 diff --git a/apps/rest.py b/apps/rest.py new file mode 100644 index 0000000..d1a830a --- /dev/null +++ b/apps/rest.py @@ -0,0 +1,34 @@ +""" + This app creates the REST interface to be used + by all apps that want to. +""" +from flask import Flask +import apps.ofp_stats + + +app = Flask(__name__) + + +class CreateRest(object): + + def __init__(self): + self.run() + + @staticmethod + @app.route("/ofp_sniffer/ofp_stats/start_time") + def start_time(): + return apps.ofp_stats.OFStats().get_start_time() + + @staticmethod + @app.route("/ofp_sniffer/ofp_stats/packet_totals") + def index(): + return apps.ofp_stats.OFStats().get_counter() + + @staticmethod + @app.route("/ofp_sniffer/ofp_stats/last_msgs") + def last_msgs(): + return apps.ofp_stats.OFStats().get_last_msgs() + + @staticmethod + def run(): + app.run(host='0.0.0.0') diff --git a/docs/devices_list.json b/docs/devices_list.json new file mode 100644 index 0000000..403920c --- /dev/null +++ b/docs/devices_list.json @@ -0,0 +1,9 @@ +{ + "cc4e249102000000": "andes2", + "cc4e249126000000": "andes1", + "cc4e244b11000000": "sol2", + "0024389406000000": "mct01", + "002438af17000000": "mct02", + "2438af17000000": "mct02", + "24389406000000": "mct01" +} \ No newline at end of file diff --git a/docs/filters.json b/docs/filters.json new file mode 100644 index 0000000..bd0ac12 --- /dev/null +++ b/docs/filters.json @@ -0,0 +1,34 @@ +{ + "allowed_of_versions": { + "1.0": { + "rejected_of_types": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21 + ] + }, + "1.3": { + "rejected_of_types": [ + 0, 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 + ] + } + }, + "filters":{ + "ethertypes": { + "lldp" : 0, + "fvd" : 0, + "arp" : 0, + "others": [ "88b5" ] + }, + "packetIn_filter": { + "switch_dpid": "any", + "in_port": "any" + }, + "packetOut_filter": { + "switch_dpid": "any", + "out_port": "any" + } + } +} diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..47438ba --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,6 @@ +termcolor +hexdump +netaddr +pcapy +python-openflow +flask \ No newline at end of file diff --git a/docs/topology.json b/docs/topology.json new file mode 100644 index 0000000..51d367c --- /dev/null +++ b/docs/topology.json @@ -0,0 +1,48 @@ +{ + "switches": { + "sw1": { + "aliases": ["sw1", "switch01"], + "dpids": ["1", "0000000000000001"], + "ports": { + "eth1": {"alias": "eth1", "ofport_no": 1, "snmp_no:": 1, "type": "link", "speed": "10G"}, + "eth2": {"alias": "eth2", "ofport_no": 2, "snmp_no:": 2, "type": "link", "speed": "10G"}, + "eth3": {"alias": "eth3", "ofport_no": 3, "snmp_no:": 3, "type": "link", "speed": "10G"} + } + }, + "sw2": { + "aliases": ["sw2", "switch02"], + "dpids": ["2", "0000000000000002"], + "ports": { + "eth1": {"alias": "eth1", "ofport_no": 1, "snmp_no:": 1, "type": "link", "speed": "10G"}, + "eth2": {"alias": "eth2", "ofport_no": 2, "snmp_no:": 2, "type": "link", "speed": "10G"}, + "eth3": {"alias": "eth3", "ofport_no": 3, "snmp_no:": 3, "type": "link", "speed": "10G"} + } + }, + "sw3": { + "aliases": ["sw3", "switch03"], + "dpids": ["3", "0000000000000003"], + "ports": { + "eth1": {"alias": "eth1", "ofport_no": 1, "snmp_no:": 1, "type": "link", "speed": "10G"}, + "eth2": {"alias": "eth2", "ofport_no": 2, "snmp_no:": 2, "type": "link", "speed": "10G"}, + "eth3": {"alias": "eth3", "ofport_no": 3, "snmp_no:": 3, "type": "link", "speed": "10G"} + } + } + }, + + "links": { + "link1": { + "aliases": ["link1"], + "datapath_a": "sw1", + "port_a": "eth1", + "datapath_z": "sw2", + "port_z": "eth1" + }, + "link2": { + "aliases": ["link2"], + "datapath_a": "sw2", + "port_a": "eth2", + "datapath_z": "sw3", + "port_z": "eth1" + } + } +} diff --git a/example_filter.json b/example_filter.json deleted file mode 100644 index b231b6e..0000000 --- a/example_filter.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "allowed_of_versions": { - "1.0": { - "rejected_of_types": [ - 2, - 3, - 10, - 13 - ] - }, - "1.3": { - "rejected_of_types": [ - 2, - 3, - 10, - 13 - ] - } - }, - "packetIn_filter": { - "switch_dpid": "any", - "in_port": "any" - }, - "packetOut_filter": { - "switch_dpid": "dpid:5", - "out_port": "any" - }, - "flowMod_logs": { - "filter_1": { - "match": { - "vlan": "any", - "in_port": "1" - }, - "action": { - "output": "2", - "mod_dl_src": "14" - }, - "label": "onos" - }, - "filter_2": { - "match": { - "any": "any" - }, - "action": { - "mod_dl_dst": "any" - }, - "label": "oess_test" - }, - "filter_3": { - "match": { - "in_port": "4" - }, - "action": { - "any": "any" - }, - "label": "fibre" - }, - "all": { - "match": { - "any": "any" - }, - "action": { - "any": "any" - }, - "label": 0 - } - } -} diff --git a/libs/__init__.py b/libs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/core/__init__.py b/libs/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/core/cli.py b/libs/core/cli.py new file mode 100644 index 0000000..aa1b295 --- /dev/null +++ b/libs/core/cli.py @@ -0,0 +1,166 @@ +""" + This code handles the CLI parameters +""" + + +import sys +import getopt +import pcapy +from libs.core.printing import PrintingOptions + + +VERSION = '0.4' +# Change variable below to activate debugging +DEBUGGING = False + + +def usage(filename, msg=None): + """ + This funcion prints the Usage in case of errors or help needed. + Always ends after printing this lines below. + Args: + filename: name of the script called (usually ofp_sniffer.py) + msg: an error msg + """ + if msg is not None: + print(msg) + + print(('Usage: \n %s [-p min|full] [-f pcap_filter] [-F filter_file]' + ' [-i dev] [-r pcap_file]\n' + '\t -p : print full headers' + ' packet headers. Default: min\n' + '\t -f pcap_filter or --pcap-filter=pcap_filter: add a libpcap' + ' filter\n' + '\t -F filters_file.json or --filters-file=filters.json\n' + '\t -i interface or --interface=interface. Default: eth0\n' + '\t -r captured.pcap or --src-file=captured.pcap\n' + '\t -P topology.json or --topology-file=topology.json\n' + '\t -o or --print-ovs : print using ovs-ofctl format\n' + '\t -h or --help : prints this guidance\n' + '\t -c or --no-colors: removes colors\n' + '\t -v or --version : prints version\n' + '\t -O or --oess-fvd: monitor OESS FVD status\n' + '\t -S or --enable-statistics: creates statistics') % filename) + + sys.exit(0) + + +def check_file_position(filename): + """ + Check if -r file was inserted with colon (:) + If yes, only read the position specified after colon + Args: + filename: User's input -r + Returns: + position number + """ + new_file = filename.partition(":")[0] + position = filename.partition(":")[2] + return new_file, int(position) if len(position) is not 0 else 0 + + +def start_capture(capfile, infilter, dev): + """ + With all information in hand, start capturing packets + Args: + capfile: in case user provides a pcap file + infilter: any tcpdump filters + dev: network device to sniffer + Returns: + cap object + position number + """ + position = 0 + try: + if len(capfile) > 0: + capfile, position = check_file_position(capfile) + print("Using file %s " % capfile) + cap = pcapy.open_offline(capfile) + else: + print("Sniffing device %s" % dev) + cap = pcapy.open_live(dev, 65536, 1, 0) + + except Exception as exception: + print("Error: %s" % exception) + print("Exiting...") + sys.exit(3) + + if len(infilter) is 0: + infilter = " port 6633 " + cap.setfilter(infilter) + + return cap, position + + +def read_params(argv): + """ + Parser params received via CLI + + Args: + argv: inputs + Return: + opts: getopt object + """ + letters = 'f:F:i:r:T:pohvcOSq' + keywords = ['pcap-filter=', 'filters-file=', 'interface=', + 'src-file=', 'print-ovs', 'help', 'version', 'no-colors', + 'topology-file=', 'oess-fvd', 'enable-statistics', 'no-output'] + + try: + opts, _ = getopt.getopt(argv[1:], letters, keywords) + return opts + except getopt.GetoptError as err: + usage(argv[0], err) + + +def get_params(argv): + """ + + Get CLI params provided by user + Args: + argv: CLI params + Returns: + cap: pcap object + position: packet number to read + load_apps: apps to load + filters_file: filters + """ + # Default Values + input_filter, filters_file, dev, captured_file = '', '', 'eth0', '' + topology_file = "./docs/topology.json" + load_apps = [] + + opts = read_params(argv) + + for option, param in opts: + if option in ['-p']: + PrintingOptions().min = False + elif option in ['-f', '--pcap-filter']: + input_filter = param + elif option in ['-F', '--filters-file']: + filters_file = param + elif option in ['-i', '--interface']: + dev = param + elif option in ['-r', '--captured-file']: + captured_file = param + elif option in ['-o', '--print-ovs']: + PrintingOptions().print_ovs = True + elif option in ['-T', '--topology-file']: + topology_file = param + elif option in ['-c', '--no-colors']: + PrintingOptions().colors = False + elif option in ['-q', '--no-output']: + PrintingOptions().set_no_print() + elif option in ['-O', '--oess-fvd']: + load_apps.append('oess_fvd') + elif option in ['-S', '--enable-statistics']: + load_apps.append('statistics') + elif option in ['-v', '--version']: + print('OpenFlow Sniffer version %s' % VERSION) + sys.exit(0) + else: + usage(argv[0]) + + cap, position = start_capture(captured_file, input_filter, dev) + + return cap, position, load_apps, filters_file, topology_file diff --git a/libs/core/debugging.py b/libs/core/debugging.py new file mode 100644 index 0000000..3a5b5f4 --- /dev/null +++ b/libs/core/debugging.py @@ -0,0 +1,41 @@ +""" + Module created just to help with debugging +""" + + +from functools import wraps +import libs.core.cli + + +def debug(func): + """ + Decorator to help troubleshooting + Prints method names + Args: + func: + Returns: + """ + @wraps(func) + def wrapper(*args, **kwargs): + """ + Wraps to preserve docs + """ + print('*** %s' % func.__name__) + return func(*args, **kwargs) + return wrapper + + +def debugclass(cls): + """ + Decorator to help troubleshooting classes + Call debug for each method of the cls + Args: + cls: Any Class to be printed + Returns: wrapper with cls, printing all method names + """ + if libs.core.cli.DEBUGGING: + for key, val in vars(cls).items(): + if callable(val): + setattr(cls, key, debug(val)) + + return cls diff --git a/libs/core/filters.py b/libs/core/filters.py new file mode 100644 index 0000000..9eb876f --- /dev/null +++ b/libs/core/filters.py @@ -0,0 +1,220 @@ +""" + Filters to be used + Any customized print filters should be inserted in this file + Filters are provided via CLI option -F json-file +""" + + +from libs.core.printing import PrintingOptions +from libs.core.sanitizer import Sanitizer +from libs.tcpiplib.tcpip import get_ofp_version +from libs.tcpiplib.process_data import is_protocol +from libs.tcpiplib.process_data import get_protocol +from libs.gen.dpid_handling import clear_dpid + + +def filter_msg(msg): + """ + This method will be the core of all filters. Any new filter comes here + Args: + msg: OFMessage class + Returns: + False: Don't filter packet + True: Filter it (don't print) + """ + + if PrintingOptions().quiet: + # Don't print anything. Used in conjunction with some apps. + return True + + if PrintingOptions().filters is 0: + # User hasn't selected CLI option -F + return False + + # Filter per OF Version + if filter_of_version(msg): + return True + + # Filter per OF Message Type + if filter_of_type(msg): + return True + + # Filter Ethertypes from PacketIn/Out messages + if ethertype_filters(msg): + return True + + # Filter PacketIn/Out based on DPID and Port + if dpid_filters(msg): + return True + + # Don't filter + return False + + +def filter_of_version(msg): + """ + Check if the OpenFlow version is allowed + Args: + msg: OFMessage class + Returns: + False: Don't filter packet + True: Filter it (don't print) + """ + name_version = get_ofp_version(msg.ofp.header.version.value) + supported_versions = [] + try: + for version in Sanitizer().allowed_of_versions: + supported_versions.append(version) + if name_version not in supported_versions: + return True + except KeyError: + pass + return False + + +def filter_of_type(msg): + """ + Filter per OF Message Type + Args: + msg: OFMessage class + Returns: + False: Don't filter packet + True: Filter it (don't print) + """ + name_version = get_ofp_version(msg.ofp.header.version.value) + # OF Types to be ignored through json file (-F) + try: + rejected_types = Sanitizer().allowed_of_versions[name_version] + if msg.ofp.header.message_type in rejected_types['rejected_of_types']: + return True + except KeyError: + pass + return False + + +def ethertype_filters(msg): + """ + Filter PacketIn and PacketOut messages based on Ethertype + Sanitizer filter (-F), entry "filters", "ethertype" + Args: + msg: class OFMessage + Returns: + False: Don't filter packet + True: Filter it (don't print) + """ + if msg.ofp.header.message_type in [10, 13]: + try: + filters = Sanitizer().filters['ethertypes'] + except KeyError: + return False + + if not len(filters): + # No filters + return False + + # Go to payload + try: + if is_protocol(msg.ofp.data, lldp=True) and filters['lldp']: + return True + if is_protocol(msg.ofp.data, oess=True) and filters['fvd']: + return True + if is_protocol(msg.ofp.data, arp=True) and filters['arp']: + return True + except KeyError: + pass + + # Other Ethertypes listed as hex + for protocol in filters['others']: + try: + if is_protocol(msg.ofp.data) == int(protocol, 16): + return True + except ValueError: + pass + + return False + + +def dpid_filters(msg): + """ + Filter PacketIn and PacketOut messages based on DPID and ports + Sanitizer filter (-F), entry "filters", "packetIn_filter" or + "packetOut_filter" + If switch_dpid AND in_port are Any, don't filter (print it) + If switch_dpid OR in_port are NOT Any, print only what matches the + most specific (filter everything else) + Args: + msg: class OFMessage + Returns: + False: Don' filter packet (print it) + True: Filter it (don't print) + """ + + # It has to be a PacketOut or PacketIn + if msg.ofp.header.message_type not in [10, 13]: + return False + + # It has to be a LLDP packet + if not is_protocol(msg.ofp.data, lldp=True): + return False + + try: + # If it is a PacketIn ... + if msg.ofp.header.message_type in [10]: + # It has to have a packetIn_filter filter + filters = Sanitizer().filters['packetIn_filter'] + filter_port = filters['in_port'] + + # If it a PacketOut... + else: + # It has to have a packetOut_filter filter + filters = Sanitizer().filters['packetOut_filter'] + filter_port = filters['out_port'] + + filter_dpid = filters['switch_dpid'] + + except KeyError: + return False + if not len(filters): + return False + + # Was switch_dpid or in_port specified by user? + if filter_dpid in ['any', 'Any', 'ANY']: + if filter_port in ['any', 'Any', 'ANY']: + return False + + # If we got here, it means we have content to avoid printing + print_it = False + lldp_msg = get_protocol(msg.ofp.data, lldp=True) + switch_dpid = clear_dpid(filter_dpid) + + if print_switch_dpid(switch_dpid, lldp_msg.c_id): + if msg.ofp.header.message_type in [10]: + if print_port(filter_port, str(msg.ofp.in_port)): + print_it = True + else: + if print_port(filter_port, str(lldp_msg.p_id)): + print_it = True + + if print_it: + return False + + return True + + +def print_switch_dpid(filter_dpid, packet_dpid): + """ + Confirm if filter_dpid is packet_dpid or any + """ + packet_dpid = clear_dpid(packet_dpid) + if filter_dpid in [packet_dpid, 'Any', 'any', 'ANY']: + return True + return False + + +def print_port(filter_port, packet_port): + """ + Confirm if filter_port is packet_port or any + """ + if filter_port in [packet_port, 'Any', 'any', 'ANY']: + return True + return False diff --git a/libs/core/printing.py b/libs/core/printing.py new file mode 100644 index 0000000..6f5bac9 --- /dev/null +++ b/libs/core/printing.py @@ -0,0 +1,25 @@ +""" + Class with CLI printing options +""" +from libs.core.singleton import Singleton + + +class PrintingOptions(metaclass=Singleton): + """ + This is a Singleton class with all printing + options. + """ + + def __init__(self): + self.min = 1 # print minimal headers + self.colors = True # print colors + self.filters = 0 # apply filters + self.proxy = 0 # add proxy support + self.print_ovs = False # print ovs format + self.quiet = False # don't print anything + + def set_no_print(self): + """ + Set to avoiding printing. + """ + self.quiet = True diff --git a/libs/core/sanitizer.py b/libs/core/sanitizer.py new file mode 100644 index 0000000..727a668 --- /dev/null +++ b/libs/core/sanitizer.py @@ -0,0 +1,63 @@ +""" + Filters/Sanitizer Class + Used for filtering specific OpenFlow versions and + message types. Filters are provided via JSON file + in the cli with option -F or --filters-file +""" + + +import json +import sys +from libs.core.singleton import Singleton +from libs.core.printing import PrintingOptions + + +class Sanitizer(metaclass=Singleton): + """ + Filters/Sanitizer Class + Used for filtering specific OpenFlow versions and + message types. Filters are provided via JSON file + in the cli with option -F or --filters-file + """ + + def __init__(self): + self.allowed_of_versions = dict() + self.filters = dict() + + @staticmethod + def read_file(filters_file): + """ + Read the JSON file provided through -F + Args: + filters_file: file provided + Returns: + json content of the file provided + """ + try: + with open(filters_file) as jfile: + json_content = json.loads(jfile.read()) + + except Exception as error: + msg = 'Error Opening the sanitizer file\n' + msg += 'Please check your JSON file. Maybe the permission is wrong' + msg += ' or the JSON syntax is incorrect. Try the following:\n' + msg += 'cat %s | python -m json.tool' + print(msg % filters_file) + print("Error seen: %s" % error) + sys.exit(0) + return json_content + + def process_filters(self, filters_file): + """ + If -F file is provided, read the file and import all filters. + Args: + filters_file: file with filters + + """ + if len(filters_file) == 0: + return + configs = self.read_file(filters_file) + if len(configs) != 0: + PrintingOptions().filters = 1 + self.allowed_of_versions = configs['allowed_of_versions'] + self.filters = configs['filters'] diff --git a/libs/core/singleton.py b/libs/core/singleton.py new file mode 100644 index 0000000..48a969c --- /dev/null +++ b/libs/core/singleton.py @@ -0,0 +1,16 @@ +""" + Metaclass do define singleton classes + Taken from http://stackoverflow.com/questions/6760685/creating-a-singleton-in-python +""" + + +class Singleton(type): + """ + Singleton Class + """ + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instances[cls] diff --git a/libs/core/topo_reader.py b/libs/core/topo_reader.py new file mode 100644 index 0000000..3d0a713 --- /dev/null +++ b/libs/core/topo_reader.py @@ -0,0 +1,171 @@ +""" + Class to read the topology.json file in use at AmLight +""" + + +import json +from libs.core.singleton import Singleton + + +class TopoReader(metaclass=Singleton): + """ + Under construction + """ + + def __init__(self): + self._dpids = {} + self._links = {} + self._port_map = {} + self._topology = None + + def add_datapath(self, switch): + """ + + :param switch: + :return: + """ + for dpid in switch["dpids"]: + self._dpids[dpid] = switch["aliases"][0] + + for port in switch["ports"]: + alias = switch["ports"][port]["alias"] + ofport_no = switch["ports"][port]["ofport_no"] + self._port_map[switch["aliases"][0], ofport_no] = alias + + def add_datapath_port(self, switch, port): + """ + + :param switch: + :param port: + :return: + """ + pass + + def add_link(self, link): + """ + + :param link: + :return: + """ + self._links[link["datapath_a"], link["port_a"], + link["datapath_z"], link["port_z"]] = link["aliases"] + self._links[link["datapath_z"], link["port_z"], + link["datapath_a"], link["port_a"]] = link["aliases"] + + def get_datapath_name(self, dpid=None): + """ + + :param dpid: + :return: + """ + try: + if dpid.find(":"): + dpid = dpid.replace(":", "") + + return self._dpids[dpid] + except KeyError: + return dpid + + def get_datapath_id(self, alias=None): + """ + + :param alias: + :return: + """ + pass + + def get_port_name(self, dpid=None, port_no=None): + """ + + :param dpid: + :param port_no: + :return: + """ + try: + return self._port_map[dpid, port_no] + except KeyError: + return port_no + + def get_port_id(self, dpid=None, alias=None): + """ + + :param dpid: + :param alias: + :return: + """ + pass + + def clear_dpid(self, dpid): + """ + + :param dpid: + :return: + """ + return self.get_datapath_name(dpid) + + def get_link_aliases(self, dp_a, port_a, dp_z, port_z, option="Full"): + """ + + :param dp_a: + :param port_a: + :param dp_z: + :param port_z: + :param option: + :return: + """ + dp_a = self.clear_dpid(dp_a) + dp_z = self.clear_dpid(dp_z) + + port_a = self.get_port_name(dp_a, port_a) + port_z = self.get_port_name(dp_z, port_z) + + try: + if option == "First": + return self._links[dp_a, port_a, dp_z, port_z][0] + elif option == "Full": + return self._links[dp_a, port_a, dp_z, port_z][1] + return self._links[dp_a, port_a, dp_z, port_z] + except KeyError: + return {} + + def get_link_config(self, alias=None): + """ + + :param alias: + :return: + """ + try: + for link in self._topology["links"]: + + if alias in self._topology["links"][link]["aliases"]: + link = self._topology["links"][link] + return (link["datapath_a"], link["port_a"], + link["datapath_z"], link["port_z"]) + except KeyError: + return {} + + def get_topology(self): + """ + + :return: + """ + return self._topology + + def readfile(self, topo_file): + """ + + Args: + topo_file: + """ + try: + with open(topo_file) as jfile: + self._topology = json.loads(jfile.read()) + except Exception as error: + print("Error %s " % error) + return + + for switch in self._topology['switches']: + self.add_datapath(self._topology['switches'][switch]) + + for link in self._topology['links']: + self.add_link(self._topology['links'][link]) diff --git a/libs/gen/__init__.py b/libs/gen/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/gen/dpid_handling.py b/libs/gen/dpid_handling.py new file mode 100644 index 0000000..1b10b49 --- /dev/null +++ b/libs/gen/dpid_handling.py @@ -0,0 +1,48 @@ +""" + This module does all DPID cleaning needed by all other modules, + such as removing substrings 'dpid:', removing ':' and others +""" + + +def clear_dpid(dpid): + """ + clear_dpid removes any non useful info from DPID. Some + examples of DPIDs: + + "dpid:11:11:11:11:11:11" + "dp:11:11:11:11:11:11" + "11:11:11:11:11:11" + "111111111111" + + The goal is to return the last one: "111111111111" + + Args: + dpid: dpid to be fixed + + Returns: + dpid fixed + + >>> clear_dpid("dpid:11:11:11:11:11:11") + '111111111111' + >>> clear_dpid("dp:11:11:11:11:11:11") + '111111111111' + >>> clear_dpid("11:11:11:11:11:11") + '111111111111' + >>> clear_dpid("111111111111") + '111111111111' + """ + dpid_names = ["dpid:", "dp:"] + + for dpid_name in dpid_names: + pos = dpid.find(dpid_name) + if pos != -1: + # substring found + dpid = dpid[pos+len(dpid_name):] + + if len(dpid.split(":")) == 2: + return dpid.split(":")[1] + + elif len(dpid.split(":")) > 2: + return dpid.replace(":", "") + + return dpid diff --git a/libs/gen/ofmessage.py b/libs/gen/ofmessage.py new file mode 100644 index 0000000..528d3f8 --- /dev/null +++ b/libs/gen/ofmessage.py @@ -0,0 +1,54 @@ +""" + This is the class for the OpenFlow message. With the python-openflow + library, introduced in the version 0.4, this class became much + simpler. +""" + +from pyof.v0x01.common.utils import unpack_message +import libs.core.filters +import libs.tcpiplib.packet +import libs.tcpiplib.prints +from libs.core.debugging import debugclass +from libs.openflow.of10.prints import prints_ofp + + +@debugclass +class OFMessage(object): + """ + Used to process all data regarding this OpenFlow message. With + the python-openflow (pyof) lib, only one variable became + necessary. + """ + def __init__(self, this_packet): + """ + Instantiate OFMessage class + Args: + self: this class + this_packet: OpenFlow msg in binary format + """ + try: + self.ofp = unpack_message(this_packet) + except: + # if there is a problem with the python-openflow + # just set the self.ofp to 0. It will be ignored + # by the Packet().add_of_msg_to_list + self.ofp = 0 + + def print_packet(self, pkt): + """ + Generic printing function + Args: + pkt: Packet class + """ + # Check if there is any printing filter + if not libs.core.filters.filter_msg(self): + # Only prints TCP/IP header once + # A TCP/IP packet might contain multiple OpenFlow messages + if pkt.printed_header is False: + libs.tcpiplib.prints.print_headers(pkt) + pkt.printed_header = True + # Print OpenFlow header - version independent + libs.tcpiplib.prints.print_openflow_header(self.ofp) + # Print OpenFlow message body + prints_ofp(self.ofp) + print() diff --git a/libs/gen/packet.py b/libs/gen/packet.py new file mode 100644 index 0000000..75dbcd6 --- /dev/null +++ b/libs/gen/packet.py @@ -0,0 +1,142 @@ +""" + Class Packet: used to process EACH IP packet. Each IP packet + might have multiple OpenFlow messages (class OFMessage) +""" +import libs.tcpiplib.packet +import libs.tcpiplib.prints +from libs.core.debugging import debugclass +from libs.gen.ofmessage import OFMessage +from libs.tcpiplib.packet import IP_PROTOCOL, TCP_PROTOCOL, TCP_FLAG_PUSH +from libs.tcpiplib.tcpip import get_openflow_header + + +@debugclass +class Packet: + """ + Used to save all data about the TCP/IP packet + """ + def __init__(self, packet, packet_number, header): + """ + Instantiate this class + Args: + packet: the whole captured packet from NIC or pcap file + packet_number: position of this packet in the packet capture + header: packet header + """ + # Raw packet + self.packet = packet + + # Controls + self.position = packet_number + self.offset = 0 + self.is_openflow_packet = False + self.printed_header = False + self.this_packet = None + self.remaining_bytes = None + + # Instantiate TCP/IP headers + self.l1 = libs.tcpiplib.packet.L1() + self.l2 = libs.tcpiplib.packet.Ethernet() + self.l3 = libs.tcpiplib.packet.IP() + self.l4 = libs.tcpiplib.packet.TCP() + + # OpenFlow messages Array + # As multiple OpenFlow messages per packet is possible + # an list of messages needs to be created + self.cur_msg = 0 + self.ofmsgs = [] + + # Process packet + self.process_packet_header(header) + + def process_packet_header(self, header): + """ + Process TCP/IP Header, from Layer 1 to TCP. + Each layer has a different class. Methods parse are used + per layer to dissect it + Args: + header: header of the captured packet + """ + self.l1.parse(header) + self.offset = self.l2.parse(self.packet) + if self.l2.protocol == IP_PROTOCOL: + self.offset = self.l3.parse(self.packet, self.offset) + if self.l3.protocol == TCP_PROTOCOL: + self.offset = self.l4.parse(self.packet, self.offset) + if self.l4.flag_psh == TCP_FLAG_PUSH: + self.is_openflow_packet = True + elif self.l4.flag_fyn and self.l4.flag_ack: + libs.tcpiplib.prints.print_connection_restablished(self) + + def process_openflow_messages(self): + """ + Because an IP packet can have multiple OpenFlow messages + let's get all of them here. + """ + self.remaining_bytes = self.get_remaining_bytes() + + while self.remaining_bytes >= 8: + # self.this_packet is the OpenFlow message + # let's get the current OpenFlow message from the packet + length = self.get_of_message_length() + if length < 8: + # MalFormed Packet - it could be a fragment + return False + + self.this_packet = self.packet[self.offset:self.offset+length] + if len(self.this_packet) != length: + # it means packet is smaller than it should be + # propably MTU issue or message was cut by the datapath + return False + + self.add_of_msg_to_list(OFMessage(self.this_packet)) + + self.remaining_bytes -= length + self.offset += length + + # If there are other OpenFlow messages, let's continue + if self.remaining_bytes >= 8: + self.cur_msg += 1 + + return True + + def add_of_msg_to_list(self, ofmsg): + """ + Instantiate the OpenFlow message in the ofmsgs array + A TCP/IP packet might contain multiple OpenFlow messages + Process the content, using cur_msg position of the array of msgs + """ + try: + if isinstance(ofmsg, OFMessage): + if not isinstance(ofmsg.ofp, int): + self.ofmsgs.insert(self.cur_msg, ofmsg) + + else: + raise TypeError + + except TypeError as err: + print("ERROR on Packet no. %s: " % self.position, end='') + print(err) + print() + return ofmsg + + def print_packet(self): + """ + This method iterate over the self.ofmsgs (array of OF messages), + printing each one of them. + """ + for msg in self.ofmsgs: + msg.print_packet(self) + + def get_remaining_bytes(self): + """ + Calculate how many bytes to be processed + """ + return self.l1.caplen - self.offset + + def get_of_message_length(self): + """ + Get the openflow header length + """ + of_h = get_openflow_header(self.packet, self.offset) + return of_h['length'] diff --git a/libs/gen/prints.py b/libs/gen/prints.py new file mode 100644 index 0000000..ccfa977 --- /dev/null +++ b/libs/gen/prints.py @@ -0,0 +1,72 @@ +""" + Generic printing: colors +""" + + +from termcolor import colored +from libs.core.printing import PrintingOptions + + +def red(string): + """ + If PrintingOptions().colors, prints string + in the color RED + + Args: + string: text to be printed + """ + if PrintingOptions().colors is False: + return string + return colored(string, 'red') + + +def green(string): + """ + If PrintingOptions().colors, prints string + in the color GREEN + + Args: + string: text to be printed + """ + if PrintingOptions().colors is False: + return string + return colored(string, 'green') + + +def blue(string): + """ + If PrintingOptions().colors, prints string + in the color BLUE + + Args: + string: text to be printed + """ + if PrintingOptions().colors is False: + return string + return colored(string, 'blue') + + +def yellow(string): + """ + If PrintingOptions().colors, prints string + in the color YELLOW + + Args: + string: text to be printed + """ + if PrintingOptions().colors is False: + return string + return colored(string, 'yellow') + + +def cyan(string): + """ + If PrintingOptions().colors, prints string + in the color CYAN + + Args: + string: text to be printed + """ + if PrintingOptions().colors is False: + return string + return colored(string, 'cyan') diff --git a/libs/openflow/__init__.py b/libs/openflow/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/openflow/of10/__init__.py b/libs/openflow/of10/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ofp_dissector_v10.py b/libs/openflow/of10/dissector.py similarity index 61% rename from ofp_dissector_v10.py rename to libs/openflow/of10/dissector.py index 1453bd6..5684639 100644 --- a/ofp_dissector_v10.py +++ b/libs/openflow/of10/dissector.py @@ -1,59 +1,45 @@ -''' +""" This is the OpenFlow 1.0 dictionary/dissector - Here messages, types and codes are converted to names -''' - - -def get_ofp_version(version): - of_versions = {0: 'Experimental', - 1: '1.0', - 2: '1.1', - 3: '1.2', - 4: '1.3', - 5: '1.4', - 6: '1.5'} - try: - return of_versions[version] - except: - return 'Unknown(%s)' % version + Here messages, types and codes are converted to names. +""" def get_ofp_type(of_type): - of_types = {0: 'Hello', - 1: 'Error', - 2: 'EchoReq', - 3: 'EchoRes', - 4: 'Vendor', - 5: 'FeatureReq', - 6: 'FeatureRes', - 7: 'GetConfigReq', - 8: 'GetConfigRes', - 9: 'SetConfig', - 10: 'PacketIn', - 11: 'FlowRemoved', - 12: 'PortStatus', - 13: 'PacketOut', - 14: 'FlowMod', - 15: 'PortMod', - 16: 'StatsReq', - 17: 'StatsRes', - 18: 'BarrierReq', - 19: 'BarrierRes', - 20: 'QueueGetConfigReq', - 21: 'QueueGetConfigRes'} + of_types = {0: 'OFPT_HELLO', + 1: 'OFPT_ERROR', + 2: 'OFPT_ECHO_REQUEST', + 3: 'OFPT_ECHO_REPLY', + 4: 'OFPT_VENDOR', + 5: 'OFPT_FEATURES_REQUEST', + 6: 'OFPT_FEATURES_REPLY', + 7: 'OFPT_GET_CONFIG_REQUEST', + 8: 'OFPT_GET_CONFIG_REPLY', + 9: 'OFPT_SET_CONFIG', + 10: 'OFPT_PACKET_IN', + 11: 'OFPT_FLOW_REMOVED', + 12: 'OFPT_PORT_STATUS', + 13: 'OFPT_PACKET_OUT', + 14: 'OFPT_FLOW_MOD', + 15: 'OFPT_PORT_MOD', + 16: 'OFPT_STATS_REQUEST', + 17: 'OFPT_STATS_REPLY', + 18: 'OFPT_BARRIER_REQUEST', + 19: 'OFPT_BARRIER_REPLY', + 20: 'OFPT_QUEUE_GET_CONFIG_REQUEST', + 21: 'OFPT_QUEUE_GET_CONFIG_REPLY'} try: return of_types[of_type] - except: + except KeyError: return 'UnknownType(%s)' % of_type def get_ofp_error(error_type, code): - errors_types = {} - codes = {} + errors_types = dict() + error_codes = dict() - # Starts with an Error + # Starts with an Error - exception errors_types[error_type] = 'UnknownType(%s)' % error_type - codes[code] = 'UnknownCode(%s)' % code + error_codes[code] = 'UnknownCode(%s)' % code # Error Types if error_type in range(0, 6): @@ -67,60 +53,60 @@ def get_ofp_error(error_type, code): # Error Codes per Error Type if error_type == 0: if code in range(0, 2): - codes = {0: 'Incompatible(0)', - 1: 'EPerm(1)'} + error_codes = {0: 'Incompatible(0)', + 1: 'EPerm(1)'} elif error_type == 1: if code in range(0, 9): - codes = {0: 'BadVersion(0)', - 1: 'BadType(1)', - 2: 'BadStat(2)', - 3: 'BadVendor(3)', - 4: 'BadSubtype(4)', - 5: 'EPerm(5)', - 6: 'BadLength(6)', - 7: 'BufferEmpty(7)', - 8: 'BufferUnknown(8)'} + error_codes = {0: 'BadVersion(0)', + 1: 'BadType(1)', + 2: 'BadStat(2)', + 3: 'BadVendor(3)', + 4: 'BadSubtype(4)', + 5: 'EPerm(5)', + 6: 'BadLength(6)', + 7: 'BufferEmpty(7)', + 8: 'BufferUnknown(8)'} elif error_type == 2: if code in range(0, 9): - codes = {0: 'BadType', - 1: 'BadLength', - 2: 'BadVendor', - 3: 'BadVendorType', - 4: 'BadOutPort', - 5: 'BadArgument', - 6: 'EPerm', - 7: 'TooMany', - 8: 'BadQueue'} + error_codes = {0: 'BadType', + 1: 'BadLength', + 2: 'BadVendor', + 3: 'BadVendorType', + 4: 'BadOutPort', + 5: 'BadArgument', + 6: 'EPerm', + 7: 'TooMany', + 8: 'BadQueue'} elif error_type == 3: if code == 0 or code in range(2, 7): - codes = {0: 'AllTablesFull(0)', - 2: 'Overlap(2)', - 3: 'EPerm(3)', - 4: 'BadEmergTimeout(4)', - 5: 'BadCommand(5)', - 6: 'Unsupported(6)'} + error_codes = {0: 'AllTablesFull(0)', + 2: 'Overlap(2)', + 3: 'EPerm(3)', + 4: 'BadEmergTimeout(4)', + 5: 'BadCommand(5)', + 6: 'Unsupported(6)'} elif error_type == 4: if code in range(0, 2): - codes = {0: 'BadPort(0)', - 1: 'BadHwAddr(1)'} + error_codes = {0: 'BadPort(0)', + 1: 'BadHwAddr(1)'} elif error_type == 5: if code in range(0, 3): - codes = {0: 'BadPort(0)', - 1: 'BadQueue(1)', - 2: 'EPerm(2)'} + error_codes = {0: 'BadPort(0)', + 1: 'BadQueue(1)', + 2: 'EPerm(2)'} - return errors_types[error_type], codes[code] + return errors_types[error_type], error_codes[code] def get_ofp_vendor(vendor_id): # NICIRA / OVS: 0x2320 or 8992 if vendor_id == 8992: - return 'NICIRA(' + hex(vendor_id) + ')' + return "NICIRA(%s)" % (hex(vendor_id)) else: return str(vendor_id) @@ -133,10 +119,18 @@ def get_ofp_command(command): 4: 'DeleteStrict(4)'} try: return commands[command] - except: + except KeyError: return 'UnknownCommand(%s)' % command +def get_vlan(vlan): + vlans = {65535: 'Untagged(0xFFFF)'} + try: + return vlans[vlan] + except KeyError: + return vlan + + def get_ofp_flags(flag): flags = {0: 'NoFlagSet(0)', 1: 'SendFlowRem(1)', @@ -144,7 +138,7 @@ def get_ofp_flags(flag): 3: 'Emerg(3)'} try: return flags[flag] - except: + except KeyError: return 'UnknownFlag(%s)' % flag @@ -154,7 +148,7 @@ def get_flow_removed_reason(reason): 2: 'Delete(2)'} try: return rsn[reason] - except: + except KeyError: return 'UnknownReason(%s)' % reason @@ -169,7 +163,7 @@ def get_feature_res_capabilities(cap): 128: 'ARP_MATCH_IP(0x80)'} try: return caps[cap] - except: + except KeyError: return 'UnknownCapability(%s)' % cap @@ -188,7 +182,7 @@ def get_feature_res_actions(action): 2048: 'ENQUEUE(0x800)'} try: return actions[action] - except: + except KeyError: return 'UnknownAction(%s)' % action @@ -204,7 +198,7 @@ def get_phy_port_id(p_id): 65535: 'None(0xFFFF)'} try: return ids[p_id] - except: + except KeyError: return '%s' % p_id @@ -218,7 +212,7 @@ def get_phy_config(p_cfg): 64: 'NoPacketIn(0x40)'} try: return cfg[p_cfg] - except: + except KeyError: return 'UnknownConfig(%s)' % p_cfg @@ -231,7 +225,7 @@ def get_phy_state(p_state): 16: 'STPMask(0x10)'} try: return state[p_state] - except: + except KeyError: return 'UnknownState(%s)' % p_state @@ -250,7 +244,7 @@ def get_phy_feature(p_feature): 2048: 'PauseAsym(0x800)'} try: return ftr[p_feature] - except: + except KeyError: return 'UnknownFeature(%s)' % p_feature @@ -261,24 +255,24 @@ def get_configres_flags(flag): 3: 'FRAG_MASK(3)'} try: return flags[flag] - except: + except KeyError: return 'UnknownFlag(%s)' % flag -def get_portStatus_reason(reason): +def get_port_status_reason(reason): reasons = {0: 'OFPPR_ADD(0)', 1: 'OFPPR_DELETE(1)', 2: 'OFPPR_MODIFY(2)'} try: return reasons[reason] - except: + except KeyError: return 'UnknownReason(%s)' % reason -def get_packetIn_reason(reason): +def get_packet_in_reason(reason): reasons = {0: 'OFPR_NO_MATCH(0)', 1: 'OFPR_ACTION(1)'} try: return reasons[reason] - except: + except KeyError: return 'UnknownReason(%s)' % reason diff --git a/libs/openflow/of10/prints.py b/libs/openflow/of10/prints.py new file mode 100644 index 0000000..e3b9930 --- /dev/null +++ b/libs/openflow/of10/prints.py @@ -0,0 +1,1037 @@ +""" + Prints for OpenFlow 1.0 only +""" +from hexdump import hexdump +from pyof.foundation.basic_types import BinaryData +from pyof.v0x01.common.phy_port import ListOfPhyPorts +from libs.gen.prints import red, green, yellow +import libs.tcpiplib.tcpip +import libs.openflow.of10.dissector as dissector +from libs.tcpiplib.prints import print_openflow_header +from libs.tcpiplib.process_data import dissect_data + + +# ******************** Points to the right printing function **************** + + +def prints_ofp(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + Returns: + + """ + + try: + of_types = {0: print_ofpt_hello, + 1: print_ofpt_error, + 2: print_ofpt_echo_request, + 3: print_ofpt_echo_reply, + 4: print_ofpt_vendor, + 5: print_ofpt_features_request, + 6: print_ofpt_features_reply, + 7: print_ofpt_get_config_request, + 8: print_ofpt_get_config_reply, + 9: print_ofpt_set_config, + 10: print_ofpt_packet_in, + 11: print_ofpt_flow_removed, + 12: print_ofpt_port_status, + 13: print_ofpt_packet_out, + 14: print_ofpt_flow_mod, + 15: print_ofpt_port_mod, + 16: print_ofpt_stats_request, + 17: print_ofpt_stats_reply, + 18: print_ofpt_barrier_request, + 19: print_ofpt_barrier_reply, + 20: print_ofpt_queue_get_config_request, + 21: print_ofpt_queue_get_config_reply} + + return of_types[msg.header.message_type.value](msg) + except Exception as err: + print("Error: %s" % err) + + +# *************************** OFPT_HELLO ************************************* + + +def print_ofpt_hello(msg): + """ OFPT_HELLO has no payload, so it does not print anything. + It is here just for educational purposes. + Args: + msg: OpenFlow message unpacked by python-openflow + """ + pass + + +# *************************** OFPT_ERROR ************************************** + + +def print_ofpt_error(msg): + """ Prints OFPT_ERROR messages. msg.data is the error payload, which is + the OpenFlow message that triggered the error. + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + etype, ecode = dissector.get_ofp_error(msg.error_type.value, msg.code.value) + print('OpenFlow Error - Type: %s Code: %s' % (red(etype), red(ecode))) + + if not isinstance(msg.data, BinaryData): + print('OpenFlow Error Message:\n------ BEGIN ------') + print_openflow_header(msg.data) + prints_ofp(msg.data) + print('OpenFlow Error Message:\n------- END -------') + else: + print(red('OpenFlow Error Data could not be processed!!')) + + +# *************************** OFPT_ECHO_REQUEST ****************************** + + +def print_ofpt_echo_request(msg): + """ Prints OFPT_ECHO_REQUEST messages. msg.data can be any content. The + controller that sent the ECHO knows what it means. As a sniffer, we do + not know what it means so we print in hex. + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + if len(msg.data.value) > 0: + hexdump(msg.data.value) + + +# *************************** OFPT_ECHO_REPLY ******************************** + + +def print_ofpt_echo_reply(msg): + """ Prints OFPT_ECHO_REPLY messages. msg.data can be any content. The + controller that sent the ECHO knows what it means. As a sniffer, we do + not know what it means so we print in hex. + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + if len(msg.data.value) > 0: + hexdump(msg.data.value) + + +# ****************************** OFPT_VENDOR ********************************* + + +def print_ofpt_vendor(msg): + """ Prints OFPT_VENDOR messages. Any vendor can extend the OpenFlow + specification with new messages. OVS/NICIRA uses this message a lot. + We do not dissect vendor-specific messages but it would be great to have + such support (which means we are looking for volunteers!). + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + vendor = dissector.get_ofp_vendor(msg.vendor.value) + print('OpenFlow Vendor: %s' % vendor) + + +# ************************ OFPT_FEATURES_REQUEST ***************************** + + +def print_ofpt_features_request(msg): + """ OFPT_FEATURES_REQUEST has no payload, so this function does not print + anything. It is here just for educational purposes. + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + pass + + +# ************************* OFPT_FEATURES_REPLY ***************************** + + +def print_ofpt_features_reply(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + dpid = msg.datapath_id + print('FeatureRes - datapath_id: %s n_buffers: %s n_tables: %s, pad: %s' + % (green(dpid), msg.n_buffers, msg.n_tables, msg.pad)) + + print('FeatureRes - Capabilities: ', end='') + capabilities = _parse_capabilities(msg.capabilities) + for i in capabilities: + print(dissector.get_feature_res_capabilities(i) + ' ', end='') + print() + + print('FeatureRes - Actions: ', end='') + actions = _parse_actions(msg.actions) + for i in actions: + print(dissector.get_feature_res_actions(i) + ' ', end='') + print() + + print_of_ports(msg.ports) + + +# ************************* OFPT_GET_CONFIG_REQUEST ***************************** + + +def print_ofpt_get_config_request(msg): + """ OFPT_GET_CONFIG_REQUEST has no payload, so this function does not print + anything. It is here just for educational purposes. + Args: + msg: OpenFlow message unpacked by python-openflow + """ + pass + + +# ************************* OFPT_GET_CONFIG_REPLY ******************************* + + +def print_ofpt_get_config_reply(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + print('OpenFlow GetConfigRes - Flag: %s Miss_send_len: %s' % + (msg.flags, msg.miss_send_len)) + + +# ************************* OFPT_SET_CONFIG ********************************** + + +def print_ofpt_set_config(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + flags = str(msg.flags) + print('OpenFlow SetConfig - Flag: %s Miss_send_len: %s' % + (yellow(flags.split('.')[1]), msg.miss_send_len)) + + +# ************************* OFPT_PACKET_IN *********************************** + + +def print_ofpt_packet_in(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + print('PacketIn: buffer_id: %s total_len: %s in_port: %s reason: %s ' + 'pad: %s' % + (hex(msg.buffer_id.value), msg.total_len.value, + green(msg.in_port.value), green(msg.reason.value), msg.pad)) + print_data(msg.data) + + +def print_data(data): + """ + Print msg.data from both PacketIn and PacketOut + Args: + data: msg.data - array of protocols + """ + if isinstance(data, BinaryData): + data = dissect_data(data) + + try: + eth = data.pop(0) + libs.tcpiplib.prints.print_layer2(eth) + next_protocol = eth.protocol + + if next_protocol in [33024]: + vlan = data.pop(0) + libs.tcpiplib.prints.print_vlan(vlan) + next_protocol = vlan.protocol + + if next_protocol in [35020, 35138]: + lldp = data.pop(0) + libs.tcpiplib.prints.print_lldp(lldp) + elif next_protocol in [34998]: + fvd = data.pop(0) + libs.tcpiplib.prints.print_oessfvd(fvd) + elif next_protocol in [2048]: + ip = data.pop(0) + libs.tcpiplib.prints.print_layer3(ip) + if ip.protocol is 6: + tcp = data.pop(0) + libs.tcpiplib.prints.print_tcp(tcp) + elif next_protocol in [2054]: + arp = data.pop(0) + libs.tcpiplib.prints.print_arp(arp) + except Exception as error: + print("ERROR: %s" % error) + + +# ************************* OFPT_FLOW_REMOVED ******************************** + + +def print_ofpt_flow_removed(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + print_ofp_match(msg.match) + + string = ('Body - Cookie: %s Priority: %s Reason: %s Pad: %s\nBody - ' + 'Duration Secs/NSecs: %s/%s Idle Timeout: %s Pad2/Pad3: %s/%s' + ' Packet Count: %s Byte Count: %s') + + print(string % (msg.cookie, msg.priority, red(msg.reason), + msg.pad, msg.duration_sec, msg.duration_nsec, + msg.idle_timeout, msg.pad2, + msg.pad3, msg.packet_count, msg.byte_count)) + + +# ************************* OFPT_PORT_STATUS ******************************** + + +def print_ofpt_port_status(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + print('OpenFlow PortStatus - Reason: %s Pad: %s' % + (msg.reason, msg.pad)) + print_of_ports(msg.desc) + + +# ************************* OFPT_PACKET_OUT ******************************** + + +def print_ofpt_packet_out(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + print('PacketOut: buffer_id: %s in_port: %s actions_len: %s' % + (hex(msg.buffer_id.value), + green(dissector.get_phy_port_id(msg.in_port.value)), + msg.actions_len.value)) + if msg.actions_len is not 0: + print_actions(msg.actions) + print_data(msg.data) + + +# ************************* OFPT_FLOW_MOD ********************************* + + +def print_ofpt_flow_mod(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + print_ofp_match(msg.match) + print_ofp_body(msg) + print_actions(msg.actions) + # Print OVS is deactivated for now + # print_ofp_ovs(msg) + + +def print_ofp_match(match): + """ + + Args: + match: + """ + print('Match - ', end='') + for match_item in match.__dict__: + match_item_value = match.__dict__[match_item] + if match_item_value.value not in [0, "00:00:00:00:00:00", "0.0.0.0", 65535]: + if match_item is 'dl_vlan' and match_item_value.value not in [65535]: + match_item_value = dissector.get_vlan(match_item_value.value) + elif match_item is 'wildcards': + match_item_value = hex(match_item_value.value) + elif match_item is 'dl_type' and match_item_value.value not in [65535]: + match_item_value = libs.tcpiplib.tcpip.get_ethertype(match_item_value.value) + elif match_item in ['pad1', 'pad2']: + continue + + print("%s: %s " % (match_item, green(match_item_value)), end='') + print() + + +def print_ofp_body(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + string = ('Body - Cookie: %s Command: %s Idle/Hard Timeouts: ' + '%s/%s\nBody - Priority: %s Buffer ID: %s Out Port: %s Flags: %s') + command = green(dissector.get_ofp_command(msg.command.value)) + flags = green(dissector.get_ofp_flags(msg.flags.value)) + out_port = green(dissector.get_phy_port_id(msg.out_port.value)) + + print(string % (msg.cookie.value, command, msg.idle_timeout, msg.hard_timeout, + green(msg.priority), msg.buffer_id.value, out_port, flags)) + + +def print_actions(actions): + """ + + Args: + actions: + """ + for action in actions: + print_ofp_action(action) + + +def print_ofp_action(action): + """ + + Args: + action: + """ + if action.action_type == 0: + port = dissector.get_phy_port_id(action.port.value) + print('Action - Type: %s Length: %s Port: %s ' + 'Max Length: %s' % + (green('OUTPUT'), action.length, green(port), action.max_length)) + + elif action.action_type == 1: + print('Action - Type: %s Length: %s VLAN ID: %s Pad: %s' % + (green('SetVLANID'), action.length, green(str(action.vlan_id.value)), action.pad2)) + + elif action.action_type == 2: + print('Action - Type: %s Length: %s VLAN PCP: %s Pad: %s' % + (green('SetVLANPCP'), action.length, green(str(action.vlan_pcp.value)), action.pad)) + + elif action.action_type == 3: + print('Action - Type: %s Length: %s' % + (green('StripVLAN'), action.length)) + + elif action.action_type == 4: + print('Action - Type: %s Length: %s SetDLSrc: %s Pad: %s' % + (green('SetDLSrc'), action.length, green(action.dl_src), + action.pad)) + + elif action.action_type == 5: + print('Action - Type: %s Length: %s SetDLDst: %s Pad: %s' % + (green('SetDLDst'), action.length, green(action.dl_dst), + action.pad)) + + elif action.action_type == 6: + print('Action - Type: %s Length: %s SetNWSrc: %s' % + (green('SetNWSrc'), action.length, green(action.nw_addr))) + + elif action.action_type == 7: + print('Action - Type: %s Length: %s SetNWDst: %s' % + (green('SetNWDst'), action.length, green(action.nw_addr))) + + elif action.action_type == 8: + print('Action - Type: %s Length: %s SetNWTos: %s Pad: %s' % + (green('SetNWTos'), action.length, green(action.nw_tos.value), + action.pad)) + + elif action.action_type == 9: + print('Action - Type: %s Length: %s SetTPSrc: %s Pad: %s' % + (green('SetTPSrc'), action.length, green(action.port_no.value), + action.pad)) + + elif action.action_type == int('a', 16): + print('Action - Type: %s Length: %s SetTPDst: %s Pad: %s' % + (green('SetTPDst'), action.length, green(action.port_no.value), + action.pad)) + + elif action.action_type == int('b', 16): + print(('Action - Type: %s Length: %s Enqueue: %s Pad: %s' + ' Queue: %s') % + (green('Enqueue'), action.length, green(action.port_no.value), + action.pad, green(action.queue_id.value))) + + elif action.action_type == int('ffff', 16): + print('Action - Type: %s Length: %s Vendor: %s' % + (green('VENDOR'), action.length, green(action.vendor))) + + else: + print('Unknown Action Type') + + +# ************************* OFPT_PORT_MOD ********************************* + + +def print_ofpt_port_mod(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + + def _print_port_mod_config_mask(variable, name): + + print('PortMod %s: ' % name, end='') + printed = False + variable = _parse_phy_curr(variable) + for i in variable: + print(red(dissector.get_phy_config(i)), end='') + printed = True + else: + _dont_print_0(printed) + print() + + print('PortMod Port_no: %s HW_Addr: %s Pad: %s' % + (yellow(msg.port_no.value), yellow(msg.hw_addr.value), msg.pad)) + _print_port_mod_config_mask(msg.config.value, 'config') + _print_port_mod_config_mask(msg.mask.value, 'mask') + _print_port_mod_config_mask(msg.advertise.value, 'advertise') + + +# ************************* OFPT_STATS_REQUEST ********************************** + + +def print_ofpt_stats_request(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + def print_ofpt_stats_request_description(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + body = str(msg.body_type) + print('StatReq Type: %s' % body.split('.')[1]) + + def print_ofpt_stats_request_flow_aggregate(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + if msg.body_type == 1: + type_name = 'Flow' + else: + type_name = 'Aggregate' + body_type = "%s" % msg.body_type + print('StatReq Type: %s(%s)' % (type_name, body_type.split('.')[1])) + print_ofp_match(msg.body[0].match) + out_port = dissector.get_phy_port_id(msg.body[0].out_port.value) + print('StatReq Table_id: %s Pad: %s Out_Port: %s' % (msg.body[0].table_id.value, + msg.body[0].pad, out_port)) + + def print_ofpt_stats_request_table(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + body = "%s" % msg.body_type + print('StatReq Type: %s' % body.split('.')[1]) + + def print_ofpt_stats_request_port(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + port_number = dissector.get_phy_port_id(msg.body[0].port_no.value) + print('StatReq Type: Port(4) Port_Number: %s Pad: %s' % + (green(port_number), msg.body[0].pad)) + + def print_ofpt_stats_request_queue(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + port_number = dissector.get_phy_port_id(msg.body[0].port_no.value) + print('StatReq Type: OFPST_QUEUE: Port_Number: %s Pad: %s Queue_id: %s' % + (green(port_number), msg.body[0].pad, msg.body[0].queue_id)) + + def print_ofps_stats_request_vendor(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + vendor_id = dissector.get_ofp_vendor(msg.body[0].vendor.value) + print('StatReq Type: Vendor(%s): Vendor_ID: %s' % + (hex(msg.body_type.value), vendor_id)) + print("StatReq Vendor Data:") + hexdump(msg.body[0].body.value) + + if msg.body_type == 0: + print_ofpt_stats_request_description(msg) + elif msg.body_type == 1 or msg.body_type == 2: + print_ofpt_stats_request_flow_aggregate(msg) + elif msg.body_type == 3: + print_ofpt_stats_request_table(msg) + elif msg.body_type == 4: + print_ofpt_stats_request_port(msg) + elif msg.body_type == 5: + print_ofpt_stats_request_queue(msg) + elif msg.body_type == 65535: + print_ofps_stats_request_vendor(msg) + + +# ************************* OFPT_STATS_REPLY ********************************** + + +def print_ofpt_stats_reply(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + + def print_ofpt_stats_reply_description(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + print('StatRes Type: OFPST_DESC') + print('StatRes mfr_desc: %s' % msg.body.mfr_desc) + print('StatRes hw_desc: %s' % msg.body.hw_desc) + print('StatRes sw_desc: %s' % msg.body.sw_desc) + print('StatRes serial_num: %s' % msg.body.serial_num) + print('StatRes dp_desc: %s' % msg.body.dp_desc) + + def print_ofpt_stats_reply_flow_array(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + + def print_ofpt_stats_reply_flow(flow): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + print('StatRes Type: Flow(1)') + print('StatRes Length: %s Table_id: %s Pad: %s ' % + (flow.length, flow.table_id, flow.pad)) + print('StatRes ', end='') + print_ofp_match(flow.match) + print('StatRes duration_sec: %s, duration_nsec: %s, priority: %s,' + ' idle_timeout: %s, hard_timeout: %s, pad: %s, cookie: %s,' + ' packet_count: %s, byte_count: %s' % + (flow.duration_sec, flow.duration_nsec, + flow.priority, flow.idle_timeout, + flow.hard_timeout, flow.pad, + flow.cookie, + flow.packet_count, flow.byte_count)) + print('StatRes ', end='') + print_actions(flow.actions) + + if len(msg.body) == 0: + print('StatRes Type: Flow(1)\nNo Flows') + return + + for flow in msg.body: + print_ofpt_stats_reply_flow(flow) + + def print_ofpt_stats_reply_aggregate(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + print('StatRes Type: Aggregate(2)') + print('StatRes packet_count: %s, byte_count: %s flow_count: %s ' + 'pad: %s' % + (msg.stats.packet_count, msg.stats.byte_count, + msg.stats.flow_count, msg.stats.pad)) + + def print_ofpt_stats_reply_table_array(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + + def print_ofpt_stats_reply_table(table): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + print('StatRes table_id: %s, pad: %s, name: "%s", wildcards: %s, ' + 'max_entries: %s, active_count: %s, lookup_count: %s, ' + 'matched_count: %s' % + (table.table_id.value, table.pad, table.name.value, hex(table.wildcards.value), + table.max_entries.value, table.active_count.value, + table.count_lookup.value, table.count_matched.value)) + + if len(msg.body) == 0: + print('StatRes Type: Table(3)\nNo Tables') + return + + print('StatRes Type: Table(3)') + for table in msg.body: + print_ofpt_stats_reply_table(table) + + def print_ofp_stats_reply_port_array(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + + def print_ofpt_stats_reply_port(port): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + print('StatRes Type: Port(4)') + print('StatRes port_number: %s rx_packets: %s rx_bytes: %s rx_errors: %s' + ' rx_crc_err: %s rx_dropped: %s rx_over_err: %s rx_frame_err: %s\n' + 'StatRes port_number: %s tx_packets: %s tx_bytes: %s tx_errors: %s' + ' tx_dropped: %s collisions: %s pad: %s' % + (red(port.port_no), port.rx_packets, + port.rx_bytes, port.rx_errors, port.rx_crc_err, + port.rx_dropped, port.rx_over_err, + port.rx_frame_err, red(port.port_no), + port.tx_packets, port.tx_bytes, port.tx_errors, + port.tx_dropped, port.collisions, port.pad)) + + if len(msg.body) == 0: + print('StatRes Type: Port(4)\nNo Ports') + return + for port in msg.body: + print_ofpt_stats_reply_port(port) + + def print_ofpt_stats_reply_queue_array(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + + def print_ofpt_stats_reply_queue(queue): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + print('StatRes Type: Queue(5)') + print('StatRes queue_id: %s length: %s pad: %s' + ' tx_bytes: %s tx_packets: %s tx_errors: %s' % + (queue.queue_id, queue.length, queue.pad, + queue.tx_bytes, queue.tx_packets, queue.tx_errors)) + + if len(msg.body) == 0: + print('StatRes Type: Queue(5)\nNo Queues') + return + + for queue in msg.body: + print_ofpt_stats_reply_queue(queue) + + def print_ofpt_stats_reply_vendor(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + + def print_ofpt_stats_reply_vendor_data(data): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + print('StatRes Vendor Data: ') + hexdump(data) + + + print('StatRes Type: Vendor(%s)' % hex(msg.body_type.value)) + print('StatRes Vendor_Id: %s' % red(hex(msg.body[0].vendor.value))) + print_ofpt_stats_reply_vendor_data(msg.body[0].body.value) + + if msg.body_type == 0: + print_ofpt_stats_reply_description(msg) + elif msg.body_type == 1: + print_ofpt_stats_reply_flow_array(msg) + elif msg.body_type == 2: + print_ofpt_stats_reply_aggregate(msg) + elif msg.body_type == 3: + print_ofpt_stats_reply_table_array(msg) + elif msg.body_type == 4: + print_ofp_stats_reply_port_array(msg) + elif msg.body_type == 5: + print_ofpt_stats_reply_queue_array(msg) + elif msg.body_type == 65535: + print_ofpt_stats_reply_vendor(msg) + + +# ************************* OFPT_BARRIER_REQUEST ********************************** + + +def print_ofpt_barrier_request(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + pass + + +# ************************* OFPT_BARRIER_REPLY ********************************** + + +def print_ofpt_barrier_reply(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + pass + + +# ************************* OFPT_QUEUE_GET_CONFIG_REQUEST ********************************** + + +def print_ofpt_queue_get_config_request(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + print('QueueGetConfigReq Port: %s Pad: %s' % (msg.port, msg.pad)) + + +# ************************* OFPT_QUEUE_GET_CONFIG_REPLY ********************************** + + +def print_ofpt_queue_get_config_reply(msg): + """ + + Args: + msg: OpenFlow message unpacked by python-openflow + """ + + def print_ofpt_queue_reply_prop_payload(payload): + print('Payload: Rate %s Pad: %s' % (payload.rate, payload.pad)) + + def print_ofpt_queue_reply_properties(qproperty): + print('Property: %s Length: %s Pad: %s' % + (qproperty.property, qproperty.length, qproperty.pad)) + print_ofpt_queue_reply_prop_payload(qproperty.payload) + + def print_ofpt_queue_reply_queue(queue): + print('Queue_ID: %s Length: %s Pad: %s' % + (queue.queue_id, queue.length, queue.pad)) + if len(queue.properties) == 0: + print('QueueGetConfigRes: No Properties') + return + for property in queue.properties: + print_ofpt_queue_reply_properties(property) + + print('QueueGetConfigRes Port: %s Pad: %s' % + (msg.port, msg.pad)) + + if len(msg.queues) == 0: + print('QueueGetConfigRes: No Queues') + return + + for queue in msg.queues: + print_ofpt_queue_reply_queue(queue) + + +# ******************** Multipurpose port functions ******************************* + + +def _dont_print_0(printed): + if printed is False: + print('0', end='') + return False + + +def print_port_field(port_id, variable, name): + port_id = '%s' % green(port_id) + printed = False + + print('Port_id: %s - %s: ' % (port_id, name), end='') + variable = _parse_phy_curr(variable) + for i in variable: + print(dissector.get_phy_feature(i) + ' ', end='') + printed = True + else: + _dont_print_0(printed) + print() + + +def print_ofp_phy_port(port): + port_id = '%s' % green(port.port_no) + + print('Port_id: %s - hw_addr: %s name: %s' % ( + port_id, green(port.hw_addr), green(port.name))) + + print('Port_id: %s - config: ' % port_id, end='') + printed = False + config = _parse_phy_config(port.config.value) + for i in config: + print(dissector.get_phy_config(i), end='') + printed = True + else: + printed = _dont_print_0(printed) + print() + + print('Port_id: %s - state: ' % port_id, end='') + state = _parse_phy_state(port.state.value) + for i in state: + print(dissector.get_phy_state(i), end='') + printed = True + else: + _dont_print_0(printed) + print() + + print_port_field(port_id, port.curr, 'curr') + print_port_field(port_id, port.advertised, 'advertised') + print_port_field(port_id, port.supported, 'supported') + print_port_field(port_id, port.peer, 'peer') + + +def print_of_ports(ports): + + if type(ports) is not ListOfPhyPorts: + print_ofp_phy_port(ports) + else: + for port in ports: + print_ofp_phy_port(port) + + +def _parse_bitmask(bitmask, array): + size = len(array) + for i in range(0, size): + mask = 2**i + aux = bitmask & mask + if aux == 0: + array.remove(mask) + return array + + +def _parse_capabilities(capabilities): + caps = [1, 2, 4, 8, 16, 32, 64, 128] + return _parse_bitmask(capabilities, caps) + + +def _parse_actions(actions): + acts = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048] + return _parse_bitmask(actions, acts) + + +def _parse_phy_config(config): + confs = [1, 2, 4, 8, 16, 32, 64] + return _parse_bitmask(config, confs) + + +def _parse_phy_state(state): + states = [1, 2, 4, 8, 16] + return _parse_bitmask(state, states) + + +def _parse_phy_curr(values): + confs = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048] + return _parse_bitmask(values, confs) + + +# def print_ofp_ovs(msg): +# """ +# If -o or --print-ovs is provided by user, print a ovs-ofctl add-dump +# """ +# +# def get_command(command): +# commands = {0: 'add-flow', 1: 'mod-flows', 3: 'del-flows'} +# try: +# return commands[command] +# except KeyError: +# return 0 +# +# def get_flag(flag): +# flags = {0: '', 1: 'send_flow_rem', 2: 'check_overlap', 3: 'Emerg'} +# try: +# return flags[flag] +# except KeyError: +# return 0 +# +# def get_actions(action_type, action_length, payload): +# if action_type == 0: +# port, max_len = libs.openflow.of10.parser.get_action(action_type, payload) +# return 'output:%s' % (port if port != 65533 else 'CONTROLLER') +# elif action_type == 1: +# vlan, pad = libs.openflow.of10.parser.get_action(action_type, payload) +# return 'mod_vlan_vid:' + str(vlan) +# elif action_type == 2: +# vlan_pc, pad = libs.openflow.of10.parser.get_action(action_type, payload) +# return 'mod_vlan_pcp:' + str(vlan_pc) +# elif action_type == 3: +# return 'strip_vlan' +# elif action_type == 4: +# setDLSrc, pad = libs.openflow.of10.parser.get_action(action_type, payload) +# return 'mod_dl_src:' + str(eth_addr(setDLSrc)) +# elif action_type == 5: +# setDLDst, pad = libs.openflow.of10.parser.get_action(action_type, payload) +# return 'mod_dl_dst:' + str(eth_addr(setDLDst)) +# elif action_type == 6: +# nw_addr = libs.openflow.of10.parser.get_action(action_type, payload) +# return 'mod_nw_src:' + str(nw_addr) +# elif action_type == 7: +# nw_addr = libs.openflow.of10.parser.get_action(action_type, payload) +# return 'mod_nw_src:' + str(nw_addr) +# elif action_type == 8: +# nw_tos, pad = libs.openflow.of10.parser.get_action(action_type, payload) +# return 'mod_nw_tos:' + str(nw_tos) +# elif action_type == 9: +# port, pad = libs.openflow.of10.parser.get_action(action_type, payload) +# return 'mod_tp_src:' + str(port) +# elif action_type == int('a', 16): +# port, pad = libs.openflow.of10.parser.get_action(action_type, payload) +# return 'mod_tp_dst:' + str(port) +# elif action_type == int('b', 16): +# port, pad, queue_id = libs.openflow.of10.parser.get_action(action_type, payload) +# return 'set_queue:' + str(queue_id) +# +# if PrintingOptions().print_ovs is not True: +# return +# +# switch_ip = 'SWITCH_IP' +# switch_port = '6634' +# +# ofm = [] +# ofactions = [] +# +# ovs_command = get_command(msg.command) +# +# for K in msg.match.__dict__: +# if K != 'wildcards': +# if msg.match.__dict__[K] is not None: +# value = "%s=%s," % (K, msg.match.__dict__[K]) +# ofm.append(value) +# +# matches = ''.join(ofm) +# +# if msg.command is not 3: +# for action in msg.actions: +# value = get_actions(action.type, action.length, action.payload) +# value = "%s," % value +# ofactions.append(value) +# +# flag = get_flag(msg.flags) +# print('ovs-ofctl %s tcp:%s:%s \"' % (ovs_command, switch_ip, switch_port), end='') +# if msg.flags != 0: +# print('%s,' % flag, end='') +# if msg.priority != 32678: +# print('priority=%s,' % msg.priority, end='') +# if msg.idle_timeout != 0: +# print('idle_timeout=%s,' % msg.idle_timeout, end='') +# if msg.hard_timeout != 0: +# print('hard_timeout=%s,' % msg.hard_timeout, end='') +# print('%s ' % matches, end='') +# print('action=%s\"' % ''.join(ofactions)) +# else: +# ovs_msg_del = 'ovs-ofctl %s tcp:%s:%s %s ' +# print(ovs_msg_del % (ovs_command, switch_ip, switch_port, matches)) diff --git a/libs/openflow/of13/__init__.py b/libs/openflow/of13/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/openflow/of13/dissector.py b/libs/openflow/of13/dissector.py new file mode 100644 index 0000000..369bfb8 --- /dev/null +++ b/libs/openflow/of13/dissector.py @@ -0,0 +1,450 @@ +""" + This is the OpenFlow 1.3 dictionary/dissector + Here messages, types and codes are converted to names. +""" + + +def get_ofp_type(of_type): + of_types = {0: 'OFPT_HELLO', + 1: 'OFPT_ERROR', + 2: 'OFPT_ECHO_REQUEST', + 3: 'OFPT_ECHO_REPLY', + 4: 'OFPT_EXPERIMENTER', + 5: 'OFPT_FEATURES_REQUEST', + 6: 'OFPT_FEATURES_REPLY', + 7: 'OFPT_GET_CONFIG_REQUEST', + 8: 'OFPT_GET_CONFIG_REPLY', + 9: 'OFPT_SET_CONFIG', + 10: 'OFPT_PACKET_IN', + 11: 'OFPT_FLOW_REMOVED', + 12: 'OFPT_PORT_STATUS', + 13: 'OFPT_PACKET_OUT', + 14: 'OFPT_FLOW_MOD', + 15: 'OFPT_GROUP_MOD', + 16: 'OFPT_PORT_MOD', + 17: 'OFPT_TABLE_MOD', + 18: 'OFPT_MULTIPART_REQUEST', + 19: 'OFPT_MULTIPART_REPLY', + 20: 'OFPT_BARRIER_REQUEST', + 21: 'OFPT_BARRIER_REPLY', + 22: 'OFPT_QUEUE_GET_CONFIG_REQUEST', + 23: 'OFPT_QUEUE_GET_CONFIG_REPLY', + 24: 'OFPT_ROLE_REQUEST', + 25: 'OFPT_ROLE_REPLY', + 26: 'OFPT_GET_ASYNC_REQUEST', + 27: 'OFPT_GET_ASYNC_REPLY', + 28: 'OFPT_SET_ASYNC', + 29: 'OFPT_METER_MOD'} + try: + return of_types[of_type] + except KeyError: + return 'UnknownType(%s)' % of_type + + +def get_ofp_error(error_type, code): + errors_types = dict() + codes = dict() + + # Starts with an Error + errors_types[error_type] = 'UnknownType(%s)' % error_type + codes[code] = 'UnknownCode(%s)' % code + + # Error Types + if error_type in range(0, 14) or error_type == 65535: + errors_types = {0: 'OFPET_HELLO_FAILED(0)', + 1: 'OFPET_BAD_REQUEST(1)', + 2: 'OFPET_BAD_ACTION(2)', + 3: 'OFPET_BAD_INSTRUCTION(3)', + 4: 'OFPET_BAD_MATCH(4)', + 5: 'OFPET_FLOW_MOD_FAILED(5)', + 6: 'OFPET_GROUP_MOD_FAILED(6)', + 7: 'OFPET_PORT_MOD_FAILED(7)', + 8: 'OFPET_TABLE_MOD_FAILED(8)', + 9: 'OFPET_QUEUE_OP_FAILED(9)', + 10: 'OFPET_SWITCH_CONFIG_FAILED(10)', + 11: 'OFPET_ROLE_REQUEST_FAILED(11)', + 12: 'OFPET_METER_MOD_FAILED(12)', + 13: 'OFPET_TABLE_FEATURES_FAILED(13)', + 65535: 'Experimenter(0xffff)'} + + # Error Codes per Error Type + if error_type == 0: + if code in range(0, 2): + codes = {0: 'OFPHFC_INCOMPATIBLE(0)', + 1: 'OFPHFC_EPERM(1)'} + + elif error_type == 1: + if code in range(0, 14): + codes = {0: 'OFPBRC_BAD_VERSION(0)', + 1: 'OFPBRC_BAD_TYPE(1)', + 2: 'OFPBRC_BAD_MULTIPART(2)', + 3: 'OFPBRC_BAD_EXPERIMENTER(3)', + 4: 'OFPBRC_BAD_EXP_TYPE(4)', + 5: 'OFPBRC_EPERM(5)', + 6: 'OFPBRC_BAD_LEN(6)', + 7: 'OFPBRC_BUFFER_EMPTY(7)', + 8: 'OFPBRC_BUFFER_UNKNOWN(8)', + 9: 'OFPBRC_BAD_TABLE_ID(9)', + 10: 'OFPBRC_IS_SLAVE(10)', + 11: 'OFPBRC_BAD_PORT(11)', + 12: 'OFPBRC_BAD_PACKET(12)', + 13: 'OFPBRC_MULTIPART_BUFFER_OVERFLOW(13)'} + + elif error_type == 2: + if code in range(0, 16): + codes = {0: 'OFPBAC_BAD_TYPE(0)', + 1: 'OFPBAC_BAD_LEN(1)', + 2: 'OFPBAC_BAD_EXPERIMENTER(2)', + 3: 'OFPBAC_BAD_EXP_TYPE(3)', + 4: 'OFPBAC_BAD_OUT_PORT(4)', + 5: 'OFPBAC_BAD_ARGUMENT(5)', + 6: 'OFPBAC_EPERM(6)', + 7: 'OFPBAC_TOO_MANY(7)', + 8: 'OFPBAC_BAD_QUEUE(8)', + 9: 'OFPBAC_BAD_OUT_GROUP(9)', + 10: 'OFPBAC_MATCH_INCONSISTENT(10)', + 11: 'OFPBAC_UNSUPPORTED_ORDER(11)', + 12: 'OFPBAC_BAD_TAG(12)', + 13: 'OFPBAC_BAD_SET_TYPE(13)', + 14: 'OFPBAC_BAD_SET_LEN(14)', + 15: 'OFPBAC_BAD_SET_ARGUMENT(15)'} + + elif error_type == 3: + if code in range(0, 9): + codes = {0: 'OFPBIC_UNKNOWN_INST(0)', + 1: 'OFPBIC_UNSUP_INST(1)', + 2: 'OFPBIC_BAD_TABLE_ID(2)', + 3: 'OFPBIC_UNSUP_METADATA(3)', + 4: 'OFPBIC_UNSUP_METADATA_MASK(4)', + 5: 'OFPBIC_BAD_EXPERIMENTER(5)', + 6: 'OFPBIC_BAD_EXP_TYPE(6)', + 7: 'OFPBIC_BAD_LEN(7)', + 8: 'OFPBIC_EPERM(8)'} + + elif error_type == 4: + if code in range(0, 12): + codes = {0: 'OFPBMC_BAD_TYPE(0)', + 1: 'OFPBMC_BAD_LEN(1)', + 2: 'OFPBMC_BAD_TAG(2)', + 3: 'OFPBMC_BAD_DL_ADDR_MASK(3)', + 4: 'OFPBMC_BAD_NW_ADDR_MASK(4)', + 5: 'OFPBMC_BAD_WILDCARDS(5)', + 6: 'OFPBMC_BAD_FIELD(6)', + 7: 'OFPBMC_BAD_VALUE(7)', + 8: 'OFPBMC_BAD_MASK(8)', + 9: 'OFPBMC_BAD_PREREQ(9)', + 10: 'OFPBMC_DUP_FIELD(10)', + 11: 'OFPBMC_EPERM(11)'} + + elif error_type == 5: + if code in range(0, 8): + codes = {0: 'OFPFMFC_UNKNOWN(0)', + 1: 'OFPFMFC_TABLE_FULL(1)', + 2: 'OFPFMFC_BAD_TABLE_ID(2)', + 3: 'OFPFMFC_OVERLAP(3)', + 4: 'OFPFMFC_EPERM(4)', + 5: 'OFPFMFC_BAD_TIMEOUT(5)', + 6: 'OFPFMFC_BAD_COMMAND(6)', + 7: 'OFPFMFC_BAD_FLAGS(7)'} + + elif error_type == 6: + if code in range(0, 15): + codes = {0: 'OFPGMFC_GROUP_EXISTS(0)', + 1: 'OFPGMFC_INVALID_GROUP(1)', + 2: 'OFPGMFC_WEIGHT_UNSUPPORTED(2)', + 3: 'OFPGMFC_OUT_OF_GROUPS(3)', + 4: 'OFPGMFC_OUT_OF_BUCKETS(4)', + 5: 'OFPGMFC_CHAINING_UNSUPPORTED(5)', + 6: 'OFPGMFC_WATCH_UNSUPPORTED(6)', + 7: 'OFPGMFC_LOOP(7)', + 8: 'OFPGMFC_UNKNOWN_GROUP(8)', + 9: 'OFPGMFC_CHAINED_GROUP(9)', + 10: 'OFPGMFC_BAD_TYPE(10)', + 11: 'OFPGMFC_BAD_COMMAND(11)', + 12: 'OFPGMFC_BAD_BUCKET(12)', + 13: 'OFPGMFC_BAD_WATCH(13)', + 14: 'OFPGMFC_EPERM(14)'} + + elif error_type == 7: + if code in range(0, 5): + codes = {0: 'OFPPMFC_BAD_PORT(0)', + 1: 'OFPPMFC_BAD_HW_ADDR(1)', + 2: 'OFPPMFC_BAD_CONFIG(2)', + 3: 'OFPPMFC_BAD_ADVERTISE(3)', + 4: 'OFPPMFC_EPERM(4)'} + + elif error_type == 8: + if code in range(0, 3): + codes = {0: 'OFPTMFC_BAD_TABLE(0)', + 1: 'OFPTMFC_BAD_CONFIG(1)', + 2: 'OFPTMFC_EPERM(2)'} + + elif error_type == 9: + if code in range(0, 3): + codes = {0: 'OFPQOFC_BAD_PORT(0)', + 1: 'OFPQOFC_BAD_QUEUE(1)', + 2: 'OFPQOFC_EPERM(2)'} + + elif error_type == int('A', 16): + if code in range(0, 3): + codes = {0: 'OFPSCFC_BAD_FLAGS(0)', + 1: 'OFPSCFC_BAD_LEN(1)', + 2: 'OFPSCFC_EPERM(2)'} + + elif error_type == int('B', 16): + if code in range(0, 3): + codes = {0: 'OFPRRFC_STALE(0)', + 1: 'OFPRRFC_UNSUP(1)', + 2: 'OFPRRFC_BAD_ROLE(2)'} + + elif error_type == int('C', 16): + if code in range(0, 12): + codes = {0: 'OFPMMFC_UNKNOWN(0)', + 1: 'OFPMMFC_METER_EXISTS(1)', + 2: 'OFPMMFC_INVALID_METER(2)', + 3: 'OFPMMFC_UNKNOWN_METER(3)', + 4: 'OFPMMFC_BAD_COMMAND(4)', + 5: 'OFPMMFC_BAD_FLAGS(5)', + 6: 'OFPMMFC_BAD_RATE(6)', + 7: 'OFPMMFC_BAD_BURST(7)', + 8: 'OFPMMFC_BAD_BAND(8)', + 9: 'Bad_BOFPMMFC_BAD_BAND_VALUEand_Value(9)', + 10: 'OFPMMFC_OUT_OF_METERS(10)', + 11: 'OFPMMFC_OUT_OF_BANDS(11)'} + + elif error_type == int('D', 16): + if code in range(0, 6): + codes = {0: 'OFPTFFC_BAD_TABLE(0)', + 1: 'OFPTFFC_BAD_METADATA(1)', + 2: 'OFPTFFC_BAD_TYPE(2)', + 3: 'OFPTFFC_BAD_LEN(3)', + 4: 'OFPTFFC_BAD_ARGUMENT(4)', + 5: 'OFPTFFC_EPERM(5)'} + + return errors_types[error_type], codes[code] + + +def get_feature_res_capabilities(cap): + caps = {1: 'FLOW_STATS(0x1)', + 2: 'TABLE_STATS(0x2)', + 4: 'PORT_STATS(0x4)', + 8: 'GROUP_STATS(0x8)', + 32: 'IP_REASM(0x20)', + 64: 'QUEUE_STATS(0x40)', + 256: 'PORT_BLOCKED(0x100)'} + try: + return caps[cap] + except KeyError: + return 'UnknownCapability(%s)' % cap + + +def get_config_flags(flag): + flags = {0: 'FRAG_NORMAL(0)', + 1: 'FRAG_DROP(1)', + 2: 'FRAG_REASM(2)', + 3: 'FRAG_MASK(3)'} + try: + return flags[flag] + except KeyError: + return 'UnknownFlag(%s)' % flag + + +def get_packet_in_reason(reason): + reasons = {0: 'OFPR_NO_MATCH(0)', + 1: 'OFPR_ACTION(1)', + 2: 'OFPR_INVALID_TTL'} + try: + return reasons[reason] + except KeyError: + return 'UnknownReason(%s)' % reason + + +def get_flow_removed_reason(reason): + rsn = {0: 'OFPRR_IDLE_TIMEOUT(0)', + 1: 'OFPRR_HARD_TIMEOUT(1)', + 2: 'OFPRR_DELETE(2)', + 3: 'OFPRR_GROUP_DELETE'} + try: + return rsn[reason] + except KeyError: + return 'UnknownReason(%s)' % reason + + +def get_port_status_reason(reason): + reasons = {0: 'OFPPR_ADD(0)', + 1: 'OFPPR_DELETE(1)', + 2: 'OFPPR_MODIFY(2)'} + try: + return reasons[reason] + except KeyError: + return 'UnknownReason(%s)' % reason + + +def get_phy_port_id(p_id): + ids = {4294967040: 'OFPP_MAX(OxFFFFFF00)', + 4294967288: 'OFPP_IN_PORT(0xFFFFFFF8)', + 4294967289: 'OFPP_TABLE(0xFFFFFFF9)', + 4294967290: 'OFPP_NORMAL(0xFFFFFFFA)', + 4294967291: 'OFPP_FLOOD(0xFFFFFFFB)', + 4294967292: 'OFPP_ALL(0xFFFFFFFC)', + 4294967293: 'OFPP_CONTROLLER(0xFFFFFFFD)', + 4294967294: 'OFPP_LOCAL(0xFFFFFFFE)', + 4294967295: 'OFPP_ANY(0xFFFFFFFF)'} + try: + return ids[p_id] + except KeyError: + return '%s' % p_id + + +def get_flow_match_fields(value): + values = {0: 'In_Port', + 1: 'In_Phy_Port', + 2: 'Metadata', + 3: 'Eth_Dst', + 4: 'Eth_Src', + 5: 'Eth_Type', + 6: 'Vlan_VID', + 7: 'Vlan_PCP', + 8: 'IP_DSCP', + 9: 'IP_ECN', + 10: 'IP_PROTO', + 11: 'IPv4_Src', + 12: 'IPv4_Dst', + 13: 'TCP_Src', + 14: 'TCP_Dst', + 15: 'UDP_Src', + 16: 'UDP_Dst', + 17: 'SCTP_Src', + 18: 'SCTP_Dst', + 19: 'ICMPv4_Type', + 20: 'ICMPv4_Code', + 21: 'ARP_OP', + 22: 'ARP_SPA', + 23: 'ARP_TPA', + 24: 'ARP_SHA', + 25: 'ARP_THA', + 26: 'IPv6_Src', + 27: 'IPv6_Dst', + 28: 'IPv6_FLabel', + 29: 'ICMPv6_Type', + 30: 'ICMPv6_Code', + 31: 'IPv6_ND_Target', + 32: 'IPv6_ND_SLL', + 33: 'IPv6_ND_TLL', + 34: 'MPLS_Label', + 35: 'MPLS_TC', + 36: 'MPLS_BoS', + 37: 'PBB_ISID', + 38: 'Tunnel_ID', + 39: 'IPv6_EXTHDR'} + + try: + return '%s(%s)' % (values[value], value) + except KeyError: + return 'UnknownMatchField(%s)' % value + + +def get_flow_mod_command(command): + commands = {0: 'OFPFC_ADD(0)', + 1: 'OFPFC_MODIFY(1)', + 2: 'OFPFC_MODIFY_STRICT(2)', + 3: 'OFPFC_DELETE(3)', + 4: 'OFPFC_DELETE_STRICT(4)'} + try: + return commands[command] + except KeyError: + return 'UnknownCommand(%s)' % command + + +def get_flow_mod_flags(flag): + flags = {0: 'NoFlagSet(0)', + 1: 'OFPFF_SEND_FLOW_REM(0x1)', + 2: 'OFPFF_CHECK_OVERLAP(0x2)', + 4: 'OFPFF_RESET_COUNTS(0x4)', + 16: 'OFPFF_NO_PKT_COUNTS(0x10)', + 32: 'OFPFF_NO_BYT_COUNTS(0x20)'} + try: + return flags[flag] + except KeyError: + return 'UnknownFlag(%s)' % flag + + +def get_ipv6_extension(bit): + options = {1: 'NO_NEXT', + 2: 'ESP', + 4: 'AUTH', + 8: 'DEST', + 16: 'FRAG', + 32: 'ROUTER', + 64: 'HOP', + 128: 'UNREP', + 256: 'UNSEQ'} + try: + return '%s(%s)' % (options[bit], hex(bit)) + except KeyError: + return 'UnknownBit(%s)' % bit + + +def get_instructions(instruction): + instructions = {1: 'GOTO_TABLE', + 2: 'WRITE_METADATA', + 3: 'WRITE_ACTIONS', + 4: 'APPLY_ACTIONS', + 5: 'CLEAR_ACTIONS', + 6: 'METER', + 65535: 'EXPERIMENTER'} + + try: + return '%s(%s)' % (instructions[instruction], instruction) + except KeyError: + return 'UnknownInstruction(%s)' % instruction + + +def get_group_mod_command(command): + commands = {0: 'OFPGC_ADD(0)', + 1: 'OFPGC_MODIFY(1)', + 2: 'OFPGC_DELETE(2)'} + try: + return commands[command] + except KeyError: + return 'UnknownCommand(%s)' % command + + +def get_group_mod_type(type): + types = {0: 'OFPGT_ALL(0)', + 1: 'OFPGT_SELECT(1)', + 2: 'OFPGT_INDIRECT(2)', + 3: 'OFPGT_FF'} + try: + return types[type] + except KeyError: + return 'UnknownType(%s)' % type + + +def get_multipart_request_flags(flag): + flags = {0: 'NOT_FLAG_SET(0)', + 1: 'OFPMPF_REQ_MORE(1)'} + try: + return flags[flag] + except KeyError: + return 'UnknownFlag(%s)' % flag + + +def get_multipart_reply_flags(flag): + flags = {0: 'NOT_FLAG_SET(0)', + 1: 'OFPMPF_REPLY_MORE(1)'} + try: + return flags[flag] + except KeyError: + return 'UnknownFlag(%s)' % flag + + +def get_controller_role(role): + roles = {0: 'OFPCR_ROLE_NOCHANGE(0)', + 1: 'OFPCR_ROLE_EQUAL(1)', + 2: 'OFPCR_ROLE_MASTER(2)', + 3: 'OFPCR_ROLE_SLAVE(3)'} + try: + return roles[role] + except KeyError: + return 'UnknownRole(%s)' % role \ No newline at end of file diff --git a/libs/openflow/of13/prints.py b/libs/openflow/of13/prints.py new file mode 100644 index 0000000..7ebc346 --- /dev/null +++ b/libs/openflow/of13/prints.py @@ -0,0 +1,318 @@ +""" + OpenFlow 1.3 prints +""" +from hexdump import hexdump + +import libs.tcpiplib.prints +from libs.gen.prints import red, green +from libs.openflow import of13 + + +def print_pad(pad): + """ + Used to print pads as a sequence of 0s: 0, 00, 000.. + Args: + pad: pad in int format + Returns: string with '0' + """ + pad_len = pad + + print(type(pad_len)) + string = '0' + if pad_len == 1: + return '0' + for item in range(0, pad_len-1): + string += '0' + return string + + +# ################## OFPT_HELLO ############################ + + +def print_hello(msg): + count = 0 + for element in msg.elements: + count += 1 + print("Hello - Element: %s Type: %s Length: %s" % + (count, element.type, element.length)) + count_bit = 0 + for bitmap in element.versiobitmap: + count_bit += 1 + print("Hello - Bitmap: %s Type: %s Length: %s" % + (count_bit, bitmap.type, bitmap.length)) + print('Bitmap: %08d' % int(bitmap.bitmaps.split('b')[1])) + + +# ################## OFPT_ERROR ############################ + + +def print_error_msg(msg): + print("Error - Type: %s Code: %s" % (msg.error_type, msg.code)) + if len(msg.data): + print(hexdump(msg.data)) + return 0 + + +# ################## OFPT_ECHO_REQUEST ############################ + + +def print_echo_request(msg): + if len(msg.data) > 1: + print("Echo - Data: %s" % msg.data) + return 0 + + +# ################## OFPT_ECHO_REPLY ############################ + + +def ofp_echo_reply(msg): + if len(msg.data) > 1: + print("Echo - Data: %s" % msg.data) + return 0 + + +# ################## OFPT_EXPERIMENTER ############################ + + +def print_experimenter(msg): + return 0 + + +# ################## OFPT_FEATURE_REQUEST ############################ + +def parse_bitmask(bitmask, array): + size = len(array) + for i in range(0, size): + mask = 2**i + aux = bitmask & mask + if aux == 0: + array.remove(mask) + return array + + +def parse_capabilities(capabilities): + caps = [1, 2, 4, 8, 16, 32, 64, 128, 256] + return parse_bitmask(capabilities, caps) + + +def print_switch_features(msg): + print("OpenFlow Switch Features:") + print("Datapath_id: %s N_Buffers: %s N_Tbls: %s\nAuxiliary_id: %s " + "Pad: %s Reserved: %s" % + (red(msg.datapath_id), + msg.n_buffers, msg.n_tbls, msg.auxiliary_id, + msg.pad, green(msg.reserved))) + print("Capabilities: "), + caps = parse_capabilities(msg.capabilities) + for i in caps: + print(libs.openflow.of13.dissector.get_feature_res_capabilities(i)), + print + +# ########## OFPT_GET_CONFIG_REPLY & OFPT_SET_CONFIG ############### + + +def print_switch_config(msg): + print('Switch Configuration - Flag: %s Miss_Send_Len: %s' % + (msg.flag, msg.miss_send_len)) + return 0 + + +# ################## OFPT_PACKET_IN ############################ + + +def print_packet_in(msg): + return 0 + + +# ################## OFPT_FLOW_REMOVED ############################ + + +def print_flow_removed(msg): + return 0 + + +# ################## OFPT_PORT_STATUS ############################ + + +def print_port_status(msg): + print(msg.reason.__dict__) + print(msg.desc.__dict__) + string = ('PortStatus - Reason: %s Desc: %s Pad: %s' % + (msg.reason, msg.desc, msg.pad)) + + print(string) + return 0 + + +# ################## OFPT_PACKET_OUT ############################ + + +def print_packet_out(msg): + return 0 + + +# ################## OFPT_FLOW_MOD ############################ + + +def print_flow_mod(msg): + # Print main flow_mod options + string = ('FlowMod - Cookie/Mask: %s/%s Table_id: %s Command: %s ' + 'Idle/Hard Timeouts: %s/%s\nFlowMod - Priority: %s ' + 'Buffer ID: %s Out Port: %s Out Group: %s Flags: %s Pad: %s') + + command = green(libs.openflow.of13.dissector.get_flow_mod_command(msg.command)) + flags = green(libs.openflow.of13.dissector.get_flow_mod_flags(msg.flags)) + port = green(libs.openflow.of13.dissector.get_phy_port_id(msg.out_port)) + print(string % (msg.cookie, msg.cookie_mask, + msg.table_id, command, msg.idle_timeout, + msg.hard_timeout, msg.priority, + msg.buffer_id, port, msg.out_group, + flags, print_pad(msg.pad))) + + # Print print_match_type(msg) + print_match_type(msg.match) + print_instruction(msg.instructions) + + +def print_match_type(match): + print('Flow Matches - Type: %s Length: %s' % (match.type, match.length)) + # print oxm_fields + print_match_oxm_fields(match.oxm_fields) + + +def print_match_oxm_fields(oxm_fields): + for oxm in oxm_fields: + print_match_generic(oxm) + print_match_oxm(oxm) + + +def print_match_generic(oxm): + print(' OXM Match: Class: %s Length: %s HasMask: %s Field: %s:' % + (hex(oxm.oxm_class), oxm.length, oxm.hasmask, + green(libs.openflow.of13.dissector.get_flow_match_fields(oxm.field)))), + + +def print_match_oxm(oxm): + if oxm.hasmask == 0: + if oxm.field in [0]: + oxm.payload.value = oxm.payload.value & 0xffff + oxm.payload.value = libs.openflow.of13.dissector.get_phy_port_id(oxm.payload.value) + # DL_DST or DL_SRC + elif oxm.field in [3, 4, 24, 25, 32, 33]: + print(green(libs.tcpiplib.prints.eth_addr(oxm.payload.value))) + return + # DL_TYPE + elif oxm.field in [5]: + oxm.payload.value = hex(oxm.payload.value) + # DL_VLAN + elif oxm.field == 6: + if oxm.payload.value == 0: + oxm.payload.value = 'UNTAGGED' + else: + oxm.payload.value = oxm.payload.value & 0xfff + # NW_SRC or NW_DST + elif oxm.field in [11, 12, 22, 23]: + oxm.payload.value = libs.tcpiplib.prints.get_ip_from_long(oxm.payload.value) + # IPv6 Extensions + elif oxm.field in [39]: + extensions = of13.parser.parse_ipv6_extension_header(oxm.payload.values) + for i in extensions: + print(green(libs.openflow.of13.dissector.get_ipv6_extension(i))), + + print('%s' % green(oxm.payload.value)) + + elif oxm.hasmask == 1: + if oxm.field in [3, 4, 24, 25]: + oxm.payload.value = libs.tcpiplib.prints.eth_addr(oxm.payload.value) + oxm.payload.mask = libs.tcpiplib.prints.eth_addr(oxm.payload.mask) + if oxm.field in [11, 12, 22, 23]: + oxm.payload.value = libs.tcpiplib.prints.get_ip_from_long(oxm.payload.value) + oxm.payload.mask = libs.tcpiplib.prints.get_ip_from_long(oxm.payload.mask) + + print('%s/%s' % (green(oxm.payload.value), green(oxm.payload.mask))) + + +def print_instruction(instructions): + print('Flow Instructions:') + for instruction in instructions: + print(' Instruction: Type %s Length: %s' % + (instruction.type, instruction.length)) + for action in instruction.actions: + print(' Action - Type %s Length %s' % (action.type, action.length)), + if action.type == 0: + print("Port %s Max_Len %s Pad %s" % + (action.port, action.max_len, print_pad(action.pad))) + if action.type == 1: + print("VLAN_VID %s Pad %s" % + (action.vlan_vid, print_pad(action.pad))) + + +# ################## OFPT_GROUP_MOD ############################ + + +def print_group_mod(msg): + return 0 + + +# ################## OFPT_PORT_MOD ############################ + + +def print_port_mod(msg): + return 0 + + +# ################## OFPT_TABLE_MOD ############################ + + +def print_table_mod(msg): + return 0 + + +# ################## OFPT_MULTIPART_REQUEST ############################ + + +def print_multipart_request(msg): + return 0 + + +# ################## OFPT_MULTIPART_REPLY ############################ + + +def print_multipart_reply(msg): + return 0 + + +# ################## OFPT_QUEUE_GET_CONFIG_REQUEST ############################ + + +def print_queue_get_config_request(msg): + return 0 + + +# ################## OFPT_QUEUE_GET_CONFIG_REPLY ############################ + + +def print_queue_get_config_reply(msg): + return 0 + + +# ########## OFPT_ROLE_REQUEST & OFPT_ROLE_REPLY ############### + + +def print_role(msg): + return 0 + + +# ########### OFPT_GET_ASYNC_REPLY & OFPT_SET_ASYNC ##################### + + +def print_async_config(msg): + return 0 + + +# ################## OFPT_METER_MOD ############################ + + +def print_meter_mod(msg): + return 0 + diff --git a/libs/tcpiplib/__init__.py b/libs/tcpiplib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/tcpiplib/packet.py b/libs/tcpiplib/packet.py new file mode 100644 index 0000000..858d4bc --- /dev/null +++ b/libs/tcpiplib/packet.py @@ -0,0 +1,396 @@ +""" + Module to define classes in use by the TCP/IP stack +""" + +from struct import unpack +import socket +from datetime import datetime + + +IP_PROTOCOL = 8 +TCP_PROTOCOL = 6 +TCP_FLAG_PUSH = 8 +OF_HEADER_SIZE = 8 + + +class L1: + """ + Class to dissect L1 fields + """ + def __init__(self): + self.caplen = None + self.truncate = None + self.time = None + + def parse(self, header): + """ + Parse object + """ + self.caplen = header.getlen() + self.truncate = header.getcaplen() + self.time = self.set_time(header.getts()) + + @staticmethod + def set_time(date): + """ + Get the time when the packet was captured + + Args: + date: array provided by libpcap + Returns: + date processed by datetime + """ + date = date[0] + date[1] / 100000 + date = datetime.fromtimestamp(date) + date = date.strftime('%Y-%m-%d %H:%M:%S.%f') + return date + + +class Ethernet: + """ + Class to dissect Ethernet fields + """ + def __init__(self): + self.src_mac = None + self.dst_mac = None + self.protocol = None + self.length = 14 # Ethernet header has 14 bytes + + def parse(self, packet, host_order=0): + """ + Parses packet, converting from binary to Ethernet + + Args: + packet: binary object + host_order: just to help defining the host order + """ + eth_raw = packet[:self.length] + ethernet = unpack('!6s6sH', eth_raw) + self.dst_mac = ethernet[0] + self.src_mac = ethernet[1] + + # When Ethernet is captured directly from the wire, + # it uses host_order big-endian. When the frame is encapsulated + # inside an OpenFlow PacketIn or Out, it is little-endian + if not host_order: + self.protocol = socket.ntohs(ethernet[2]) + else: + self.protocol = ethernet[2] + return self.length + + +class IP: + """ + Class to dissect IP fields + """ + def __init__(self): + self.version = None + self.ihl = None + self.length = 20 # Minimum length + self.ttl = None + self.protocol = None + self.s_addr = None + self.d_addr = None + + def parse(self, packet, offset): + """ + Parses packet, converting from binary to IP + + Args: + packet: binary object + offset: position to start + """ + ip_raw = packet[offset:self.length + offset] + iph = unpack('!BBHHHBBH4s4s', ip_raw) + version_ihl = iph[0] + self.version = version_ihl >> 4 + self.ihl = version_ihl & 0xF + self.length = self.ihl * 4 + self.ttl = iph[5] + self.protocol = iph[6] + self.s_addr = socket.inet_ntoa(iph[8]) + self.d_addr = socket.inet_ntoa(iph[9]) + return self.length + offset + + +class TCP: + """ + Class to dissect TCP fields + """ + def __init__(self): + self.source_port = None + self.dest_port = None + self.sequence = None + self.acknowledgement = None + self.length = 20 # minimum length. + self.flag_cwr = None + self.flag_ece = None + self.flag_urg = None + self.flag_ack = None + self.flag_psh = None + self.flag_rst = None + self.flag_syn = None + self.flag_fyn = None + + def parse(self, packet, offset): + """ + Parses packet, converting from binary to TCP + + Args: + packet: binary object + offset: position to start + """ + tcp_raw = packet[offset:offset + self.length] + tcph = unpack('!HHLLBBHHH', tcp_raw) + self.source_port = tcph[0] + self.dest_port = tcph[1] + self.sequence = tcph[2] + self.acknowledgement = tcph[3] + doff_reserved = tcph[4] + tcph_length = doff_reserved >> 4 + self.length = tcph_length * 4 + flags = tcph[5] + self.flag_cwr = flags & 0x80 + self.flag_ece = flags & 0x40 + self.flag_urg = flags & 0x20 + self.flag_ack = flags & 0x10 + self.flag_psh = flags & 0x08 + self.flag_rst = flags & 0x04 + self.flag_syn = flags & 0x02 + self.flag_fyn = flags & 0x01 + return self.length + offset + + +class VLAN: + """ + Class to dissect VLAN fields + """ + def __init__(self): + self.vid = None + self.cfi = None + self.pcp = None + self.protocol = None + self.ethertype = None + + def parse(self, packet): + """ + Parses packet, converting from binary to VLAN + + Args: + packet: binary object + """ + vlan_length = 2 + ethertype = 2 + vlan_raw = packet[:vlan_length + ethertype] + vlan_p = unpack('!HH', vlan_raw) + self.pcp = vlan_p[0] >> 13 + self.cfi = (vlan_p[0] & 0x1000) >> 12 + self.vid = vlan_p[0] & 0xfff + self.protocol = vlan_p[1] + self.ethertype = self.protocol + + +class LLDP: + """ + Class to dissect LLDP fields + This is not a full LLDP dissection, just basic fields + The idea is to get the DPID and ports + """ + def __init__(self): + self.c_type = None + self.c_length = None + self.c_subtype = None + self.c_id = None + self.p_type = None + self.p_length = None + self.p_subtype = None + self.p_id = None + self.t_type = None + self.t_length = None + self.t_ttl = None + self.e_type = None + self.e_length = None + + def parse(self, packet): + """ + Parses packet, converting from binary to LLDP + + Args: + packet: binary object + """ + + # Chassis + # TLV (1) + Length = 2 bytes | Sub-type = 1 Byte + chassis_raw = packet[:3] + chassis = unpack('!HB', chassis_raw) + self.c_type = chassis[0] >> 9 + if self.c_type is not 1: + return {} + + self.c_length = chassis[0] & 0xFF + self.c_subtype = chassis[1] + length = self.c_length - 1 + # Get C_ID + chassis_raw = packet[3:3 + length] + string = '!%ss' % length + chassis = unpack(string, chassis_raw) + self.c_id = chassis[0].decode("utf-8") + + start = 3 + length + + # Port + # TLV (2) + Length = 2 Bytes | Port_id = 1 Byte + port_raw = packet[start:start + 3] + port = unpack('!HB', port_raw) + self.p_type = port[0] >> 9 + if self.p_type is not 2: + return {} + self.p_length = port[0] & 0xFF + self.p_subtype = port[1] + length = self.p_length - 1 + # Get P_ID + port_raw = packet[start + 3:start + 3 + length] + # string = '!%ss' % length + if length is 1: + string = '!B' + elif length is 2: + string = '!H' + elif length is 4: + string = '!L' + else: + string = '!%ss' % length + port = unpack(string, port_raw) + self.p_id = port[0] + + start = start + 3 + length + + # TTL + ttl_raw = packet[start:start + 4] + ttl = unpack('!HH', ttl_raw) + self.t_type = ttl[0] >> 9 + if self.t_type is not 3: + return {} + self.t_length = ttl[0] & 0xFF + self.t_ttl = ttl[1] + + start += 4 + + # Loop to get User-Specific TLVs + while len(packet[start:]) > 2: + next_raw = packet[start:start + 2] + nraw = unpack('!H', next_raw) + n_type = nraw[0] >> 9 + n_length = nraw[0] & 0xFF + length = n_length - 4 + if n_type == 0: + break + elif n_type == 127: + # We only want TLV 127, OUI a42305 (ONOS) + # Then we will look for Subtype 2 and get the content + # Skip the OUI - 3 bytes + subtype_raw = packet[start + 5:start + 6] + subtype = unpack('!B', subtype_raw) + if subtype[0] == 2: + content_raw = packet[start + 6:start + 6 + length] + string = '!%ss' % length + content = unpack(string, content_raw) + self.c_id = content[0] + + start = start + n_length + 2 + + # END + end_raw = packet[start:start + 2] + end = unpack('!H', end_raw) + self.e_type = end[0] >> 9 + self.e_length = end[0] & 0xFF + + +class ARP: + """ + Class to dissect ARP fields + """ + def __init__(self): + self.hw_type = None + self.prot_type = None + self.hw_len = None + self.prot_len = None + self.opcode = None + self.src_mac = None + self.src_ip = None + self.dst_mac = None + self.dst_ip = None + + def parse(self, packet): + """ + Parses packet, converting from binary to ARP + + Args: + packet: binary object + """ + arp_raw = packet[:28] + arp = unpack('!HHBBH6sL6sL', arp_raw) + self.hw_type = arp[0] + self.prot_type = arp[1] + self.hw_len = arp[2] + self.prot_len = arp[3] + self.opcode = arp[4] + self.src_mac = arp[5] + self.src_ip = arp[6] + self.dst_mac = arp[7] + self.dst_ip = arp[8] + + +class OessFvd: + """ + OESS' FVD module + """ + def __init__(self): + self.side_a = None + self.port_a = None + self.side_z = None + self.port_z = None + self.timestamp = None + + def parse(self, packet): + """ + Parses packet, converting from binary to OESS FVD + + Args: + packet: binary object + """ + self.side_a = self.get_datapath_id(packet[0:8]) + self.port_a = self.port_id(packet[8:16]) + self.side_z = self.get_datapath_id(packet[16:24]) + self.port_z = self.port_id(packet[24:32]) + self.timestamp = self.get_timestamp(packet[32:]) + + @staticmethod + def read_field(value): + """ + Method used to convert from bytes to string + Created to work with Python3 + """ + string = '%02x' * len(value) + return string % tuple(value)[::-1] + + def get_datapath_id(self, dpid): + """ + Convert OpenFlow Datapath ID to human format + """ + dpid = self.read_field(dpid) + return ':'.join(dpid[i:i+2] for i in range(0, 16, 2)) + + def get_timestamp(self, timestamp): + """ + Convert binary to timestamp + """ + timestamp = self.read_field(timestamp) + + return int(timestamp, base=16) / 1000 + + def port_id(self, port): + """ + Convert OpenFlow Port ID to human format + """ + port = self.read_field(port) + return int(port, base=16) diff --git a/libs/tcpiplib/prints.py b/libs/tcpiplib/prints.py new file mode 100644 index 0000000..c0bfab6 --- /dev/null +++ b/libs/tcpiplib/prints.py @@ -0,0 +1,242 @@ +""" + Printing TCP/IP classes +""" +import socket +import struct +from datetime import datetime +import libs.openflow.of10.dissector +import libs.tcpiplib.tcpip +from libs.core.printing import PrintingOptions +from libs.gen.prints import red, green, blue, yellow, cyan +from libs.tcpiplib.tcpip import get_ethertype +from apps.ofp_proxies import OFProxy + + +def eth_addr(a): + """ + Print Mac Address in the human format + Args: + a: string "6s" + Returns: + mac in the human format + """ + if isinstance(a, bytes): + a = a.decode("latin") + string = "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x" + mac = string % (ord(a[0]), ord(a[1]), ord(a[2]), + ord(a[3]), ord(a[4]), ord(a[5])) + return mac + + +def get_ip_from_long(long_ip): + """ + Get IP from a long int + Args: + long_ip: IP in the long int format + + Returns: IP in the format x.x.x.x + """ + return socket.inet_ntoa(struct.pack('!L', long_ip)) + + +def datapath_id(a): + """ + Convert OpenFlow Datapath ID to human format + Args: + a: DPID in "8s" format + Returns: + DPID in human format + """ + string = "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x:%.2x:%.2x" + if isinstance(a, bytes): + a = a.decode("latin") + dpid = string % (ord(a[0]), ord(a[1]), ord(a[2]), ord(a[3]), + ord(a[4]), ord(a[5]), ord(a[6]), ord(a[7])) + return dpid + + +def print_headers(pkt, overwrite_min=0): + """ + Print TCP/IP header. It uses command line option -p + to print 'mininal' or 'full' headers + Args: + pkt: OFMessage class + overwrite_min: in case of problems, overwrite user definition + """ + if PrintingOptions().min == 1 and overwrite_min == 0: + print_minimal(pkt.position, pkt.l1.time, pkt.l1.caplen, pkt.l3, + pkt.l4) + else: + print_position(pkt.position) + print_layer1(pkt.l1.time, pkt.l1.caplen, pkt.l1.truncate) + print_layer2(pkt.l2) + print_layer3(pkt.l3) + print_tcp(pkt.l4) + + +def print_minimal(position, date, getlen, ip_addr, tcp): + """ + Print TCP/IP header with minimal information + Args: + position: packet count + date: date/time packet was captured + getlen: total number of bytes captured + ip_addr: IP class + tcp: TCP class + """ + string = 'Packet #%s - %s %s:%s -> %s:%s Size: %s Bytes' + + source = OFProxy().get_name(ip_addr.s_addr, tcp.source_port) + dest = OFProxy().get_name(ip_addr.d_addr, tcp.dest_port) + + print(string % (position, date, cyan(source), cyan(tcp.source_port), + cyan(dest), cyan(tcp.dest_port), getlen)) + + +def print_position(position): + """ + Print the packet counter (ctr) number + Args: + position: number of the packet captured in the sequence + """ + print('Packet Number # %s' % position) + + +def print_layer1(date, getlen, caplen): + """ + Prints information about the captured packet + Args: + date: date/time when the packet was captured + getlen: total packet captured + caplen: truncated size of the packet captured + """ + print('%s: captured %d bytes, truncated to %d bytes' % + (date, getlen, caplen)) + + +def print_layer2(eth): + """ + Prints the Ethernet frame + Args: + eth: Ethernet class + """ + print('Ethernet: Destination MAC: %s Source MAC: %s Protocol: %s' % + (eth_addr(eth.dst_mac), eth_addr(eth.src_mac), + red(libs.tcpiplib.tcpip.get_ethertype(eth.protocol)))) + + +def print_vlan(vlan): + """ + Print VLAN fields + Args: + vlan: VLAN class + """ + print('VLAN: PCP: %s CFI: %s VID: %s Protocol: %s' % + (vlan.pcp, vlan.cfi, red(vlan.vid), + red(get_ethertype(vlan.ethertype)))) + + +def print_arp(arp): + """ + Print ARP fields + Args: + arp: ARP class + """ + print('ARP: Hardware Type: %s Protocol Type: %s ' + 'HW Length: %s Prot Length: %s Opcode: %s ' + '\nARP: Source MAC: %s Source IP: %s Destination MAC: %s ' + 'Destination IP: %s' + % (arp.hw_type, arp.prot_type, arp.hw_len, + arp.prot_len, arp.opcode, + eth_addr(arp.src_mac), get_ip_from_long(arp.src_ip), + eth_addr(arp.dst_mac), get_ip_from_long(arp.dst_ip))) + + +def print_layer3(ip_addr): + """ + Prints IP headers + Args: + ip: IP class + """ + print(('IP Version: %d IP Header Length: %d TTL: %d Protocol: %d ' + 'Source Address: %s Destination Address: %s') % + (ip_addr.version, ip_addr.length, ip_addr.ttl, ip_addr.protocol, + blue(ip_addr.s_addr), blue(ip_addr.d_addr))) + + +def print_tcp(tcp): + """ + Print TCP headers + Args: + tcp: TCP class + """ + print('TCP Source Port: %s Dest Port: %s Sequence Number: %s ' + 'Acknowledgement: %s TCP header length: %s Flags: CWR: %s ' + 'ECE: %s URG: %s ACK: %s PSH: %s RST: %s SYN: %s FYN: %s' % + (tcp.source_port, tcp.dest_port, tcp.sequence, + tcp.acknowledgement, tcp.length, tcp.flag_cwr, + tcp.flag_ece, tcp.flag_urg, tcp.flag_ack, tcp.flag_psh, + tcp.flag_rst, tcp.flag_syn, tcp.flag_fyn)) + + +def print_openflow_header(ofp): + """ + Print OpenFlow header + Args: + ofp: OFMessage class + """ + version = libs.tcpiplib.tcpip.get_ofp_version(ofp.header.version.value) + name_version = '%s(%s)' % (version, ofp.header.version.value) + + name = "%s" % ofp.header.message_type + name_type = '%s(%s)' % (name.split('.')[1], ofp.header.message_type.value) + + print('OpenFlow Version: %s Type: %s Length: %s XID: %s' % + (name_version, yellow(name_type), ofp.header.length, red(ofp.header.xid))) + + +def print_lldp(lldp): + """ + Print LLDP fields + Args: + lldp: LLDP class + """ + if lldp.c_type is not 1 or lldp.p_type is not 2 \ + or lldp.t_type is not 3 or lldp.e_type is not 0: + print('LLDP: Malformed packet') + return + + if lldp.c_type is 1: + print('LLDP: Chassis Type(%s) Length: %s SubType: %s ID: %s' % + (lldp.c_type, lldp.c_length, lldp.c_subtype, green(lldp.c_id))) + if lldp.p_type is 2: + print('LLDP: Port Type(%s) Length: %s SubType: %s ID: %s' % + (lldp.p_type, lldp.p_length, lldp.p_subtype, green(lldp.p_id))) + if lldp.t_type is 3: + print('LLDP: TTL(%s) Length: %s Seconds: %s' % + (lldp.t_type, lldp.t_length, lldp.t_ttl)) + + if lldp.e_type is 0: + print('LLDP: END(%s) Length: %s' % (lldp.e_type, lldp.e_length)) + + +def print_oessfvd(fvd): + """ + Print FVD fields + Args: + fvd: OessFvd class + """ + timestamp = str(datetime.fromtimestamp(fvd.timestamp)) + print('OESS FVD: %s:%s -> %s:%s time: %s' % + (red(fvd.side_a), blue(fvd.port_a), red(fvd.side_z), blue(fvd.port_z), + blue(timestamp))) + + +def print_connection_restablished(pkt): + """ + Just prints that the TCP connection was restablished. + Args: + pkt: Packet class + """ + print_headers(pkt, overwrite_min=0) + print(red("!!!! Attention: Connection Re-Established!!\n")) diff --git a/libs/tcpiplib/process_data.py b/libs/tcpiplib/process_data.py new file mode 100644 index 0000000..c84c4c8 --- /dev/null +++ b/libs/tcpiplib/process_data.py @@ -0,0 +1,182 @@ +""" + This module has functions to help processing the data from + PacketIn and PacketOut. +""" + + +from pyof.foundation.basic_types import BinaryData +from libs.tcpiplib.packet import Ethernet, VLAN, IP, TCP, LLDP, ARP, OessFvd + + +def dissect_data(data, start=0): + """ + This function aims to dissect PacketIn and PacketOut data + It assumes it is + Ethernet [vlan] (BDDP|LLDP|ARP|IP) [TCP|UDP] + Args: + data: BinaryData + start: offset + Returns: + payload: array with all classes + """ + packet = data.value + payload = [] + # Ethernet + eth = Ethernet() + eth.parse(packet[start:start + 14], 1) + payload.append(eth) + + # VLAN or not - ETYPE 0x8100 or 33024 + etype = '0x0000' + + start += 14 + if eth.protocol in [33024]: + # Frame has VLAN + + vlan = VLAN() + vlan.parse(packet[start:start + 4]) + payload.append(vlan) + etype = vlan.protocol + start += 4 + else: + etype = eth.protocol + + # if there is no content, return + if len(packet[start:]) == 0: + return payload + + # OESS FVD + if etype in [34998]: + fvd = OessFvd() + try: + fvd.parse(packet[start:]) + except Exception as error: + print(error) + payload.append(fvd) + return payload + + # LLDP - ETYPE 0x88CC or 35020 or + # BBDP - ETYPE 0x8942 or 35138 + if etype in [35020, 35138]: + lldp = LLDP() + try: + lldp.parse(packet[start:]) + except: + pass + if not isinstance(lldp, LLDP): + lldp.c_id = 0 + else: + payload.append(lldp) + return payload + + # IP - ETYPE 0x800 or 2048 + if etype in [2048]: + ip_addr = IP() + ip_addr.parse(packet, start) + payload.append(ip_addr) + if ip_addr.protocol is 6: + tcp = TCP() + tcp.parse(packet, start + ip_addr.length) + payload.append(tcp) + return payload + + # ARP - ETYPE 0x806 or 2054 + if etype in [2054]: + arp = ARP() + arp.parse(packet[start:]) + payload.append(arp) + return payload + + return payload + + +def is_protocol(data, lldp=False, oess=False, arp=False): + """ + Check if Data is protocol provided + Args: + data: PacketOut/PacketIn/OESS data + lldp: check for lldp + oess: check for oess + arp: check for arp + Returns: + protocol class if True + False if it is not + """ + protocol = [] + return_protocol = False + if lldp: + protocol.append(35020) # LLDP + protocol.append(35138) # BDDP + elif oess: + protocol.append(34998) # Private + elif arp: + protocol.append(2054) # ARP 0x806 + else: + return_protocol = True + + if isinstance(data, BinaryData): + data = dissect_data(data) + + try: + eth = data.pop(0) + next_protocol = eth.protocol + + if next_protocol in [33024]: + vlan = data.pop(0) + if return_protocol: + return vlan.protocol + + next_protocol = vlan.protocol + + if next_protocol in protocol: + return True + + return False + + except Exception as error: + print(error) + return False + + +def get_protocol(data, lldp=False, oess=False, arp=False): + """ + Get protocol from data + Args: + data: PacketOut/PacketIn/OESS data + lldp: check for lldp + oess: check for oess + arp: check for arp + Returns: + protocol class if True + False if it is not + """ + protocol = [] + if lldp: + protocol.append(35020) # LLDP + protocol.append(35138) # BDDP + elif oess: + protocol.append(34998) # Private + elif arp: + protocol.append(2054) # ARP 0x806 + else: + return False + + if isinstance(data, BinaryData): + data = dissect_data(data) + + try: + eth = data.pop(0) + next_protocol = eth.protocol + + if next_protocol in [33024]: + vlan = data.pop(0) + next_protocol = vlan.protocol + + if next_protocol in protocol: + return data.pop(0) + + return False + + except Exception as error: + print(error) + return False diff --git a/libs/tcpiplib/tcpip.py b/libs/tcpiplib/tcpip.py new file mode 100644 index 0000000..cdc9257 --- /dev/null +++ b/libs/tcpiplib/tcpip.py @@ -0,0 +1,77 @@ +""" + This module helps to translate numbers to names in the + TCP/IP stack +""" + + +from struct import unpack + + +def get_ethertype(etype): + """ + Converts Ethertype from number to name + Args: + etype: etype number collected + Returns: + etype name + """ + etypes = {8: 'IP', + 2048: 'IP', + 2054: 'ARP', + 33024: 'VLAN', + 34925: 'IPv6', + 34887: 'MPLS', + 35020: 'LLDP', + 35138: 'BBDP', + 34998: 'PRIVATE'} + try: + return '%s(%s)' % (etypes[etype], hex(etype)) + except KeyError: + return hex(etype) + + +def get_ofp_version(version): + """ + Converts OpenFlow version number to a human version + Args: + version: integer version of the OpenFlow version + Returns: + human version of the OpenFlow version + """ + of_versions = {0: 'Experimental', + 1: '1.0', + 2: '1.1', + 3: '1.2', + 4: '1.3', + 5: '1.4', + 6: '1.5'} + try: + return of_versions[version] + except KeyError: + return 'Unknown(%s)' % version + + +def get_openflow_header(packet, start): + """ + Returns OpenFlow header + It is not a version aware + Args: + packet: packet content + start: offset + """ + of_header_length = 8 + of_header = packet[start:of_header_length+start] + try: + ofh = unpack('!BBHL', of_header) + of_version = ofh[0] + of_type = ofh[1] + of_length = ofh[2] + of_xid = ofh[3] + of_header = {'version': of_version, 'type': of_type, + 'length': of_length, 'xid': of_xid} + return of_header + + except Exception as exception: + print(exception) + of_header['version'] = -1 + return of_header diff --git a/ofp_cli.py b/ofp_cli.py deleted file mode 100644 index e5e9a37..0000000 --- a/ofp_cli.py +++ /dev/null @@ -1,98 +0,0 @@ -import sys -import getopt -import json - - -VERSION = '0.2' -NO_COLOR = False - - -def usage(file): - """ This funcion prints the Usage in case of errors or help needed. - Always ends after printing this lines below. - """ - print (('Usage: \n %s [-p min|full] [-f pcap_filter] [-F filter_file]' - ' [-i dev] [-r pcap_file]\n' - '\t -p [min|full] or --print=[min|full]: print min or full' - ' packet headers. Default: min\n' - '\t -f pcap_filter or --pcap-filter=pcap_filter : add a libpcap' - ' filter\n' - '\t -F sanitizer_file.json or --sanitizer-file=sanitizerfile.json\n' - '\t -i interface or --interface=interface. Default: eth0\n' - '\t -r captured.pcap or --src-file=captured.pcap\n' - '\t -o or --print-ovs : print using ovs-ofctl format\n' - '\t -h or --help : prints this guidance\n' - '\t -c or --no-colors: removes colors\n' - '\t -v or --version : prints version\n') % file) - - sys.exit(0) - - -def read_sanitizer(sanitizer_file): - try: - jfile = open(sanitizer_file, 'ro') - json_content = json.loads(jfile.read()) - except: - msg = 'Error Opening the sanitizer file\n' - msg += 'Please check your JSON file. Maybe the permission is wrong' - msg += ' or the JSON syntax is incorrect. Try the following:\n' - msg += 'cat %s | python -m json.tool' - print msg % sanitizer_file - sys.exit(0) - return (json_content) - - -def get_params(argv): - # Handle all input params - letters = 'f:F:i:r:p:ohvc' - keywords = ['print=', 'pcap-filter=', 'sanitizer-file=', 'interface=', - 'src-file=', 'print-ovs', 'help', 'version', 'no-colors'] - - # Default Values - input_filter, sanitizer_file, dev, captured_file = '', '', 'eth0', '' - - try: - opts, extraparams = getopt.getopt(argv[1:], letters, keywords) - except getopt.GetoptError as err: - print str(err) - usage(argv[0]) - - print_options = {'min': 1, 'ovs': 0, 'colors': 0} - - for option, param in opts: - if option in ['-p', '--print']: - if param == 'full': - print_options['min'] = 0 - elif param != 'min': - print 'Use min or full for printing' - usage(argv[0]) - elif option in ['-f', '--pcap-filter']: - input_filter = param - elif option in ['-F', '--sanitizer-file']: - sanitizer_file = param - elif option in ['-i', '--interface']: - dev = param - elif option in ['-r', '--captured-file']: - captured_file = param - elif option in ['-h', '--help']: - usage(argv[0]) - elif option in ['-o', '--print-ovs']: - print_options['ovs'] = 1 - elif option in ['-v', '--version']: - print 'OpenFlow Sniffer version %s' % VERSION - sys.exit(0) - elif option in ['-c', '--no-colors']: - global NO_COLOR - NO_COLOR = True - else: - usage(argv[0]) - - if len(sanitizer_file) == 0: - sanitizer = {'allowed_of_versions': {}, - 'packetInOut_filter': {}, - 'flowMod_logs': {}, - 'packetIn_filter': {}} - else: - sanitizer = read_sanitizer(sanitizer_file) - - return print_options, input_filter, sanitizer, dev, captured_file diff --git a/ofp_fsfw_v10.py b/ofp_fsfw_v10.py deleted file mode 100644 index 4fb66bb..0000000 --- a/ofp_fsfw_v10.py +++ /dev/null @@ -1,38 +0,0 @@ -NET = {} -name = {"cc4e249102000000": "andes2", - "cc4e249126000000": "andes1", - "cc4e244b11000000": "sol2", - "0024389406000000": "mct01", - "002438af17000000": "mct02", - "2438af17000000": "mct02", - "24389406000000": "mct01"} - - -def support_fsfw(print_options, lldp): - global NET - - ip = print_options['device_ip'] - port = print_options['device_port'] - dpid = lldp['c_id'].split(':')[1] - - name = get_name_dpid(dpid) - NET[ip, port] = name - return - - -def get_name_dpid(dpid): - return '%s' % name.get(dpid) - - -def get_ip_name(ip, port): - for i, j in NET.iteritems(): - if i == (ip, port): - return '%s(%s)' % (ip, j) - return ip - - -def close(): - print '\n' - # Future: send to a file to import faster - # for i, j in NET.iteritems(): - # print i, j diff --git a/ofp_parser_v10.py b/ofp_parser_v10.py deleted file mode 100644 index 3b43009..0000000 --- a/ofp_parser_v10.py +++ /dev/null @@ -1,891 +0,0 @@ -from struct import unpack -import ofp_dissector_v10 -import ofp_prints_v10 -import socket -import struct -import ofp_tcpip_parser -import ofp_vendors_v10 -import ofp_fsfw_v10 - - -def process_ofp_type(of_type, packet, h_size, of_xid, print_options, sanitizer): - if of_type == 0: - result = parse_Hello(packet, h_size, of_xid) - elif of_type == 1: - result = parse_Error(packet, h_size, of_xid) - elif of_type == 2: - result = parse_EchoReq(packet, h_size, of_xid) - elif of_type == 3: - result = parse_EchoRes(packet, h_size, of_xid) - elif of_type == 4: - result = parse_Vendor(packet, h_size, of_xid) - elif of_type == 5: - result = parse_FeatureReq(packet, h_size, of_xid) - elif of_type == 6: - result = parse_FeatureRes(packet, h_size, of_xid) - elif of_type == 7: - result = parse_GetConfigReq(packet, h_size, of_xid) - elif of_type == 8: - result = parse_GetConfigRes(packet, h_size, of_xid) - elif of_type == 9: - result = parse_SetConfig(packet, h_size, of_xid) - elif of_type == 10: - result = parse_PacketIn(packet, h_size, of_xid, sanitizer) - elif of_type == 11: - result = parse_FlowRemoved(packet, h_size, of_xid) - elif of_type == 12: - result = parse_PortStatus(packet, h_size, of_xid) - elif of_type == 13: - result = parse_PacketOut(packet, h_size, of_xid, sanitizer, - print_options) - elif of_type == 14: - result = parse_FlowMod(packet, h_size, of_xid, print_options) - elif of_type == 15: - result = parse_PortMod(packet, h_size, of_xid) - elif of_type == 16: - result = parse_StatsReq(packet, h_size, of_xid) - elif of_type == 17: - result = parse_StatsRes(packet, h_size, of_xid) - elif of_type == 18: - result = parse_BarrierReq(packet, h_size, of_xid) - elif of_type == 19: - result = parse_BarrierRes(packet, h_size, of_xid) - elif of_type == 20: - result = parse_QueueGetConfigReq(packet, h_size, of_xid) - elif of_type == 21: - result = parse_QueueGetConfigRes(packet, h_size, of_xid) - else: - return 0 - return result - - -# *************** Hello ***************** -def parse_Hello(packet, h_size, of_xid): - ofp_prints_v10.print_of_hello(of_xid) - return 1 - - -# ************** Error ***************** -def parse_Error(packet, h_size, of_xid): - of_error = packet[h_size:h_size+4] - ofe = unpack('!HH', of_error) - ofe_type = ofe[0] - ofe_code = ofe[1] - - nameCode, typeCode = ofp_dissector_v10.get_ofp_error(ofe_type, ofe_code) - ofp_prints_v10.print_of_error(of_xid, nameCode, typeCode) - return 1 - - -# ************ EchoReq ***************** -def parse_EchoReq(packet, h_size, of_xid): - ofp_prints_v10.print_echoreq(of_xid) - return 1 - - -# ************ EchoRes ***************** -def parse_EchoRes(packet, h_size, of_xid): - ofp_prints_v10.print_echores(of_xid) - return 1 - - -# ************ Vendor ****************** -def parse_Vendor(packet, h_size, of_xid): - of_vendor = packet[h_size:h_size+4] - ofv = unpack('!L', of_vendor) - ofp_prints_v10.print_of_vendor(ofv[0], of_xid) - - # If code 8992 = NICIRA - if ofv[0] == 8992: - ofp_vendors_v10.parse_nicira(packet, h_size+4, of_xid) - print - return 1 - - -# *********** FeatureReq *************** -def parse_FeatureReq(packet, h_size, of_xid): - ofp_prints_v10.print_of_feature_req(of_xid) - return 1 - - -# *********** FeatureRes *************** -def _parse_bitmask(bitmask, array): - size = len(array) - for i in range(0, size): - mask = 2**i - aux = bitmask & mask - if aux == 0: - array.remove(mask) - return array - - -def _parse_capabilities(capabilities): - caps = [1, 2, 4, 8, 16, 32, 64, 128] - return _parse_bitmask(capabilities, caps) - - -def _parse_actions(actions): - acts = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048] - return _parse_bitmask(actions, acts) - - -def _parse_phy_config(config): - confs = [1, 2, 4, 8, 16, 32, 64] - return _parse_bitmask(config, confs) - - -def _parse_phy_state(state): - states = [1, 2, 4, 8, 16] - return _parse_bitmask(state, states) - - -def _parse_phy_curr(values): - confs = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048] - return _parse_bitmask(values, confs) - - -def _parse_phy_ports(packet, of_xid): - phy = unpack('!H6s16sLLLLLL', packet) - - port_id = ofp_dissector_v10.get_phy_port_id(phy[0]) - hw_addr = ofp_prints_v10.eth_addr(phy[1]) - config = _parse_phy_config(phy[3]) - state = _parse_phy_state(phy[4]) - curr = _parse_phy_curr(phy[5]) - advertised = _parse_phy_curr(phy[6]) - supported = _parse_phy_curr(phy[7]) - peer = _parse_phy_curr(phy[8]) - - phy_ports = {'port_id': port_id, - 'hw_addr': hw_addr, - 'name': phy[2], - 'config': config, - 'state': state, - 'curr': curr, - 'advertised': advertised, - 'supported': supported, - 'peer': peer} - return phy_ports - - -def parse_FeatureRes(packet, h_size, of_xid): - of_fres = packet[h_size:h_size+24] - ofrs = unpack('!8sLB3sLL', of_fres) - f_res = {'datapath_id': ofrs[0], 'n_buffers': ofrs[1], 'n_tbls': ofrs[2], - 'pad': ofrs[3]} - ofp_prints_v10.print_of_feature_res(of_xid, f_res) - - # 'capabilities': ofrs[4], 'actions': ofrs[5]} - caps = [] - caps = _parse_capabilities(ofrs[4]) - actions = [] - actions = _parse_actions(ofrs[5]) - ofp_prints_v10.print_of_feature_res_caps_and_actions(of_xid, caps, actions) - - # Ports description? - start = h_size + 24 - while len(packet[start:]) > 0: - ports = _parse_phy_ports(packet[start:start+48], of_xid) - ofp_prints_v10.print_of_feature_res_ports(of_xid, ports) - start = start + 48 - - return 1 - - -# ***************** GetConfigReq ********************* -def parse_GetConfigReq(packet, h_size, of_xid): - ofp_prints_v10.print_of_getconfig_req(of_xid) - return 1 - - -# ***************** GetConfigRes ******************** -def _parse_SetGetConfig(packet, h_size): - pkt_raw = packet[h_size:h_size+4] - pkt_list = unpack('!HH', pkt_raw) - flag = ofp_dissector_v10.get_configres_flags(pkt_list[0]) - miss_send_len = pkt_list[1] - return flag, miss_send_len - - -def parse_GetConfigRes(packet, h_size, of_xid): - flag, miss_send_len = _parse_SetGetConfig(packet, h_size) - ofp_prints_v10.print_ofp_getConfigRes(of_xid, flag, miss_send_len) - return 1 - - -# ******************* SetConfig ********************** -def parse_SetConfig(packet, h_size, of_xid): - flag, miss_send_len = _parse_SetGetConfig(packet, h_size) - ofp_prints_v10.print_ofp_setConfig(of_xid, flag, miss_send_len) - return 1 - - -# ****************** PacketIn ************************ -def _parse_ethernet_lldp_PacketInOut(packet, start): - # Ethernet - eth = ofp_tcpip_parser.get_ethernet_frame(packet[start:start+14], 1) - start = start + 14 - etype = '0x0000' - vlan = {} - # VLAN or not - if eth['protocol'] in [33024]: - vlan = ofp_tcpip_parser.get_ethernet_vlan(packet[start:start+2]) - start = start + 2 - # If VLAN exists, there is a next eth['protocol'] - etype = ofp_tcpip_parser.get_next_etype(packet[start:start+2]) - start = start + 2 - else: - etype = eth['protocol'] - # LLDP - lldp = {} - if etype in [35020, 35138]: - lldp = ofp_tcpip_parser.get_lldp(packet[start:]) - return eth, vlan, lldp, start - eth['protocol'] = etype - return eth, vlan, {}, start - - -def _parse_other_types(packet, start, eth): - # OESS FVD - if eth['protocol'] in [34998]: - print 'OESS FVD' - elif eth['protocol'] in [35020]: - # If it gets here, means that the LLDP packet is MalFormed - print 'LLDP Packet MalFormed' - elif eth['protocol'] in [2048]: - ip = ofp_tcpip_parser.get_ip_packet(packet, start) - ofp_prints_v10.print_layer3(ip) - if ip['protocol'] is 6: - tcp = ofp_tcpip_parser.get_tcp_stream(packet, start + ip['length']) - ofp_prints_v10.print_tcp(tcp) - elif eth['protocol'] in [2054]: - arp = ofp_tcpip_parser.get_arp(packet[start:]) - ofp_prints_v10.print_arp(arp) - else: - print 'Ethertype %s not dissected' % hex(eth['protocol']) - - -def _print_packetIn(of_xid, packetIn, eth, vlan, lldp): - ofp_prints_v10.print_ofp_packetIn(of_xid, packetIn) - ofp_prints_v10.print_packetInOut_layer2(of_xid, eth) - if len(vlan) != 0: - ofp_prints_v10.print_packetInOut_vlan(of_xid, vlan) - if len(lldp) != 0: - ofp_prints_v10.print_packetInOut_lldp(of_xid, lldp) - - -def parse_PacketIn(packet, h_size, of_xid, sanitizer): - # buffer_id(32), total_len(16), in_port(16), reason(8), pad(8) - pkt_raw = packet[h_size:h_size+10] - p_in = unpack('!LHHBB', pkt_raw) - reason = ofp_dissector_v10.get_packetIn_reason(p_in[3]) - packetIn = {'buffer_id': p_in[0], 'total_len': p_in[1], 'in_port': p_in[2], - 'reason': reason, 'pad': p_in[4]} - - eth, vlan, lldp, offset = _parse_ethernet_lldp_PacketInOut(packet, - h_size + 10) - if len(lldp) == 0: - _print_packetIn(of_xid, packetIn, eth, vlan, {}) - _parse_other_types(packet[offset:], 0, eth) - return 1 - - # If we have filters (-F) - filters = sanitizer['packetIn_filter'] - - if len(filters) > 0: - if filters['switch_dpid'] == "any": - _print_packetIn(of_xid, packetIn, eth, vlan, lldp) - elif filters['switch_dpid'] == lldp['c_id']: - if (filters['in_port'] == "any" or - filters['in_port'] == lldp['in_port']): - _print_packetIn(of_xid, packetIn, eth, vlan, lldp) - else: - _print_packetIn(of_xid, packetIn, eth, vlan, lldp) - - return 1 - - -# ******************** FlowRemoved *************************** -def parse_FlowRemoved(packet, h_size, of_xid): - ofmatch = _parse_OFMatch(packet, h_size) - ofp_prints_v10.print_ofp_match(of_xid, ofmatch) - - of_rem_body = packet[h_size+40:h_size+40+40] - ofrem = unpack('!8sHBBLLHBBQQ', of_rem_body) - cookie = ofrem[0] if not len(ofrem[0]) else 0 - cookie = '0x' + format(cookie, '02x') - reason = ofp_dissector_v10.get_flow_removed_reason(ofrem[2]) - - ofrem = {'cookie': cookie, 'priority': ofrem[1], 'reason': reason, - 'pad': ofrem[3], 'duration_sec': ofrem[4], - 'duration_nsec': ofrem[5], 'idle_timeout': ofrem[6], - 'pad2': ofrem[7], 'pad3': ofrem[8], - 'packet_count': ofrem[9], 'byte_count': ofrem[10]} - - ofp_prints_v10.print_ofp_flow_removed(of_xid, ofrem) - return 1 - - -# ******************* PortStatus ***************************** -def parse_PortStatus(packet, h_size, of_xid): - port_raw = packet[h_size:h_size+8] - port = unpack('!B7s', port_raw) - reason = ofp_dissector_v10.get_portStatus_reason(port[0]) - pad = port[1] - ofp_prints_v10.print_portStatus(of_xid, reason, pad) - ports = _parse_phy_ports(packet[h_size+8:h_size+64], of_xid) - ofp_prints_v10.print_of_feature_res_ports(of_xid, ports) - return 1 - - -# ******************* PacketOut ***************************** -# Actions need to be handled -def parse_PacketOut(packet, h_size, of_xid, sanitizer, print_options): - # buffer_id(32), in_port(16), actions_len(16) - pkt_raw = packet[h_size:h_size+8] - p_out = unpack('!LHH', pkt_raw) - actions_len = p_out[2] - packetOut = {'buffer_id': p_out[0], 'in_port': p_out[1], - 'actions_len': actions_len} - - ofp_prints_v10.print_ofp_packetOut(of_xid, packetOut) - # Actions - start = h_size + 8 - _parse_OFAction(of_xid, packet[start:start+packetOut['actions_len']], 0) - # Ethernet - start = h_size + 8 + packetOut['actions_len'] - eth = ofp_tcpip_parser.get_ethernet_frame(packet[start:start+14], 1) - ofp_prints_v10.print_packetInOut_layer2(of_xid, eth) - start = start + 14 - etype = '0x0000' - # VLAN or not - if eth['protocol'] in [33024]: - vlan = ofp_tcpip_parser.get_ethernet_vlan(packet[start:start+2]) - ofp_prints_v10.print_packetInOut_vlan(of_xid, vlan) - start = start + 2 - # If VLAN exists, there is a next eth['protocol'] - etype = ofp_tcpip_parser.get_next_etype(packet[start:start+2]) - start = start + 2 - else: - etype = eth['protocol'] - if etype in [35020, 35138]: - # LLDP TLV - lldp = ofp_tcpip_parser.get_lldp(packet[start:]) - if len(lldp) is 0: - print 'LLDP Packet MalFormed' - else: - # Support for FSFW/Proxy - ofp_fsfw_v10.support_fsfw(print_options, lldp) - ofp_prints_v10.print_packetInOut_lldp(of_xid, lldp) - - return 1 - - -# ********************* FlowMod *************************** -def process_dst_subnet(wcard): - OFPFW_NW_DST_SHIFT = 14 - OFPFW_NW_DST_MASK = 1032192 - nw_dst_bits = (wcard & OFPFW_NW_DST_MASK) >> OFPFW_NW_DST_SHIFT - return ((32 - nw_dst_bits) if nw_dst_bits < 32 else 0) - - -def process_src_subnet(wcard): - OFPFW_NW_SRC_SHIFT = 8 - OFPFW_NW_SRC_MASK = 16128 - nw_src_bits = (wcard & OFPFW_NW_SRC_MASK) >> OFPFW_NW_SRC_SHIFT - return ((32 - nw_src_bits) if nw_src_bits < 32 else 0) - - -def _process_wildcard(wcard): - wildcard = {1: 'in_port', - 2: 'dl_vlan', - 4: 'dl_src', - 8: 'dl_dst', - 16: 'dl_type', - 32: 'nw_prot', - 64: 'tp_src', - 128: 'tp_dst', - 1048576: 'dl_vlan_pcp', - 2097152: 'nw_tos'} - - return wildcard.get(wcard) - - -def get_ip_from_long(long_ip): - return (socket.inet_ntoa(struct.pack('!L', long_ip))) - - -def _parse_OFMatch(packet, h_size): - of_match = packet[h_size:h_size+40] - ofm = unpack('!LH6s6sHBBHBBHLLHH', of_match) - wildcard = ofm[0] - dl_src = ofp_prints_v10.eth_addr(ofm[2]) - dl_dst = ofp_prints_v10.eth_addr(ofm[3]) - nw_src = get_ip_from_long(ofm[11]) - nw_dst = get_ip_from_long(ofm[12]) - etype = hex(ofm[7]) - - ofmatch = {'wildcards': ofm[0], 'in_port': ofm[1], 'dl_src': dl_src, - 'dl_dst': dl_dst, 'dl_vlan': ofm[4], 'dl_vlan_pcp': ofm[5], - 'dl_type': etype, 'nw_tos': ofm[8], 'nw_prot': ofm[9], - 'nw_src': nw_src, 'nw_dst': nw_dst, 'tp_src': ofm[13], - 'tp_dst': ofm[14]} - - if wildcard >= ((1 << 22) - 1): - ofmatch = {'wildcards': '4194303'} - return ofmatch - elif wildcard == 0: - ofmatch = {'wildcards': '0'} - return ofmatch - else: - src_netmask = process_src_subnet(wildcard) - if src_netmask == 0: - ofmatch.pop('nw_src') - else: - ofmatch['nw_src'] = str(ofmatch['nw_src']) + '/' + str(src_netmask) - dst_netmask = process_dst_subnet(wildcard) - if dst_netmask == 0: - ofmatch.pop('nw_dst') - else: - ofmatch['nw_dst'] = str(ofmatch['nw_dst']) + '/' + str(dst_netmask) - for i in range(0, 8): - mask = 2**i - aux = wildcard & mask - if aux != 0: - ofmatch.pop(_process_wildcard(mask)) - - for i in range(20, 22): - mask = 2**i - aux = wildcard & mask - if aux != 0: - ofmatch.pop(_process_wildcard(mask)) - - return ofmatch - - -def _parse_OFBody(packet, h_size): - of_mod_body = packet[h_size+40:h_size+40+24] - ofmod = unpack('!8sHHHHLHH', of_mod_body) - ofmod_cookie = ofmod[0] if not len(ofmod[0]) else 0 - ofmod_cookie = '0x' + format(ofmod_cookie, '02x') - ofmod_buffer_id = '0x' + format(ofmod[5], '02x') - - ofbody = {'cookie': ofmod_cookie, 'command': ofmod[1], - 'idle_timeout': ofmod[2], 'hard_timeout': ofmod[3], - 'priority': ofmod[4], 'buffer_id': ofmod[5], - 'buffer_id': ofmod_buffer_id, 'out_port': ofmod[6], - 'flags': ofmod[7]} - return ofbody - - -def get_action(action_type, length, payload): - # 0 - OUTPUT. Returns port and max_length - if action_type == 0: - type_0 = unpack('!HH', payload) - return type_0[0], type_0[1] - # 1 - SetVLANID. Returns VID and pad - elif action_type == 1: - type_1 = unpack('!HH', payload) - return type_1[0], type_1[1] - # 2 - SetVLANPCP - elif action_type == 2: - type_2 = unpack('!B3s', payload) - return type_2[0], type_2[1] - # 3 - StripVLAN - elif action_type == 3: - pass - # 4 - SetDLSrc - elif action_type == 4: - type_4 = unpack('!6s6s', payload) - return type_4[0], type_4[1] - # 5 - SetDLDst - elif action_type == 5: - type_5 = unpack('!6s6s', payload) - return type_5[0], type_5[1] - # 6 - SetNWSrc - elif action_type == 6: - type_6 = unpack('!L', payload) - return get_ip_from_long(type_6[0]) - # 7 - SetNWDst - elif action_type == 7: - type_7 = unpack('!L', payload) - return get_ip_from_long(type_7[0]) - # 8 - SetNWTos - elif action_type == 8: - type_8 = unpack('!B3s', payload) - return type_8[0], type_8[1] - # 9 - SetTPSrc - elif action_type == 9: - type_9 = unpack('!HH', payload) - return type_9[0], type_9[1] - # a - SetTPDst - elif action_type == int('a', 16): - type_a = unpack('!HH', payload) - return type_a[0], type_a[1] - # b - Enqueue - elif action_type == int('b', 16): - type_b = unpack('!H6sL', payload) - return type_b[0], type_b[1], type_b[2] - # ffff - Vendor - elif action_type == int('ffff', 16): - type_f = unpack('!L', payload) - return type_f[0] - - -def _parse_OFAction(of_xid, packet, start, ofactions=[]): - ''' - Actions - ''' - # Actions: Header = 4 , plus each possible action - # Payload varies: - # 4 for types 0,1,2,6,7,8,9,a,ffff - # 0 for type 3 - # 12 for types 4,5,b - action_header = 4 - while (1): - ofp_action = packet[start:start + action_header] - if len(ofp_action) > 0: - # Get type and length - ofa = unpack('!HH', ofp_action) - ofa_type = ofa[0] - ofa_length = ofa[1] - - start = start + action_header - if ofa_type == 4 or ofa_type == 5 or ofa_type == int('b', 16): - total_length = 12 - ofa_action_payload = packet[start:start + 12] - else: - total_length = 4 - ofa_action_payload = packet[start:start + 4] - - ofa_temp = ofp_prints_v10.print_ofp_action(of_xid, ofa_type, - ofa_length, - ofa_action_payload) - - # Print OVS format - ofactions.append(ofa_temp) - ofactions.append(',') - # Next packet would start at.. - start = start + total_length - else: - break - - return ofactions - - -def parse_FlowMod(packet, h_size, of_xid, print_options): - - ofmatch = _parse_OFMatch(packet, h_size) - ofp_prints_v10.print_ofp_match(of_xid, ofmatch) - - ofbody = _parse_OFBody(packet, h_size) - ofp_prints_v10.print_ofp_body(of_xid, ofbody) - - if ofbody['command'] == 3: - ovs_command = 'del-flows' - else: - ovs_command = 'add-flow' - - # Print OVS - ofactions = [] - ofactions.append("action=") - - # Actions: Header = 4 , plus each possible action - # Payload varies: - # 4 for types 0,1,2,6,7,8,9,a,ffff - # 0 for type 3 - # 12 for types 4,5,b - start = h_size+64 - - ofactions = _parse_OFAction(of_xid, packet, start, ofactions) - - if print_options['ovs'] == 1: - ofp_prints_v10.print_ofp_ovs(print_options, ofmatch, ofactions, - ovs_command, ofbody['priority']) - return 1 - - -# ********************* PortMod **************************** -def parse_PortMod(packet, h_size, of_xid): - # port(16), hw_addr(48), config(32), mask(32), advertise(32), pad(32) - pmod_raw = packet[h_size:h_size+24] - pmod = unpack('!H6sLLLL', pmod_raw) - - config = _parse_phy_config(pmod[2]) - mask = _parse_phy_config(pmod[3]) - advertise = _parse_phy_curr(pmod[4]) - portMod = {'port': pmod[0], 'hw_addr': pmod[1], 'config': config, - 'mask': mask, 'advertise': advertise, 'pad': pmod[5]} - - ofp_prints_v10.print_PortMod(of_xid, portMod) - return 1 - - -# ******************** StatReq **************************** -def parse_StatsReq(packet, h_size, of_xid): - ''' - Process the StatsReq - ''' - # Get type = 16bits - # Get flags = 16bits - of_stat_req = packet[h_size:h_size+4] - ofstat = unpack('!HH', of_stat_req) - stat_type = ofstat[0] - # FLags were not defined yet. Ignoring. - # flags = ofstat[1] - start = h_size+4 - - # 7 Types available - if stat_type == 0: - # Description - # No extra fields - ofp_prints_v10.print_ofp_statReqDesc(of_xid, stat_type) - - elif stat_type == 1 or stat_type == 2: - # Flow(1) or Aggregate(2) - # Fields: match(40), table_id(8), pad(8), out_port(16) - of_match = _parse_OFMatch(packet, start) - # 44 Bytes (40B from Match, 4 from header) - of_stat_req = packet[start+40:start+40+4] - ofstat = unpack('!BBH', of_stat_req) - table_id = ofstat[0] - pad = ofstat[1] - out_port = ofstat[2] - ofp_prints_v10.print_ofp_statReqFlowAggregate(of_xid, stat_type, - of_match, table_id, pad, - out_port) - elif stat_type == 3: - # Table - # No extra fields - ofp_prints_v10.print_ofp_statReqTable(of_xid, stat_type) - - elif stat_type == 4: - # Port - # Fields: port_number(16), pad(48) - of_stat_req = packet[start:start+8] - ofstat = unpack('!H6s', of_stat_req) - port_number = ofstat[0] - pad = ofstat[1] - ofp_prints_v10.print_ofp_statReqPort(of_xid, stat_type, port_number, - pad) - - elif stat_type == 5: - # Queue - # Fields: port_number(16), pad(16), queue_id(32) - of_stat_req = packet[start:start+8] - ofstat = unpack('!HHL', of_stat_req) - port_number = ofstat[0] - pad = ofstat[1] - queue_id = ofstat[2] - ofp_prints_v10.print_ofp_statReqQueue(of_xid, stat_type, port_number, - pad, queue_id) - elif stat_type == 65535: - # Vendor - # Fields: vendor_id(32) + data - of_stat_req = packet[start:start+4] - ofstat = unpack('!L', of_stat_req) - vendor_id = ofstat[0] - ofp_prints_v10.print_ofp_statReqVendor(of_xid, stat_type, vendor_id) - - else: - print ('%s StatReq: Unknown Type: %s' % (of_xid, stat_type)) - return 0 - return 1 - - -# *********************** StatsRes **************************** -# Actions need to be handled -def parse_StatsRes(packet, h_size, of_xid): - # Get type = 16bits - # Get flags = 16bits - of_stat_req = packet[h_size:h_size+4] - ofstat = unpack('!HH', of_stat_req) - stat_type = ofstat[0] - # flags = ofstat[1] - start = h_size+4 - - # 7 Types available - if stat_type == 0: - # Description - # Fields: mfr_desc(2048), hw_desc(2048), sw_desc(2048), serial_num(256), - # dp_desc(2048) - desc_raw = packet[start:start+1056] - desc = unpack('!256s256s256s32s256s', desc_raw) - mfr_desc = desc[0] - hw_desc = desc[1] - sw_desc = desc[2] - serial_num = desc[3] - dp_desc = desc[4] - ofp_prints_v10.print_ofp_statResDesc(of_xid, stat_type, mfr_desc, - hw_desc, sw_desc, serial_num, - dp_desc) - - elif stat_type == 1: - # Flow(1) - # Fields: length(16), table_id(8), pad(8), match(40), duration_sec(32), - # duration_nsec(32), priority(16), idle_timeout(16), hard_timeout(16), - # pad(48), cookie(64), packet_count(64), byte_count(64), actions[] - count = len(packet[h_size:]) - 4 - while (count > 0): - flow_raw = packet[start:start+4] - flow = unpack('!HBB', flow_raw) - res_flow = {'length': flow[0], 'table_id': flow[1], 'pad': flow[2]} - of_match = _parse_OFMatch(packet, start+4) - flow_raw = packet[start+44:start+44+44] - flow = unpack('!LLHHH6sQQQ', flow_raw) - res_flow.update({'duration_sec': flow[0], 'duration_nsec': flow[1], - 'priority': flow[2], 'idle_timeout': flow[3], - 'hard_timeout': flow[4], 'pad2': flow[5], - 'cookie': flow[6], 'packet_count': flow[7], - 'byte_count': flow[8]}) - - ofp_prints_v10.print_ofp_statResFlow(of_xid, stat_type, of_match, - res_flow) - # Process Actions[] - end = res_flow['length'] - (4 + 40 + 44) - actions = packet[start+88:start+88+end] - _parse_OFAction(of_xid, actions, 0) - - count = count - int(res_flow['length']) - start = start + int(res_flow['length']) - - elif stat_type == 2: - # Aggregate(2) - # Fields: packet_count(64), byte_count(64), flow_count(32), pad(32) - flow_raw = packet[start:start+24] - flow = unpack('!QQLL', flow_raw) - res_flow = {'packet_count': flow[0], 'byte_count': flow[1], - 'flow_count': flow[2], 'pad': flow[3]} - ofp_prints_v10.print_ofp_statResAggregate(of_xid, stat_type, res_flow) - - elif stat_type == 3: - # Table - # Fields: table_id(8), pad(24), name(256), wildcards(32), - # max_entries(32), active_count(32), lookup_count(64), - # matched_count(64) - flow_raw = packet[start:start+64] - flow = unpack('!B3s32sLLLQQ', flow_raw) - res_flow = {'table_id': flow[0], 'pad': flow[1], 'name': flow[2], - 'wildcards': flow[3], 'max_entries': flow[4], - 'active_count': flow[5], 'lookup_count': flow[6], - 'matched_count': flow[7]} - ofp_prints_v10.print_ofp_statResTable(of_xid, stat_type, res_flow) - - elif stat_type == 4: - # Port - # Fields: port_number(16), pad(48), rx_packets(64), tx_packets(64), - # rx_bytes(64), tx_bytes(64), rx_dropped(64), tx_dropped(64), - # rx_errors(64), tx_errors(64), rx_frame_err(64), rx_over_err(64), - # rx_crc_err(64), collisions(64) - count = len(packet[h_size:]) - 4 - while (count > 0): - flow_raw = packet[start:start+104] - flow = unpack('!H6sQQQQQQQQQQQQ', flow_raw) - res_flow = {'port_number': flow[0], 'pad': flow[1], - 'rx_packets': flow[2], 'tx_packets': flow[3], - 'rx_bytes': flow[4], 'tx_bytes': flow[5], - 'rx_dropped': flow[6], 'tx_dropped': flow[7], - 'rx_errors': flow[8], 'tx_errors': flow[9], - 'rx_frame_err': flow[10], 'rx_over_err': flow[11], - 'rx_crc_err': flow[12], 'collisions': flow[13]} - ofp_prints_v10.print_ofp_statResPort(of_xid, stat_type, res_flow) - - count = count - 104 - start = start + 104 - - elif stat_type == 5: - # Queue - # Fields: length(16), pad(16), queue_id(32), tx_bytes(64), - # tx_packets(64), tx_errors(64) - count = len(packet[h_size:]) - 4 - while (count > 0): - flow_raw = packet[start:start+32] - flow = unpack('!HHLQQQ', flow_raw) - res_flow = {'length': flow[0], 'pad': flow[1], 'queue_id': flow[2], - 'tx_bytes': flow[3], 'tx_packets': flow[4], - 'tx_errors': flow[5]} - ofp_prints_v10.print_ofp_statResQueue(of_xid, stat_type, res_flow) - count = count - 32 - start = start + 32 - else: - print ('%s StatRes Type: Queue(%s)' % (of_xid, stat_type)) - print ('%s No Queues' % (of_xid)) - - elif stat_type == 65535: - # Vendor - # Fields: vendor_id(32), data(?) - flow_raw = packet[start:start+4] - flow = unpack('!L', flow_raw) - res_flow = {'vendor_id': flow[0]} - ofp_prints_v10.print_ofp_statResVendor(of_xid, stat_type, res_flow) - - start = start + 4 - data = [] - count = len(packet[h_size:]) - while (start < count): - flow_raw = packet[start:start+1] - flow = unpack('!B', flow_raw) - data.append(str(flow[0])) - start = start + 1 - ofp_prints_v10.print_ofp_statResVendorData(of_xid, ''.join(data)) - - else: - print ('%s StatRes: Unknown Type: %s' % (of_xid, stat_type)) - return 0 - return 1 - - -# ********************** BarrierReq *********************** -def parse_BarrierReq(packet, h_size, of_xid): - ofp_prints_v10.print_of_BarrierReq(of_xid) - return 1 - - -# ********************** BarrierRes *********************** -def parse_BarrierRes(packet, h_size, of_xid): - ofp_prints_v10.print_of_BarrierReply(of_xid) - return 1 - - -# ******************* QueueGetConfigReq ******************* -def parse_QueueGetConfigReq(packet, h_size, of_xid): - queue_raw = packet[h_size:h_size+4] - queue = unpack('!HH', queue_raw) - queueConfReq = {'port': queue[0], 'pad': queue[1]} - ofp_prints_v10.print_queueReq(of_xid, queueConfReq) - return 1 - - -# ****************** QueueGetConfigRes ******************** -def parse_QueueGetConfigRes(packet, h_size, of_xid): - queue_raw = packet[h_size:h_size+8] - queue = unpack('!H6s', queue_raw) - queueConfRes = {'port': queue[0], 'pad': queue[1]} - - ofp_prints_v10.print_queueRes(of_xid, queueConfRes) - - start = h_size + 8 - while (packet[start:] > 0): - # Queues - it could be multiple - # queue_id(32), length(16), pad(16) - queue_raw = packet[start:start+8] - queue = unpack('!LHH', queue_raw) - queues = {'queue_id': queue[0], 'length': queue[1], 'pad': queue[2]} - ofp_prints_v10.print_queues(of_xid, queues) - - q_start = start + 8 - - # Look of properties - # property(16), length(16), pad(32), rate(16), pad(48) - properties = packet[q_start:q_start+queues['length']-8] - - while (len(properties[q_start:]) > 0): - prop_raw = packet[q_start:q_start+8] - prop = unpack('!HHLH6s', prop_raw) - properties = {'type': prop[0], 'length': prop[1], - 'pad': prop[2], 'rate': prop[3], 'pad2': prop[4]} - ofp_prints_v10.print_queueRes_properties(of_xid, properties) - - start = start + queues['length'] - - return 1 diff --git a/ofp_prints_v10.py b/ofp_prints_v10.py deleted file mode 100644 index 3d58094..0000000 --- a/ofp_prints_v10.py +++ /dev/null @@ -1,590 +0,0 @@ -from termcolor import colored -import ofp_dissector_v10 -from ofp_parser_v10 import get_action, get_ip_from_long -import ofp_cli # NO_COLOR variable -import ofp_fsfw_v10 - - -def red(string): - if ofp_cli.NO_COLOR is True: - return string - return colored(string, 'red') - - -def green(string): - if ofp_cli.NO_COLOR is True: - return string - return colored(string, 'green') - - -def blue(string): - if ofp_cli.NO_COLOR is True: - return string - return colored(string, 'blue') - - -def yellow(string): - if ofp_cli.NO_COLOR is True: - return string - return colored(string, 'yellow') - - -def cyan(string): - if ofp_cli.NO_COLOR is True: - return string - return colored(string, 'cyan') - - -def eth_addr(a): - mac = "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x" % (ord(a[0]), ord(a[1]), ord(a[2]), - ord(a[3]), ord(a[4]), ord(a[5])) - return mac - - -def datapath_id(a): - dpid = "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x:%.2x:%.2x" % (ord(a[0]), ord(a[1]), - ord(a[2]), ord(a[3]), - ord(a[4]), ord(a[5]), - ord(a[6]), ord(a[7])) - return dpid - - -def print_headers(print_options, date, getlen, caplen, eth, ip, tcp): - if print_options['min'] == 1: - print_minimal(date, getlen, ip, tcp) - else: - print_layer1(date, getlen, caplen) - print_layer2(eth) - print_layer3(ip) - print_tcp(tcp) - - -def print_minimal(date, getlen, ip, tcp): - string = '%s %s:%s -> %s:%s Size: %s Bytes' - - source = ofp_fsfw_v10.get_ip_name(ip['s_addr'], tcp['source_port']) - dest = ofp_fsfw_v10.get_ip_name(ip['d_addr'], tcp['dest_port']) - - print string % (date, cyan(source), cyan(tcp['source_port']), - cyan(dest), cyan(tcp['dest_port']), getlen) - - -def print_layer1(date, getlen, caplen): - print ('%s: captured %d bytes, truncated to %d bytes' % - (date, getlen, caplen)) - - -def print_layer2(eth): - print ('Ethernet: Destination MAC: %s Source MAC: %s Protocol: %s' % - (eth_addr(eth['dst_mac']), eth_addr(eth['src_mac']), - red(hex(eth['protocol'])))) - - -def print_vlan(vlan): - print ('Prio: %s CFI: %s VID: %s' % - (vlan['prio'], vlan['cfi'], red(vlan['vid']))) - - -def print_arp(arp): - print ('ARP: Hardware Type: %s Protocol Type: %s ' - 'HW Length: %s Prot Length: %s Opcode: %s ' - '\nARP: Source MAC: %s Source IP: %s Destination MAC: %s ' - 'Destination IP: %s' - % (arp['hw_type'], arp['prot_type'], arp['hw_len'], arp['prot_len'], - arp['opcode'], - eth_addr(arp['src_mac']), get_ip_from_long(arp['src_ip']), - eth_addr(arp['dst_mac']), get_ip_from_long(arp['dst_ip']))) - - -def print_layer3(ip): - print (('IP Version: %d IP Header Length: %d TTL: %d Protocol: %d ' - 'Source Address: %s Destination Address: %s') % - (ip['version'], (ip['ihl'] * 4), ip['ttl'], ip['protocol'], - blue(ip['s_addr']), blue(ip['d_addr']))) - - -def print_tcp(tcp): - print ('TCP Source Port: %s Dest Port: %s Sequence Number: %s ' - 'Acknowledgement: %s TCP header length: %s Flags: (CWR: %s ' - 'ECE: %s URG: %s ACK: %s PSH: %s RST: %s SYN: %s FYN: %s' % - (tcp['source_port'], tcp['dest_port'], tcp['sequence'], - tcp['acknowledgement'], (tcp['length']), tcp['flag_cwr'], - tcp['flag_ece'], tcp['flag_urg'], tcp['flag_ack'], tcp['flag_psh'], - tcp['flag_rst'], tcp['flag_syn'], tcp['flag_fyn'])) - - -def print_openflow_header(of): - version = ofp_dissector_v10.get_ofp_version(of['version']) - name_version = '%s(%s)' % (version, of['version']) - if version == '1.0': - name = ofp_dissector_v10.get_ofp_type(of['type']) - name_type = '%s(%s)' % (name, of['type']) - else: - name_type = '%s' % (of['type']) - - print ('OpenFlow Version: %s Type: %s Length: %s XID: %s' % - (name_version, yellow(name_type), of['length'], red(of['xid']))) - - -def print_of_hello(of_xid): - print '%s OpenFlow Hello' % of_xid - - -def print_of_error(of_xid, nameCode, typeCode): - print ('%s OpenFlow Error - Type: %s Code: %s' % - (of_xid, red(nameCode), red(typeCode))) - - -def print_of_feature_req(of_xid): - print '%s OpenFlow Feature Request' % of_xid - - -def print_of_getconfig_req(of_xid): - print '%s OpenFlow GetConfig Request' % of_xid - - -def print_of_feature_res(of_xid, f_res): - print '%s OpenFlow Feature Reply' % of_xid - dpid = datapath_id(f_res['datapath_id']) - print ('%s FeatureRes - datapath_id: %s n_buffers: %s n_tbls: %s, pad: %s' - % (of_xid, green(dpid), f_res['n_buffers'], f_res['n_tbls'], - f_res['pad'])) - - -def print_of_feature_res_caps_and_actions(of_xid, caps, actions): - print ('%s FeatureRes - Capabilities:' % of_xid), - for i in caps: - print ofp_dissector_v10.get_feature_res_capabilities(i), - print - print ('%s FeatureRes - Actions:' % of_xid), - for i in actions: - print ofp_dissector_v10.get_feature_res_actions(i), - print - - -def _dont_print_0(printed): - if printed is False: - print '0', - return False - - -def print_of_feature_res_ports(of_xid, ports): - print ('%s FeatureRes - port_id: %s hw_addr: %s name: %s' % (of_xid, - green(ports['port_id']), green(ports['hw_addr']), - green(ports['name']))) - print ('%s FeatureRes - config:' % of_xid), - printed = False - for i in ports['config']: - print ofp_dissector_v10.get_phy_config(i), - printed = True - else: - printed = _dont_print_0(printed) - print - print ('%s FeatureRes - state:' % of_xid), - for i in ports['state']: - print ofp_dissector_v10.get_phy_state(i), - printed = True - else: - printed = _dont_print_0(printed) - print - print ('%s FeatureRes - curr:' % of_xid), - for i in ports['curr']: - print ofp_dissector_v10.get_phy_feature(i), - printed = True - else: - printed = _dont_print_0(printed) - print - print ('%s FeatureRes - advertised:' % of_xid), - for i in ports['advertised']: - print ofp_dissector_v10.get_phy_feature(i), - printed = True - else: - printed = _dont_print_0(printed) - print - print ('%s FeatureRes - supported:' % of_xid), - for i in ports['supported']: - print ofp_dissector_v10.get_phy_feature(i), - printed = True - else: - printed = _dont_print_0(printed) - print - print ('%s FeatureRes - peer:' % of_xid), - for i in ports['peer']: - print ofp_dissector_v10.get_phy_feature(i), - printed = True - else: - printed = _dont_print_0(printed) - print - - -def print_ofp_match(xid, ofmatch): - if xid == '': - print 'OpenFlow Match -', - else: - print ('%s OpenFlow Match -' % (xid)), - for K in ofmatch: - print ("%s: %s" % (K, green(ofmatch[K]))), - print - - -def print_ofp_body(xid, ofbody): - string = ('%s OpenFlow Body - Cookie: %s Command: %s Idle/Hard Timeouts: ' - '%s/%s Priority: %s Buffer ID: %s Out Port: %s Flags: %s') - command = green(ofp_dissector_v10.get_ofp_command(ofbody['command'])) - flags = green(ofp_dissector_v10.get_ofp_flags(ofbody['flags'])) - - print string % (xid, ofbody['cookie'], command, ofbody['idle_timeout'], - ofbody['hard_timeout'], ofbody['priority'], - ofbody['buffer_id'], ofbody['out_port'], flags) - - -def print_ofp_flow_removed(xid, ofrem): - string = ('%s OpenFlow Body - Cookie: %s Priority: %s Reason: %s Pad: %s ' - 'Duration Secs/NSecs: %s/%s Idle Timeout: %s Pad2/Pad3: %s/%s' - ' Packet Count: %s Byte Count: %s') - - print string % (xid, ofrem['cookie'], ofrem['priority'], - red(ofrem['reason']), ofrem['pad'], ofrem['duration_sec'], - ofrem['duration_nsec'], ofrem['idle_timeout'], - ofrem['pad2'], ofrem['pad3'], ofrem['packet_count'], - ofrem['byte_count']) - - -def print_ofp_action(xid, action_type, length, payload): - if action_type == 0: - port, max_len = get_action(action_type, length, payload) - - port = ofp_dissector_v10.get_phy_port_id(port) - print ('%s OpenFlow Action - Type: %s Length: %s Port: %s ' - 'Max Length: %s' % - (xid, green('OUTPUT'), length, green(port), max_len)) - return 'output:' + port - - elif action_type == 1: - vlan, pad = get_action(action_type, length, payload) - print ('%s OpenFlow Action - Type: %s Length: %s VLAN ID: %s Pad: %s' % - (xid, green('SetVLANID'), length, green(str(vlan)), pad)) - return 'mod_vlan_vid:' + str(vlan) - - elif action_type == 2: - vlan_pc, pad = get_action(action_type, length, payload) - print ('%s OpenFlow Action - Type: %s Length: %s VLAN PCP: %s Pad: %s' % - (xid, green('SetVLANPCP'), length, green(str(vlan_pc)), pad)) - return 'mod_vlan_pcp:' + str(vlan_pc) - - elif action_type == 3: - print ('%s OpenFlow Action - Type: %s Length: %s' % - (xid, green('StripVLAN'), length)) - return 'strip_vlan' - - elif action_type == 4: - setDLSrc, pad = get_action(action_type, length, payload) - print ('%s OpenFlow Action - Type: %s Length: %s SetDLSrc: %s Pad: %s' % - (xid, green('SetDLSrc'), length, green(str(eth_addr(setDLSrc))), - pad)) - return 'mod_dl_src:' + str(eth_addr(setDLSrc)) - - elif action_type == 5: - setDLDst, pad = get_action(action_type, length, payload) - print ('%s OpenFlow Action - Type: %s Length: %s SetDLDst: %s Pad: %s' % - (xid, green('SetDLDst'), length, green(str(eth_addr(setDLDst))), - pad)) - return 'mod_dl_dst:' + str(eth_addr(setDLDst)) - - elif action_type == 6: - nw_addr = get_action(action_type, length, payload) - print ('%s OpenFlow Action - Type: %s Length: %s SetNWSrc: %s' % - (xid, green('SetNWSrc'), length, green(str(nw_addr)))) - return 'mod_nw_src:' + str(nw_addr) - - elif action_type == 7: - nw_addr = get_action(action_type, length, payload) - print ('%s OpenFlow Action - Type: %s Length: %s SetNWDst: %s' % - (xid, green('SetNWDst'), length, green(str(nw_addr)))) - return 'mod_nw_src:' + str(nw_addr) - - elif action_type == 8: - nw_tos, pad = get_action(action_type, length, payload) - print ('%s OpenFlow Action - Type: %s Length: %s SetNWTos: %s Pad: %s' % - (xid, green('SetNWTos'), length, green(str(nw_tos)), pad)) - return 'mod_nw_tos:' + str(nw_tos) - - elif action_type == 9: - port, pad = get_action(action_type, length, payload) - print ('%s OpenFlow Action - Type: %s Length: %s SetTPSrc: %s Pad: %s' % - (xid, green('SetTPSrc'), length, green(str(port)), pad)) - return 'mod_tp_src:' + str(port) - - elif action_type == int('a', 16): - port, pad = get_action(action_type, length, payload) - print ('%s OpenFlow Action - Type: %s Length: %s SetTPDst: %s Pad: %s' % - (xid, green('SetTPDst'), length, green(str(port)), pad)) - return 'mod_tp_dst:' + str(port) - - elif action_type == int('b', 16): - port, pad, queue_id = get_action(action_type, length, payload) - print (('%s OpenFlow Action - Type: %s Length: %s Enqueue: %s Pad: %s' - ' Queue: %s') % - (xid, green('Enqueue'), length, green(str(port)), pad, - green(str(queue_id)))) - return 'set_queue:' + str(queue_id) - - elif action_type == int('ffff', 16): - vendor = get_action(action_type, length, payload) - print ('%s OpenFlow Action - Type: %s Length: %s Vendor: %s' % - (xid, green('VENDOR'), length, green(str(vendor)))) - return 'VendorType' - - else: - return 'Error' - - -def print_ofp_ovs(print_options, ofmatch, ofactions, ovs_command, prio): - - ''' - If -o or --print-ovs is provided by user, print a ovs-ofctl add-dump - ''' - switch_ip = print_options['device_ip'] - switch_port = print_options['device_port'] - - ofm = [] - - for K in ofmatch: - if K != 'wildcards': - value = "%s=%s," % (K, ofmatch[K]) - ofm.append(value) - - matches = ''.join(ofm) - actions = ''.join(ofactions) - - print ('ovs-ofctl %s tcp:%s:%s "priority=%s %s %s"' % - (ovs_command, switch_ip, switch_port, prio, matches, - (actions if ovs_command != 'del-flows' else ''))) - return - - -def _print_portMod_config_mask(of_xid, array, name): - print ('%s PortMod %s:' % (of_xid, name)), - printed = False - for i in array[name]: - print ofp_dissector_v10.get_phy_config(i), - printed = True - else: - printed = _dont_print_0(printed) - print - - -def print_PortMod(of_xid, portMod): - print ('%s PortMod Port: %s HW Addr %s Pad: %s' % - (of_xid, portMod['port'], eth_addr(portMod['hw_addr']), - portMod['pad'])) - _print_portMod_config_mask(of_xid, portMod, 'config') - _print_portMod_config_mask(of_xid, portMod, 'mask') - _print_portMod_config_mask(of_xid, portMod, 'advertise') - - -def print_of_BarrierReq(of_xid): - print '%s OpenFlow Barrier Request' % of_xid - - -def print_of_BarrierReply(of_xid): - print '%s OpenFlow Barrier Reply' % of_xid - - -def print_of_vendor(of_vendor, of_xid): - vendor = ofp_dissector_v10.get_ofp_vendor(of_vendor) - print ('%s OpenFlow Vendor: %s' % (of_xid, vendor)) - - -def print_ofp_statReqDesc(of_xid, stat_type): - print ('%s StatReq Type: Description(%s)' % (of_xid, stat_type)) - - -def print_ofp_statReqFlowAggregate(of_xid, stat_type, of_match, table_id, pad, - out_port): - if stat_type == 1: - type_name = 'Flow' - else: - type_name = 'Aggregate' - - print ('%s StatReq Type: %s(%s)' % (of_xid, type_name, stat_type)) - print_ofp_match(of_xid, of_match) - print ('%s StatReq Table_id: %s Pad: %d Out_Port: %s' % (of_xid, table_id, - pad, out_port)) - - -def print_ofp_statReqTable(of_xid, stat_type): - print ('%s StatReq Type: Table(%s)' % (of_xid, stat_type)) - - -def print_ofp_statReqPort(of_xid, stat_type, port_number, pad): - print ('%s StatReq Type: Port(%s): Port_Number: %s Pad: %s' % (of_xid, - stat_type, green(port_number), pad)) - - -def print_ofp_statReqQueue(of_xid, stat_type, port_number, pad, queue_id): - print ('%s StatReq Type: Queue(%s): Port_Number: %s Pad: %s Queue_id: %s' % - (of_xid, stat_type, green(port_number), pad, queue_id)) - - -def print_ofp_statReqVendor(of_xid, stat_type, vendor_id): - print ('%s StatReq Type: Vendor(%s): Vendor_ID: %s' % (of_xid, stat_type, - vendor_id)) - - -def print_ofp_statResDesc(of_xid, stat_type, mfr_desc, hw_desc, sw_desc, - serial_num, dp_desc): - print ('%s StatRes Type: Description(%s)' % (of_xid, stat_type)) - print ('%s StatRes mfr_desc: %s' % (of_xid, mfr_desc)) - print ('%s StatRes hw_desc: %s' % (of_xid, hw_desc)) - print ('%s StatRes sw_desc: %s' % (of_xid, sw_desc)) - print ('%s StatRes serial_num: %s' % (of_xid, serial_num)) - print ('%s StatRes dp_desc: %s' % (of_xid, dp_desc)) - - -def print_ofp_statResFlow(of_xid, stat_type, match, res_flow): - print ('%s StatRes Type: Flow(%s)' % (of_xid, stat_type)) - print ('%s StatRes Length: %s Table_id: %s Pad: %s ' % - (of_xid, res_flow['length'], res_flow['table_id'], res_flow['pad'])) - print ('%s StatRes' % of_xid), - print_ofp_match('', match) - print ('%s StatRes duration_sec: %s, duration_nsec: %s, priority: %s,' - ' idle_timeout: %s, hard_timeout: %s, pad: %s, cookie: %s,' - ' packet_count: %s, byte_count: %s' % - (of_xid, res_flow['duration_sec'], res_flow['duration_nsec'], - res_flow['priority'], res_flow['idle_timeout'], - res_flow['hard_timeout'], res_flow['pad'], - res_flow['cookie'], - res_flow['packet_count'], res_flow['byte_count'])) - - -def print_ofp_statResAggregate(of_xid, stat_type, res_flow): - print ('%s StatRes Type: Aggregate(%s)' % (of_xid, stat_type)) - print ('%s StatRes packet_count: %s, byte_count: %s flow_count: %s ' - 'pad: %s' % - (of_xid, res_flow['packet_count'], res_flow['byte_count'], - res_flow['flow_count'], res_flow['pad'])) - - -def print_ofp_statResTable(of_xid, stat_type, res_flow): - print ('%s StatRes Type: Table(%s)' % (of_xid, stat_type)) - print ('%s StatRes table_id: %s, pad: %s, name: "%s", wildcards: %s, ' - 'max_entries: %s, active_count: %s, lookup_count: %s, ' - 'matched_count: %s' % - (of_xid, res_flow['table_id'], res_flow['pad'], - res_flow['name'], hex(res_flow['wildcards']), - res_flow['max_entries'], res_flow['active_count'], - res_flow['lookup_count'], res_flow['matched_count'])) - - -def print_ofp_statResPort(of_xid, stat_type, res_flow): - print ('%s StatRes Type: Port(%s)' % (of_xid, stat_type)) - print ('%s StatRes port_no: %s rx_packets: %s rx_bytes: %s rx_errors: %s' - ' rx_crc_err: %s rx_dropped: %s rx_over_err: %s rx_frame_err: %s\n' - '%s StatRes port_no: %s tx_packets: %s tx_bytes: %s tx_errors: %s' - ' tx_dropped: %s collisions: %s pad: %s' % - (of_xid, red(res_flow['port_number']), res_flow['rx_packets'], - res_flow['rx_bytes'], res_flow['rx_errors'], res_flow['rx_crc_err'], - res_flow['rx_dropped'], res_flow['rx_over_err'], - res_flow['rx_frame_err'], of_xid, red(res_flow['port_number']), - res_flow['tx_packets'], res_flow['tx_bytes'], res_flow['tx_errors'], - res_flow['tx_dropped'], res_flow['collisions'], res_flow['pad'])) - - -def print_ofp_statResQueue(of_xid, stat_type, res_flow): - print ('%s StatRes Type: Queue(%s)' % (of_xid, stat_type)) - print ('%s StatRes queue_id: %s length: %s pad: %s' - ' tx_bytes: %s tx_packets: %s tx_errors: %s' % - (of_xid, res_flow['queue_id'], res_flow['length'], res_flow['pad'], - res_flow['tx_bytes'], res_flow['tx_packets'], - res_flow['tx_errors'])) - - -def print_ofp_statResVendor(of_xid, stat_type, res_flow): - print ('%s StatRes Type: Vendor(%s)' % (of_xid, stat_type)) - print ('%s StatRes vendor_id: %s' % (of_xid, res_flow['vendor_id'])) - - -def print_ofp_statResVendorData(of_xid, data): - print '%s StatRes Vendor Data: %s' % (of_xid, data) - - -def print_ofp_getConfigRes(of_xid, flag, miss): - print ('%s OpenFlow GetConfigRes - Flag: %s Miss_send_len: %s' % - (of_xid, flag, miss)) - - -def print_ofp_setConfig(of_xid, flag, miss): - print ('%s OpenFlow SetConfig - Flag: %s Miss_send_len: %s' % - (of_xid, flag, miss)) - - -def print_echoreq(of_xid): - print ('%s OpenFlow Echo Request' % (of_xid)) - - -def print_echores(of_xid): - print ('%s OpenFlow Echo Reply' % (of_xid)) - - -def print_portStatus(of_xid, reason, pad): - print ('%s OpenFlow PortStatus - Reason: %s Pad: %s' % - (of_xid, reason, pad)) - - -def print_packetInOut_layer2(of_xid, eth): - print ('%s' % of_xid), - print_layer2(eth) - - -def print_packetInOut_vlan(of_xid, vlan): - print ('%s Ethernet:' % of_xid), - print_vlan(vlan) - - -def print_ofp_packetIn(of_xid, packetIn): - print ('%s PacketIn: buffer_id: %s total_len: %s in_port: %s reason: %s ' - 'pad: %s' % - (of_xid, hex(packetIn['buffer_id']), packetIn['total_len'], - green(packetIn['in_port']), green(packetIn['reason']), - packetIn['pad'])) - - -def print_packetInOut_lldp(of_xid, lldp): - print ('%s LLDP: Chassis Type(%s) Length: %s SubType: %s ID: %s\n' - '%s LLDP: Port Type(%s) Length: %s SubType: %s ID: %s\n' - '%s LLDP: TTL(%s) Length: %s Seconds: %s\n' - '%s LLDP: END(%s) Length: %s' % - (of_xid, lldp['c_type'], lldp['c_length'], lldp['c_subtype'], - green(lldp['c_id']), of_xid, lldp['p_type'], lldp['p_length'], - lldp['p_subtype'], green(lldp['p_id']), of_xid, lldp['t_type'], - lldp['t_length'], lldp['t_ttl'], of_xid, lldp['e_type'], - lldp['e_length'])) - - -def print_ofp_packetOut(of_xid, packetOut): - print ('%s PacketOut: buffer_id: %s in_port: %s actions_len: %s' % - (of_xid, hex(packetOut['buffer_id']), - green(ofp_dissector_v10.get_phy_port_id(packetOut['in_port'])), - packetOut['actions_len'])) - - -def print_queueReq(of_xid, queueConfReq): - print ('%s QueueGetConfigReq Port: %s Pad: %s' % - (of_xid, queueConfReq['port'], queueConfReq['pad'])) - - -def print_queueRes(of_xid, queueConfRes): - print ('%s QueueGetConfigRes Port: %s Pad: %s' % - (of_xid, queueConfRes['port'], queueConfRes['pad'])) - - -def print_queueRes_queues(of_xid, queues): - print ('%s Queue_ID: %s Length: %s Pad: %s' % - (of_xid, queues['queue_id'], queues['length'], queues['pad'])) - - -def print_queueRes_properties(of_xid, properties): - print ('%s Property: %s Length: %s Pad: %s Rate: %s Pad: %s' % - (of_xid, properties['type'], properties['length'], properties['pad'], - properties['rate'], properties['pad2'])) diff --git a/ofp_sniffer.py b/ofp_sniffer.py index 5d06b1b..ea08044 100755 --- a/ofp_sniffer.py +++ b/ofp_sniffer.py @@ -1,177 +1,171 @@ #!/usr/bin/env python -''' - This code acts as an OpenFlow troubleshoot toolkit: it acts as a sniffer, - a topology validator and as an OpenFlow message checker, to make sure the - ONF standards are being followed. +""" + This code is the AmLight OpenFlow Sniffer - Despite of ONF standards, this code also supports OpenVSwitch/NICIRA - OpenFlow type. + Current version: 0.4 - More info on how to use it: www.sdn.amlight.net - - Current version: 0.2 - - Author: Jeronimo Bezerra - -''' - -import datetime -import pcapy + Author: AmLight Dev Team +""" +import time import sys -from ofp_prints_v10 import print_headers, print_openflow_header -from ofp_parser_v10 import process_ofp_type -from ofp_tcpip_parser import get_ethernet_frame, get_ip_packet, \ - get_tcp_stream, get_openflow_header -import ofp_cli -import ofp_dissector_v10 -import ofp_fsfw_v10 - - -def main(argv): - ''' - This is the main function - ''' - print_options, infilter, sanitizer, dev, capfile = ofp_cli.get_params(argv) - try: - if len(capfile) > 0: - print "Using file %s " % capfile - cap = pcapy.open_offline(capfile) - else: - print "Sniffing device %s" % dev - cap = pcapy.open_live(dev, 65536, 1, 0) - - main_filter = " port 6633 " - cap.setfilter(main_filter + infilter) - - # start sniffing packets - while(1): - (header, packet) = cap.next() - parse_packet(packet, datetime.datetime.now(), - header.getlen(), header.getcaplen(), - print_options, sanitizer) - except KeyboardInterrupt: - print ofp_fsfw_v10.close() - print 'Exiting...' - sys.exit(0) - except Exception as exception: - print exception - return - - -def sanitizer_filters(of_header, date, getlen, caplen, header_size, - eth, ip, tcp, sanitizer): - ''' - If -F was provided, use filters specified - ''' - if (of_header['version'] == -1): - print ('h_size : %s - caplen: %s ' % (header_size, caplen)) - print_headers(1, date, getlen, caplen, eth, ip, tcp) - print 'OpenFlow header not complete. Ignoring packet.' - return 0 - - # OF Versions supported through json file (-F) - name_version = ofp_dissector_v10.get_ofp_version(of_header['version']) - supported_versions = [] - for version in sanitizer['allowed_of_versions']: - supported_versions.append(version) - if name_version not in supported_versions: - return 0 - - # OF Types to be ignored through json file (-F) - rejected_types = sanitizer['allowed_of_versions'][name_version] - if of_header['type'] in rejected_types['rejected_of_types']: - return 0 - - return 1 - - -def parse_packet(packet, date, getlen, caplen, print_options, sanitizer): - ''' - This functions gets the raw packet and dissassembly it. - Only TCP + OpenFlow are analysed. Others are discarted - ''' - eth = get_ethernet_frame(packet) - - # If protocol is no IP(8) returns - if (eth['protocol'] != 8): - return - - ip = get_ip_packet(packet, eth['length']) - - # If protocol is not TCP, returns - if (ip['protocol'] != 6): - return - - header_size = ip['length'] + eth['length'] - tcp = get_tcp_stream(packet, header_size) - - # If TCP flag is not PUSH, return - if (tcp['flag_psh'] != 8): - return - - # Now let's process all OpenFlow packets in the payload - header_size = header_size + tcp['length'] - remaining_bytes = caplen - header_size - - print_header_once = 0 - start = header_size - - # If there is less than 8 bytes, it is because it is fragment. - # There is no support for fragmented packet at this time - while (remaining_bytes >= 8): - of_header = get_openflow_header(packet, start) - - # If -F was passed... - if len(sanitizer['allowed_of_versions']) != 0: - result = sanitizer_filters(of_header, date, getlen, caplen, - header_size, eth, ip, tcp, sanitizer) - if result == 0: - return - - # In case there are multiple flow_mods - remaining_bytes = remaining_bytes - of_header['length'] - - # Starts printing - if print_header_once == 0: - print_headers(print_options, date, getlen, caplen, eth, ip, tcp) - print_header_once = 1 - - # Prints the OpenFlow header, it doesn't matter the OF version - print_openflow_header(of_header) - - print_options['device_ip'] = ip['d_addr'] - print_options['device_port'] = tcp['dest_port'] - - # If OpenFlow version is 1.0 - if of_header['version'] == int('1', 16): - # Process and Print OF body - # OF_Header lenght = 8 - start = start + 8 - this_packet = packet[start:start+of_header['length'] - 8] - if not process_ofp_type(of_header['type'], this_packet, 0, - of_header['xid'], print_options, sanitizer): - print ('%s OpenFlow OFP_Type %s not dissected \n' % - (of_header['xid'], of_header['type'])) - return - else: - # Get next packet - start = start + (of_header['length'] - 8) - # If OpenFlow version is 1.3 - elif of_header['version'] == int('4', 16): - print 'Coming soon...' - return - else: - print 'OpenFlow version %s not supported \n' % of_header['version'] - return - - # Do not process extra data from Hello and Error. - # Maybe in the future. - if (of_header['type'] == 0 or of_header['type'] == 1): - print - return - - print +from libs.core.printing import PrintingOptions +from libs.core.sanitizer import Sanitizer +from libs.core.topo_reader import TopoReader +from libs.core.cli import get_params +from libs.gen.packet import Packet +from apps.oess_fvd import OessFvdTracer +from apps.ofp_stats import OFStats +from apps.ofp_proxies import OFProxy + + +class RunSniffer(object): + """ + The RunSniffer class is the main class for the OpenFlow Sniffer. + This class instantiate all auxiliary classes, captures the packets, + instantiate new OpenFlow messages and triggers all applications. + """ + def __init__(self): + self.printing_options = PrintingOptions() + self.sanitizer = Sanitizer() + self.oft = None + self.stats = None + self.cap = None + self.packet_number = None + self.load_apps = [] + self.packet_count = 1 + self.topo_reader = TopoReader() + self.ofp_proxy = None + self.load_config() + + def load_config(self): + """ + Parses the parameters received and instantiates the + apps requested. + """ + # Get CLI params and call the pcapy loop + self.cap, self.packet_number, \ + self.load_apps, sanitizer, topo_file = get_params(sys.argv) + self.sanitizer.process_filters(sanitizer) + + # Load TopologyReader + self.topo_reader.readfile(topo_file) + + # Start Apps + if 'oess_fvd' in self.load_apps: + self.oft = OessFvdTracer() + + if 'statistics' in self.load_apps: + self.stats = OFStats() + + # Load Proxy + self.ofp_proxy = OFProxy() + + def run(self): + """ + cap.loop continuously capture packets w/ pcapy. For every + captured packet, self.process_packet method is called. + + Exits: + 0 - Normal, reached end of file + 1 - Normal, user requested with CRTL + C + 2 - Error + 3 - Interface or file not found + """ + exit_code = 0 + + # Debug: + # self.cap.loop(-1, self.process_packet) + try: + self.cap.loop(-1, self.process_packet) + + if 'statistics' in self.load_apps: + # If OFP_Stats is running, set a timer + # before closing the app. Useful in cases + # where the ofp_sniffer is reading from a + # pcap file instead of real time. + time.sleep(200) + + except KeyboardInterrupt: + exit_code = 1 + + except Exception as exception: + print('Error on packet %s: %s ' % (self.packet_count, exception)) + exit_code = 2 + + finally: + print('Exiting...') + sys.exit(exit_code) + + def process_packet(self, header, packet): + """ + Every packet captured by cap.loop is then processed here. + If packets are bigger than 62 Bytes, we process them. + If it is 0, means there are no more packets. If it is + something in between, it is a fragment, we ignore for now. + + Args: + header: header of the captured packet + packet: packet captured from file or interface + """ + if len(packet) >= 62 and self.packet_number_defined(): + + # DEBUG: + # print("Packet Number: %s" % self.packet_count) + pkt = Packet(packet, self.packet_count, header) + + if pkt.is_openflow_packet: + valid_result = pkt.process_openflow_messages() + if valid_result: + + # Apps go here: + if isinstance(self.oft, OessFvdTracer): + # FVD_Tracer does not print the packets + self.oft.process_packet(pkt) + + if isinstance(self.ofp_proxy, OFProxy): + # OFP_PROXY associates IP:PORT to DPID + self.ofp_proxy.process_packet(pkt) + + if isinstance(self.stats, OFStats): + # OFStats print the packets + self.stats.process_packet(pkt) + + if not isinstance(self.oft, OessFvdTracer): + # Print Packets + pkt.print_packet() + + del pkt + + elif len(packet) is 0: + sys.exit(0) + + self.packet_count += 1 + + def packet_number_defined(self): + """ + In case user wants to see a specific packet inside a + specific pcap file, provide file name with the specific + packet number + -r file.pcap:packet_number + Returns: + True if packet_count matches + False: if packet_count does not match + """ + if self.packet_number > 0: + return True if self.packet_count == self.packet_number else False + + return True + + +def main(): + """ + Main function. + Instantiates RunSniffer and run it + """ + sniffer = RunSniffer() + sniffer.run() + if __name__ == "__main__": - main(sys.argv) + main() diff --git a/ofp_tcpip_parser.py b/ofp_tcpip_parser.py deleted file mode 100644 index 9a26e8b..0000000 --- a/ofp_tcpip_parser.py +++ /dev/null @@ -1,206 +0,0 @@ -from struct import unpack -import socket - - -def get_ethernet_frame(packet, host_order=0): - # Ethernet Header has 14 bytes - eth_length = 14 - eth_header = packet[:eth_length] - dst_mac, src_mac, prot = unpack('!6s6sH', eth_header) - if not host_order: - eth_protocol = socket.ntohs(prot) - else: - eth_protocol = prot - eth_frame = {'src_mac': src_mac, 'dst_mac': dst_mac, - 'protocol': eth_protocol, 'length': eth_length} - return eth_frame - - -def get_ethernet_vlan(packet): - vlan_length = 2 - vlan_pq = packet[:vlan_length] - vlan_p = unpack('!H', vlan_pq) - prio = vlan_p[0] >> 13 - cfi = (vlan_p[0] & 0x1000) >> 12 - vid = vlan_p[0] & 0xfff - vlan = {'prio': prio, 'cfi': cfi, 'vid': vid} - return vlan - - -def get_next_etype(packet): - etype_length = 2 - et = packet[:etype_length] - return unpack('!H', et)[0] - - -def get_arp(packet): - arp_raw = packet[:28] - arp = unpack('!HHBBH6sL6sL', arp_raw) - src_ip = arp[6] - dst_ip = arp[8] - arp_frame = {'hw_type': arp[0], 'prot_type': arp[1], 'hw_len': arp[2], - 'prot_len': arp[3], 'opcode': arp[4], 'src_mac': arp[5], - 'src_ip': src_ip, 'dst_mac': arp[7], 'dst_ip': dst_ip} - return arp_frame - - -def get_ip_packet(packet, eth_length): - ''' - Returns IP Header fields - ''' - ip_min_len = 20 - ip_header = packet[eth_length:ip_min_len+eth_length] - iph = unpack('!BBHHHBBH4s4s', ip_header) - version_ihl = iph[0] - version = version_ihl >> 4 - ihl = version_ihl & 0xF - iph_length = ihl * 4 - ttl = iph[5] - protocol = iph[6] - s_addr = socket.inet_ntoa(iph[8]) - d_addr = socket.inet_ntoa(iph[9]) - ip_pkt = {'version': version, 'ihl': ihl, 'length': iph_length, - 'ttl': ttl, 'protocol': protocol, 's_addr': s_addr, - 'd_addr': d_addr} - return ip_pkt - - -def get_tcp_stream(packet, header_size): - ''' - Returns TCP Header fields - ''' - tcp_length = 20 - tcp_header = packet[header_size:header_size+tcp_length] - tcph = unpack('!HHLLBBHHH', tcp_header) - source_port = tcph[0] - dest_port = tcph[1] - sequence = tcph[2] - acknowledgement = tcph[3] - doff_reserved = tcph[4] - tcph_length = doff_reserved >> 4 - tcph_length = tcph_length * 4 - flags = tcph[5] # Ignoring Flag NS - flag_cwr = flags & 0x80 - flag_ece = flags & 0x40 - flag_urg = flags & 0x20 - flag_ack = flags & 0x10 - flag_psh = flags & 0x08 - flag_rst = flags & 0x04 - flag_syn = flags & 0x02 - flag_fyn = flags & 0x01 - tcp_stream = {'source_port': source_port, 'dest_port': dest_port, - 'sequence': sequence, 'acknowledgement': acknowledgement, - 'length': tcph_length, 'flag_cwr': flag_cwr, - 'flag_ece': flag_ece, 'flag_urg': flag_urg, - 'flag_ack': flag_ack, 'flag_psh': flag_psh, - 'flag_rst': flag_rst, 'flag_syn': flag_syn, - 'flag_fyn': flag_fyn} - return tcp_stream - - -def get_openflow_header(packet, start): - ''' - Returns OpenFlow header - It is non-version aware - ''' - of_header_length = 8 - of_header = packet[start:of_header_length+start] - try: - ofh = unpack('!BBHL', of_header) - of_version = ofh[0] - of_type = ofh[1] - of_length = ofh[2] - of_xid = ofh[3] - of_header = {'version': of_version, 'type': of_type, - 'length': of_length, 'xid': of_xid} - return of_header - - except Exception as exception: - print exception - of_header['version'] = -1 - return of_header - - -def get_lldp(packet): - # Chassis - # TLV (1) + Length = 2 bytes | Sub-type = 1 Byte - chassis_raw = packet[:3] - chassis = unpack('!HB', chassis_raw) - c_type = chassis[0] >> 9 - if c_type is not 1: - return {} - c_length = chassis[0] & 0xFF - c_subtype = chassis[1] - length = c_length - 1 - # Get C_ID - chassis_raw = packet[3:3+length] - string = '!%ss' % length - chassis = unpack(string, chassis_raw) - c_id = chassis[0] - - start = 3 + length - - # Port - # TLV (2) + Length = 2 Bytes | Port_id = 1 Byte - port_raw = packet[start:start+3] - port = unpack('!HB', port_raw) - p_type = port[0] >> 9 - if p_type is not 2: - return {} - p_length = port[0] & 0xFF - p_subtype = port[1] - length = p_length - 1 - # Get P_ID - port_raw = packet[start+3:start+3+length] - string = '!%ss' % length - port = unpack(string, port_raw) - p_id = port[0] - - start = start + 3 + length - - # TTL - ttl_raw = packet[start:start+4] - ttl = unpack('!HH', ttl_raw) - t_type = ttl[0] >> 9 - if t_type is not 3: - return {} - t_length = ttl[0] & 0xFF - t_ttl = ttl[1] - - start = start + 4 - # Loop to get User-Specific TLVs - while len(packet[start:]) > 0: - next_raw = packet[start:start+2] - nraw = unpack('!H', next_raw) - n_type = nraw[0] >> 9 - n_length = nraw[0] & 0xFF - length = n_length - 4 - if n_type == 0: - break - elif n_type == 127: - # We only want TLV 127, OUI a42305 (ONOS) - # Then we will look for Subtype 2 and get the content - # Skip the OUI - 3 bytes - subtype_raw = packet[start+5:start+6] - subtype = unpack('!B', subtype_raw) - start = start + 6 - if subtype[0] == 2: - content_raw = packet[start:start+length] - string = '!%ss' % length - content = unpack(string, content_raw) - c_id = content[0] - start = start + length - - # END - end_raw = packet[start:start+2] - end = unpack('!H', end_raw) - e_type = end[0] >> 9 - e_length = end[0] & 0xFF - - lldp = {'c_type': c_type, 'c_length': c_length, 'c_subtype': c_subtype, - 'c_id': c_id, 'p_type': p_type, 'p_length': p_length, - 'p_subtype': p_subtype, 'p_id': p_id, 't_type': t_type, - 't_length': t_length, 't_ttl': t_ttl, 'e_type': e_type, - 'e_length': e_length} - - return lldp diff --git a/ofp_vendors_v10.py b/ofp_vendors_v10.py deleted file mode 100644 index 37cf2e1..0000000 --- a/ofp_vendors_v10.py +++ /dev/null @@ -1,9 +0,0 @@ -from struct import unpack - - -def parse_nicira(packet, start, of_xid): - print ('%s OpenFlow Vendor Data: ' % of_xid), - while len(packet[start:start+4]) > 0: - ofv_subtype = unpack('!L', packet[start:start+4]) - print ('%s ' % ofv_subtype[0]), - start = start + 4 diff --git a/termcolor.py b/termcolor.py deleted file mode 100644 index f11b824..0000000 --- a/termcolor.py +++ /dev/null @@ -1,168 +0,0 @@ -# coding: utf-8 -# Copyright (c) 2008-2011 Volvox Development Team -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# Author: Konstantin Lepa - -"""ANSII Color formatting for output in terminal.""" - -from __future__ import print_function -import os - - -__ALL__ = [ 'colored', 'cprint' ] - -VERSION = (1, 1, 0) - -ATTRIBUTES = dict( - list(zip([ - 'bold', - 'dark', - '', - 'underline', - 'blink', - '', - 'reverse', - 'concealed' - ], - list(range(1, 9)) - )) - ) -del ATTRIBUTES[''] - - -HIGHLIGHTS = dict( - list(zip([ - 'on_grey', - 'on_red', - 'on_green', - 'on_yellow', - 'on_blue', - 'on_magenta', - 'on_cyan', - 'on_white' - ], - list(range(40, 48)) - )) - ) - - -COLORS = dict( - list(zip([ - 'grey', - 'red', - 'green', - 'yellow', - 'blue', - 'magenta', - 'cyan', - 'white', - ], - list(range(30, 38)) - )) - ) - - -RESET = '\033[0m' - - -def colored(text, color=None, on_color=None, attrs=None): - """Colorize text. - - Available text colors: - red, green, yellow, blue, magenta, cyan, white. - - Available text highlights: - on_red, on_green, on_yellow, on_blue, on_magenta, on_cyan, on_white. - - Available attributes: - bold, dark, underline, blink, reverse, concealed. - - Example: - colored('Hello, World!', 'red', 'on_grey', ['blue', 'blink']) - colored('Hello, World!', 'green') - """ - if os.getenv('ANSI_COLORS_DISABLED') is None: - fmt_str = '\033[%dm%s' - if color is not None: - text = fmt_str % (COLORS[color], text) - - if on_color is not None: - text = fmt_str % (HIGHLIGHTS[on_color], text) - - if attrs is not None: - for attr in attrs: - text = fmt_str % (ATTRIBUTES[attr], text) - - text += RESET - return text - - -def cprint(text, color=None, on_color=None, attrs=None, **kwargs): - """Print colorize text. - - It accepts arguments of print function. - """ - - print((colored(text, color, on_color, attrs)), **kwargs) - - -if __name__ == '__main__': - print('Current terminal type: %s' % os.getenv('TERM')) - print('Test basic colors:') - cprint('Grey color', 'grey') - cprint('Red color', 'red') - cprint('Green color', 'green') - cprint('Yellow color', 'yellow') - cprint('Blue color', 'blue') - cprint('Magenta color', 'magenta') - cprint('Cyan color', 'cyan') - cprint('White color', 'white') - print(('-' * 78)) - - print('Test highlights:') - cprint('On grey color', on_color='on_grey') - cprint('On red color', on_color='on_red') - cprint('On green color', on_color='on_green') - cprint('On yellow color', on_color='on_yellow') - cprint('On blue color', on_color='on_blue') - cprint('On magenta color', on_color='on_magenta') - cprint('On cyan color', on_color='on_cyan') - cprint('On white color', color='grey', on_color='on_white') - print('-' * 78) - - print('Test attributes:') - cprint('Bold grey color', 'grey', attrs=['bold']) - cprint('Dark red color', 'red', attrs=['dark']) - cprint('Underline green color', 'green', attrs=['underline']) - cprint('Blink yellow color', 'yellow', attrs=['blink']) - cprint('Reversed blue color', 'blue', attrs=['reverse']) - cprint('Concealed Magenta color', 'magenta', attrs=['concealed']) - cprint('Bold underline reverse cyan color', 'cyan', - attrs=['bold', 'underline', 'reverse']) - cprint('Dark blink concealed white color', 'white', - attrs=['dark', 'blink', 'concealed']) - print(('-' * 78)) - - print('Test mixing:') - cprint('Underline red on grey color', 'red', 'on_grey', - ['underline']) - cprint('Reversed green on red color', 'green', 'on_red', ['reverse']) -