| 1 | # vim: set encoding=utf-8 : |
|---|
| 2 | |
|---|
| 3 | # Copyright (C) 2009 Adriano Monteiro Marques. |
|---|
| 4 | # |
|---|
| 5 | # Author: Joao Paulo de Souza Medeiros <ignotus@umitproject.org> |
|---|
| 6 | # |
|---|
| 7 | # This program is free software; you can redistribute it and/or modify |
|---|
| 8 | # it under the terms of the GNU General Public License as published by |
|---|
| 9 | # the Free Software Foundation; either version 2 of the License, or |
|---|
| 10 | # (at your option) any later version. |
|---|
| 11 | # |
|---|
| 12 | # This program is distributed in the hope that it will be useful, |
|---|
| 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 15 | # GNU General Public License for more details. |
|---|
| 16 | # |
|---|
| 17 | # You should have received a copy of the GNU General Public License |
|---|
| 18 | # along with this program; if not, write to the Free Software |
|---|
| 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|---|
| 20 | |
|---|
| 21 | """ |
|---|
| 22 | """ |
|---|
| 23 | |
|---|
| 24 | import pcap |
|---|
| 25 | import socket |
|---|
| 26 | import struct |
|---|
| 27 | |
|---|
| 28 | import umpa.sniffing |
|---|
| 29 | |
|---|
| 30 | class Frame(object): |
|---|
| 31 | """ |
|---|
| 32 | """ |
|---|
| 33 | header_size = None # Base header size. |
|---|
| 34 | header_format = None |
|---|
| 35 | |
|---|
| 36 | def __init__(self, buffer): |
|---|
| 37 | """ |
|---|
| 38 | """ |
|---|
| 39 | self._raw = buffer |
|---|
| 40 | |
|---|
| 41 | def __str__(self): |
|---|
| 42 | """ |
|---|
| 43 | """ |
|---|
| 44 | s = ['+ Frame'] |
|---|
| 45 | raw = struct.unpack('B' * len(self._raw), self._raw) |
|---|
| 46 | col = 16 |
|---|
| 47 | |
|---|
| 48 | for i in range(len(raw) / col): |
|---|
| 49 | s.append('| ' + '%.2x ' * col % (raw[i * col: (i + 1) * col])) |
|---|
| 50 | |
|---|
| 51 | rest = len(raw) % col |
|---|
| 52 | i = len(raw) / col |
|---|
| 53 | if rest: |
|---|
| 54 | s.append('| ' + '%.2x ' * rest % (raw[i * col: i * col + rest])) |
|---|
| 55 | |
|---|
| 56 | s[-1] = s[-1].replace('| ', '|_') |
|---|
| 57 | |
|---|
| 58 | return '\n'.join(s) |
|---|
| 59 | |
|---|
| 60 | class SLL(Frame): |
|---|
| 61 | """ |
|---|
| 62 | """ |
|---|
| 63 | # FIXME: seems that SLL protocol allow headers bigger than 16 bytes. So, a |
|---|
| 64 | # complete disassemble is needed to avoid problems. |
|---|
| 65 | header_size = 16 |
|---|
| 66 | header_format = '>HHH8BH' |
|---|
| 67 | |
|---|
| 68 | def __init__(self, buffer): |
|---|
| 69 | """ |
|---|
| 70 | """ |
|---|
| 71 | super(SLL, self).__init__(buffer) |
|---|
| 72 | self.payload = self._raw[self.header_size:] |
|---|
| 73 | self._fields = struct.unpack(self.header_format, |
|---|
| 74 | self._raw[:self.header_size]) |
|---|
| 75 | |
|---|
| 76 | # Assuming structure found in: `libpcap/pcap/sll.h,v 1.3' |
|---|
| 77 | |
|---|
| 78 | self.pkttype = self._fields[0] |
|---|
| 79 | self.hatype = self._fields[1] |
|---|
| 80 | self.halen = self._fields[2] |
|---|
| 81 | self.addr = ':'.join(['%.2x'] * 8) % (self._fields[3:11]) |
|---|
| 82 | self.protocol = self._fields[11] |
|---|
| 83 | |
|---|
| 84 | def __str__(self): |
|---|
| 85 | """ |
|---|
| 86 | """ |
|---|
| 87 | s = ['+ SLL'] |
|---|
| 88 | s.append('| (pkttype 0x%.4x)' % self.pkttype) |
|---|
| 89 | s.append('| (hatype 0x%.4x)' % self.hatype) |
|---|
| 90 | s.append('| (halen 0x%.4x)' % self.halen) |
|---|
| 91 | s.append('| (addr %s)' % self.addr) |
|---|
| 92 | s.append('|_(protocol 0x%.4x)' % self.protocol) |
|---|
| 93 | |
|---|
| 94 | return "\n".join(s) |
|---|
| 95 | |
|---|
| 96 | class Ethernet(Frame): |
|---|
| 97 | """ |
|---|
| 98 | """ |
|---|
| 99 | header_size = 14 |
|---|
| 100 | header_format = '>6B6BH' |
|---|
| 101 | |
|---|
| 102 | def __init__(self, buffer): |
|---|
| 103 | """ |
|---|
| 104 | """ |
|---|
| 105 | super(Ethernet, self).__init__(buffer) |
|---|
| 106 | self.payload = self._raw[self.header_size:] |
|---|
| 107 | self._fields = struct.unpack(self.header_format, |
|---|
| 108 | self._raw[:self.header_size]) |
|---|
| 109 | |
|---|
| 110 | self.dst = ':'.join(['%.2x'] * 6) % (self._fields[0:6]) |
|---|
| 111 | self.src = ':'.join(['%.2x'] * 6) % (self._fields[6:12]) |
|---|
| 112 | self.type = self._fields[12] |
|---|
| 113 | |
|---|
| 114 | def __str__(self): |
|---|
| 115 | """ |
|---|
| 116 | """ |
|---|
| 117 | s = ['+ Ethernet'] |
|---|
| 118 | s.append('| (dst %s)' % self.dst) |
|---|
| 119 | s.append('| (src %s)' % self.src) |
|---|
| 120 | s.append('|_(type 0x%.4x)' % self.type) |
|---|
| 121 | |
|---|
| 122 | return '\n'.join(s) |
|---|
| 123 | |
|---|
| 124 | class IPv4(Frame): |
|---|
| 125 | """ |
|---|
| 126 | """ |
|---|
| 127 | header_size = 20 |
|---|
| 128 | header_format = '>BBHHHBBH4B4B' |
|---|
| 129 | |
|---|
| 130 | def __init__(self, buffer): |
|---|
| 131 | """ |
|---|
| 132 | """ |
|---|
| 133 | super(IPv4, self).__init__(buffer) |
|---|
| 134 | self._fields = struct.unpack(self.header_format, |
|---|
| 135 | self._raw[:self.header_size]) |
|---|
| 136 | |
|---|
| 137 | self.version = self._fields[0] >> 4 |
|---|
| 138 | self.hdr_len = self._fields[0] & 0x0F |
|---|
| 139 | self.tos = self._fields[1] |
|---|
| 140 | self.len = self._fields[2] |
|---|
| 141 | self.id = self._fields[3] |
|---|
| 142 | self.flags = (self._fields[4] & 0xE000) >> 13 |
|---|
| 143 | self.frag_offset = (self._fields[4] & 0x1fff) |
|---|
| 144 | self.ttl = self._fields[5] |
|---|
| 145 | self.proto = self._fields[6] |
|---|
| 146 | self.checksum = self._fields[7] |
|---|
| 147 | self.src = '%d.%d.%d.%d' % (self._fields[8:12]) |
|---|
| 148 | self.dst = '%d.%d.%d.%d' % (self._fields[12:16]) |
|---|
| 149 | |
|---|
| 150 | # FIXME: verify header size |
|---|
| 151 | self.payload = self._raw[self.header_size:] |
|---|
| 152 | |
|---|
| 153 | def __str__(self): |
|---|
| 154 | """ |
|---|
| 155 | """ |
|---|
| 156 | s = ['+ IPv4'] |
|---|
| 157 | s.append('| (version 0x%x)' % self.version) |
|---|
| 158 | s.append('| (hdr_len 0x%x)' % self.hdr_len) |
|---|
| 159 | s.append('| (tos 0x%.2x)' % self.tos) |
|---|
| 160 | s.append('| (len 0x%.4x)' % self.len) |
|---|
| 161 | s.append('| (id 0x%.4x)' % self.id) |
|---|
| 162 | s.append('| (flags 0x%x)' % self.flags) |
|---|
| 163 | s.append('| (frag_offset 0x%.4x)' % self.frag_offset) |
|---|
| 164 | s.append('| (ttl 0x%.2x)' % self.ttl) |
|---|
| 165 | s.append('| (proto 0x%.2x)' % self.proto) |
|---|
| 166 | s.append('| (checksum 0x%.2x)' % self.checksum) |
|---|
| 167 | s.append('| (src %s)' % self.src) |
|---|
| 168 | s.append('|_(dst %s)' % self.dst) |
|---|
| 169 | |
|---|
| 170 | return '\n'.join(s) |
|---|
| 171 | |
|---|
| 172 | class IPv6(Frame): |
|---|
| 173 | """ |
|---|
| 174 | """ |
|---|
| 175 | header_size = 40 |
|---|
| 176 | header_format = '>IHBB8H8H' |
|---|
| 177 | |
|---|
| 178 | def __init__(self, buffer): |
|---|
| 179 | """ |
|---|
| 180 | """ |
|---|
| 181 | super(IPv6, self).__init__(buffer) |
|---|
| 182 | self.payload = self._raw[self.header_size:] |
|---|
| 183 | self._fields = struct.unpack(self.header_format, |
|---|
| 184 | self._raw[:self.header_size]) |
|---|
| 185 | |
|---|
| 186 | self.version = self._fields[0] >> 28 |
|---|
| 187 | self.clas = (self._fields[0] & 0x0FF00000) >> 8 # Should be self.class |
|---|
| 188 | self.flow = (self._fields[0] & 0x000FFFFF) |
|---|
| 189 | self.plen = self._fields[1] |
|---|
| 190 | self.nxt = self._fields[2] |
|---|
| 191 | self.hlim = self._fields[3] |
|---|
| 192 | self.dst = ':'.join(["%.4x"] * 8) % (self._fields[4:12]) |
|---|
| 193 | self.src = ':'.join(["%.4x"] * 8) % (self._fields[12:20]) |
|---|
| 194 | |
|---|
| 195 | def __str__(self): |
|---|
| 196 | """ |
|---|
| 197 | """ |
|---|
| 198 | s = ['+ IPv6'] |
|---|
| 199 | s.append('| (version 0x%x)' % self.version) |
|---|
| 200 | s.append('| (clas 0x%.2x)' % self.clas) |
|---|
| 201 | s.append('| (flow 0x%.3x)' % self.flow) |
|---|
| 202 | s.append('| (plen 0x%.4x)' % self.plen) |
|---|
| 203 | s.append('| (nxt 0x%.2x)' % self.nxt) |
|---|
| 204 | s.append('| (hlim 0x%.2x)' % self.hlim) |
|---|
| 205 | s.append('| (src %s)' % self.src) |
|---|
| 206 | s.append('|_(dst %s)' % self.dst) |
|---|
| 207 | |
|---|
| 208 | return '\n'.join(s) |
|---|
| 209 | |
|---|
| 210 | class TCP(Frame): |
|---|
| 211 | """ |
|---|
| 212 | """ |
|---|
| 213 | header_size = 20 |
|---|
| 214 | header_format = '>HHIIBBHHH' |
|---|
| 215 | |
|---|
| 216 | def __init__(self, buffer): |
|---|
| 217 | """ |
|---|
| 218 | """ |
|---|
| 219 | super(TCP, self).__init__(buffer) |
|---|
| 220 | self._fields = struct.unpack(self.header_format, |
|---|
| 221 | self._raw[:self.header_size]) |
|---|
| 222 | |
|---|
| 223 | self.srcport = self._fields[0] |
|---|
| 224 | self.dstport = self._fields[1] |
|---|
| 225 | self.seq = self._fields[2] |
|---|
| 226 | self.ack = self._fields[3] |
|---|
| 227 | self.hdr_len = self._fields[4] >> 4 |
|---|
| 228 | self.reserved = self._fields[4] & 0x0F |
|---|
| 229 | self.flags = self._fields[5] |
|---|
| 230 | self.window_size = self._fields[6] |
|---|
| 231 | self.checksum = self._fields[7] |
|---|
| 232 | self.urgent_pointer = self._fields[8] |
|---|
| 233 | |
|---|
| 234 | # FIXME: verify header size |
|---|
| 235 | self.payload = self._raw[self.header_size:] |
|---|
| 236 | |
|---|
| 237 | def __str__(self): |
|---|
| 238 | """ |
|---|
| 239 | """ |
|---|
| 240 | s = ['+ TCP'] |
|---|
| 241 | |
|---|
| 242 | s.append('| (srcport %d)' % self.srcport) |
|---|
| 243 | s.append('| (dstport %d)' % self.dstport) |
|---|
| 244 | s.append('| (seq %d)' % self.seq) |
|---|
| 245 | s.append('| (ack %d)' % self.ack) |
|---|
| 246 | s.append('| (hdr_len 0x%x)' % self.hdr_len) |
|---|
| 247 | s.append('| (reserved 0x%x)' % self.reserved) |
|---|
| 248 | s.append('| (flags 0x%.2x)' % self.flags) |
|---|
| 249 | s.append('| (window_size 0x%.4x)' % self.window_size) |
|---|
| 250 | s.append('| (checksum 0x%.4x)' % self.checksum) |
|---|
| 251 | s.append('|_(urgent_pointer 0x%.4x)' % self.urgent_pointer) |
|---|
| 252 | |
|---|
| 253 | return "\n".join(s) |
|---|
| 254 | |
|---|
| 255 | class Packet(object): |
|---|
| 256 | """ |
|---|
| 257 | """ |
|---|
| 258 | def __init__(self, timestamp, buffer, linktype): |
|---|
| 259 | """ |
|---|
| 260 | """ |
|---|
| 261 | self.__timestamp = timestamp |
|---|
| 262 | self.__linktype = linktype |
|---|
| 263 | self.__buffer = buffer |
|---|
| 264 | self.__packet = None |
|---|
| 265 | |
|---|
| 266 | def get_timestamp(self): |
|---|
| 267 | """ |
|---|
| 268 | """ |
|---|
| 269 | return self.__timestamp |
|---|
| 270 | |
|---|
| 271 | def get_packet(self): |
|---|
| 272 | """ |
|---|
| 273 | """ |
|---|
| 274 | return self.__packet |
|---|
| 275 | |
|---|
| 276 | def disassemble(self): |
|---|
| 277 | """ |
|---|
| 278 | """ |
|---|
| 279 | self.__packet = [] |
|---|
| 280 | next_type = None |
|---|
| 281 | |
|---|
| 282 | # Disassembling first frame. |
|---|
| 283 | # There are two options to make this work right: |
|---|
| 284 | # 1. Disassembly the link layer and see what the next protocol on |
|---|
| 285 | # their data fields; |
|---|
| 286 | # 2. Ignore the first `n' bytes of link layer data. Where `n' stands |
|---|
| 287 | # for size of link layer protocol header. |
|---|
| 288 | # The second one can fail when the link layer protocol has a variable |
|---|
| 289 | # header length. So, is not correct do this way. Moreover, we can be |
|---|
| 290 | # sure what is the next protocol without looking at link layer header. |
|---|
| 291 | # For while we will assume that IP is next packet when the link layer |
|---|
| 292 | # disassembling is not implemented |
|---|
| 293 | if self.__linktype == pcap.DLT_EN10MB: |
|---|
| 294 | e = Ethernet(self.__buffer) |
|---|
| 295 | self.__packet.append(e) |
|---|
| 296 | next_type = e.type |
|---|
| 297 | elif self.__linktype == pcap.DLT_LINUX_SLL: |
|---|
| 298 | s = SLL(self.__buffer) |
|---|
| 299 | self.__packet.append(s) |
|---|
| 300 | # Bytes 15 and 16 of SLL says the next protocol. |
|---|
| 301 | next_type = s.protocol |
|---|
| 302 | else: |
|---|
| 303 | # If the link layer type is unknown return all payload as a base |
|---|
| 304 | # frame. |
|---|
| 305 | f = Frame(self.__buffer) |
|---|
| 306 | self.__packet.append(f) |
|---|
| 307 | return self.__packet |
|---|
| 308 | |
|---|
| 309 | # Disassembling second frame. |
|---|
| 310 | # For a while we are using hexadecimal of Ethernet a SLL next protocol |
|---|
| 311 | # fields to identify next frames. Maybe this can be changed for keep |
|---|
| 312 | # compatibility with other link layer protocols. |
|---|
| 313 | if next_type == 0x0800: |
|---|
| 314 | i = IPv4(self.__packet[-1].payload) |
|---|
| 315 | self.__packet.append(i) |
|---|
| 316 | next_type = i.proto |
|---|
| 317 | elif next_type == 0x86DD: |
|---|
| 318 | i = IPv6(self.__packet[-1].payload) |
|---|
| 319 | self.__packet.append(i) |
|---|
| 320 | next_type = i.nxt |
|---|
| 321 | else: |
|---|
| 322 | f = Frame(self.__packet[-1].payload) |
|---|
| 323 | self.__packet.append(f) |
|---|
| 324 | return self.__packet |
|---|
| 325 | |
|---|
| 326 | # Disassembling third frame. |
|---|
| 327 | # For a while we are using IPv4 protocol and IPv6 next header fields to |
|---|
| 328 | # check what the next protocol header. |
|---|
| 329 | if next_type == 0x06: |
|---|
| 330 | t = TCP(self.__packet[-1].payload) |
|---|
| 331 | self.__packet.append(t) |
|---|
| 332 | else: |
|---|
| 333 | f = Frame(self.__packet[-1].payload) |
|---|
| 334 | self.__packet.append(f) |
|---|
| 335 | return self.__packet |
|---|
| 336 | |
|---|
| 337 | if len(self.__packet[-1].payload): |
|---|
| 338 | f = Frame(self.__packet[-1].payload) |
|---|
| 339 | self.__packet.append(f) |
|---|
| 340 | |
|---|
| 341 | return self.__packet |
|---|
| 342 | |
|---|
| 343 | def get_field(self, field): |
|---|
| 344 | """ |
|---|
| 345 | """ |
|---|
| 346 | proto, attr = field.split('.') |
|---|
| 347 | |
|---|
| 348 | for p in self.__packet: |
|---|
| 349 | if proto == 'ether' and type(p) == Ethernet and hasattr(p, attr): |
|---|
| 350 | return getattr(p, attr) |
|---|
| 351 | elif proto == 'ipv4' and type(p) == IPv4 and hasattr(p, attr): |
|---|
| 352 | return getattr(p, attr) |
|---|
| 353 | elif proto == 'ipv6' and type(p) == IPv6 and hasattr(p, attr): |
|---|
| 354 | return getattr(p, attr) |
|---|
| 355 | elif proto == 'tcp' and type(p) == TCP and hasattr(p, attr): |
|---|
| 356 | return getattr(p, attr) |
|---|
| 357 | |
|---|
| 358 | return None |
|---|
| 359 | |
|---|
| 360 | def __str__(self): |
|---|
| 361 | """ |
|---|
| 362 | """ |
|---|
| 363 | s = ['timestamp %f' % self.__timestamp] |
|---|
| 364 | |
|---|
| 365 | for p in self.__packet: |
|---|
| 366 | s.append(p.__str__()) |
|---|
| 367 | |
|---|
| 368 | return '\n'.join(s) |
|---|
| 369 | |
|---|
| 370 | class Device(object): |
|---|
| 371 | """ |
|---|
| 372 | """ |
|---|
| 373 | def __init__(self, name): |
|---|
| 374 | """ |
|---|
| 375 | """ |
|---|
| 376 | self.name = name |
|---|
| 377 | try: |
|---|
| 378 | self.naddr, self.mask = pcap.lookupnet(self.name) |
|---|
| 379 | except: |
|---|
| 380 | self.naddr, self.mask = None, None |
|---|
| 381 | |
|---|
| 382 | class Sniff(object): |
|---|
| 383 | """ |
|---|
| 384 | """ |
|---|
| 385 | def __init__(self): |
|---|
| 386 | """ |
|---|
| 387 | """ |
|---|
| 388 | self.amount = None |
|---|
| 389 | self.filter = '' |
|---|
| 390 | self.fields = [] |
|---|
| 391 | self.packets = [] |
|---|
| 392 | self.devices = {} |
|---|
| 393 | for d in umpa.sniffing.get_available_devices(): |
|---|
| 394 | self.devices[d] = Device(d) |
|---|
| 395 | |
|---|
| 396 | def start(self, device): |
|---|
| 397 | """ |
|---|
| 398 | """ |
|---|
| 399 | capture = pcap.pcap(device) |
|---|
| 400 | capture.setfilter(self.filter) |
|---|
| 401 | number = 0 |
|---|
| 402 | |
|---|
| 403 | for timestamp, packet in capture: |
|---|
| 404 | p = Packet(timestamp, packet, capture.datalink()) |
|---|
| 405 | p.disassemble() |
|---|
| 406 | self.packets.append(p) |
|---|
| 407 | number += 1 |
|---|
| 408 | |
|---|
| 409 | if len(self.fields): |
|---|
| 410 | s = [] |
|---|
| 411 | for f in self.fields: |
|---|
| 412 | s.append(str(p.get_field(f))) |
|---|
| 413 | print '(timestamp %f)' % p.get_timestamp(),', '.join(s) |
|---|
| 414 | else: |
|---|
| 415 | print '\n', p |
|---|
| 416 | |
|---|
| 417 | if number == self.amount: |
|---|
| 418 | break |
|---|