| 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, size=None): |
|---|
| 37 | """ |
|---|
| 38 | """ |
|---|
| 39 | self._raw = buffer |
|---|
| 40 | |
|---|
| 41 | if size: |
|---|
| 42 | self.header_size = size |
|---|
| 43 | self.payload = self._raw[self.header_size:] |
|---|
| 44 | |
|---|
| 45 | def __str__(self): |
|---|
| 46 | """ |
|---|
| 47 | """ |
|---|
| 48 | s = ["+ Frame"] |
|---|
| 49 | raw = struct.unpack('B' * len(self._raw), self._raw) |
|---|
| 50 | col = 16 |
|---|
| 51 | |
|---|
| 52 | for i in range(len(raw) / col): |
|---|
| 53 | s.append("| " + "%.2x " * col % (raw[i * col: (i + 1) * col])) |
|---|
| 54 | |
|---|
| 55 | rest = len(raw) % col |
|---|
| 56 | i = len(raw) / col |
|---|
| 57 | if rest: |
|---|
| 58 | s.append("| " + "%.2x " * rest % (raw[i * col: i * col + rest])) |
|---|
| 59 | |
|---|
| 60 | s[-1] = s[-1].replace("| ", "|_") |
|---|
| 61 | |
|---|
| 62 | return "\n".join(s) |
|---|
| 63 | |
|---|
| 64 | class Ethernet(Frame): |
|---|
| 65 | """ |
|---|
| 66 | """ |
|---|
| 67 | header_size = 14 |
|---|
| 68 | header_format = '>6B6BH' |
|---|
| 69 | |
|---|
| 70 | def __init__(self, buffer): |
|---|
| 71 | """ |
|---|
| 72 | """ |
|---|
| 73 | super(Ethernet, self).__init__(buffer) |
|---|
| 74 | self.payload = self._raw[self.header_size:] |
|---|
| 75 | self._fields = struct.unpack(self.header_format, |
|---|
| 76 | self._raw[:self.header_size]) |
|---|
| 77 | |
|---|
| 78 | self.dst = "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x" % (self._fields[0:6]) |
|---|
| 79 | self.src = "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x" % (self._fields[6:12]) |
|---|
| 80 | self.type = self._fields[12] |
|---|
| 81 | |
|---|
| 82 | def __str__(self): |
|---|
| 83 | """ |
|---|
| 84 | """ |
|---|
| 85 | s = ["+ Ethernet"] |
|---|
| 86 | s.append("| (dst %s)" % self.dst) |
|---|
| 87 | s.append("| (src %s)" % self.src) |
|---|
| 88 | s.append("|_(type 0x%.4x)" % self.type) |
|---|
| 89 | |
|---|
| 90 | return "\n".join(s) |
|---|
| 91 | |
|---|
| 92 | class IPv4(Frame): |
|---|
| 93 | """ |
|---|
| 94 | """ |
|---|
| 95 | header_size = 20 |
|---|
| 96 | header_format = '>BBHHHBBH4B4B' |
|---|
| 97 | |
|---|
| 98 | def __init__(self, buffer): |
|---|
| 99 | """ |
|---|
| 100 | """ |
|---|
| 101 | super(IPv4, self).__init__(buffer) |
|---|
| 102 | self._fields = struct.unpack(self.header_format, |
|---|
| 103 | self._raw[:self.header_size]) |
|---|
| 104 | |
|---|
| 105 | self.version = self._fields[0] >> 4 |
|---|
| 106 | self.hdr_len = self._fields[0] & 0x0F |
|---|
| 107 | self.tos = self._fields[1] |
|---|
| 108 | self.len = self._fields[2] |
|---|
| 109 | self.id = self._fields[3] |
|---|
| 110 | self.flags = (self._fields[4] & 0xE000) >> 13 |
|---|
| 111 | self.frag_offset = (self._fields[4] & 0x1fff) |
|---|
| 112 | self.ttl = self._fields[5] |
|---|
| 113 | self.proto = self._fields[6] |
|---|
| 114 | self.checksum = self._fields[7] |
|---|
| 115 | self.src = "%d.%d.%d.%d" % (self._fields[8:12]) |
|---|
| 116 | self.dst = "%d.%d.%d.%d" % (self._fields[12:16]) |
|---|
| 117 | |
|---|
| 118 | # FIXME: verify header size |
|---|
| 119 | self.payload = self._raw[self.header_size:] |
|---|
| 120 | |
|---|
| 121 | def __str__(self): |
|---|
| 122 | """ |
|---|
| 123 | """ |
|---|
| 124 | s = ["+ IPv4"] |
|---|
| 125 | s.append("| (version 0x%x)" % self.version) |
|---|
| 126 | s.append("| (hdr_len 0x%x)" % self.hdr_len) |
|---|
| 127 | s.append("| (tos 0x%.2x)" % self.tos) |
|---|
| 128 | s.append("| (len 0x%.4x)" % self.len) |
|---|
| 129 | s.append("| (id 0x%.4x)" % self.id) |
|---|
| 130 | s.append("| (flags 0x%x)" % self.flags) |
|---|
| 131 | s.append("| (frag_offset 0x%.4x)" % self.frag_offset) |
|---|
| 132 | s.append("| (ttl 0x%.2x)" % self.ttl) |
|---|
| 133 | s.append("| (proto 0x%.2x)" % self.proto) |
|---|
| 134 | s.append("| (checksum 0x%.2x)" % self.checksum) |
|---|
| 135 | s.append("| (src %s)" % self.src) |
|---|
| 136 | s.append("|_(dst %s)" % self.dst) |
|---|
| 137 | |
|---|
| 138 | return "\n".join(s) |
|---|
| 139 | |
|---|
| 140 | class IPv6(Frame): |
|---|
| 141 | """ |
|---|
| 142 | """ |
|---|
| 143 | header_size = 40 |
|---|
| 144 | header_format = '>IHBB8H8H' |
|---|
| 145 | |
|---|
| 146 | def __init__(self, buffer): |
|---|
| 147 | """ |
|---|
| 148 | """ |
|---|
| 149 | super(IPv6, self).__init__(buffer) |
|---|
| 150 | self.payload = self._raw[self.header_size:] |
|---|
| 151 | self._fields = struct.unpack(self.header_format, |
|---|
| 152 | self._raw[:self.header_size]) |
|---|
| 153 | |
|---|
| 154 | self.version = self._fields[0] >> 28 |
|---|
| 155 | self.clas = (self._fields[0] & 0x0FF00000) >> 8 # Should be self.class |
|---|
| 156 | self.flow = (self._fields[0] & 0x000FFFFF) |
|---|
| 157 | self.plen = self._fields[1] |
|---|
| 158 | self.nxt = self._fields[2] |
|---|
| 159 | self.hlim = self._fields[3] |
|---|
| 160 | self.dst = ":".join(["%.4x"] * 8) % (self._fields[4:12]) |
|---|
| 161 | self.src = ":".join(["%.4x"] * 8) % (self._fields[12:20]) |
|---|
| 162 | |
|---|
| 163 | def __str__(self): |
|---|
| 164 | """ |
|---|
| 165 | """ |
|---|
| 166 | s = ["+ IPv6"] |
|---|
| 167 | s.append("| (version 0x%x)" % self.version) |
|---|
| 168 | s.append("| (clas 0x%.2x)" % self.clas) |
|---|
| 169 | s.append("| (flow 0x%.3x)" % self.flow) |
|---|
| 170 | s.append("| (plen 0x%.4x)" % self.plen) |
|---|
| 171 | s.append("| (nxt 0x%.2x)" % self.nxt) |
|---|
| 172 | s.append("| (hlim 0x%.2x)" % self.hlim) |
|---|
| 173 | s.append("| (src %s)" % self.src) |
|---|
| 174 | s.append("|_(dst %s)" % self.dst) |
|---|
| 175 | |
|---|
| 176 | return "\n".join(s) |
|---|
| 177 | |
|---|
| 178 | class TCP(Frame): |
|---|
| 179 | """ |
|---|
| 180 | """ |
|---|
| 181 | header_size = 20 |
|---|
| 182 | header_format = '>HHIIBBHHH' |
|---|
| 183 | |
|---|
| 184 | def __init__(self, buffer): |
|---|
| 185 | """ |
|---|
| 186 | """ |
|---|
| 187 | super(TCP, self).__init__(buffer) |
|---|
| 188 | self._fields = struct.unpack(self.header_format, |
|---|
| 189 | self._raw[:self.header_size]) |
|---|
| 190 | |
|---|
| 191 | self.srcport = self._fields[0] |
|---|
| 192 | self.dstport = self._fields[1] |
|---|
| 193 | self.seq = self._fields[2] |
|---|
| 194 | self.ack = self._fields[3] |
|---|
| 195 | self.hdr_len = self._fields[4] >> 4 |
|---|
| 196 | self.reserved = self._fields[4] & 0x0F |
|---|
| 197 | self.flags = self._fields[5] |
|---|
| 198 | self.window_size = self._fields[6] |
|---|
| 199 | self.checksum = self._fields[7] |
|---|
| 200 | self.urgent_pointer = self._fields[8] |
|---|
| 201 | |
|---|
| 202 | # FIXME: verify header size |
|---|
| 203 | self.payload = self._raw[self.header_size:] |
|---|
| 204 | |
|---|
| 205 | def __str__(self): |
|---|
| 206 | """ |
|---|
| 207 | """ |
|---|
| 208 | s = ["+ TCP"] |
|---|
| 209 | |
|---|
| 210 | s.append("| (srcport %d)" % self.srcport) |
|---|
| 211 | s.append("| (dstport %d)" % self.dstport) |
|---|
| 212 | s.append("| (seq %d)" % self.seq) |
|---|
| 213 | s.append("| (ack %d)" % self.ack) |
|---|
| 214 | s.append("| (hdr_len 0x%x)" % self.hdr_len) |
|---|
| 215 | s.append("| (reserved 0x%x)" % self.reserved) |
|---|
| 216 | s.append("| (flags 0x%.2x)" % self.flags) |
|---|
| 217 | s.append("| (window_size 0x%.4x)" % self.window_size) |
|---|
| 218 | s.append("| (checksum 0x%.4x)" % self.checksum) |
|---|
| 219 | s.append("|_(urgent_pointer 0x%.4x)" % self.urgent_pointer) |
|---|
| 220 | |
|---|
| 221 | return "\n".join(s) |
|---|
| 222 | |
|---|
| 223 | class Packet(object): |
|---|
| 224 | """ |
|---|
| 225 | """ |
|---|
| 226 | def __init__(self, timestamp, buffer, linktype): |
|---|
| 227 | """ |
|---|
| 228 | """ |
|---|
| 229 | self.__timestamp = timestamp |
|---|
| 230 | self.__linktype = linktype |
|---|
| 231 | self.__buffer = buffer |
|---|
| 232 | self.__packet = None |
|---|
| 233 | |
|---|
| 234 | def get_packet(self): |
|---|
| 235 | """ |
|---|
| 236 | """ |
|---|
| 237 | return self.__packet |
|---|
| 238 | |
|---|
| 239 | def disassemble(self): |
|---|
| 240 | """ |
|---|
| 241 | """ |
|---|
| 242 | self.__packet = [] |
|---|
| 243 | next_type = None |
|---|
| 244 | |
|---|
| 245 | # Disassembling first frame. |
|---|
| 246 | # There are two options to make this work right: |
|---|
| 247 | # 1. Disassembly the link layer and see what the next protocol on |
|---|
| 248 | # their data fields; |
|---|
| 249 | # 2. Ignore the first `n' bytes of link layer data. Where `n' stands |
|---|
| 250 | # for size of link layer protocol header. |
|---|
| 251 | # The second one can fail when the link layer protocol has a variable |
|---|
| 252 | # header length. So, is not correct do this way. Moreover, we can be |
|---|
| 253 | # sure what is the next protocol without looking at link layer header. |
|---|
| 254 | # For while we will assume that IP is next packet when the link layer |
|---|
| 255 | # disassembling is not implemented |
|---|
| 256 | if self.__linktype == pcap.DLT_EN10MB: |
|---|
| 257 | e = Ethernet(self.__buffer) |
|---|
| 258 | self.__packet.append(e) |
|---|
| 259 | next_type = e.type |
|---|
| 260 | elif self.__linktype == pcap.DLT_LINUX_SLL: |
|---|
| 261 | # FIXME: the SLL protocol allow headers bigger than 16 bytes. So, a |
|---|
| 262 | # complete disassemble is needed to avoid problems. |
|---|
| 263 | f = Frame(self.__buffer, 16) |
|---|
| 264 | self.__packet.append(f) |
|---|
| 265 | # Bytes 15 and 16 of SLL says the next protocol. |
|---|
| 266 | next_type = struct.unpack('>H', f._raw[14:16])[0] |
|---|
| 267 | else: |
|---|
| 268 | # If the link layer type is unknown return all payload as a base |
|---|
| 269 | # frame. |
|---|
| 270 | f = Frame(self.__buffer) |
|---|
| 271 | self.__packet.append(f) |
|---|
| 272 | return self.__packet |
|---|
| 273 | |
|---|
| 274 | # Disassembling second frame. |
|---|
| 275 | # For a while we are using hexadecimal of Ethernet a SLL next protocol |
|---|
| 276 | # fields to identify next frames. Maybe this can be changed for keep |
|---|
| 277 | # compatibility with other link layer protocols. |
|---|
| 278 | if next_type == 0x0800: |
|---|
| 279 | i = IPv4(self.__packet[-1].payload) |
|---|
| 280 | self.__packet.append(i) |
|---|
| 281 | next_type = i.proto |
|---|
| 282 | elif next_type == 0x86DD: |
|---|
| 283 | i = IPv6(self.__packet[-1].payload) |
|---|
| 284 | self.__packet.append(i) |
|---|
| 285 | next_type = i.nxt |
|---|
| 286 | else: |
|---|
| 287 | f = Frame(self.__packet[-1].payload) |
|---|
| 288 | self.__packet.append(f) |
|---|
| 289 | return self.__packet |
|---|
| 290 | |
|---|
| 291 | # Disassembling third frame. |
|---|
| 292 | # For a while we are using IPv4 protocol and IPv6 next header fields to |
|---|
| 293 | # check what the next protocol header. |
|---|
| 294 | if next_type == 0x06: |
|---|
| 295 | t = TCP(self.__packet[-1].payload) |
|---|
| 296 | self.__packet.append(t) |
|---|
| 297 | else: |
|---|
| 298 | f = Frame(self.__packet[-1].payload) |
|---|
| 299 | self.__packet.append(f) |
|---|
| 300 | return self.__packet |
|---|
| 301 | |
|---|
| 302 | if len(self.__packet[-1].payload): |
|---|
| 303 | f = Frame(self.__packet[-1].payload) |
|---|
| 304 | self.__packet.append(f) |
|---|
| 305 | |
|---|
| 306 | return self.__packet |
|---|
| 307 | |
|---|
| 308 | def get_field(self, field): |
|---|
| 309 | """ |
|---|
| 310 | """ |
|---|
| 311 | proto, attr = field.split('.') |
|---|
| 312 | |
|---|
| 313 | for p in self.__packet: |
|---|
| 314 | if proto == 'ether' and type(p) == Ethernet and hasattr(p, attr): |
|---|
| 315 | return getattr(p, attr) |
|---|
| 316 | elif proto == 'ipv4' and type(p) == IPv4 and hasattr(p, attr): |
|---|
| 317 | return getattr(p, attr) |
|---|
| 318 | elif proto == 'ipv6' and type(p) == IPv6 and hasattr(p, attr): |
|---|
| 319 | return getattr(p, attr) |
|---|
| 320 | elif proto == 'tcp' and type(p) == TCP and hasattr(p, attr): |
|---|
| 321 | return getattr(p, attr) |
|---|
| 322 | |
|---|
| 323 | return None |
|---|
| 324 | |
|---|
| 325 | def __str__(self): |
|---|
| 326 | """ |
|---|
| 327 | """ |
|---|
| 328 | s = [str(self.__timestamp)] |
|---|
| 329 | |
|---|
| 330 | for p in self.__packet: |
|---|
| 331 | s.append(p.__str__()) |
|---|
| 332 | |
|---|
| 333 | return '\n'.join(s) |
|---|
| 334 | |
|---|
| 335 | class Device(object): |
|---|
| 336 | """ |
|---|
| 337 | """ |
|---|
| 338 | def __init__(self, name): |
|---|
| 339 | """ |
|---|
| 340 | """ |
|---|
| 341 | self.name = name |
|---|
| 342 | try: |
|---|
| 343 | self.naddr, self.mask = pcap.lookupnet(self.name) |
|---|
| 344 | except: |
|---|
| 345 | self.naddr, self.mask = None, None |
|---|
| 346 | |
|---|
| 347 | class Sniff(object): |
|---|
| 348 | """ |
|---|
| 349 | """ |
|---|
| 350 | def __init__(self): |
|---|
| 351 | """ |
|---|
| 352 | """ |
|---|
| 353 | self.amount = None |
|---|
| 354 | self.filter = '' |
|---|
| 355 | self.fields = [] |
|---|
| 356 | self.packets = [] |
|---|
| 357 | self.devices = {} |
|---|
| 358 | for d in umpa.sniffing.get_available_devices(): |
|---|
| 359 | self.devices[d] = Device(d) |
|---|
| 360 | |
|---|
| 361 | def start(self, device): |
|---|
| 362 | """ |
|---|
| 363 | """ |
|---|
| 364 | capture = pcap.pcap(device) |
|---|
| 365 | capture.setfilter(self.filter) |
|---|
| 366 | number = 0 |
|---|
| 367 | |
|---|
| 368 | for timestamp, packet in capture: |
|---|
| 369 | p = Packet(timestamp, packet, capture.datalink()) |
|---|
| 370 | p.disassemble() |
|---|
| 371 | self.packets.append(p) |
|---|
| 372 | number += 1 |
|---|
| 373 | |
|---|
| 374 | if len(self.fields): |
|---|
| 375 | s = [] |
|---|
| 376 | for f in self.fields: |
|---|
| 377 | s.append(str(p.get_field(f))) |
|---|
| 378 | print '[%d]' % number, ', '.join(s) |
|---|
| 379 | else: |
|---|
| 380 | print '[%d]' % number, p |
|---|
| 381 | |
|---|
| 382 | if number == self.amount: |
|---|
| 383 | break |
|---|