#!/usr/bin/env python
#
# pcap.py
#
# Pcap file decoder for the standard TCP/IP stack
#
# This is a basic decoder for TCP packets composed of IP and ETH packets within
# a pcap file. Other types of packets are simply denoted with 'UNK' and are not
# decoded any further. This is used for post-mortem analysis of network traffic
# captured beforehand.
#
# As an example use, `tcpdump` can be used to capture packets to a pcap file:
#
#     sudo tcpdump -w example.pcap
#
# Output file can then be given to this script to print the decoded output:
#
#     ./pcap.py example.pcap
#
# An example output of this script is as follows:
#
#     -- ETH Layer -- Tue Jun  5 10:52:34 2012 -- 54 bytes
#             src: 00:12:cf:e5:54:a0
#             dst: 00:1f:3c:23:db:d3
#             typ: 8
#             -- IP Layer --
#                     src: 192.168.10.226
#                     dst: 192.168.11.12
#                     typ: 6
#                     -- TCP Layer --
#                             src: 64332
#                             dst: 5888
#                             seq: 1492699879
#                             ack: 3729068838
#                             dat: b''
#     -- ETH Layer -- Tue Jun  5 10:52:35 2012 -- 60 bytes
#             src: 00:1f:3c:23:db:d3
#             dst: 00:12:cf:e5:54:a0
#             typ: 8
#             -- IP Layer --
#                     src: 192.168.11.12
#                     dst: 192.168.10.226
#                     typ: 6
#                     -- TCP Layer --
#                             src: 5888
#                             dst: 64332
#                             seq: 3729068838
#                             ack: 1509477095
#                             dat: b'\x00\x00\x00\x00\x00\x00'
#
# Indentation follows packet levels so that you can utilize folding feature in
# your text editor to display packets at the desired level. Following settings
# are used for vim editor:
#
#     set foldmethod=indent
#     set foldenable
#     set foldlevel=0 " only show packets
#     set foldlevel=1 " decode ETH layer
#     set foldlevel=2 " decode IP layer
#     set foldlevel=3 " decode TCP layer
#
# You can also directly read from stdin as follows:
#
#     ./pcap example.pcap | vim -
#


import sys
import time
import struct

if len(sys.argv) < 2:
    sys.stderr.write(f"usage: {sys.argv[0]} [PCAP_FILE]\n")
    sys.exit(1)


def decode_eth(packet):
    src = struct.unpack("6B", packet[0:6])
    dst = struct.unpack("6B", packet[6:12])
    typ = struct.unpack("H", packet[12:14])[0]
    return src, dst, typ


def decode_ip(packet):
    typ = struct.unpack("B", packet[9:10])[0]
    src = struct.unpack("4B", packet[12:16])
    dst = struct.unpack("4B", packet[16:20])
    return src, dst, typ


def decode_tcp(packet):
    src = struct.unpack("H", packet[0:2])[0]
    dst = struct.unpack("H", packet[2:4])[0]
    seq = struct.unpack("I", packet[4:8])[0]
    ack = struct.unpack("I", packet[8:12])[0]
    return src, dst, seq, ack


with open(sys.argv[1], "rb") as f:
    bom, major, minor, off, cap, typ = struct.unpack("IHHQII", f.read(24))
    if typ != 1:
        sys.stderr.write("error: only ethernet frame is supported\n")
        sys.exit(1)

    while True:
        header = f.read(16)
        if not header:
            break

        sec, msec, real, size = struct.unpack("IIII", header)
        print("-- ETH Layer --", time.ctime(sec), "--", size, "bytes")
        packet = f.read(size)

        eth_src, eth_dst, eth_typ = decode_eth(packet)
        print("\tsrc:", ":".join("%02x" % i for i in eth_src))
        print("\tdst:", ":".join("%02x" % i for i in eth_dst))
        print("\ttyp:", eth_typ)

        if eth_typ != 8:
            print("\t-- UNK Layer --")
            print("\t\tdat:", packet[14:])
            continue

        ip_src, ip_dst, ip_typ = decode_ip(packet[14:])
        print("\t-- IP Layer --")
        print("\t\tsrc:", ".".join(str(i) for i in ip_src))
        print("\t\tdst:", ".".join(str(i) for i in ip_dst))
        print("\t\ttyp:", ip_typ)

        if ip_typ != 6:
            print("\t\t-- UNK Layer --")
            print("\t\t\tdat:", packet[34:])
            continue

        tcp_src, tcp_dst, tcp_seq, tcp_ack = decode_tcp(packet[34:])
        print("\t\t-- TCP Layer --")
        print("\t\t\tsrc:", tcp_src)
        print("\t\t\tdst:", tcp_dst)
        print("\t\t\tseq:", tcp_seq)
        print("\t\t\tack:", tcp_ack)
        print("\t\t\tdat:", packet[54:])