| #!/usr/bin/env python |
| # coding: UTF-8 |
| |
| # TcpdumpWrapper |
| # |
| # Copyright(c) 2015 Uptime Technologies, LLC. |
| |
| from datetime import datetime, timedelta, date, time |
| import hashlib |
| import os |
| import re |
| import subprocess |
| import sys |
| |
| import log |
| |
| class TcpdumpPacket: |
| def __init__(self, ts, src, dst, bytes, debug=None): |
| self.ts = self.string2timestamp(ts) |
| self.src = src |
| self.dst = dst |
| self.debug = debug |
| |
| self.bytes = bytes |
| |
| self.messages = [] |
| |
| log.debug("+ " + ts + " " + src + " " + dst) |
| |
| self.end() |
| |
| self.session_id = self.get_session_id() |
| |
| def string2timestamp(self, ts): |
| t = datetime.strptime(ts, '%Y-%m-%d %H:%M:%S.%f') |
| log.debug("ts = " + str(t)) |
| return t |
| |
| def get_session_id(self): |
| src_dst = [ self.iph_src + ":" + str(self.tcph_src), self.iph_dst + ":" + str(self.tcph_dst) ] |
| ss = "" |
| for s in sorted(src_dst): |
| ss = ss + s + " " |
| |
| return hashlib.md5(ss).hexdigest()[0:12] |
| |
| def get_timestamp(self): |
| log.debug("get_timestamp: %s" % str(self.ts)) |
| return self.ts |
| |
| def get_messages(self): |
| return self.messages |
| |
| def parse_ip_header(self, data): |
| self.iph_version = (data[0] >> 4) & 0b1111 |
| self.iph_header_len = data[0] & 0b1111 |
| self.iph_tos = data[1] |
| self.iph_dgram_len = (data[2] <<8) + data[3] |
| self.iph_id = (data[4] <<8) + data[5] |
| self.iph_dst = "%d.%d.%d.%d" % (data[12], data[13], data[14], data[15]) |
| self.iph_src = "%d.%d.%d.%d" % (data[16], data[17], data[18], data[19]) |
| |
| if self.debug is True: |
| print("version : %d" % self.iph_version) |
| print("hd len : %d (%d)" % (self.iph_header_len, self.iph_header_len * 4)) |
| print("tos : %d" % self.iph_tos) |
| print("dgram len: %d" % self.iph_dgram_len) |
| print("data len: %d" % (self.iph_dgram_len - self.iph_header_len*4)) |
| print("id : %d" % self.iph_id) |
| print("dst : %s" % (self.iph_dst)) |
| print("src : %s" % (self.iph_src)) |
| |
| return self.iph_header_len * 4 |
| |
| def parse_tcp_header(self, data): |
| self.tcph_src = (data[0] << 8) + data[1] |
| self.tcph_dst = (data[2] << 8) + data[3] |
| self.tcph_seq = (data[4] << 24) + (data[5] << 16) + (data[6] << 8) + data[7] |
| self.tcph_offset = (data[12] >> 4) & 0b1111 |
| |
| if self.debug is True: |
| print("src port : %d" % (self.tcph_src)) |
| print("dst port : %d" % (self.tcph_dst)) |
| print("seq : %d" % (self.tcph_seq)) |
| print("offset : %d (%d)" % (self.tcph_offset, self.tcph_offset * 4)) |
| |
| return self.tcph_offset * 4 |
| |
| def end(self): |
| cur = 0 |
| iph_len = self.parse_ip_header(self.bytes[cur:]) |
| cur = cur + iph_len |
| |
| tcph_len = self.parse_tcp_header(self.bytes[cur:]) |
| cur = cur + tcph_len |
| |
| self.payload = self.bytes[cur:] |
| |
| s = "" |
| for d in self.payload: |
| s = s + "%02x " % (d) |
| log.debug("payload: " + s) |
| |
| if len(self.payload) >= 5: |
| pos = 0 |
| cont = True |
| while cont: |
| if len(self.payload[pos:]) < 5: |
| cont = False |
| break |
| |
| ch = self.read_char(self.payload[pos:]) |
| # if not(ch >= 48 and ch <= 122): |
| # break |
| |
| pos = pos + 1 |
| |
| i = self.read_int32(self.payload[pos:]) |
| pos = pos + 4 |
| |
| log.debug("sess: " + self.get_session_id() + ": " + str(self.ts) + ": %c[%x] len=%d" % (ch, ch, i)) |
| |
| # client to server |
| if ch == ord('S'): |
| s = self.read_string(self.payload[pos:], i - 4) |
| self.messages.append( [chr(ch), s] ) |
| log.debug(s) |
| |
| elif ch == ord('Q'): |
| s = self.read_string(self.payload[pos:], i - 4) |
| log.debug(s) |
| self.messages.append( [chr(ch), s] ) |
| |
| elif ch == ord('P'): |
| s = self.read_string(self.payload[pos:], i - 4) |
| s1 = s.split('\0') |
| log.debug("> " + s1[0] + "," + s1[1]) |
| self.messages.append( [chr(ch), s1[0], s1[1]] ) |
| |
| elif ch == ord('E'): |
| s = self.read_string(self.payload[pos:], i - 4) |
| self.messages.append( [chr(ch), s] ) |
| |
| elif ch == ord('B'): |
| s = self.read_string(self.payload[pos:], i - 4) |
| s1 = s.split('\0') |
| log.debug("> " + s1[0] + "," + s1[1]) |
| self.messages.append( [chr(ch), s1[0], s1[1]] ) |
| |
| elif ch == ord('X'): |
| self.messages.append( [chr(ch), None] ) |
| cont = False |
| |
| # server to client |
| elif ch == ord('T'): |
| s = self.read_string(self.payload[pos:], i - 4) |
| self.messages.append( [chr(ch), s] ) |
| log.debug(s) |
| |
| elif ch == ord('D'): |
| s = self.read_string(self.payload[pos:], i - 4) |
| self.messages.append( [chr(ch), s] ) |
| log.debug(s) |
| |
| elif ch == ord('C'): |
| s = self.read_string(self.payload[pos:], i - 4) |
| self.messages.append( [chr(ch), s] ) |
| log.debug(s) |
| |
| elif ch == ord('1'): |
| self.messages.append( [chr(ch), None] ) |
| |
| elif ch == ord('2'): |
| self.messages.append( [chr(ch), None] ) |
| |
| elif ch == ord('n'): |
| self.messages.append( [chr(ch), None] ) |
| |
| elif ch == ord('Z'): |
| self.messages.append( [chr(ch), None] ) |
| cont = False |
| |
| pos = pos + (i - 4) |
| |
| def parse(self): |
| self.pos = 12 |
| while len(self.payload) > self.pos + 5: |
| c = self.read_char() |
| log.debug("%02x(%c)" % (c, c)) |
| l = self.read_int32() |
| log.debug(l) |
| self.pos = self.pos + l |
| |
| def read_char(self, data): |
| ch = data[0] |
| return ch |
| |
| def read_int32(self, data): |
| i = (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + (data[3]) |
| return i |
| |
| def read_string(self, data, size): |
| s = "" |
| i = 0 |
| while i < size: |
| s = s + "%c" % data[i] |
| i = i + 1 |
| return s |
| |
| class TcpdumpWrapper: |
| pkt = None |
| tcpdump = None |
| process = None |
| line = None |
| |
| def __init__(self, host=None, port=None, interface=None, inputfile=None, debug=None): |
| if debug is True: |
| log.setLevel(log.DEBUG) |
| |
| self.host = host |
| self.port = port |
| self.iface = interface |
| self.inputfile = inputfile |
| self.debug = debug |
| |
| if self.port is None: |
| self.port = "5432" |
| |
| if self.iface is None: |
| self.iface = "any" |
| |
| self.tcpdump = "tcpdump -tttt" |
| |
| if self.inputfile is not None: |
| self.tcpdump = self.tcpdump + " -r " + self.inputfile |
| |
| self.tcpdump = self.tcpdump + " -l -i " + self.iface + " -s 0 -X -p tcp port " + str(self.port) |
| |
| if self.host is not None: |
| self.tcpdump = self.tcpdump + " and host " + self.host |
| |
| log.info(self.tcpdump) |
| |
| self.process = subprocess.Popen([self.tcpdump], |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE, |
| shell=True, |
| bufsize=0) |
| |
| # header |
| self.p1 = re.compile('^([\d-]+) ([\d:\.]+) IP (.*) > (.*): Flags') |
| # data |
| self.p2 = re.compile('^\t+0x(.+): (.+) ') |
| |
| def get_packet(self): |
| self.data = "" |
| self.hdr = None |
| |
| while True: |
| if self.line is None: |
| self.line = self.readline() |
| |
| if self.line is None: |
| if self.hdr is not None and self.data is not None: |
| # EOF found. |
| pkt = TcpdumpPacket(self.hdr[0], self.hdr[1], self.hdr[2], self.parse_data(self.data), self.debug) |
| |
| self.data = "" |
| self.hdr = None |
| |
| return pkt |
| else: |
| return None |
| |
| if self.line[0] != '\t': |
| if len(self.data) > 0: |
| pkt = TcpdumpPacket(self.hdr[0], self.hdr[1], self.hdr[2], self.parse_data(self.data), self.debug) |
| self.data = "" |
| self.hdr = None |
| return pkt |
| |
| self.hdr = self.parse_header(self.line) |
| # d: ts, src, dest |
| log.debug("Header: ts=" + self.hdr[0] + ", src=" + self.hdr[1] + ", dest=" + self.hdr[2]) |
| |
| else: |
| #log.debug(">" + self.line[10:50] + "<") |
| self.data = self.data + self.line[10:50] |
| |
| self.line = None |
| |
| def get_last_packet(self): |
| pkt = None |
| |
| if self.hdr is not None and self.data is not None: |
| pkt = TcpdumpPacket(self.hdr[0], self.hdr[1], self.hdr[2], self.parse_data(self.data), self.debug) |
| |
| self.data = "" |
| self.hdr = None |
| |
| return pkt |
| |
| def readline(self): |
| self.process.stdout.flush() |
| line = self.process.stdout.readline() |
| |
| if len(line) == 0: |
| # EOF |
| self.process.poll() |
| if self.process.returncode != 0: |
| log.error("%s" % self.process.stderr.readline()) |
| sys.exit(1) |
| |
| log.debug("readline: EOF") |
| return None |
| |
| return line.rstrip() |
| |
| def parse_header(self, line): |
| r1 = self.p1.match(line) |
| |
| if r1 is not None: |
| # Header line may look like this: |
| # 2015-04-30 13:33:27.579311 IP localhost.55641 > localhost.postgres: Flags [.], ack 66, win 664, options [nop,nop,TS val 265008484 ecr 265008484], length 0 |
| ts = r1.group(1) + " " + r1.group(2) |
| src = r1.group(3) |
| dest = r1.group(4) |
| |
| return [ts, src, dest] |
| |
| return None |
| |
| def parse_data(self, line): |
| bytes = [] |
| # line: |
| # 4500 0039 e080 4000 4006 5c3c 7f00 0001 |
| offset = 0 |
| length = len(line) |
| |
| log.debug("! " + line) |
| cur = 0 |
| while cur < length: |
| if line[cur] == ' ': |
| cur = cur + 1 |
| continue |
| # chr to hex |
| h = int(line[cur:cur+2], 16) |
| bytes.append(h) |
| # print(payload[cur:cur+2] + ", " + str(h)) |
| cur = cur + 2 |
| |
| return bytes |
| |
| def parse(self, line): |
| # return true when messages avalable. |
| msg_avail = False |
| |
| log.debug("parse: " + line) |
| |
| # packet header info |
| r1 = self.p1.match(line) |
| if r1 is not None: |
| # Header line may look like this: |
| # 13:33:27.579311 IP localhost.55641 > localhost.postgres: Flags [.], ack 66, win 664, options [nop,nop,TS val 265008484 ecr 265008484], length 0 |
| log.debug("Header: " + line) |
| ts = r1.group(1) |
| src = r1.group(2) |
| dest = r1.group(3) |
| |
| # close the previous packet |
| if self.pkt is not None: |
| self.pkt.end() |
| |
| # retreive all info/messages in the previous packet. |
| self.session_id = self.pkt.get_session_id() |
| self.timestamp = self.pkt.get_timestamp() |
| self.messages = copy.deepcopy(self.pkt.get_messages()) |
| msg_avail = True |
| |
| for m in self.pkt.get_messages(): |
| log.debug("ts:%s cmd:%c msg:%s" % (str(self.pkt.get_timestamp()), m[0], m[1])) |
| |
| # new packet coming |
| self.pkt = TcpdumpPacket(ts, src, dest, self.debug) |
| self.packets.append(self.pkt) |
| |
| # packet bytes |
| r2 = self.p2.match(line) |
| if r2 is not None: |
| log.debug("matched r2: " + line) |
| |
| if self.pkt is not None: |
| log.debug("append_bytes: " + line) |
| self.pkt.append_bytes(r2.group(2)) |
| |
| return msg_avail |
| |
| def print_packets(self): |
| print(self.packets) |