blob: a456f83a1e7d792b40a6e2408684985c5c971554 [file] [log] [blame]
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -08001#!/usr/bin/env python
2#
3# Copyright (c) 2013 Big Switch Networks, Inc.
4#
5# Licensed under the Eclipse Public License, Version 1.0 (the
6# "License"); you may not use this file except in compliance with the
7# License. You may obtain a copy of the License at
8#
9# http://www.eclipse.org/legal/epl-v10.html
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14# implied. See the License for the specific language governing
15# permissions and limitations under the License.
16#
17
18#
19# Copyright (c) 2011,2012 Big Switch Networks, Inc.
20# All rights reserved.
21#
22
23from sys import stderr
24from re import compile, match, search
25from optparse import OptionParser
26from collections import defaultdict
27from subprocess import Popen, PIPE, STDOUT
28
29import socket, fcntl, struct
30def get_ipaddr(ifname):
31 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
32 ipaddr = fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15])) # SIOCGIFADDR
33 return socket.inet_ntoa(ipaddr[20:24])
34
35import urllib, json
36def get_aliases(controller='localhost', controller_port=6633, ifname='eth0'):
37 url = "http://%s/rest/v1/model/switch/" % controller
38 aliases = {get_ipaddr(ifname)+':%d'%controller_port: 'controller'}
39 for switch in json.loads(urllib.urlopen(url).read()):
40 socketaddr = switch.get('socket-address')
41 if socketaddr:
42 socketaddr = socketaddr[1:]
43 alias = switch.get('alias')
44 if not alias:
45 alias = switch.get('dpid')
46 aliases[socketaddr] = alias
47 return aliases
48
49DEBUG=False
50
51class OpenFlowTracer(object):
52 # Formats
53 SUMMARY = "summary"
54 DETAIL = "detail"
55 ONELINE = "oneline"
56
57 def __init__(self, msg_filters=None, tcpdump_filter='', fmt=SUMMARY, alias=False, printer=None):
58 self.msg_filters = msg_filters
59 self.tcpdump_filter = tcpdump_filter
60 self.detail = (fmt != self.SUMMARY)
61 self.oneline = (fmt == self.ONELINE)
62 self.printer = self.prn
63 if printer:
64 self.printer = printer
65
66 self.pkt = defaultdict(str)
67 self.reading_detail = False
68 self.alias = alias
69 self.aliases = None
70 if self.alias:
71 self.aliases = get_aliases()
72
73 def run(self):
74 self.tcpdump_trace(self.tcpdump_open())
75 return
76
77 # filter packets
78 def filter(self):
79 msg_type = self.pkt['of-msg']
80 if not msg_type:
81 return False
82 if self.msg_filters:
83 return any(map(lambda pattern: search(pattern, msg_type), self.msg_filters))
84 return True
85
86 # format packet output
87 def fmt(self):
88 pkt = self.pkt
89 sep = '\n '
90 if self.oneline: sep = ', '
91
92 src = '%s:%s' % (pkt['src-ip'], pkt['src-port'])
93 dst = '%s:%s' % (pkt['dst-ip'], pkt['dst-port'])
94 if self.alias:
95 if not self.aliases.get(src) or not self.aliases.get(dst):
96 self.aliases = get_aliases()
97 src = self.aliases.get(src, src)
98 dst = self.aliases.get(dst, dst)
99
100 ret = '%-18s %-18s [ %s -> %s ]' % (
101 pkt['timestamp'], pkt['of-msg'], src, dst)
102 if self.detail and pkt['of-data']:
103 for data in pkt['of-data'].split(','):
104 data = data.strip()
105 if data:
106 ret += (sep + data)
107 return ret
108
109 def prn(self, x):
110 print x
111
112 def tcpdump_open(self):
113 tcpdump_cmd = 'sudo /usr/local/sbin/tcpdump -i any \'%s\' %s'
114 tcpdump_args = '-l -nn -vv'
115 self.process = Popen(tcpdump_cmd % (self.tcpdump_filter, tcpdump_args),
116 shell=True, bufsize=1, stdout=PIPE, stderr=STDOUT)
117 return self.process.stdout
118
119 def tcpdump_trace(self, fd):
120 # tcpdump decoder patterns
121 empty_line = compile('^$')
122 tcpdump_line = compile('^tcpdump.*$')
123 ip_line = compile('^(\d{2}\:\d{2}:\d{2}\.\d+) IP .*$')
124 tcp_line = compile('^\s*(\d+\.\d+\.\d+\.\d+)\.(\d+) \> (\d+\.\d+\.\d+\.\d+)\.(\d+)\:.*length \d+(.*)$')
125 of_hdr = compile('^([^ ]*) \(xid\=([^\)]*)\)\:(.*)$')
126 of_line = compile('^(.*in_port\=.*)$')
127 if_cont_line = compile('^\s+current\:')
128 inline_err = compile('^\s*\(\*\*\*.*$')
129 pkt_count = compile('^\d+ packets ')
130
131 # helper fn
132 def of_data(pkt, data):
133 if pkt['of-msg'] == 'packet_out':
134 data = data.replace(' ', ',')
135 pkt['of-data'] += (',' + data)
136
137 # decode packet data
138 line = fd.readline()
139 while line:
140 try:
141 line = line.strip()
142 pkt = self.pkt
143 if empty_line.match(line):
144 pass
145 elif pkt_count.match(line):
146 print>>stderr, line
147 elif tcpdump_line.match(line):
148 pass
149 elif ip_line.match(line):
150 if self.filter(): self.printer(self.fmt())
151 self.pkt = defaultdict(str)
152 self.reading_detail = False
153 pkt = self.pkt
154 pkt['timestamp'] = ip_line.match(line).groups()[0]
155 elif tcp_line.match(line):
156 m = tcp_line.match(line).groups()
157 pkt['src-ip'] = m[0]
158 pkt['src-port'] = m[1]
159 pkt['dst-ip'] = m[2]
160 pkt['dst-port'] = m[3]
161 if of_hdr.match(m[4]):
162 self.reading_detail = True
163 m = of_hdr.match(m[4]).groups()
164 pkt['of-msg'] = m[0].strip()
165 pkt['of-xid'] = m[1]
166 data = m[2]
167 if not inline_err.match(m[2]):
168 of_data(pkt, data)
169 else:
170 if DEBUG:
171 print>>stderr, 'Inline Error', data
172 elif of_line.match(line):
173 of_data(pkt, of_line.match(line).groups()[0])
174 elif if_cont_line.match(line):
175 line = line.replace(',', '; ')
176 of_data(pkt, '; '+line)
177 elif self.reading_detail:
178 line = line.replace(',', '; ')
179 of_data(pkt, line)
180 else:
181 if DEBUG:
182 print>>stderr, 'Unexpected: Line', line
183 line = fd.readline()
184 except KeyboardInterrupt:
185 print
186 if self.process:
187 self.process.terminate()
188 self.process = None
189 else:
190 break
191
192def main():
193 usage = "usage: %prog [--detail] [--oneline] [--filter <tcpdump-filter>] [<msg-type> ...]"
194 optparser = OptionParser(usage=usage)
195 optparser.add_option("-f", "--filter", dest="tcpdump_filter",
196 action="store", type="string", default='((tcp) and (port 6633))',
197 help="tcpdump filter rule", metavar="FILTER")
198 optparser.add_option("-d", "--detail", dest="detail",
199 action="store_true", default=False,
200 help="print message detail")
201 optparser.add_option("-o", "--oneline", dest="oneline",
202 action="store_true", default=False,
203 help="print message detail on one line")
204 optparser.add_option("-a", "--alias", dest="alias",
205 action="store_true", default=False,
206 help="query controller to resolve address to switch-dpid/alias")
207 (options, msg_filters) = optparser.parse_args()
208
209 try:
210 print 'Starting openflow trace, use ^C to quit'
211 fmt = OpenFlowTracer.SUMMARY
212 if options.detail: fmt = OpenFlowTracer.DETAIL
213 if options.oneline: fmt = OpenFlowTracer.ONELINE
214 tracer = OpenFlowTracer(msg_filters, options.tcpdump_filter, fmt=fmt, alias=options.alias)
215 tracer.run()
216 except KeyboardInterrupt:
217 pass
218
219if __name__ == '__main__':
220 main()