Skip to content

Commit

Permalink
Merge pull request #3 from amlight/1.0
Browse files Browse the repository at this point in the history
Releasing OFP_Sniffer version 1.0
  • Loading branch information
jab1982 committed Sep 3, 2017
2 parents bd7c8e6 + 58fedb3 commit aad2e16
Show file tree
Hide file tree
Showing 48 changed files with 4,608 additions and 2,407 deletions.
15 changes: 15 additions & 0 deletions .idea/webServers.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

125 changes: 75 additions & 50 deletions 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 #########################
Expand Down Expand Up @@ -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:

Expand All @@ -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
Expand All @@ -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 <jab@amlight.net>
Questions/Suggestions: AmLight Dev Team <dev@amlight.net>

23 changes: 0 additions & 23 deletions TODO

This file was deleted.

5 changes: 0 additions & 5 deletions __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
Empty file added apps/__init__.py
Empty file.
148 changes: 148 additions & 0 deletions 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'

0 comments on commit aad2e16

Please sign in to comment.