blob: a456f83a1e7d792b40a6e2408684985c5c971554 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright (c) 2013 Big Switch Networks, Inc.
#
# Licensed under the Eclipse Public License, Version 1.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.eclipse.org/legal/epl-v10.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied. See the License for the specific language governing
# permissions and limitations under the License.
#
#
# Copyright (c) 2011,2012 Big Switch Networks, Inc.
# All rights reserved.
#
from sys import stderr
from re import compile, match, search
from optparse import OptionParser
from collections import defaultdict
from subprocess import Popen, PIPE, STDOUT
import socket, fcntl, struct
def get_ipaddr(ifname):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ipaddr = fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15])) # SIOCGIFADDR
return socket.inet_ntoa(ipaddr[20:24])
import urllib, json
def get_aliases(controller='localhost', controller_port=6633, ifname='eth0'):
url = "http://%s/rest/v1/model/switch/" % controller
aliases = {get_ipaddr(ifname)+':%d'%controller_port: 'controller'}
for switch in json.loads(urllib.urlopen(url).read()):
socketaddr = switch.get('socket-address')
if socketaddr:
socketaddr = socketaddr[1:]
alias = switch.get('alias')
if not alias:
alias = switch.get('dpid')
aliases[socketaddr] = alias
return aliases
DEBUG=False
class OpenFlowTracer(object):
# Formats
SUMMARY = "summary"
DETAIL = "detail"
ONELINE = "oneline"
def __init__(self, msg_filters=None, tcpdump_filter='', fmt=SUMMARY, alias=False, printer=None):
self.msg_filters = msg_filters
self.tcpdump_filter = tcpdump_filter
self.detail = (fmt != self.SUMMARY)
self.oneline = (fmt == self.ONELINE)
self.printer = self.prn
if printer:
self.printer = printer
self.pkt = defaultdict(str)
self.reading_detail = False
self.alias = alias
self.aliases = None
if self.alias:
self.aliases = get_aliases()
def run(self):
self.tcpdump_trace(self.tcpdump_open())
return
# filter packets
def filter(self):
msg_type = self.pkt['of-msg']
if not msg_type:
return False
if self.msg_filters:
return any(map(lambda pattern: search(pattern, msg_type), self.msg_filters))
return True
# format packet output
def fmt(self):
pkt = self.pkt
sep = '\n '
if self.oneline: sep = ', '
src = '%s:%s' % (pkt['src-ip'], pkt['src-port'])
dst = '%s:%s' % (pkt['dst-ip'], pkt['dst-port'])
if self.alias:
if not self.aliases.get(src) or not self.aliases.get(dst):
self.aliases = get_aliases()
src = self.aliases.get(src, src)
dst = self.aliases.get(dst, dst)
ret = '%-18s %-18s [ %s -> %s ]' % (
pkt['timestamp'], pkt['of-msg'], src, dst)
if self.detail and pkt['of-data']:
for data in pkt['of-data'].split(','):
data = data.strip()
if data:
ret += (sep + data)
return ret
def prn(self, x):
print x
def tcpdump_open(self):
tcpdump_cmd = 'sudo /usr/local/sbin/tcpdump -i any \'%s\' %s'
tcpdump_args = '-l -nn -vv'
self.process = Popen(tcpdump_cmd % (self.tcpdump_filter, tcpdump_args),
shell=True, bufsize=1, stdout=PIPE, stderr=STDOUT)
return self.process.stdout
def tcpdump_trace(self, fd):
# tcpdump decoder patterns
empty_line = compile('^$')
tcpdump_line = compile('^tcpdump.*$')
ip_line = compile('^(\d{2}\:\d{2}:\d{2}\.\d+) IP .*$')
tcp_line = compile('^\s*(\d+\.\d+\.\d+\.\d+)\.(\d+) \> (\d+\.\d+\.\d+\.\d+)\.(\d+)\:.*length \d+(.*)$')
of_hdr = compile('^([^ ]*) \(xid\=([^\)]*)\)\:(.*)$')
of_line = compile('^(.*in_port\=.*)$')
if_cont_line = compile('^\s+current\:')
inline_err = compile('^\s*\(\*\*\*.*$')
pkt_count = compile('^\d+ packets ')
# helper fn
def of_data(pkt, data):
if pkt['of-msg'] == 'packet_out':
data = data.replace(' ', ',')
pkt['of-data'] += (',' + data)
# decode packet data
line = fd.readline()
while line:
try:
line = line.strip()
pkt = self.pkt
if empty_line.match(line):
pass
elif pkt_count.match(line):
print>>stderr, line
elif tcpdump_line.match(line):
pass
elif ip_line.match(line):
if self.filter(): self.printer(self.fmt())
self.pkt = defaultdict(str)
self.reading_detail = False
pkt = self.pkt
pkt['timestamp'] = ip_line.match(line).groups()[0]
elif tcp_line.match(line):
m = tcp_line.match(line).groups()
pkt['src-ip'] = m[0]
pkt['src-port'] = m[1]
pkt['dst-ip'] = m[2]
pkt['dst-port'] = m[3]
if of_hdr.match(m[4]):
self.reading_detail = True
m = of_hdr.match(m[4]).groups()
pkt['of-msg'] = m[0].strip()
pkt['of-xid'] = m[1]
data = m[2]
if not inline_err.match(m[2]):
of_data(pkt, data)
else:
if DEBUG:
print>>stderr, 'Inline Error', data
elif of_line.match(line):
of_data(pkt, of_line.match(line).groups()[0])
elif if_cont_line.match(line):
line = line.replace(',', '; ')
of_data(pkt, '; '+line)
elif self.reading_detail:
line = line.replace(',', '; ')
of_data(pkt, line)
else:
if DEBUG:
print>>stderr, 'Unexpected: Line', line
line = fd.readline()
except KeyboardInterrupt:
print
if self.process:
self.process.terminate()
self.process = None
else:
break
def main():
usage = "usage: %prog [--detail] [--oneline] [--filter <tcpdump-filter>] [<msg-type> ...]"
optparser = OptionParser(usage=usage)
optparser.add_option("-f", "--filter", dest="tcpdump_filter",
action="store", type="string", default='((tcp) and (port 6633))',
help="tcpdump filter rule", metavar="FILTER")
optparser.add_option("-d", "--detail", dest="detail",
action="store_true", default=False,
help="print message detail")
optparser.add_option("-o", "--oneline", dest="oneline",
action="store_true", default=False,
help="print message detail on one line")
optparser.add_option("-a", "--alias", dest="alias",
action="store_true", default=False,
help="query controller to resolve address to switch-dpid/alias")
(options, msg_filters) = optparser.parse_args()
try:
print 'Starting openflow trace, use ^C to quit'
fmt = OpenFlowTracer.SUMMARY
if options.detail: fmt = OpenFlowTracer.DETAIL
if options.oneline: fmt = OpenFlowTracer.ONELINE
tracer = OpenFlowTracer(msg_filters, options.tcpdump_filter, fmt=fmt, alias=options.alias)
tracer.run()
except KeyboardInterrupt:
pass
if __name__ == '__main__':
main()