├── __init__.py ├── arp2.cap ├── dpkt.odp ├── sctp.cap ├── ftp_ipv6.cap ├── http_ipv4.cap ├── http_ipv6.cap ├── ipv4_dns.cap ├── ipv6_tcp_wan.cap ├── http_ipv4_complex.cap ├── 4000_byte_ipv6_packet.cap ├── 60000_byte_ipv6_packet.cap ├── ftp_active_ipv4_FAIL.cap ├── ftp_passive_ipv4_FAIL.cap ├── http_ipv6_wireshark_1.cap ├── Python_course_notes-2011-01-11.odt ├── Python_course_notes_spring_2011.odt ├── simple_udp_receiver.py ├── simple_udp_sender.py ├── README ├── test_cases.txt ├── decode_udp.py ├── homepage.css ├── simple_udp_sender2.py ├── decode_http.py ├── dpkt_udp_sender.py ├── decode_http_2.py ├── decode_arp.py ├── decode_tcp.py ├── decode_dns.py ├── decode_mdns.py ├── decode_tcp_iterator.py ├── decode_tcp_iterator_2.py ├── decode_tcp_iterator_2P.py ├── dpkt_notes.html └── dpkt.html /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arp2.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffsilverm/dpkt_doc/HEAD/arp2.cap -------------------------------------------------------------------------------- /dpkt.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffsilverm/dpkt_doc/HEAD/dpkt.odp -------------------------------------------------------------------------------- /sctp.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffsilverm/dpkt_doc/HEAD/sctp.cap -------------------------------------------------------------------------------- /ftp_ipv6.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffsilverm/dpkt_doc/HEAD/ftp_ipv6.cap -------------------------------------------------------------------------------- /http_ipv4.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffsilverm/dpkt_doc/HEAD/http_ipv4.cap -------------------------------------------------------------------------------- /http_ipv6.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffsilverm/dpkt_doc/HEAD/http_ipv6.cap -------------------------------------------------------------------------------- /ipv4_dns.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffsilverm/dpkt_doc/HEAD/ipv4_dns.cap -------------------------------------------------------------------------------- /ipv6_tcp_wan.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffsilverm/dpkt_doc/HEAD/ipv6_tcp_wan.cap -------------------------------------------------------------------------------- /http_ipv4_complex.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffsilverm/dpkt_doc/HEAD/http_ipv4_complex.cap -------------------------------------------------------------------------------- /4000_byte_ipv6_packet.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffsilverm/dpkt_doc/HEAD/4000_byte_ipv6_packet.cap -------------------------------------------------------------------------------- /60000_byte_ipv6_packet.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffsilverm/dpkt_doc/HEAD/60000_byte_ipv6_packet.cap -------------------------------------------------------------------------------- /ftp_active_ipv4_FAIL.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffsilverm/dpkt_doc/HEAD/ftp_active_ipv4_FAIL.cap -------------------------------------------------------------------------------- /ftp_passive_ipv4_FAIL.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffsilverm/dpkt_doc/HEAD/ftp_passive_ipv4_FAIL.cap -------------------------------------------------------------------------------- /http_ipv6_wireshark_1.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffsilverm/dpkt_doc/HEAD/http_ipv6_wireshark_1.cap -------------------------------------------------------------------------------- /Python_course_notes-2011-01-11.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffsilverm/dpkt_doc/HEAD/Python_course_notes-2011-01-11.odt -------------------------------------------------------------------------------- /Python_course_notes_spring_2011.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffsilverm/dpkt_doc/HEAD/Python_course_notes_spring_2011.odt -------------------------------------------------------------------------------- /simple_udp_receiver.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # 3 | # A simple UDP packet receiver with a twist. This gets a potentially very large 4 | # packet to demonstrate fragmentation of a UDP packet, so we can test packet 5 | # reassembly 6 | 7 | import socket 8 | 9 | UDP_IP="" # listen to anything IPv4 or IPv6 10 | UDP_PORT=50005 11 | 12 | sock = socket.socket( socket.AF_INET6, # Internet IPv4 or IPv6 13 | socket.SOCK_DGRAM ) # UDP 14 | sock.bind( (UDP_IP,UDP_PORT) ) 15 | 16 | while True: 17 | data, addr = sock.recvfrom( 1048576 ) # buffer size huge 18 | print "received message from ", addr, "Lenth of data: ", len(data) 19 | -------------------------------------------------------------------------------- /simple_udp_sender.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # 3 | # A simple UDP packet sender with a twist. This sends a very large packet to demonstrate fragmentation 4 | # of a UDP packet, so we can test packet reassembly 5 | import socket 6 | 7 | UDP_IP="192.168.1.104" 8 | UDP_PORT=50005 9 | 10 | message="" 11 | while len(message) <= 16384 : 12 | message = message + str(len(message)) + " " + 16*"+" + "\n" 13 | 14 | print "UDP target IP:", UDP_IP 15 | print "UDP target port:", UDP_PORT 16 | print "message:", message, "Length: ", len(message) 17 | 18 | sock = socket.socket( socket.AF_INET, # Internet 19 | socket.SOCK_DGRAM ) # UDP 20 | sock.sendto( message, (UDP_IP, UDP_PORT) ) 21 | 22 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | dpkt is a great packet decoding library that is poorly documented and has few 2 | examples. In this project, I propose that we document dpkt. For each 3 | protocol, there is a set of functions. Each function needs to be documented. 4 | We will also create sample packet captures and software that decodes the sample 5 | packet captures. 6 | 7 | Each protocol is a deliverable. Each function is a deliverable. Each sample 8 | packet capture is a deliverable. 9 | 10 | The list of packet captures for testing purposes is given in the file 11 | test_cases.txt, which is a tab-delimited file giving the file name and test 12 | conditions the file was generated under. 13 | 14 | To install dpkt under ubuntu: 15 | sudo apt-get install python-dpkt 16 | sudo apt-get install python-libpcap 17 | 18 | -------------------------------------------------------------------------------- /test_cases.txt: -------------------------------------------------------------------------------- 1 | File Test conditions 2 | 4000_byte_ipv6_packet.cap UDP over IPv6 showing IP fragmentation 3 | 60000_byte_ipv6_packet.cap UDP over IPv6 showing IP fragmentation 4 | arp2.cap ARP showing both an ARP failure and success 5 | ftp_active_ipv4_FAIL.cap Active mode FTP over IPv4, showing a failure 6 | ftp_ipv6.cap Extended passive mode FTP over IPv6 7 | ftp_passive_ipv4_FAIL.cap Passive mode FTP over IPv4, showing a failure 8 | http_ipv4.cap HTTP over IPv4, simple page, 13 packets 9 | http_ipv6.cap HTTP over IPv6 captured with tcpdump (This doesn't play nice with libpcap) 10 | http_ipv4_complex.cap HTTP over IPv4, complex page 171 packets 11 | http_ipv6_wireshark_1.cap HTTP over IPv6 captured with wireshark 12 | ipv4_dns.cap DNS over IPv4 showing a variety of different lookups 13 | ipv6_tcp_wan.cap TCP over IPv6 over a WAN 14 | sctp.cap Stream Control Transmission Protocol 15 | -------------------------------------------------------------------------------- /decode_udp.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # 3 | # This program decodes UDP packets from the wire 4 | 5 | import dpkt 6 | import sys 7 | import socket 8 | import pcap 9 | 10 | 11 | 12 | 13 | def decode_udp ( pc ) : 14 | """decode_udp is a generator function that listens to a pcap.pcap object and returns a UDP object when it hears a packet""" 15 | for ts, pkt in pc: 16 | # parse the packet. Decode the ethertype 17 | eth = dpkt.ethernet.Ethernet(pkt) 18 | if eth.type == dpkt.ethernet.ETH_TYPE_IP : 19 | ip = eth.data 20 | if ip.p == dpkt.ip.IP_PROTO_UDP : 21 | # This doesn't deal with IP fragments 22 | udp = ip.data 23 | # Pass the IP addresses, source port, destination port, and data back to the caller. 24 | yield ( ip.src, udp.sport, ip.dst, udp.dport, udp.data, ip.v) 25 | elif eth.type == dpkt.ethernet.ETH_TYPE_IP6 : 26 | ip = eth.data 27 | if ip.nxt == dpkt.ip.IP_PROTO_UDP : 28 | # This doesn't deal with IP fragments 29 | udp = ip.data 30 | # Pass the IP addresses, source port, destination port, and data back to the caller. 31 | yield ( ip.src, udp.sport, ip.dst, udp.dport, udp.data, ip.v) 32 | else : 33 | # If the packet is something else, then I need to figure out a better way of handling it. 34 | pass 35 | 36 | def main() : 37 | if sys.argv[1] == "-i" : 38 | pc = pcap.pcap( sys.argv[2] ) 39 | elif sys.argv[1] == "-f" : 40 | pc = dpkt.pcap.Reader( open ( sys.argv[2] ) ) 41 | else : 42 | print """Use -i INTERFACE to packet capture from an interface. 43 | Use -f FILENAME to read a packet capture file""" 44 | sys.exit(2) 45 | 46 | for src, sport, dst, dport, data, ip_version in decode_udp( pc ) : 47 | if ip_version == 4 : 48 | print "from ", socket.inet_ntoa(src),":",sport, " to ", socket.inet_ntoa(dst),":",dport 49 | else : 50 | print "from ", socket.inet_ntop(AF_INET6, src),".",sport, " to ", socket.inet_ntop(AF_INET6, dst), ".", dport 51 | 52 | 53 | 54 | if __name__ == "__main__" : 55 | main() 56 | 57 | -------------------------------------------------------------------------------- /homepage.css: -------------------------------------------------------------------------------- 1 | /* -- add 2 | 3 | 4 | 5 | to the header section of the HTML file to reference this */ 6 | BODY { background: white; color: black} 7 | A:link { color: red } 8 | A:visited { color: maroon } 9 | A:active { color: fuchsia } 10 | H1.myclass {border-width: thin; border: solid; text-align: center} 11 | H1, .h1 { font-size: 36pt; font-weight: bold; background: #ffdcdc; margin-left: 18pt } 12 | H2, .h2 { font-size: 30pt; font-weight: bold; background: #ffffcc; margin-left: 36pt } 13 | H3, .h3 { font-size: 24pt; font-weight: bold; background: #ccffcc; margin-left: 54pt } 14 | H4, .h4 { font-size: 20pt; font-weight: bold; background: #ccffff; margin-left: 72pt } 15 | H5, .h5 { font-size: 16pt; font-weight: bold; background: #ccccff; margin-left: 72pt } 16 | H6, .h6 { font-size: 14pt; font-weight: bold; background: #cccccc; margin-left: 90pt } 17 | P { background: #fefefe; text-indent: 5em } 18 | .sat { background: #c0ffc0 } 19 | .sun { background: #c0c0ff } 20 | .wkd { background: #e0e0e0 } 21 | .hol { background: #ffffc0 } 22 | .toc { background: #ffc0c0 } 23 | code, samp, kbd, var { white-space: pre; font-size: 10pt; } 24 | samp { background: #fefeff; } 25 | kbd { text-decoration: underline; } 26 | var { font-style: italic; } 27 | /* kbd { white-space: pre; text-decoration: underline; font-size: 10pt; } */ 28 | .appointments { color: red; background: #f0f0f0 } 29 | .important { background: #00ff00 } 30 | .tbd { color: white; background: red } 31 | .inferred { color: blue; background: yellow } 32 | OL { list-style-type: decimal } /* 1 2 3 4 5 etc. */ 33 | OL OL { list-style-type: upper-alpha } /* A B C D E etc. */ 34 | OL OL OL { list-style-type: decimal } 35 | OL OL OL OL { list-style-type: lower-roman } /* i ii iii iv v etc. */ 36 | OL OL OL OL OL { list-style-type: lower-alpha } /* a b c d e etc. */ 37 | .hotthought {border-width: thin; border: solid; color: red } 38 | .thought { color: red } 39 | .caution { border-width: thin; border: dashed; color: blue } 40 | .warning { border-width: medium; border: double; color: red } 41 | .button { border-width: thin; border: solid; background: #f0f0fc } 42 | 43 | -------------------------------------------------------------------------------- /simple_udp_sender2.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # 3 | # A simple UDP packet sender This can send a very large UDP packet to demonstrate fragmentation 4 | # of a UDP packet, so we can test packet reassembly 5 | import socket 6 | import getopt 7 | import sys 8 | 9 | 10 | def help() : 11 | print sys.argv[0] + """ -6h -s size -d destination_IP_addr 12 | -6 Use IPv6 (default is IP4) (must come before -d if ipV6) 13 | -d IP destination IP address (either IPv4 or IPv6) (required) 14 | -s size size of the UDP packet (0 < size < 65536, upper limit is not enforced, default is 4096 15 | -p port destination port (default is 50005, 0< port < 65536) 16 | -h help 17 | """ 18 | sys.exit(2) 19 | 20 | 21 | def parse_args ( ) : 22 | """Parse the command line arguments. The arguments are: 23 | -6 Use IPv6 (default is IP4) (must come before -d if ipV6) 24 | -d IP destination IP address (either IPv4 or IPv6) (required) 25 | -s size size of the UDP packet (0 < size < 65536, upper limit is not enforced, default is 4096 26 | -p port destination port (default is 50005, 0< port < 65536) 27 | -h help 28 | """ 29 | try : 30 | optlist, args = getopt.getopt ( sys.argv[1:], "6d:s:h") 31 | ipv6 = 0 32 | destination_ip = "" 33 | destination_port = 50005 34 | message_size = 4096 35 | for opt in optlist : 36 | if opt[0] == "-6" : ipv6 = 1 37 | elif opt[0] == "-d" : 38 | destination_ip = opt[1] 39 | elif opt[0] == "-s" : 40 | message_size = int( opt[1] ) 41 | elif opt[0] == "-p" : 42 | destination_port = int( opt[1] ) 43 | if not ( 0 < destination_port < 65536 ) : 44 | raise ValueError 45 | elif opt[0] == "-h" : 46 | help() 47 | if destination_ip == "" : 48 | print "The -d DESTINATION_IP switch is required" 49 | raise ValueError 50 | except ( getopt.GetoptError, ValueError ) : 51 | help() 52 | return (destination_ip, destination_port, message_size, ipv6) 53 | 54 | 55 | 56 | 57 | if __name__ == "__main__" : 58 | (destination_ip, destination_port, message_size, ipv6) = parse_args( ) 59 | 60 | 61 | message = message_size * "*" 62 | 63 | print "UDP target IP:", destination_ip 64 | print "UDP target port:", destination_port 65 | 66 | 67 | sock = socket.socket( ( socket.AF_INET6 if ipv6 else socket.AF_INET ), socket.SOCK_DGRAM ) 68 | sock.sendto( message, (destination_ip, destination_port) ) # destination IP address must be a string, not a packeted IP address 69 | 70 | 71 | -------------------------------------------------------------------------------- /decode_http.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import dpkt 4 | import sys 5 | 6 | f = open(sys.argv[1]) 7 | pcap = dpkt.pcap.Reader(f) 8 | 9 | for ts, buf in pcap: 10 | eth = dpkt.ethernet.Ethernet(buf) 11 | ip = eth.data # This is an oversimplification - IP packets can fragment if an MTU in the path is smaller than the MTU of the LAN 12 | # Also, this changes a little bit with IPv6. To tell the difference between IPv4 and IPv6, you have to look 13 | # at the ethertype field, which is given by http://www.iana.org/assignments/ethernet-numbers. IPv4 is 0x800 or 2048 14 | # and IPv6 is 0x86DD or 34525 15 | tcp = ip.data # This is an oversimplification - this is true if and only if the protocol field of the IP packet is 6. 16 | # See http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml for details of protocol numbers. 17 | # See http://tools.ietf.org/html/rfc791 for details on IPv4 18 | 19 | # If the destination port is 80 and there is data in the packet, then probably this is an HTTP request. TO be certain, there should 20 | # be a TCP finite state machine in here. 21 | if tcp.dport == 80 and len(tcp.data) > 0: 22 | http_req = dpkt.http.Request(tcp.data) 23 | print "URI is ", http_req.uri 24 | for header in http_req.headers.keys() : 25 | print header, http_req.headers[header] 26 | # ['_Request__methods', '_Request__proto', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__hash__', '__hdr_defaults__', '__init__', '__len__', '__metaclass__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'body', 'data', 'headers', 'method', 'pack', 'pack_hdr', 'unpack', 'uri', 'version'] 27 | print "method is ", http_req.method 28 | # 'body', 'data', 'headers', 'method', 'pack', 'pack_hdr', 'unpack', 'uri', 'version' 29 | print "HTTP headers, packed ", http_req.pack() 30 | print "HTTP version", http_req.version 31 | print "HTTP data ", http_req.data # I think this is valid if the method is POST 32 | if tcp.sport == 80 and len(tcp.data) > 0: 33 | try : 34 | http = dpkt.http.Response(tcp.data) 35 | print "HTTP version is ", http.version 36 | print "Status code is ", http.status 37 | print "Status reason ", http.reason 38 | for header in http.headers.keys() : 39 | print header, http.headers[header] 40 | # print "date", http.headers['date'] 41 | # print "accept-ranges", http.headers['accept-ranges'] 42 | # print "content-type", http.headers['content-type'] 43 | # print "connection", http.headers['connection'] 44 | # print "server", http.headers['server'] 45 | except dpkt.dpkt.UnpackError : 46 | print "Encounted an unpacking error" 47 | f.close() 48 | -------------------------------------------------------------------------------- /dpkt_udp_sender.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # A simple UDP packet sender This can send a very large UDP packet to demonstrate fragmentation 5 | # of a UDP packet, so we can test packet reassembly 6 | import socket 7 | import getopt 8 | import sys 9 | import dpkt 10 | import struct 11 | 12 | 13 | def help() : 14 | print sys.argv[0] + """ -6h -s size -d destination_IP_addr 15 | -6 Use IPv6 (default is IP4) (must come before -d if ipV6) 16 | -d IP destination IP address (either IPv4 or IPv6) (required) 17 | -s size size of the UDP packet (0 < size < 65536, upper limit is not enforced, default is 4096 18 | -p port destination port (default is 50005, 0< port < 65536) 19 | -h help 20 | """ 21 | sys.exit(2) 22 | 23 | 24 | def parse_args ( ) : 25 | """Parse the command line arguments. The arguments are: 26 | -6 Use IPv6 (default is IPv4) (must come before -d if IPv6) 27 | -d IP destination IP address (either IPv4 or IPv6) (required) 28 | -l length size of the UDP packet (0 < size < 65536, upper limit is not enforced, default is 4096 29 | -p port destination port (default is 50005, 0< port < 65536) 30 | -s port source port (default is 50004, 0< port < 65536) 31 | -h help 32 | """ 33 | try : 34 | optlist, args = getopt.getopt ( sys.argv[1:], "6d:s:h") 35 | ipv6 = 0 36 | destination_ip = "" 37 | destination_port = 50005 38 | source_port = 50004 39 | message_length = 4096 40 | for opt in optlist : 41 | if opt[0] == "-6" : ipv6 = 1 42 | elif opt[0] == "-d" : 43 | destination_ip = opt[1] 44 | elif opt[0] == "-l" : 45 | message_length = int( opt[1] ) 46 | elif opt[0] == "-p" : 47 | destination_port = int( opt[1] ) 48 | if not ( 0 < destination_port < 65536 ) : 49 | raise ValueError 50 | elif opt[0] == "-s" : 51 | source_port = int ( opt[1] ) 52 | if not ( 0 < destination_port < 65536 ) : 53 | raise ValueError 54 | elif opt[0] == "-h" : 55 | help() 56 | if destination_ip == "" : 57 | print "The -d DESTINATION_IP switch is required" 58 | raise ValueError 59 | except ( getopt.GetoptError, ValueError ) : 60 | help() 61 | return (destination_ip, destination_port, source_port, message_length, ipv6) 62 | 63 | 64 | def create_udp_packet (destination_ip, destination_port, source_port, message_length, ipv6) : 65 | udp = dpkt.udp.UDP() 66 | udp.sport = source_port 67 | udp.dport = destination_port 68 | udp.ulen = message_length + 8 # add 8 because that's the length of the UDP header 69 | message = message_length * "*" 70 | udp.data = message 71 | # Need to calculate a UDP checksum 72 | return udp.pack() 73 | 74 | def createRawIPSocket( ipv6 ): 75 | sock = socket.socket(socket.AF_INET6 if ipv6 else socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_UDP) 76 | return sock 77 | 78 | if __name__ == "__main__" : 79 | ( destination_ip, destination_port, source_port, message_length, ipv6) = parse_args( ) 80 | sock = createRawIPSocket( ipv6 ) 81 | # create_udp_packet needs the destination IP address so it can calculate the UDP checksum 82 | udp_packet = create_udp_packet ( destination_ip, destination_port, source_port, message_length, ipv6) 83 | sock.sendto(udp_packet, (destination_ip, destination_port ) ) 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /decode_http_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # This program uses decode_tcp_iterator_2.py to get data for HTTP parsing. That way, 5 | # the input strings can be longer than what will fit into a single packet. 6 | 7 | 8 | import dpkt 9 | import sys 10 | import decode_tcp_iterator_2P as d 11 | import socket 12 | 13 | 14 | 15 | def main(pc) : 16 | """This is the outer loop that prints strings that have been captured from the TCP streams, terminated by a packet that 17 | has the PUSH flag set.""" 18 | for cid, received_string, ip_version in d.decode_tcp(pc) : # cid = connection_id 19 | # This next line is for debugging only 20 | # print d.connection_id_to_str (cid, ip_version), received_string 21 | if cid[3] == 80 : # cid[3] is the destination port 22 | # This is a message going to the server, a request 23 | src_addr = socket.inet_ntoa(cid[0]) if ip_version == 4 else socket.inet_ntop(socket.AF_INET6, cid[0]) 24 | print "HTTP request from ", src_addr 25 | http_req = dpkt.http.Request(received_string) 26 | print "URI is ", http_req.uri 27 | for header in http_req.headers.keys() : 28 | print header, http_req.headers[header] 29 | # ['_Request__methods', '_Request__proto', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__hash__', '__hdr_defaults__', '__init__', '__len__', '__metaclass__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'body', 'data', 'headers', 'method', 'pack', 'pack_hdr', 'unpack', 'uri', 'version'] 30 | print "method is ", http_req.method 31 | # 'body', 'data', 'headers', 'method', 'pack', 'pack_hdr', 'unpack', 'uri', 'version' 32 | print "HTTP headers, packed ", http_req.pack() 33 | print "HTTP version", http_req.version 34 | # print "HTTP data ", http_req.data # I think this is valid if the method is POST 35 | 36 | elif cid[1] == 80 : # cid[1] is the source port 37 | try : 38 | http = dpkt.http.Response(received_string) 39 | print "HTTP version is ", http.version 40 | print "Status code is ", http.status 41 | print "Status reason is ", http.reason 42 | for header in http.headers.keys() : 43 | print header, " is ", http.headers[header] 44 | # print "date", http.headers['date'] 45 | # print "accept-ranges", http.headers['accept-ranges'] 46 | # print "content-type", http.headers['content-type'] 47 | # print "connection", http.headers['connection'] 48 | # print "server", http.headers['server'] 49 | except dpkt.dpkt.UnpackError : 50 | print "Encounted an unpacking error" 51 | else : 52 | print "skipping ", d.connection_id_to_str( cid, ip_version ) 53 | 54 | 55 | if __name__ == "__main__" : 56 | if len(sys.argv) < 2 : 57 | decode_tcp_help() 58 | # create an interator to return the next packet. The source can be either an interface using the libpcap library or it can be a file in pcap 59 | # format such as created by tcpdump. 60 | if sys.argv[1] == "eth0" : # wired ethernet interface 61 | pc = pcap.pcap("eth0", promisc=True ) 62 | elif sys.argv[1] == "wlan0" : # wireless interface 63 | pc = pcap.pcap("wlan0", promisc=True ) 64 | elif sys.argv[1] == "sixxs" : # IPv6 tunnel pseudo device 65 | pc = pcap.pcap("sixxs", promisc=True ) 66 | else : 67 | pc = dpkt.pcap.Reader ( open(sys.argv[1] ) ) # file interface 68 | main(pc) 69 | 70 | -------------------------------------------------------------------------------- /decode_arp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This program listens to an ethernet, filters on ARP packets, and builds a table of physical (Ethernet) addresses <=> IPv4 addresses 3 | 4 | import dpkt 5 | import sys 6 | import socket 7 | import pcap 8 | import subprocess 9 | import binascii 10 | import time 11 | 12 | def add_colons_to_mac( mac_addr ) : 13 | """This function accepts a 12 hex digit string and converts it to a colon 14 | separated string""" 15 | s = list() 16 | for i in range(12/2) : # mac_addr should always be 12 chars, we work in groups of 2 chars 17 | s.append( mac_addr[i*2:i*2+2] ) 18 | r = ":".join(s) # I know this looks strange, refer to http://docs.python.org/library/stdtypes.html#sequence-types-str-unicode-list-tuple-bytearray-buffer-xrange 19 | return r 20 | 21 | 22 | def main() : 23 | # This code allows this program to run equally well on my laptop and my desktop. I did it this 24 | # way to demonstrate different interface names. If I was really clever, I'd figure out how to this 25 | # under MS-Windows 26 | if sys.argv[1] == "-i" : 27 | pc = pcap.pcap( sys.argv[2] ) 28 | elif sys.argv[1] == "-f" : 29 | pc = dpkt.pcap.Reader( open ( sys.argv[2] ) ) 30 | else : 31 | print """Use -i INTERFACE to [packet capture from an interface. 32 | Use -f FILENAME to read a packet capture file""" 33 | sys.exit(2) 34 | 35 | pc.setfilter('arp') # Use a kernel filter and just pass arp traffic 36 | start_time = time.time() # Return the time as a floating point number expressed in seconds since the epoch 37 | arp_cache = dict() 38 | 39 | # a pcap.pcap object listens to the network and returns a packet object when it hears a packet. 40 | for ts, pkt in pc: 41 | # parse the packet. Because the filter allows only ARP packets through, we don't have to decode the ethertype 42 | eth = dpkt.ethernet.Ethernet(pkt) 43 | arp = eth.arp 44 | print "From: ",add_colons_to_mac( binascii.hexlify(eth.src)), 45 | print " to: ", add_colons_to_mac( binascii.hexlify(eth.dst)) # Ethernet destination addresses may be broadcast addresses 46 | # print arp.hrd 47 | # print arp.pro 48 | if arp.op==1 : 49 | print "request" 50 | elif arp.op==2 : 51 | print "reply" 52 | else : 53 | print "op has an unexpected value %d", arp.op 54 | print "source protocol address", socket.inet_ntoa(arp.spa) 55 | print "source hardware address", add_colons_to_mac( binascii.hexlify(arp.sha) ) 56 | print "Target protocol address", socket.inet_ntoa(arp.tpa) #IPv4 address 57 | print "target hardware address", add_colons_to_mac( binascii.hexlify(arp.tha) ) 58 | arp_cache[arp.spa] = arp.sha 59 | if arp.op==2 : # reply 60 | if arp.tpa in arp_cache : 61 | if arp_cache[arp.tpa] != arp.tha : 62 | print "Duplicate IP addresses detected: ", socket.inet_ntoa(arp.tpa), "is assigned to both ", 63 | add_colons_to_mac( binascii.hexlify(arp.tha) ), " and ", add_colons_to_mac( binascii.hexlify(arp_cache[arp.tha] ) ) 64 | arp_cache[arp.tpa] = arp.tha 65 | # The arp cache has to be refreshed every 30 seconds, so this will catch every machine on the ethernet at least twice. However, you might want to 66 | # continuously monitor your network to catch machines surreptitiously. 67 | elapsed_time = time.time() - start_time 68 | if elapsed_time > 60.0 : 69 | break 70 | 71 | for ip in arp_cache.keys() : 72 | print socket.inet_ntoa(ip),": ", add_colons_to_mac( binascii.hexlify(arp_cache[ip])) 73 | 74 | 75 | if __name__ == "__main__" : 76 | main() 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /decode_tcp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import dpkt 4 | import sys 5 | import socket 6 | 7 | def connection_id_to_str (cid, v=4) : 8 | """This converts the connection ID cid which is a tuple of (source_ip_address, source_tcp_port, destination_ip_address, 9 | destination_tcp_port) to a string. v is either 4 for IPv4 or 6 for IPv6""" 10 | if v == 4 : 11 | src_ip_addr_str = socket.inet_ntoa(cid[0]) 12 | dst_ip_addr_str = socket.inet_ntoa(cid[2]) 13 | return src_ip_addr_str + ":" + str(cid[1])+"<=>"+dst_ip_addr_str + ":" + str(cid[3]) 14 | elif v == 6 : 15 | src_ip_addr_str = socket.inet_ntop(AF_INET6, cid[0]) 16 | dst_ip_addr_str = socket.inet_ntop(AF_INET6, cid[2]) 17 | return src_ip_addr_str + "." + str(cid[1])+"<=>"+dst_ip_addr_str + "." + str(cid[3]) 18 | else : 19 | raise ValueError('Argument to connection_id_to_str must be 4 or 6, is %d' % v) 20 | 21 | def main(f): 22 | """This function decodes a packet capture file f and breaks it up into tcp connections""" 23 | pcap = dpkt.pcap.Reader(f) 24 | print "counter\tsrc prt\tdst prt\tflags" 25 | packet_cntr = 0 26 | connection_table = {} 27 | 28 | for ts, buf in pcap: 29 | packet_cntr += 1 30 | eth = dpkt.ethernet.Ethernet(buf) 31 | # Also, this changes a little bit with IPv6. To tell the difference between IPv4 and IPv6, you have to look 32 | # at the ethertype field, which is given by http://www.iana.org/assignments/ethernet-numbers. IPv4 is 0x800 or 2048 33 | # and IPv6 is 0x86DD or 34525 34 | # This is simplistic - IP packets can be fragmented. Also, this only works for IPv4. IPv6 has a different Ethertype 35 | if eth.type != dpkt.ethernet.ETH_TYPE_IP : 36 | continue 37 | elif eth.type == dpkt.ethernet.ETH_TYPE_IP6 : 38 | pass 39 | ip = eth.data 40 | # If not a TCP paaket 41 | if ip.p != dpkt.ip.IP_PROTO_TCP: 42 | continue 43 | # This is also simplistic - TCP packets can be fragmented, and also retransmitted 44 | # 45 | tcp = ip.data 46 | fin_flag = ( tcp.flags & 0x01 ) != 0 47 | syn_flag = ( tcp.flags & 0x02 ) != 0 48 | rst_flag = ( tcp.flags & 0x04 ) != 0 49 | psh_flag = ( tcp.flags & 0x08 ) != 0 50 | ack_flag = ( tcp.flags & 0x10 ) != 0 51 | urg_flag = ( tcp.flags & 0x20 ) != 0 52 | ece_flag = ( tcp.flags & 0x40 ) != 0 53 | cwr_flag = ( tcp.flags & 0x80 ) != 0 54 | flags = ( 55 | ( "C" if cwr_flag else " " ) + 56 | ( "E" if ece_flag else " " ) + 57 | ( "U" if urg_flag else " " ) + 58 | ( "A" if ack_flag else " " ) + 59 | ( "P" if psh_flag else " " ) + 60 | ( "R" if rst_flag else " " ) + 61 | ( "S" if syn_flag else " " ) + 62 | ( "F" if fin_flag else " " ) ) 63 | 64 | print packet_cntr, "\t", tcp.sport, "\t", tcp.dport, "\t", flags 65 | connection_id = (ip.src, tcp.sport, ip.dst, tcp.dport) 66 | if syn_flag and not ack_flag : 67 | # Each TCP connection is forming. The new connection is stored as a dictionary 68 | # whose key is the tuple (source_ip_address, source_tcp_port, destination_ip_address, destination_tcp_port) 69 | # The connection is stored in a dictionary. The key is the connection_id, value of each key is a list of tcp packets 70 | # Note that there are two connections, one from the client to the server and one from the server to the client. This becomes 71 | # important when the connection is closed, because one side might FIN the connection well before the other side does. 72 | print "Forming a new connection " + connection_id_to_str( connection_id, ip.v ) 73 | connection_table[connection_id] = [] 74 | elif syn_flag and ack_flag : 75 | print "Server responding to a new connection " + connection_id_to_str( connection_id, ip.v ) 76 | connection_table[connection_id] = [] 77 | # This is where I am having a little confusion. My instinct tells me that the connection from the client to the server and the 78 | # connection from the server back to the client should be connected somehow. 79 | elif not syn_flag and ack_flag : 80 | if connection_id not in connection_table.keys(): 81 | # Not a complete connection 82 | connection_table[connection_id] = [] 83 | 84 | connection_table[connection_id].append(tcp.data) 85 | elif fin_flag : 86 | pass 87 | # Also need to think about the RESET flag 88 | 89 | f.close() 90 | for connection_id in connection_table.keys() : 91 | print "Connection "+ connection_id_to_str(connection_id) 92 | print connection_table[connection_id] 93 | 94 | 95 | 96 | if __name__ == "__main__" : 97 | f = open(sys.argv[1]) 98 | main(f) 99 | -------------------------------------------------------------------------------- /decode_dns.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # 3 | # This program decodes DNS packets from the wire or from a capture file 4 | # -*- coding: utf-8 -*- 5 | 6 | import dpkt, dpkt.dns 7 | import sys 8 | import socket 9 | import pcap 10 | import subprocess 11 | 12 | type_table={} # This is a lookup table for DNS query types 13 | 14 | def initialize_tables() : 15 | global type_table 16 | 17 | 18 | # From http://www.networksorcery.com/enp/protocol/dns.htm 19 | type_table = {1:"A", # IP v4 address, RFC 1035 20 | 2:"NS", # Authoritative name server, RFC 1035 21 | 5:"CNAME", # Canonical name for an alias, RFC 1035 22 | 6:"SOA", # Marks the start of a zone of authority, RFC 1035 23 | 12:"PTR", # Domain name pointer, RFC 1035 24 | 13:"HINFO", # Host information, RFC 1035 25 | 15:"MX", # Mail exchange, RFC 1035 26 | 28:"AAAA", # IP v6 address, RFC 3596 27 | 16:"TXT", # 28 | 33:"SRV", # RFC 2782 29 | 255:"ANY", # all cached reco 30 | } 31 | 32 | def hexify(x): 33 | "The strings from DNS resolver contain non-ASCII characters - I don't know why. This function investigates that" 34 | toHex = lambda x:"".join([hex(ord(c))[2:].zfill(2) for c in x]) 35 | return toHex(x) 36 | 37 | def udp_iterator(pc): 38 | """pc is a pcap.pcap object that listens to the network and returns a packet object when it hears a packet go by""" 39 | for ts, pkt in pc: 40 | # parse the packet. Decode the ethertype. If it is IP (IPv4) then process it further 41 | eth = dpkt.ethernet.Ethernet(pkt) 42 | if eth.type == dpkt.ethernet.ETH_TYPE_IP : 43 | ip = eth.data 44 | # If the IP protocol is UDP, then process it further 45 | if ip.p == dpkt.ip.IP_PROTO_UDP : 46 | udp = ip.data 47 | # Pass the IP addresses, source port, destination port, and data back to the caller. 48 | yield ( ip.src, udp.sport, ip.dst, udp.dport, udp.data) 49 | 50 | 51 | def decode_dns_response ( rr, response_type ) : 52 | """This subroutine decodes a DNS response packet. The packet may have more than one rr""" 53 | r_type = rr.type 54 | r_data = rr.rdata 55 | if rr.cls != 1 : 56 | print "Response class is %d, not class IN, might be Hesiod, chaos, or qclass (all of which are anachronisms)",%rr.cls 57 | print "Response is component", response_type,r_type,type_table[r_type] if r_type in type_table else 'unknown' 58 | if r_type == dpkt.dns.DNS_CNAME : 59 | #print "Response is a CNAME ", r_data," in hex: ", hexify(r_data) 60 | print "Response is a CNAME ", rr.cname 61 | elif r_type == dpkt.dns.DNS_A : 62 | print "response is an IPv4 address", socket.inet_ntoa( r_data ) 63 | elif r_type == dpkt.dns.DNS_NS : 64 | #print "Response is a NS name", r_data," in hex: ", hexify(r_data) 65 | print "Response is a NS name", rr.nsname 66 | elif r_type == dpkt.dns.DNS_AAAA : 67 | print "response is an IPv6 address", socket.inet_ntop( socket.AF_INET6, r_data ) 68 | elif r_type == dpkt.dns.DNS_PTR : 69 | #print "response is a hostname from an IP address", r_data, "in hex: ", hexify(r_data) 70 | print "response is a hostname from an IP address", rr.ptrname 71 | elif r_type == dpkt.dns.DNS_SOA : 72 | print 'DNS_SOA:',rr.mname,rr.rname,rr.serial,rr.refresh,rr.retry,rr.expire, rr.minimum 73 | elif r_type == dpkt.dns.DNS_MX : 74 | print 'DNS_MX:',rr.mxname,rr.preference 75 | elif r_type == dpkt.dns.DNS_HINFO : 76 | print 'DNS_HINFO:',rr.text 77 | elif r_type == dpkt.dns.DNS_TXT : 78 | print "TEXT:",rr.text 79 | elif r_type == dpkt.dns.DNS_SRV : 80 | print 'DNS_SRV:',rr.srvname,rr.port,rr.priority,rr.weight 81 | else : 82 | print "Response type is something other than a CNAME, PTR, IPv4 address, or IPv6 address ...", r_type, 83 | if r_type in type_table : 84 | print type_table[r_type] 85 | print "r-data is ", r_data," in hex: ", hexify(r_data) 86 | else : 87 | print "Unknown" 88 | 89 | 90 | def main() : 91 | # This code allows this program to run equally well on my laptop and my desktop. I did it this 92 | # way to demonstrate different interface names. If I was really clever, I'd figure out how to do this 93 | # under MS-Windows 94 | if sys.argv[1] == "-i" : 95 | pc = pcap.pcap( sys.argv[2] ) 96 | elif sys.argv[1] == "-f" : 97 | pc = dpkt.pcap.Reader( open ( sys.argv[2] ) ) 98 | else : 99 | print """Use -i INTERFACE to [packet capture from an interface. 100 | Use -f FILENAME to read a packet capture file""" 101 | sys.exit(2) 102 | initialize_tables() 103 | 104 | for (src, sport, dst, dport, data ) in udp_iterator(pc) : 105 | # Uncomment if you want to see all UDP packets 106 | # print "from ", socket.inet_ntoa(src),":",sport, " to ", socket.inet_ntoa(dst),":",dport 107 | if dport == 53 : 108 | # UDP/53 is a DNS query 109 | dns = dpkt.dns.DNS(data) 110 | if dns.opcode != dpkt.dns.DNS_QUERY : 111 | print "A DNS packet was sent to the nameserver, but the opcode was %d instead of DNS_QUERY (this is a software error)" % dns.opcode 112 | if dns.qr != dpkt.dns.DNS_Q : 113 | print "A DNS packet was sent to the name server, but dns.qr is not 0 and should be. It is %d" % dns.qr 114 | print "query for ", dns.qd[0].name, "ID is ", dns.id, "dns.qr is ", dns.qr, "query type is ", dns.qd[0].type, type_table[dns.qd[0].type] 115 | print "dns.qd is ", dns.qd 116 | elif sport == 53 : 117 | # UDP/53 is a DNS response 118 | dns = dpkt.dns.DNS(data) 119 | print "responding to ", dns.id, "dns.qr is ", dns.qr 120 | if dns.qr != dpkt.dns.DNS_R : 121 | print "A DNS packet was received from a name server, but dns.qr is not 1 and should be. It is %d" % dns.qr 122 | if dns.get_rcode() == dpkt.dns.DNS_RCODE_NOERR : 123 | print "Response has no error" 124 | elif dns.get_rcode() == dpkt.dns.DNS_RCODE_NXDOMAIN : 125 | print "There is no name in this domain" 126 | else : 127 | print "Response is something other than NOERR or NXDOMAIN %d - this software is incomplete" % dns.get_rcode() 128 | print "The response packet has %d RRs" % len(dns.an) 129 | # Decode the RR records in the NS section 130 | for rr in dns.ns : 131 | decode_dns_response ( rr, "NS") 132 | # Decode the answers in the DNS answer 133 | for rr in dns.an : 134 | decode_dns_response ( rr, "AN" ) 135 | # Decode the additional responses 136 | for rr in dns.ar : 137 | decode_dns_response ( rr, "AR" ) 138 | print "dns.qd is ", dns.qd 139 | 140 | 141 | if __name__ == "__main__" : 142 | main() 143 | 144 | -------------------------------------------------------------------------------- /decode_mdns.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | #https://github.com/jeffsilverm/dpkt_doc/blob/master/decode_dns.py 3 | # 4 | # This program decodes DNS packets from the wire or from a capture file 5 | # A simple MDNS decoder is in https://gist.github.com/m-mizutani/1188242 6 | # -*- coding: utf-8 -*- 7 | 8 | import dpkt, dpkt.dns 9 | import sys 10 | import socket 11 | import pcap 12 | import subprocess 13 | import datetime 14 | 15 | type_table={} # This is a lookup table for DNS query types 16 | 17 | def initialize_tables() : 18 | global type_table 19 | 20 | 21 | # From http://www.networksorcery.com/enp/protocol/dns.htm 22 | type_table = {1:"A", # IP v4 address, RFC 1035 23 | 2:"NS", # Authoritative name server, RFC 1035 24 | 5:"CNAME", # Canonical name for an alias, RFC 1035 25 | 6:"SOA", # Marks the start of a zone of authority, RFC 1035 26 | 12:"PTR", # Domain name pointer, RFC 1035 27 | 13:"HINFO", # Host information, RFC 1035 28 | 15:"MX", # Mail exchange, RFC 1035 29 | 16:"TXT", # 30 | 28:"AAAA", # IP v6 address, RFC 3596 31 | 33:"SRV", # RFC 2782 32 | 255:"ANY", # all cached records, RFC 1035 33 | } 34 | 35 | def hexify(x): 36 | "The strings from DNS resolver contain non-ASCII characters - I don't know why. This function investigates that" 37 | toHex = lambda x:"".join([hex(ord(c))[2:].zfill(2) for c in x]) 38 | return toHex(x) 39 | 40 | def udp_iterator(pc): 41 | """pc is a pcap.pcap object that listens to the network and returns a packet object when it hears a packet go by""" 42 | for ts, pkt in pc: 43 | # parse the packet. Decode the ethertype. If it is IP (IPv4) then process it further 44 | eth = dpkt.ethernet.Ethernet(pkt) 45 | if eth.type == dpkt.ethernet.ETH_TYPE_IP : 46 | ip = eth.data 47 | # If the IP protocol is UDP, then process it further 48 | if ip.p == dpkt.ip.IP_PROTO_UDP : 49 | udp = ip.data 50 | # Pass the IP addresses, source port, destination port, and data back to the caller. 51 | yield ( ip.src, udp.sport, ip.dst, udp.dport, udp.data) 52 | 53 | 54 | def decode_dns_response ( rr, response_type ) : 55 | """This subroutine decodes a DNS response packet. The packet may have more than one rr""" 56 | r_type = rr.type 57 | r_data = rr.rdata 58 | if rr.cls >32768: 59 | print 'Cache-Flush:1', 60 | rr.cls -= 32768 #MSB of rclass repurposed in mdns 61 | if rr.cls != 1 : 62 | print "Response class is %d, not class IN, might be Hesiod, chaos, or qclass (all of which are anachronisms)"%rr.cls 63 | print "Response Type:", response_type ,r_type,type_table[r_type], 64 | if r_type == dpkt.dns.DNS_CNAME : 65 | print "CNAME ", r_data," in hex: ", hexify(r_data) 66 | elif r_type == dpkt.dns.DNS_A : #for mdns response print host name too 67 | print "IPv4 address", socket.inet_ntoa( r_data ),' for ',rr.name 68 | elif r_type == dpkt.dns.DNS_NS : 69 | #print "Response is a NS name", r_data," in hex: ", hexify(r_data) 70 | print "Response is a NS name", rr.nsname 71 | elif r_type == dpkt.dns.DNS_AAAA : 72 | print "IPv6 address", socket.inet_ntop( socket.AF_INET6, r_data ),' for ',rr.name 73 | elif r_type == dpkt.dns.DNS_PTR : 74 | # 75 | print 'Name ',rr.ptrname,"for IPv4 address", rr.name 76 | elif r_type == dpkt.dns.DNS_SOA : 77 | print rr.mname,rr.rname,rr.serial,rr.refresh,rr.retry,rr.expire, rr.minimum 78 | elif r_type == dpkt.dns.DNS_MX : 79 | print rr.mxname,rr.preference 80 | elif r_type == dpkt.dns.DNS_HINFO : 81 | print rr.text 82 | elif r_type == dpkt.dns.DNS_TXT : 83 | print "TEXT",rr.text 84 | elif r_type == dpkt.dns.DNS_SRV : 85 | print rr.srvname,rr.port,rr.priority,rr.weight 86 | else : 87 | print "Response type is something other than a CNAME, PTR, IPv4 address, or IPv6 address", r_type, 88 | if r_type in type_table : 89 | print type_table[r_type] 90 | print "r-data is ",'%r'% r_data," in hex: ", hexify(r_data) 91 | else : 92 | print "Unknown" 93 | 94 | def print_hdr(dns): 95 | print 'HDR: id=%5d op=%5d Q/R=%1d AA:%1d TC:%1d RD:%1d RA:%1d opcode=%2d rcode=%2d QC=%2d AC=%2d NC=%2d AR%2d'%\ 96 | (dns.id,dns.op,dns.qr,(dns.op & dpkt.dns.DNS_AA)>>10,(dns.op & dpkt.dns.DNS_TC)>>9,(dns.op & dpkt.dns.DNS_RD)>>8,(dns.op & dpkt.dns.DNS_RA)>>7,dns.opcode,dns.rcode,len(dns.qd),len(dns.an),len(dns.ns),len(dns.ar)) 97 | 98 | 99 | def main() : 100 | # This code allows this program to run equally well on my laptop and my desktop. I did it this 101 | # way to demonstrate different interface names. If I was really clever, I'd figure out how to do this 102 | # under MS-Windows 103 | if sys.argv[1] == "-i" : 104 | pc = pcap.pcap( sys.argv[2] ) 105 | elif sys.argv[1] == "-f" : 106 | pc = dpkt.pcap.Reader( open ( sys.argv[2] ) ) 107 | else : 108 | print """Use -i INTERFACE to [packet capture from an interface. 109 | Use -f FILENAME to read a packet capture file""" 110 | sys.exit(2) 111 | initialize_tables() 112 | mdns_ip=socket.inet_aton('224.0.0.251'); 113 | 114 | for (src, sport, dst, dport, data ) in udp_iterator(pc) : 115 | # Uncomment if you want to see all UDP packets 116 | #print "from ", socket.inet_ntoa(src),":",sport, " to ", socket.inet_ntoa(dst),":",dport 117 | if src != mdns_ip and dst != mdns_ip and dport != 5353 and sport != 5353:continue 118 | assert dport == 5353 or sport == 5353 119 | dns = dpkt.dns.DNS(data) 120 | print '\n',datetime.datetime.now(), socket.inet_ntoa(src),":",sport, " to ", socket.inet_ntoa(dst),":",dport 121 | print_hdr(dns) 122 | #print dns.__repr__() 123 | #print '%r' % data 124 | if dns.qr == 0: 125 | #print "from ", socket.inet_ntoa(src),":",sport, " to ", socket.inet_ntoa(dst),":",dport,type(data),len(data),'%r' % data 126 | # UDP/53 is a DNS query 127 | if dns.opcode != dpkt.dns.DNS_QUERY : 128 | print "A DNS packet was sent to the nameserver, but the opcode was %d instead of DNS_QUERY (this is a software error)" % dns.opcode,'dns.id is',dns.id 129 | if dns.qr != dpkt.dns.DNS_Q : 130 | print "A DNS packet was sent to the name server, but dns.qr is not 0 and should be. It is %d" % dns.qr 131 | if (len(dns.qd)>0): 132 | for i in range(len(dns.qd)): 133 | print "query for ", dns.qd[i].name, "query type is ", dns.qd[i].type, type_table[dns.qd[i].type] 134 | else: 135 | print "query for ", '????' 136 | if dns.qr == 1 or len(dns.an)>0: 137 | # UDP/53 is a DNS response 138 | if dns.qr != dpkt.dns.DNS_R : #this is not an error, a query packet may contain answers 139 | print "A DNS packet was received from a name server, but dns.qr is not 1 and should be. It is %d" % dns.qr 140 | if dns.rcode == dpkt.dns.DNS_RCODE_NOERR : 141 | pass#print "no error", 142 | elif dns.rcode == dpkt.dns.DNS_RCODE_NXDOMAIN : 143 | print "There is no name in this domain" 144 | else : 145 | print "Response is something other than NOERR or NXDOMAIN %d - this software is incomplete" % dns.get_rcode() 146 | # Decode the RR records in the NS section 147 | for rr in dns.ns : 148 | decode_dns_response ( rr, "NS") 149 | # Decode the answers in the DNS answer 150 | i=0 151 | for rr in dns.an : 152 | #print 'RR[%d]:%r'%(i,rr);i +=1 153 | decode_dns_response ( rr, "AN" ) 154 | # Decode the additional responses 155 | for rr in dns.ar : 156 | decode_dns_response ( rr, "AR" ) 157 | 158 | 159 | if __name__ == "__main__" : 160 | main() 161 | 162 | -------------------------------------------------------------------------------- /decode_tcp_iterator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # This module implements a TCP receiver. Refer to RFC 793 http://www.rfc-editor.org/rfc/rfc793.txt for details. 4 | 5 | import dpkt 6 | import sys 7 | import socket 8 | import pcap 9 | import struct 10 | 11 | def connection_id_to_str (cid, v=4) : 12 | """This converts the connection ID cid which is a tuple of (source_ip_address, source_tcp_port, destination_ip_address, 13 | destination_tcp_port) to a string. v is either 4 for IPv4 or 6 for IPv6""" 14 | if v == 4 : 15 | src_ip_addr_str = socket.inet_ntoa(cid[0]) 16 | dst_ip_addr_str = socket.inet_ntoa(cid[2]) 17 | return src_ip_addr_str + ":" + str(cid[1])+"=>"+dst_ip_addr_str + ":" + str(cid[3]) 18 | elif v == 6 : 19 | src_ip_addr_str = socket.inet_ntop(AF_INET6, cid[0]) 20 | dst_ip_addr_str = socket.inet_ntop(AF_INET6, cid[2]) 21 | return src_ip_addr_str + "." + str(cid[1])+"=>"+dst_ip_addr_str + "." + str(cid[3]) 22 | else : 23 | raise ValueError('Argument to connection_id_to_str must be 4 or 6, is %d' % v) 24 | 25 | class Connection_object : 26 | """A connection object stores the state of the tcp connection""" 27 | def __init__ ( self, isn, seq, string ) : 28 | self.isn = isn # initial sequence number. All sequence numbers are relative to this number. 29 | self.seq = seq # last sequence number seen. I'm not sure I need to keep this. 30 | self.buffer = { seq: string } # the keys are the relative sequence numbers, the values are the strings 31 | 32 | 33 | def assemble_buffer( buffer_dictionary ) : 34 | """The buffer dictionary contains segment numbers, which are byte offsets into the stream, and the bytes that the offsets point to. This 35 | function assembles the buffer into order. It should raise an exception if there is missing data - this is not implemented""" 36 | return_buffer = "" 37 | for segment in sorted( buffer_dictionary.keys() ) : 38 | read_end = len(return_buffer) 39 | if read_end+1 != segment : 40 | print "There is a segment missing between %d (already read) and %d (current segment beginning)" % ( read_end, segment ) 41 | return_buffer = return_buffer + buffer_dictionary[segment] 42 | return return_buffer 43 | 44 | def get_message_segment_size (options ) : 45 | """get the maximum segment size from the options list""" 46 | options_list = dpkt.tcp.parse_opts ( options ) 47 | for option in options_list : 48 | if option[0] == 2 : 49 | # The MSS is a 16 bit number. Look at RFC 793 http://www.rfc-editor.org/rfc/rfc793.txt page 17. dpkt decodes it as a 16 50 | # bit number. An MSS is never going to be bigger than 65496 bytes. 51 | # The most common value is 1460 bytes (IPv4) which 0x05b4 or 1440 bytes (IPv6) which is 0x05a0. 52 | mss = struct.unpack(">H", option[1]) 53 | return mss 54 | 55 | 56 | def decode_tcp(pcap): 57 | """This function decodes a packet capture file f and breaks it up into tcp connections""" 58 | print "counter\tsrc prt\tdst prt\tflags" 59 | packet_cntr = 0 60 | connection_table = {} # the keys of the table are the connection ID strings: source IP, 61 | # source port, destination IP, destination port. The values are a tuple which is the 62 | # sequence number and a string which is the assembled stream 63 | 64 | for ts, buf in pcap: 65 | packet_cntr += 1 66 | eth = dpkt.ethernet.Ethernet(buf) 67 | # Also, this changes a little bit with IPv6. To tell the difference between IPv4 and IPv6, you have to look 68 | # at the ethertype field, which is given by http://www.iana.org/assignments/ethernet-numbers. IPv4 is 0x800 or 2048 69 | # and IPv6 is 0x86DD or 34525 70 | # This is simplistic - IPv4 packets can be fragmented. Also, this only works for IPv4. IPv6 has a different Ethertype 71 | if eth.type == dpkt.ethernet.ETH_TYPE_IP : 72 | ip = eth.data 73 | if ip.v != 4 : 74 | raise ValueError, "In packet %d, the ether type is IPv4 but the IP version number is %d not 4" % ( 75 | packet_cntr, ip.v ) 76 | # Deal with IP fragmentation here 77 | elif eth.type == dpkt.ethernet.ETH_TYPE_IP6 : 78 | ip = eth.data 79 | if ip.v != 6 : 80 | raise ValueError, "In packet %d, the ether type is IPv6 but the IP version number is %d not 6" % ( 81 | packet_cntr, ip.v ) 82 | # IPv6 packets don't fragment 83 | else : 84 | print "packet %d is neither IPv4 nor IPv6" % packet_cntr 85 | continue # Not going to deal with anything other than IP 86 | if ip.p == dpkt.ip.IP_PROTO_TCP : 87 | tcp = ip.data 88 | fin_flag = ( tcp.flags & dpkt.tcp.TH_FIN ) != 0 89 | syn_flag = ( tcp.flags & dpkt.tcp.TH_SYN ) != 0 90 | rst_flag = ( tcp.flags & dpkt.tcp.TH_RST ) != 0 91 | psh_flag = ( tcp.flags & dpkt.tcp.TH_PUSH) != 0 92 | ack_flag = ( tcp.flags & dpkt.tcp.TH_ACK ) != 0 93 | urg_flag = ( tcp.flags & dpkt.tcp.TH_URG ) != 0 94 | ece_flag = ( tcp.flags & dpkt.tcp.TH_ECE ) != 0 95 | cwr_flag = ( tcp.flags & dpkt.tcp.TH_CWR ) != 0 96 | # The flags string is really for debugging 97 | flags = ( 98 | ( "C" if cwr_flag else " " ) + 99 | ( "E" if ece_flag else " " ) + 100 | ( "U" if urg_flag else " " ) + 101 | ( "A" if ack_flag else " " ) + 102 | ( "P" if psh_flag else " " ) + 103 | ( "R" if rst_flag else " " ) + 104 | ( "S" if syn_flag else " " ) + 105 | ( "F" if fin_flag else " " ) ) 106 | if syn_flag and not ack_flag : 107 | # Each TCP connection is forming. The new connection is stored as an object in a dictionary 108 | # whose key is the tuple (source_ip_address, source_tcp_port, destination_ip_address, destination_tcp_port) 109 | # The connection is stored in a dictionary. The key is the connection_id, value of each key is an object with fields for the 110 | # current connection state and the total of all the bytes that have been sent 111 | # Note that there are two connections, one from the client to the server and one from the server to the client. This becomes 112 | # important when the connection is closed, because one side might FIN the connection well before the other side does. 113 | connection_id = (ip.src, tcp.sport, ip.dst, tcp.dport) 114 | print "Forming a new connection " + connection_id_to_str( connection_id, ip.v ) + " Initial Sequence Number (ISN) is %d" % tcp.seq 115 | connection_table[connection_id] = Connection_object ( isn = tcp.seq, seq = tcp.seq, string = "" ) 116 | print "Message segment size client side is ", get_message_segment_size ( tcp.opts ) 117 | elif syn_flag and ack_flag : 118 | connection_id = (ip.src, tcp.sport, ip.dst, tcp.dport) 119 | print "Server responding to a new connection " + connection_id_to_str( connection_id, ip.v ) + " Initial Sequence Number (ISN) is %d" % tcp.seq 120 | connection_table[connection_id] = Connection_object ( isn = tcp.seq, seq = tcp.seq, string = "" ) 121 | print "Message segment size client side is ", get_message_segment_size ( tcp.opts ) 122 | 123 | # This is where I am having a little confusion. My instinct tells me that the connection from the client to the server and the 124 | # connection from the server back to the client should be connected somehow. But they aren't, except for the SYN-ACK 125 | # packet. Otherwise, the source IP, destination IP, source port and destination port are mirror images, but the streams 126 | # are separate. The acknowlegement numbers are related, but we don't need to worry about acknowlegements 127 | elif not syn_flag and ack_flag : 128 | sequence_number = tcp.seq 129 | byte_offset = sequence_number - connection_table[connection_id].isn 130 | print flags+" Absolute sequence number %d ISN %d relative sequence number %d" % (sequence_number, connection_table[connection_id].isn, byte_offset) 131 | connection_table[connection_id].buffer[byte_offset] = tcp.data 132 | connection_table[connection_id].seq = sequence_number 133 | # if the push flag is set, then return the string to the caller, along with identifying information so that the 134 | # caller knows which connection is getting data returned. 135 | if psh_flag or urg_flag : 136 | connection_string = assemble_buffer( connection_table[connection_id].buffer ) 137 | yield ( connection_id, connection_string ) 138 | 139 | def decode_tcp_help() : 140 | print """To run this program, give the command 141 | python """+sys.argv[0]+""" FILENAME.py INPUT 142 | where INPUT is either a PCAP file, or eth0: for a wired ethernet or wlan0: for 143 | a wireless (WiFi) network connection""" 144 | sys.exit(1) 145 | 146 | 147 | def main(pc) : 148 | """This is the outer loop that prints strings that have been captured from the TCP streams, terminated by a packet that 149 | has the PUSH flag set.""" 150 | for connection_id, received_string in decode_tcp(pc) : 151 | print connection_id_to_str (connection_id, 4), received_string # This won't work for IPv6 152 | 153 | 154 | 155 | if __name__ == "__main__" : 156 | if len(sys.argv) < 2 : 157 | decode_tcp_help() 158 | # create an interator to return the next packet. The source can be either an interface using the libpcap library or it can be a file in pcap 159 | # format such as created by tcpdump. 160 | if sys.argv[1] == "eth0" : # wired ethernet interface 161 | pc = pcap.pcap("eth0", promisc=True ) 162 | elif sys.argv[1] == "wlan0" : # wireless interface 163 | pc = pcap.pcap("wlan0", promisc=True ) 164 | else : 165 | pc = dpkt.pcap.Reader ( open(sys.argv[1] ) ) # file interface 166 | main(pc) 167 | 168 | -------------------------------------------------------------------------------- /decode_tcp_iterator_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # This module implements a TCP receiver. Refer to RFC 793 http://www.rfc-editor.org/rfc/rfc793.txt for details. 4 | 5 | import dpkt 6 | import sys 7 | import socket 8 | import pcap 9 | import struct 10 | 11 | def connection_id_to_str (cid, v=4) : 12 | """This converts the connection ID cid which is a tuple of (source_ip_address, source_tcp_port, destination_ip_address, 13 | destination_tcp_port) to a string. v is either 4 for IPv4 or 6 for IPv6""" 14 | if v == 4 : 15 | src_ip_addr_str = socket.inet_ntoa(cid[0]) 16 | dst_ip_addr_str = socket.inet_ntoa(cid[2]) 17 | return src_ip_addr_str + ":" + str(cid[1])+"=>"+dst_ip_addr_str + ":" + str(cid[3]) 18 | elif v == 6 : 19 | src_ip_addr_str = socket.inet_ntop(socket.AF_INET6, cid[0]) 20 | dst_ip_addr_str = socket.inet_ntop(socket.AF_INET6, cid[2]) 21 | return src_ip_addr_str + "." + str(cid[1])+"=>"+dst_ip_addr_str + "." + str(cid[3]) 22 | else : 23 | raise ValueError('Argument to connection_id_to_str must be 4 or 6, is %d' % v) 24 | 25 | class Connection_object : 26 | """A connection object stores the state of the tcp connection""" 27 | def __init__ ( self, isn, seq, string ) : 28 | self.isn = isn # initial sequence number. All sequence numbers are relative to this number. 29 | self.seq = seq # last sequence number seen. I'm not sure I need to keep this. 30 | self.buffer = { seq: string } # the keys are the relative sequence numbers, the values are the strings 31 | 32 | class ProtocolException(Exception): 33 | """Raise this exception if a protocol error is detected, although it is more likely that my software is broken""" 34 | def __init__(self, value): 35 | self.parameter = value 36 | def __str__(self): 37 | return repr(self.parameter) 38 | 39 | def assemble_buffer( buffer_dictionary ) : 40 | """The buffer dictionary contains segment numbers, which are byte offsets into the stream, and the bytes that the offsets point to. This 41 | function assembles the buffer into order. It should raise an exception if there is missing data - this is not implemented""" 42 | return_buffer = "" 43 | for segment in sorted( buffer_dictionary.keys() ) : 44 | read_end = len(return_buffer) 45 | if read_end+1 != segment : 46 | print "There is a segment missing between %d (already read) and %d (current segment beginning)" % ( read_end, segment ) 47 | return_buffer = return_buffer + buffer_dictionary[segment] 48 | return return_buffer 49 | 50 | def get_message_segment_size (options ) : 51 | """get the maximum segment size from the options list""" 52 | options_list = dpkt.tcp.parse_opts ( options ) 53 | for option in options_list : 54 | if option[0] == 2 : 55 | # The MSS is a 16 bit number dpkt decodes it as a 16 56 | # bit number. An MSS is never going to be bigger than 65496 bytes. 57 | # The most common value is 1460 bytes (IPv4) which 0x05b4 or 1440 bytes (IPv6) which is 0x05a0. The format string ">H" means 58 | # big-endian unsigned 16 bit number. It should be ">L" which is big-endian 32 bit number. 59 | mss = struct.unpack(">H", option[1]) 60 | return mss 61 | 62 | def unpack_tcp_packet ( tcp ) : 63 | """tcp packets are the same between IPv4 and IPv6. This function handles decoding tcp packets""" 64 | 65 | # We need to get more packets, so return to decode_tcp 66 | return 67 | 68 | def decode_tcp(pcap): 69 | """This function decodes a packet capture file pcap and breaks it up into tcp connections""" 70 | print "counter\tsrc prt\tdst prt\tflags" 71 | packet_cntr = 0 72 | connection_table = {} # the keys of the table are the connection ID strings: source IP, 73 | # source port, destination IP, destination port. The values are a tuple which is the 74 | # sequence number and a string which is the assembled stream 75 | 76 | for ts, buf in pcap: 77 | packet_cntr += 1 78 | eth = dpkt.ethernet.Ethernet(buf) 79 | # Also, this changes a little bit with IPv6. To tell the difference between IPv4 and IPv6, you have to look 80 | # at the ethertype field, which is given by http://www.iana.org/assignments/ethernet-numbers. IPv4 is 0x800 or 2048 81 | # and IPv6 is 0x86DD or 34525 82 | # This is simplistic - IPv4 packets can be fragmented. Also, this only works for IPv4. IPv6 has a different Ethertype 83 | # Part of the genius of dpkt is that if you have an ethernet packet with an IPv4 payload, you get an dpkt.ip object, but if you 84 | # have an ethernet packet with an IPv6 payload, then you get an dpkt.ip6 object, which has different fields names. 85 | # Note how similar IPv4 and IPv6 are. 86 | if eth.type == dpkt.ethernet.ETH_TYPE_IP : 87 | ip = eth.data 88 | if ip.v != 4 : 89 | raise ValueError, "In packet %d, the ether type is IPv4 but the IP version number is %d not 4" % ( 90 | packet_cntr, ip.v ) 91 | # Deal with IP fragmentation here 92 | if ip.p == dpkt.ip.IP_PROTO_TCP : 93 | tcp = ip.data 94 | else : 95 | # Some other protocol than TCP, such as UDP. See http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml. 96 | print "packet %d is IPv6 but not TCP. Orotocol field is %d" % (packet_cntr, ip.p ) 97 | continue 98 | elif eth.type == dpkt.ethernet.ETH_TYPE_IP6 : 99 | ip = eth.data 100 | if ip.v != 6 : 101 | raise ValueError, "In packet %d, the ether type is IPv6 but the IP version number is %d not 6" % ( 102 | packet_cntr, ip6.v ) 103 | # IPv6 packets don't fragment 104 | if ip.nxt == dpkt.ip.IP_PROTO_TCP : # The ip6.nxt field in IPv6 is similar to the IPv4 ip.p field 105 | tcp = ip.data 106 | else : 107 | # Some other protocol than TCP, such as UDP. See http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml. 108 | print "packet %d is IPv6 but not TCP. Next header field is %d" % (packet_cntr, ip.nxt ) 109 | continue 110 | else : 111 | print "packet %d is neither IPv4 nor IPv6" % packet_cntr 112 | continue # Not going to deal with anything other than IP 113 | # At this point, we have a TCP packet, which is independent of IPv4 or IPv6 (except we need the source and destination addresses). Form a 114 | # connection ID so we know where to store this packet. 115 | connection_id = (ip.src, tcp.sport, ip.dst, tcp.dport) 116 | fin_flag = ( tcp.flags & dpkt.tcp.TH_FIN ) != 0 117 | syn_flag = ( tcp.flags & dpkt.tcp.TH_SYN ) != 0 118 | rst_flag = ( tcp.flags & dpkt.tcp.TH_RST ) != 0 119 | psh_flag = ( tcp.flags & dpkt.tcp.TH_PUSH) != 0 120 | ack_flag = ( tcp.flags & dpkt.tcp.TH_ACK ) != 0 121 | urg_flag = ( tcp.flags & dpkt.tcp.TH_URG ) != 0 122 | ece_flag = ( tcp.flags & dpkt.tcp.TH_ECE ) != 0 123 | cwr_flag = ( tcp.flags & dpkt.tcp.TH_CWR ) != 0 124 | # The flags string is really for debugging 125 | flags = ( 126 | ( "C" if cwr_flag else " " ) + 127 | ( "E" if ece_flag else " " ) + 128 | ( "U" if urg_flag else " " ) + 129 | ( "A" if ack_flag else " " ) + 130 | ( "P" if psh_flag else " " ) + 131 | ( "R" if rst_flag else " " ) + 132 | ( "S" if syn_flag else " " ) + 133 | ( "F" if fin_flag else " " ) ) 134 | if syn_flag and not ack_flag : 135 | # Each TCP connection is forming. The new connection is stored as an object in a dictionary 136 | # whose key is the tuple (source_ip_address, source_tcp_port, destination_ip_address, destination_tcp_port) 137 | # The connection is stored in a dictionary. The key is the connection_id, value of each key is an object with fields for the 138 | # current connection state and the total of all the bytes that have been sent 139 | # Note that there are two connections, one from the client to the server and one from the server to the client. This becomes 140 | # important when the connection is closed, because one side might FIN the connection well before the other side does. 141 | print "Forming a new connection " + connection_id_to_str( connection_id, ip.v ) + " Initial Sequence Number (ISN) is %d" % tcp.seq 142 | connection_table[connection_id] = Connection_object ( isn = tcp.seq, seq = tcp.seq, string = "" ) 143 | print "Message segment size client side is ", get_message_segment_size ( tcp.opts ) 144 | elif syn_flag and ack_flag : 145 | print "Server responding to a new connection " + connection_id_to_str( connection_id, ip.v ) + " Initial Sequence Number (ISN) is %d" % tcp.seq 146 | connection_table[connection_id] = Connection_object ( isn = tcp.seq, seq = tcp.seq, string = "" ) 147 | print "Message segment size client side is ", get_message_segment_size ( tcp.opts ) 148 | 149 | # This is where I am having a little confusion. My instinct tells me that the connection from the client to the server and the 150 | # connection from the server back to the client should be connected somehow. But they aren't, except for the SYN-ACK 151 | # packet. Otherwise, the source IP, destination IP, source port and destination port are mirror images, but the streams 152 | # are separate. The acknowlegement numbers are related, but we don't need to worry about acknowlegements 153 | # Technically, I don't need to test for the ACK flag since it always set. 154 | elif not syn_flag and ack_flag : 155 | sequence_number = tcp.seq 156 | byte_offset = sequence_number - connection_table[connection_id].isn 157 | print flags+" Absolute sequence number %d ISN %d relative sequence number %d" % (sequence_number, connection_table[connection_id].isn, byte_offset) 158 | connection_table[connection_id].buffer[byte_offset] = tcp.data 159 | connection_table[connection_id].seq = sequence_number 160 | # if the push flag or urg flag is set, then return the string to the caller, along with identifying information so that the 161 | # caller knows which connection is getting data returned. 162 | if psh_flag or urg_flag : 163 | connection_string = assemble_buffer( connection_table[connection_id].buffer ) 164 | yield ( connection_id, connection_string, ip.v ) 165 | else : 166 | # syn_flag is clear and ack_flag is clear. This is probably a software in my software. 167 | raise ProtocolException ( "In packet %d, SYN is clear and ACK is clear. This is terrible" % packet_cntr ) 168 | 169 | 170 | def main(pc) : 171 | """This is the outer loop that prints strings that have been captured from the TCP streams, terminated by a packet that 172 | has the PUSH flag set.""" 173 | for connection_id, received_string, ip_version in decode_tcp(pc) : 174 | print connection_id_to_str (connection_id, ip_version), received_string 175 | 176 | 177 | 178 | if __name__ == "__main__" : 179 | if len(sys.argv) == 3 : 180 | if sys.argv[1] == "-i" : 181 | # create an interator to return the next packet. The source can be either an interface using the libpcap library or it can be a file in pcap 182 | # format such as created by tcpdump. 183 | pc = pcap.pcap( sys.argv[2] ) 184 | elif sys.argv[1] == "-f" : 185 | pc = dpkt.pcap.Reader( open ( sys.argv[2] ) ) 186 | else : 187 | print """Use -i INTERFACE to packet capture from an interface. 188 | Use -f FILENAME to read a packet capture file""" 189 | sys.exit(2) 190 | else : 191 | print """Use -i INTERFACE to packet capture from an interface. 192 | Use -f FILENAME to read a packet capture file""" 193 | sys.exit(2) 194 | 195 | main(pc) 196 | 197 | -------------------------------------------------------------------------------- /decode_tcp_iterator_2P.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # This module implements a TCP receiver. Refer to RFC 793 http://www.rfc-editor.org/rfc/rfc793.txt for details. 4 | # This is a production version, with all the debugging print statements removed 5 | 6 | import dpkt 7 | import sys 8 | import socket 9 | import pcap 10 | import struct 11 | 12 | def connection_id_to_str (cid, v=4) : 13 | """This converts the connection ID cid which is a tuple of (source_ip_address, source_tcp_port, destination_ip_address, 14 | destination_tcp_port) to a string. v is either 4 for IPv4 or 6 for IPv6""" 15 | if v == 4 : 16 | src_ip_addr_str = socket.inet_ntoa(cid[0]) 17 | dst_ip_addr_str = socket.inet_ntoa(cid[2]) 18 | return src_ip_addr_str + ":" + str(cid[1])+"=>"+dst_ip_addr_str + ":" + str(cid[3]) 19 | elif v == 6 : 20 | src_ip_addr_str = socket.inet_ntop(socket.AF_INET6, cid[0]) 21 | dst_ip_addr_str = socket.inet_ntop(socket.AF_INET6, cid[2]) 22 | return src_ip_addr_str + "." + str(cid[1])+"=>"+dst_ip_addr_str + "." + str(cid[3]) 23 | else : 24 | raise ValueError('Argument to connection_id_to_str must be 4 or 6, is %d' % v) 25 | 26 | class Connection_object : 27 | """A connection object stores the state of the tcp connection""" 28 | def __init__ ( self, isn, seq, string ) : 29 | self.isn = isn # initial sequence number. All sequence numbers are relative to this number. 30 | self.seq = seq # last sequence number seen. I'm not sure I need to keep this. 31 | self.buffer = { seq: string } # the keys are the relative sequence numbers, the values are the strings 32 | 33 | class ProtocolException(Exception): 34 | """Raise this exception if a protocol error is detected, although it is more likely that my software is broken""" 35 | def __init__(self, value): 36 | self.parameter = value 37 | def __str__(self): 38 | return repr(self.parameter) 39 | 40 | def assemble_buffer( buffer_dictionary ) : 41 | """The buffer dictionary contains segment numbers, which are byte offsets into the stream, and the bytes that the offsets point to. This 42 | function assembles the buffer into order. It should raise an exception if there is missing data - this is not implemented""" 43 | return_buffer = "" 44 | for segment in sorted( buffer_dictionary.keys() ) : 45 | read_end = len(return_buffer) 46 | if read_end+1 != segment : 47 | print "There is a segment missing between %d (already read) and %d (current segment beginning)" % ( read_end, segment ) 48 | return_buffer = return_buffer + buffer_dictionary[segment] 49 | return return_buffer 50 | 51 | def get_message_segment_size (options ) : 52 | """get the maximum segment size from the options list""" 53 | options_list = dpkt.tcp.parse_opts ( options ) 54 | for option in options_list : 55 | if option[0] == 2 : 56 | # The MSS is a 16 bit number dpkt decodes it as a 16 57 | # bit number. An MSS is never going to be bigger than 65496 bytes. 58 | # The most common value is 1460 bytes (IPv4) which 0x05b4 or 1440 bytes (IPv6) which is 0x05a0. The format string ">H" means 59 | # big-endian unsigned 16 bit number. It should be ">L" which is big-endian 32 bit number. 60 | mss = struct.unpack(">H", option[1]) 61 | return mss 62 | 63 | def unpack_tcp_packet ( tcp ) : 64 | """tcp packets are the same between IPv4 and IPv6. This function handles decoding tcp packets""" 65 | 66 | # We need to get more packets, so return to decode_tcp 67 | return 68 | 69 | def decode_tcp(pcap): 70 | """This function decodes a packet capture file pcap and breaks it up into tcp connections""" 71 | # print "counter\tsrc prt\tdst prt\tflags" 72 | packet_cntr = 0 73 | connection_table = {} # the keys of the table are the connection ID strings: source IP, 74 | # source port, destination IP, destination port. The values are a tuple which is the 75 | # sequence number and a string which is the assembled stream 76 | 77 | for ts, buf in pcap: 78 | packet_cntr += 1 79 | eth = dpkt.ethernet.Ethernet(buf) 80 | # Also, this changes a little bit with IPv6. To tell the difference between IPv4 and IPv6, you have to look 81 | # at the ethertype field, which is given by http://www.iana.org/assignments/ethernet-numbers. IPv4 is 0x800 or 2048 82 | # and IPv6 is 0x86DD or 34525 83 | # This is simplistic - IPv4 packets can be fragmented. Also, this only works for IPv4. IPv6 has a different Ethertype 84 | # Part of the genius of dpkt is that if you have an ethernet packet with an IPv4 payload, you get an dpkt.ip object, but if you 85 | # have an ethernet packet with an IPv6 payload, then you get an dpkt.ip6 object, which has different fields names. 86 | # Note how similar IPv4 and IPv6 are. 87 | if eth.type == dpkt.ethernet.ETH_TYPE_IP : 88 | ip = eth.data 89 | if ip.v != 4 : 90 | raise ValueError, "In packet %d, the ether type is IPv4 but the IP version number is %d not 4" % ( 91 | packet_cntr, ip.v ) 92 | # Deal with IP fragmentation here 93 | if ip.p == dpkt.ip.IP_PROTO_TCP : 94 | tcp = ip.data 95 | else : 96 | # Some other protocol than TCP, such as UDP. See http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml. 97 | print "packet %d is IPv6 but not TCP. Protocol field is %d" % (packet_cntr, ip.p ) 98 | continue 99 | elif eth.type == dpkt.ethernet.ETH_TYPE_IP6 : 100 | ip = eth.data 101 | if ip.v != 6 : 102 | raise ValueError, "In packet %d, the ether type is IPv6 but the IP version number is %d not 6" % ( 103 | packet_cntr, ip6.v ) 104 | # IPv6 packets don't fragment 105 | if ip.nxt == dpkt.ip.IP_PROTO_TCP : # The ip6.nxt field in IPv6 is similar to the IPv4 ip.p field 106 | tcp = ip.data 107 | else : 108 | # Some other protocol than TCP, such as UDP. See http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml. 109 | print "packet %d is IPv6 but not TCP. Next header field is %d" % (packet_cntr, ip.nxt ) 110 | continue 111 | else : 112 | print "packet %d is neither IPv4 nor IPv6" % packet_cntr 113 | continue # Not going to deal with anything other than IP 114 | # At this point, we have a TCP packet, which is independent of IPv4 or IPv6 (except we need the source and destination addresses). Form a 115 | # connection ID so we know where to store this packet. 116 | connection_id = (ip.src, tcp.sport, ip.dst, tcp.dport) 117 | fin_flag = ( tcp.flags & dpkt.tcp.TH_FIN ) != 0 118 | syn_flag = ( tcp.flags & dpkt.tcp.TH_SYN ) != 0 119 | rst_flag = ( tcp.flags & dpkt.tcp.TH_RST ) != 0 120 | psh_flag = ( tcp.flags & dpkt.tcp.TH_PUSH) != 0 121 | ack_flag = ( tcp.flags & dpkt.tcp.TH_ACK ) != 0 122 | urg_flag = ( tcp.flags & dpkt.tcp.TH_URG ) != 0 123 | ece_flag = ( tcp.flags & dpkt.tcp.TH_ECE ) != 0 124 | cwr_flag = ( tcp.flags & dpkt.tcp.TH_CWR ) != 0 125 | # The flags string is really for debugging 126 | flags = ( 127 | ( "C" if cwr_flag else " " ) + 128 | ( "E" if ece_flag else " " ) + 129 | ( "U" if urg_flag else " " ) + 130 | ( "A" if ack_flag else " " ) + 131 | ( "P" if psh_flag else " " ) + 132 | ( "R" if rst_flag else " " ) + 133 | ( "S" if syn_flag else " " ) + 134 | ( "F" if fin_flag else " " ) ) 135 | if syn_flag and not ack_flag : 136 | # Each TCP connection is forming. The new connection is stored as an object in a dictionary 137 | # whose key is the tuple (source_ip_address, source_tcp_port, destination_ip_address, destination_tcp_port) 138 | # The connection is stored in a dictionary. The key is the connection_id, value of each key is an object with fields for the 139 | # current connection state and the total of all the bytes that have been sent 140 | # Note that there are two connections, one from the client to the server and one from the server to the client. This becomes 141 | # important when the connection is closed, because one side might FIN the connection well before the other side does. 142 | print "Forming a new connection " + connection_id_to_str( connection_id, ip.v ) + " Initial Sequence Number (ISN) is %d" % tcp.seq 143 | connection_table[connection_id] = Connection_object ( isn = tcp.seq, seq = tcp.seq, string = "" ) 144 | print "Message segment size client side is ", get_message_segment_size ( tcp.opts ) 145 | elif syn_flag and ack_flag : 146 | print "Server responding to a new connection " + connection_id_to_str( connection_id, ip.v ) + " Initial Sequence Number (ISN) is %d" % tcp.seq 147 | connection_table[connection_id] = Connection_object ( isn = tcp.seq, seq = tcp.seq, string = "" ) 148 | print "Message segment size client side is ", get_message_segment_size ( tcp.opts ) 149 | 150 | # This is where I am having a little confusion. My instinct tells me that the connection from the client to the server and the 151 | # connection from the server back to the client should be connected somehow. But they aren't, except for the SYN-ACK 152 | # packet. Otherwise, the source IP, destination IP, source port and destination port are mirror images, but the streams 153 | # are separate. The acknowlegement numbers are related, but we don't need to worry about acknowlegements 154 | # Technically, I don't need to test for the ACK flag since it always set. 155 | elif not syn_flag and ack_flag : 156 | sequence_number = tcp.seq 157 | byte_offset = sequence_number - connection_table[connection_id].isn 158 | # print flags+" Absolute sequence number %d ISN %d relative sequence number %d" % (sequence_number, connection_table[connection_id].isn, byte_offset) 159 | connection_table[connection_id].buffer[byte_offset] = tcp.data 160 | connection_table[connection_id].seq = sequence_number 161 | # if the push flag or urg flag is set, then return the string to the caller, along with identifying information so that the 162 | # caller knows which connection is getting data returned. 163 | if psh_flag or urg_flag : 164 | connection_string = assemble_buffer( connection_table[connection_id].buffer ) 165 | yield ( connection_id, connection_string, ip.v ) 166 | else : 167 | # syn_flag is clear and ack_flag is clear. This is probably a software in my software. 168 | raise ProtocolException ( "In packet %d, SYN is clear and ACK is clear. This is terrible" % packet_cntr ) 169 | 170 | 171 | def main(pc) : 172 | """This is the outer loop that prints strings that have been captured from the TCP streams, terminated by a packet that 173 | has the PUSH flag set.""" 174 | for connection_id, received_string, ip_version in decode_tcp(pc) : 175 | print connection_id_to_str (connection_id, ip_version), received_string 176 | 177 | 178 | 179 | if __name__ == "__main__" : 180 | if len(sys.argv) == 3 : 181 | if sys.argv[1] == "-i" : 182 | # create an interator to return the next packet. The source can be either an interface using the libpcap library or it can be a file in pcap 183 | # format such as created by tcpdump. 184 | pc = pcap.pcap( sys.argv[2] ) 185 | elif sys.argv[1] == "-f" : 186 | pc = dpkt.pcap.Reader( open ( sys.argv[2] ) ) 187 | else : 188 | print """Use -i INTERFACE to packet capture from an interface. 189 | Use -f FILENAME to read a packet capture file""" 190 | sys.exit(2) 191 | else : 192 | print """Use -i INTERFACE to packet capture from an interface. 193 | Use -f FILENAME to read a packet capture file""" 194 | sys.exit(2) 195 | 196 | main(pc) 197 | 198 | -------------------------------------------------------------------------------- /dpkt_notes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 6 |eth = dpkt.ethernet.Ethernet(buf)72 |
ip = eth.data
tcp = ip.data
# If the destination port is 80 and there is data in the packet, then probably this is an HTTP request. TO be certain, there should
# be a TCP finite state machine in here.
if tcp.dport == 80 and len(tcp.data) > 0:
http_req = dpkt.http.Request(tcp.data)
The dpkt.http.Request object has the following attributes:
73 |
['_Request__methods', '_Request__proto', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__hash__', '__hdr_defaults__', '__init__', '__len__', '__metaclass__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'body', 'data', 'headers', 'method', 'pack', 'pack_hdr', 'unpack', 'uri', 'version']75 |
(Pdb) print http.method
GET
(Pdb) print http.body
(Pdb) print http.headers
{'accept-language': 'en-us,en;q=0.5', 'accept-encoding': 'gzip,deflate', 'connection': 'keep-alive', 'keep-alive': '115', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'user-agent': 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.16) Gecko/20110323 Ubuntu/10.04 (lucid) Firefox/3.6.16', 'accept-charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 'host': 'www.commercialventvac.com'}
(Pdb) print http.pack
<bound method Request.pack of Request(version='1.1')>
(Pdb) print http.pack()
GET / HTTP/1.1
accept-language: en-us,en;q=0.5
accept-encoding: gzip,deflate
connection: keep-alive
keep-alive: 115
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
user-agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.16) Gecko/20110323 Ubuntu/10.04 (lucid) Firefox/3.6.16
accept-charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
host: www.commercialventvac.com
(Pdb) print http.pack_hdr()
accept-language: en-us,en;q=0.5
accept-encoding: gzip,deflate
connection: keep-alive
keep-alive: 115
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
user-agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.16) Gecko/20110323 Ubuntu/10.04 (lucid) Firefox/3.6.16
accept-charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
host: www.commercialventvac.com
(Pdb)
100 | I think the decode_tcp_iterator.py file is not doing the sequence 101 | numbers correctly. The ack numbers that are n/a are because the ACK 102 | flag is clear. 103 |
104 || Packet | 108 |direction | 109 |absolute seq num | 110 |relative seq num | 111 |absolute ack num | 112 |relative ack num | 113 |
|---|---|---|---|---|---|
| 1 | 116 |C->S | 117 |988184821 | 118 |0 | 119 |n/a | 120 |n/a | 121 |
| 2 | 124 |S->C | 125 |2171931972 | 126 |1 | 127 |988184822 | 128 |1 | 129 |
| 3 | 132 |C->S | 133 |988184822 | 134 |1 | 135 |2171931972 | 136 |1 | 137 |
| 4 | 140 |C->S | 141 |988184822 | 142 |111 | 143 |2171931972 | 144 |111 | 145 |
| 5 | 148 |S->C | 149 |2171931973 | 150 |1 | 151 |2171931973 | 152 |111 | 153 |
| 6 | 156 |C->S | 157 |988184832 | 158 |111 | 159 |2171932883 | 160 |911 | 161 |
| 7 | 164 |S->C | 165 |2171932883 | 166 |911 | 167 |988184932 | 168 |2339 | 169 |
| 8 | 172 |C->S | 173 | 988184932 174 | |
175 | 911 | 176 |2171934311 177 | |
178 | 2339 | 179 |
| 9 182 | |
183 | S->C 184 | |
185 | 2171934311 186 | |
187 | 2339 188 | |
189 | 988184932 190 | |
191 | 3391 192 | |
193 |
| 10 196 | |
197 | C->S 198 | |
199 | 988184932 200 | |
201 | 3391 202 | |
203 | 2171935363 204 | |
205 | 111 206 | |
207 |
| 11 210 | |
211 | S->C (fin) 212 | |
213 | 2171935363 214 | |
215 | 111 216 | |
217 | 988184932 218 | |
219 | 3392 220 | |
221 |
| 12 224 | |
225 | C->S (fin) 226 | |
227 | 988184932 228 | |
229 | 3392 230 | |
231 | 2171935364 232 | |
233 | 112 234 | |
235 |
| 13 238 | |
239 | S->C 240 | |
241 | 2171935364 242 | |
243 | 3392 244 | |
245 | 988184933 246 | |
247 | 112 248 | |
249 |
jeffs@heavy:~/python/PythonClass/dpkt_doc$ python -m pdb decode_tcp_iterator_2.py http_ipv6.cap258 | If I use a capture file from wireshark, it gets past this point, but 259 | then it has a bad value for the ethertype:
> /home/jeffs/python/PythonClass/dpkt_doc/decode_tcp_iterator_2.py(5)<module>()
-> import dpkt
(Pdb) c
Traceback (most recent call last):
File "/usr/lib/python2.6/pdb.py", line 1285, in main
pdb._runscript(mainpyfile)
File "/usr/lib/python2.6/pdb.py", line 1204, in _runscript
self.run(statement)
File "/usr/lib/python2.6/bdb.py", line 368, in run
exec cmd in globals, locals
File "<string>", line 1, in <module>
File "decode_tcp_iterator_2.py", line 159, in <module>
pc = dpkt.pcap.Reader ( open(sys.argv[1] ) ) # file interface
File "dpkt/pcap.py", line 105, in __init__
self.dloff = dltoff[self.__fh.linktype]
KeyError: 101
Uncaught exception. Entering post mortem debugging
Running 'cont' or 'step' will restart the program
> /home/jeffs/python/PythonClass/dpkt_doc/dpkt/pcap.py(105)__init__()
-> self.dloff = dltoff[self.__fh.linktype]
(Pdb) print self.__fh.linktype
*** AttributeError: 'Reader' object has no attribute '__fh'
(Pdb) print self.__fh.linktype
*** AttributeError: 'Reader' object has no attribute '__fh'
(Pdb)
jeffs@heavy:~/python/PythonClass/dpkt_doc$ python -m pdb decode_tcp_iterator_2.py http_ipv6_wireshark_2.cap262 |
> /home/jeffs/python/PythonClass/dpkt_doc/decode_tcp_iterator_2.py(5)<module>()
-> import dpkt
(Pdb) list 72
67 eth = dpkt.ethernet.Ethernet(buf)
68 # Also, this changes a little bit with IPv6. To tell the difference between IPv4 and IPv6, you have to look
69 # at the ethertype field, which is given by http://www.iana.org/assignments/ethernet-numbers. IPv4 is 0x800 or 2048
70 # and IPv6 is 0x86DD or 34525
71 # This is simplistic - IPv4 packets can be fragmented. Also, this only works for IPv4. IPv6 has a different Ethertype
72 if eth.type == dpkt.ethernet.ETH_TYPE_IP :
73 ip = eth.data
74 if ip.v != 4 :
75 raise ValueError, "In packet %d, the ether type is IPv4 but the IP version number is %d not 4" % (
76 packet_cntr, ip.v )
77 # Deal with IP fragmentation here
(Pdb) break 72
Breakpoint 1 at /home/jeffs/python/PythonClass/dpkt_doc/decode_tcp_iterator_2.py:72
(Pdb) c
counter src prt dst prt flags
> /home/jeffs/python/PythonClass/dpkt_doc/decode_tcp_iterator_2.py(72)decode_tcp()
-> if eth.type == dpkt.ethernet.ETH_TYPE_IP :
(Pdb) print eth.type
129
(Pdb) print dpkt.ethernet.ETH_TYPE_IP
2048
(Pdb) print dpkt.ethernet.ETH_TYPE_IP6
34525
(Pdb)
I think this is a bug in wireshark, because in the packet decoding
263 | process, it says that there is no information available for the link
264 | layer. Wireshark recognizes that the packet is IPv6. 129 is
265 | in the range of the IEEE802.3 length field, so the problem may be that
266 | this is an IEEE802.3 packet and not an Ethernet packet. I am also
267 | doing the packet capture from the sixxs device.
268 |
269 |
271 |
272 | 273 | 274 | -------------------------------------------------------------------------------- /dpkt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 |By Jeff Silverman, jeffsilverm at gmail dot com
20 |
I am doing this work as a classroom project for The 80 | University of Washington Python Programming class. Most of the 81 | material is in github.com. 82 | To 83 | get 84 | it, 85 | give 86 | the 87 | following 88 | commands: 89 |
90 |[ps37854]$ mkdir dpkt_doc 91 | [ps37854]$ cd dpkt_doc 92 | [ps37854]$ git init . 93 | Initialized empty Git repository in /home/jeffsilverm/commercialventvac/dpkt_doc/.git/ 94 | [ps37854]$ git pull git://github.com/jeffsilverm/dpkt_doc.git 95 | remote: Counting objects: 3, done. 96 | remote: Compressing objects: 100% (2/2), done. 97 | remote: Total 3 (delta 0), reused 0 (delta 0) 98 | Unpacking objects: 100% (3/3), done. 99 | From git://github.com/jeffsilverm/dpkt_doc 100 | * branch HEAD -> FETCH_HEAD 101 | [ps37854]$ 102 |103 |
dpkt is an ethernet packet decoding module. It was written by
286 | Dugsong.
287 |
Fundemental to understanding how dpkt works is the fact that it
289 | decodes single network packets. This has two consequences:
290 |
For example, if you are doing an HTTP GET operation, then probably 296 | the 297 | entire header will fit into 1500 bytes, which is the default message 298 | transfer unit (MTU) size of an ethernet and most modern wide area 299 | networks as well. However, if you are doing an HTTP POST 300 | operation with a lot of data moving from the client browser to the web 301 | server, dpkt will be able to parse many of the headers but not all of 302 | them.
303 |
304 | These problems I have tried to solve with software that sits on top of
305 | dpkt's low level interfaces. For example,
306 | decode_tcp_iterator_2.py implements a crude TCP stack on top of dpkt.
307 |
jeffs@heavy:/usr$ find . -name "*dpkt*" -print314 |
./share/doc/python-dpkt
./share/pyshared/dpkt
./share/pyshared/dpkt/dpkt.py
./share/pyshared/dpkt-1.6.egg-info
./share/python-support/python-dpkt.public
./lib/pymodules/python2.6/dpkt
./lib/pymodules/python2.6/dpkt/dpkt.py
./lib/pymodules/python2.6/dpkt/dpkt.pyc
./lib/pymodules/python2.6/dpkt-1.6.egg-info
ls /usr/share/pyshared/dpkt
ah.py dpkt.py icmp.py ntp.py rip.py stp.py
ah.pyc dpkt.pyc icmp.pyc ntp.pyc rip.pyc stp.pyc
aim.py dtp.py igmp.py ospf.py rpc.py stun.py
aim.pyc dtp.pyc igmp.pyc ospf.pyc rpc.pyc stun.pyc
arp.py esp.py __init__.py pcap.py rtp.py tcp.py
arp.pyc esp.pyc __init__.pyc pcap.pyc rtp.pyc tcp.pyc
asn1.py ethernet.py ip6.py pim.py rx.py telnet.py
asn1.pyc ethernet.pyc ip6.pyc pim.pyc rx.pyc telnet.pyc
bgp.py gre.py ip.py pmap.py sccp.py tftp.py
bgp.pyc gre.pyc ip.pyc pmap.pyc sccp.pyc tftp.pyc
cdp.py gzip.py ipx.py pppoe.py sctp.py tns.py
cdp.pyc gzip.pyc ipx.pyc pppoe.pyc sctp.pyc tns.pyc
crc32c.py h225.py loopback.py ppp.py sip.py tpkt.py
crc32c.pyc h225.pyc loopback.pyc ppp.pyc sip.pyc tpkt.pyc
dhcp.py hsrp.py mrt.py qq.py sll.py udp.py
dhcp.pyc hsrp.pyc mrt.pyc qq.pyc sll.pyc udp.pyc
diameter.py http.py netbios.py radius.py smb.py vrrp.py
diameter.pyc http.pyc netbios.pyc radius.pyc smb.pyc vrrp.pyc
dns.py icmp6.py netflow.py rfb.py ssl.py yahoo.py
dns.pyc icmp6.pyc netflow.pyc rfb.pyc ssl.pyc yahoo.pyc
This decodes an IPSEC authentication header. Insofar as I can
331 | tell, dpkt.ah will only work for IPSEC over IPv4.
332 |
334 |
AOL instant messenger
337 |Address resolution protocol. If the ethernet packet has type 339 | ETH_TYPE_ARP, then an dpkt.ethernet.Ethernet object will have an arp 340 | attribute. Refer to decode_arp.py.
341 |The length of the hardware address. For Ethernet, this is 6
344 | bytes.
345 |
The operation. 1=request, 2=reply
348 |
The protocol address length. For IPv4, this is 4.
351 |
The upper layer protocol for which the ARP request is intended. For
354 | IPv4, this has the value 0x0800. The permitted values share a
355 | numbering space with those for ethertype.
360 |
The source hardware address (SHA). This should be the same as
363 | the
364 | source ethernet address but doesn't have to be. If the SHA and
365 | the ethernet source address are different, then you might suspect
366 | somebody trying to poison your arp cache. However, there are
367 | legitimate reasons why they might be different, most of which are
368 | anacronisms these days. Just because the source ethernet address
369 | and the SHA are the same, doesn't mean that nobody is trying to poison
370 | your arp cache.
371 |
The source protocol address. This will usually be an IPv4
374 | address. You will never see an IPv6 address here, because IPv6
375 | uses neighbor discovery protocol.
376 |
The target hardware address. In an ARP request, this will be
379 | 0. In an ARP reply, it should be the same as the Ethernet source
380 | address, but it doesn't have to be. You don't see this very often
381 | any more, but there used to be ARP servers which would supply ARP
382 | replies for hosts too dumb to provide their own ARP replies. You
383 | can also use this field to poison an ARP cache.
384 |
The target protocol address. This is the address that ARP is 387 | going to translate into a MAC address. The IPv4 hosts in the 388 | network listen for ARP requests, which are sent to the broadcast 389 | Ethernet address. When a host sees its own IP address in the tpa 390 | field, it knows it has to reply with an ARP reply.
391 |In general, DNS runs on top of UDP. DNS can run on top of TCP
405 | and will do so if the query is large and also if there is a zone
406 | transfer. Many of the values in these fields come from IANA.
408 |
Insofar as I can tell, dpkt doesn't provide the AD bit. The AD
410 | bit is set if this response is authoritative according to the policies
411 | of the server. A caching name server is generally not
412 | authoritative.
413 |
Name servers return answers that have character values outside of
415 | the range 33 to 126 decimal inclusive. I don't know why.
416 | Both dig and wireshark are able to decipher the strings properly.
417 |
A list of answers. Each answer is an RR record. For a
420 | DNS query, this is an empty list. To get the results from the DNS
421 | query, you have to iterate over the list and decode each RR record.
422 |
Class. For modern resolvers, this should always be 1.
425 | There were other classes defined in RFC 1035, but nobody uses them
426 | anymore.
427 |
This is the canonical name of the thing being looked up. The
430 | response will be applicable to this cname.
431 |
The name that was looked up, before translation to a canonical name,
434 | if any.
435 |
This is either an Authority Record or an Additional Record.
439 |
qr is 0 if this message is a query and 1 if this message is a 444 | response.
445 |The ID field is set by the DNS resolver when it makes a query.
448 | The ID field is also set by the nameserver when it responds to the
449 | query. That way, the resolver knows which query the response is
450 | in response to.
451 |
A list of name servers for this domain. To decode this list,
454 | iterate over each entry in the list and decode as in dpt.dns.an
455 |
The opcode is 0 a standard query (QUERY), 1 an inverse query
460 | (IQUERY) (obsoleted by RFC 3425), 2 a server status
461 | request (STATUS), 3 is unassigned, 4 is Notify (RFC 1996), 5 is
462 | update, defined by RFC 2136
463 |
qr is 0 if this message is a query and 1 if this message is a
473 | response.
474 |
Contains the data payload of the ethernet packet.
498 |Contains the destination address of the ethernet packet as a 6 byte 501 | strings.
502 |6 Byte Ethernet addresses can be converted to strings in format 504 | nn:nn:nn:nn:nn:nn with the code:
505 |def add_colons_to_mac( mac_addr ) :506 |
"""This function accepts a 12 hex digit string and converts it to a colon separated string"""
s = list()
for i in range(12/2) : # mac_addr should always be 12 chars, we work in groups of 2 chars
s.append( mac_addr[i*2:i*2+2] )
r = ":".join(s) # I know this looks strange, refer to http://docs.python.org/library/stdtypes.html#sequence-types-str-unicode-list-tuple-bytearray-buffer-xrange
return r
add_colons_to_mac( binascii.hexlify(arp.sha) )
Returns a class which is something from the Ethernet Type field
509 |(Pdb) print eth._typesw.keys()510 |
[2048, 8192, 34916, 2054, 34827, 33079, 8196, 34525]
(Pdb) print eth._typesw.values()
[<class 'dpkt.ip.IP'>, <class 'dpkt.cdp.CDP'>, <class
'dpkt.pppoe.PPPoE'>, <class 'dpkt.arp.ARP'>, <class
'dpkt.ppp.PPP'>, <class 'dpkt.ipx.IPX'>, <class
'dpkt.dtp.DTP'>, <class 'dpkt.ip6.IP6'>]
(Pdb) print eth.get_type(2048)
<class 'dpkt.ip.IP'>
(Pdb) print eth.get_type(34525)
<class 'dpkt.ip6.IP6'>
(Pdb)
Another way to do the same thing is:
511 |
import dpkt513 |
eth = dpkt.ethernet.Ethernet()
print eth._typesw[2048]
<class 'dpkt.ip.IP'>
print eth._typesw[34525]
<class 'dpkt.ip6.IP6'>
print eth._typesw[33079]
<class 'dpkt.ipx.IPX'>
Contains the source address of the ethernet packet as a 6 byte
515 | string. To decode to ASCII, see dst,
516 | above.
517 |
Returns the Ethernet type. For example, type 2048 (0x0800) is
521 | IPv4 and 34525 (0x86DD) is IPv6. For a complete list of Ethernet
522 | types, refer to http://www.iana.org/assignments/ethernet-numbers
524 | To get a list of ethernet types that are supported by dpkt, refer to
525 | the code at get_type.
526 |
This is spanning tree protocol.
536 |
Objects for decoding HTTP. The message going from the client
540 | browser to the server is the request, the message going from the server
541 | to the client browser is the response.
542 |
Create a dpkt.http.Reply object from the received string. The
545 | received string probably will include the body of the response and may
546 | be quite large. Use a module such as decode_tcp_iterator_2 to
547 | combine the payloads of several packets into a single received string.
548 |
This is the numeric code that the server returns to the browser
551 | which describes the outcome of the request. Values in the range
552 | of 1xx are continuation, values in the range of 2xx are success, values
553 | in the range of 3xx are relocations, values in the range of 4xx are
554 | errors from the client, values in the range of 5xx are errors in the
555 | server. The status is just 3 digits, there is an explanation of
556 | the error that is returned in dpkt.htt.Reply.reason. The full
557 | list of status codes is given in RFC
559 | 2616 section 10.
560 |
The reason is a brief explanation of the status code.
563 | According to RFC
565 | 2616 section 6.1.1 the reason is for human use only and has no
566 | significance to the protocol.
567 |
The body is the payload of the HTTP response. Its type is
570 | given by value of content-type header, which is a MIME type as
571 | described in RFC 2045,
572 | RFC 2046, and RFC 2047. The
574 | list of MIME types is maintained by the
576 | Internet Assigned Numbers Authority (IANA).
577 |
headers is a dictionary of the headers of the reply. The keys
580 | are the headers that are present, the values are the values of those
581 | headers. The list of allowed headers is given by RFC 2616
583 | section 14.
584 |
Create a dpkt.http.Request object from the received string.
588 |
If the HTTP request method is POST, then this attribute will contain
591 | the input that is passed to the server. If the method is GET,
592 | then this attribute will be an empty string.
593 |
This is a dictionary. The keys of the dictionary are the
596 | header fields that are present and the values of the dictionary are the
597 | values of the field. For example:
598 |
(Pdb) print http_req.headers600 |
{'host': 'www.kame.net', 'connection': 'Keep-Alive', 'accept': '*/*', 'user-agent': 'Wget/1.12 (linux-gnu)'}
(Pdb)
When doing an HTTP request, the client browser refers to the object
603 | with a method. The most common methods are GET and PUT. The
604 | difference between GET and PUT has to do with how arguments are passed
605 | from the client to the server. With a GET, the arguments are
606 | passed in the URI separated by & characters. With a PUT, the
607 | values are passed as key value pairs in the body of the request.
608 | Refer to RFC
610 | 2616 section 9.1.2 for a discussion of methods and idempotence.
611 |
For a list of valid methods, refer to RFC
614 | 2616 section 5.1.
615 |
The URI (Uniform Resource Identifier) the the part of the URL
618 | (Uniform Resource Locator) which comes after the hostname. So,
619 | for example, the URI of the URL http://www.commercialventvac.com/dpkt.html
621 | is /dpkt.html. The URI of the URL http://www.commercialventvac.com
623 | is /
624 |
The version of HTTP. Valid values (as of this writing) are
627 | 0.9, 1.0, and 1.1
628 |
'data', 'dst', 'get_proto', 'hl', 'id', 'len', 'off', 'opts', 'p', 'pack', 'pack_hdr', 'set_proto', 'src', 'sum', 'tcp', 'tos', 'ttl', 'unpack', 'v', 'v_hl'635 |
This constructor can be used to create a packet.
638 |
from dpkt.udp import UDP640 |
from dpkt.ip import IP
import socket
udp = UDP(data="testing")
src=socket.inet_aton("127.0.0.1")
dst=socket.inet_aton("67.205.52.141")
ip = IP(src=src, dst=dst, data=udp )
ip
IP(src='\x7f\x00\x00\x01', dst='C\xcd4\x8d', data=UDP(data='testing'))
This is the payload of the IP packet
643 |
645 |
The destination IPv4 address of the packet. You can convert
647 | the destination IP address to an ASCII string in dotted quad format
648 | using the socket package:
649 |
import socket651 |
dst_ip_addr_str = socket.inet_ntoa(ip.dst)
Internet Header Length is the length of the internet header in 32 653 | bit 654 | words, and thus points to the beginning of the data. Note that the 655 | minimum value for a correct header is 5.
656 |This is the payload of the packet. Depending on ip6.nxt, this
686 | will be UDP, TCP, or similar. dpkt magically casts this into the
687 | proper datatype.
688 |
This is the destination IPv6 address, 128 bits. To create a
692 | packed IPv6 address from an ASCII string:
693 |
import socket695 |
dst_addr = socket.inet_pton(socket.AF_INET6, "2001:1938:26f:1:204:4bff:0:1")
IPv6 defines a optimization called a "flow". If a router sees
698 | a packet with a non-zero flow for the first time, it makes its routing
699 | decision and stores that decision in a fast hash table. Then when
700 | subsequent packets come by with the same flow, the router can makes its
701 | routing decision faster. The flow is initialized by a random
702 | number generator on the host that is originating the connection.
703 | It can be left 0.
704 |
The hop limit. Each time the packet hops from router to
709 | router, this field is decremented by 1. When the hlim reaches 0,
710 | the packet is discarded, and an ICMP6 type 3 packet is sent to the
711 | sender. tracepath6 uses this field to probe the path to a
712 | destination. It sends a packet with a small hlim. When the
713 | router decrements the hlim to zero and sends back the ICMPv6 packet to
714 | the sender, tracepath6 records the source address of the ICMPv6 packet
715 | and knows the IP address of the router.
716 |
The next header type. If there is no next header, then the
721 | protocol of the next level in the stack. Typical values are 6 for
722 | TCP, 17 for UDP, 58 for ICMPv6, 132 for SCTP. For a list of
723 | protocols, see http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml
725 |
The payload length, not counting the IPv6 header. This is a 16
730 | bit unsigned number.
731 |
The source IPv6 address, which is 128 bits long. To decode the
735 | IPv6 address into an ASCII string comprehensible by humans, use the
736 | socket.inet_ntop method:
737 |
import socket739 |
import dpkt
eth = dpkt.ethernet.Ethernet(buf)
ip = eth.data
dst_ip_addr_str = socket.inet_ntop(AF_INET6, ip.dst)
print dst_ip_addr_str
This will give an output that looks like: 740 | 2001:1938:26f:1:204:4bff:0:1
741 |This is the version of IP, which must be 6. If the ethernet
744 | type is 34525 and this is not 6, then throw an exception because
745 | something is wrong.
746 |
dpkt.pcap.Reader(f) implements an iterator. Each iteration
760 | returns a tuple which is a timestamp and a buffer. The timestamp
761 | contains a time as a floating point number. The buffer is a
762 | complete packet. For example:
763 |
765 |#!/usr/bin/env python
# -*- coding: utf-8 -*-
import dpkt
import sys
f = open(sys.argv[1])
pcap = dpkt.pcap.Reader(f)
frame_counter = 0
for ts, buf in pcap:
frame_counter += 1
if frame_counter > 1 :
print "%d: %f %f" % ( frame_counter, ts, ts - last_time )
last_time = ts
f.close()
TCP is a reliable, stream oriented protocol. Refer to RFC 793 for more 768 | information.
769 |This is the last byte that the receiver has received. The
772 | sender then knows that it need not resend any bytes prior to this point.
773 |
The payload of this TCP segment. Note that the seqments may be
776 | delivered out of order, so it is not sufficient to simply join the
777 | payloads together. The maximum length of a TCP segment payload is
778 | the MTU size of this network less the length of the IP header less the
779 | length of a TCP header with no options. For example, Ethernet has
780 | an MTU of 1500 bytes (Most modern networks use this size to avoid
781 | fragmentation on all links - IPv6 the smallest MTU must be at least
782 | 1200 bytes but may be longer). The IPv4 header is 20 bytes,
783 | minimum. An IPv6 header is 40 bytes. The TCP header is 20
784 | bytes, so the maximum length of a TCP segment is 1460 bytes for IPv4
785 | and 1440 bytes for IPv6.
786 |
The destination port of the packet, a 16 bit unsigned number.
789 | For a list of well known destination ports, refer to http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers.
791 | If
792 | the SYN flag is set and the ACK flag is cleared, then this packet is
793 | the beginning of a connection and you may use the dport to determine
794 | what service is being used and how to decode the conversation.
795 |
The TCP flags. To decode them, use the following code:
798 |799 |fin_flag = ( tcp.flags & dpkt.tcp.TH_FIN ) != 0
syn_flag = ( tcp.flags & dpkt.tcp.TH_SYN ) != 0
rst_flag = ( tcp.flags & dpkt.tcp.TH_RST ) != 0
psh_flag = ( tcp.flags & dpkt.tcp.TH_PUSH) != 0
ack_flag = ( tcp.flags & dpkt.tcp.TH_ACK ) != 0
urg_flag = ( tcp.flags & dpkt.tcp.TH_URG ) != 0
ece_flag = ( tcp.flags & dpkt.tcp.TH_ECE ) != 0
cwr_flag = ( tcp.flags & dpkt.tcp.TH_CWR ) != 0
4 bits. Specifies the size of the TCP header in 32 bit 802 | longwords. The minimum size of a TCP header is 20 bytes (5 803 | longwords) and the maximum is 60 bytes (15 longwords). This field 804 | gets its name because it is the offset from the beginning of the header 805 | to the data.
806 |This method parses the TCP options field into a list of (option
819 | number, option value) tuples. Use the code sequence
820 |
822 |option_list = dpkt.tcp.parse_opts ( tcp.opts )
to decode the options. For a fully worked, example, see the
823 | get_message_segment_size method in decode_tcp_iterator_2.py.
824 |
A 32 bit unsigned number. If the SYN flag is set, then this is
827 | the initial sequence number, and all subsequent sequence numbers are
828 | relative to this number. If the SYN flag is clear, then this
829 | number is the location of the payload of this sequence in the data
830 | stream. Note that segment can be delivered out of order, so the
831 | sequence number is used to get the bytes back in order.
832 |
The source port, a 16 bit unsigned number. On the initial
836 | connection, this will be an ephemeral port in the range 49152 through
837 | 6553 for most modern operating systems.
838 |
A 16 bit unsigned number. Specifies how far past the current 847 | sequence number in this acknowlegement the receiver is willing to 848 | receive.
849 |There are a lot of classes that inherit from dpkt.Packet. I
855 | found these with the command egrep "class.*dpkt.Packet" *.py
856 |
ah.py:class AH(dpkt.Packet):858 |
aim.py:class FLAP(dpkt.Packet):
aim.py:class SNAC(dpkt.Packet):
arp.py:class ARP(dpkt.Packet):
bgp.py:class BGP(dpkt.Packet):
bgp.py: class Open(dpkt.Packet):
bgp.py: class Parameter(dpkt.Packet):
bgp.py: class Authentication(dpkt.Packet):
bgp.py: class Capability(dpkt.Packet):
bgp.py: class Update(dpkt.Packet):
bgp.py: class Attribute(dpkt.Packet):
bgp.py: class Origin(dpkt.Packet):
bgp.py: class ASPath(dpkt.Packet):
bgp.py: class ASPathSegment(dpkt.Packet):
bgp.py: class NextHop(dpkt.Packet):
bgp.py: class MultiExitDisc(dpkt.Packet):
bgp.py: class LocalPref(dpkt.Packet):
bgp.py: class AtomicAggregate(dpkt.Packet):
bgp.py: class Aggregator(dpkt.Packet):
bgp.py: class Communities(dpkt.Packet):
bgp.py: class Community(dpkt.Packet):
bgp.py: class ReservedCommunity(dpkt.Packet):
bgp.py: class OriginatorID(dpkt.Packet):
bgp.py: class ClusterList(dpkt.Packet):
bgp.py: class MPReachNLRI(dpkt.Packet):
bgp.py: class MPUnreachNLRI(dpkt.Packet):
bgp.py: class Notification(dpkt.Packet):
bgp.py: class Keepalive(dpkt.Packet):
bgp.py: class RouteRefresh(dpkt.Packet):
bgp.py:class RouteGeneric(dpkt.Packet):
bgp.py:class RouteIPV4(dpkt.Packet):
bgp.py:class RouteIPV6(dpkt.Packet):
cdp.py:class CDP(dpkt.Packet):
cdp.py: class Address(dpkt.Packet):
cdp.py: class TLV(dpkt.Packet):
dhcp.py:class DHCP(dpkt.Packet):
diameter.py:class Diameter(dpkt.Packet):
diameter.py:class AVP(dpkt.Packet):
dns.py:class DNS(dpkt.Packet):
dns.py: class Q(dpkt.Packet):
dtp.py:class DTP(dpkt.Packet):
esp.py:class ESP(dpkt.Packet):
ethernet.py:class Ethernet(dpkt.Packet):
gre.py:class GRE(dpkt.Packet):
gre.py: class SRE(dpkt.Packet):
gzip.py:class GzipExtra(dpkt.Packet):
gzip.py:class Gzip(dpkt.Packet):
h225.py:class H225(dpkt.Packet):
h225.py: class IE(dpkt.Packet):
hsrp.py:class HSRP(dpkt.Packet):
http.py:class Message(dpkt.Packet):
icmp6.py:class ICMP6(dpkt.Packet):
icmp6.py: class Error(dpkt.Packet):
icmp6.py: class Echo(dpkt.Packet):
icmp.py:class ICMP(dpkt.Packet):
icmp.py: class Echo(dpkt.Packet):
icmp.py: class Quote(dpkt.Packet):
igmp.py:class IGMP(dpkt.Packet):
ip6.py:class IP6(dpkt.Packet):
ip.py:class IP(dpkt.Packet):
ipx.py:class IPX(dpkt.Packet):
loopback.py:class Loopback(dpkt.Packet):
mrt.py:class MRTHeader(dpkt.Packet):
mrt.py:class TableDump(dpkt.Packet):
mrt.py:class BGP4MPMessage(dpkt.Packet):
mrt.py:class BGP4MPMessage_32(dpkt.Packet):
netbios.py:class Session(dpkt.Packet):
netbios.py:class Datagram(dpkt.Packet):
netflow.py:class NetflowBase(dpkt.Packet):
netflow.py: class NetflowRecordBase(dpkt.Packet):
ntp.py:class NTP(dpkt.Packet):
ospf.py:class OSPF(dpkt.Packet):
pcap.py:class PktHdr(dpkt.Packet):
pcap.py:class FileHdr(dpkt.Packet):
pim.py:class PIM(dpkt.Packet):
pmap.py:class Pmap(dpkt.Packet):
pppoe.py:class PPPoE(dpkt.Packet):
ppp.py:class PPP(dpkt.Packet):
radius.py:class RADIUS(dpkt.Packet):
rfb.py:class RFB(dpkt.Packet):
rfb.py:class SetPixelFormat(dpkt.Packet):
rfb.py:class SetEncodings(dpkt.Packet):
rfb.py:class FramebufferUpdateRequest(dpkt.Packet):
rfb.py:class KeyEvent(dpkt.Packet):
rfb.py:class PointerEvent(dpkt.Packet):
rfb.py:class FramebufferUpdate(dpkt.Packet):
rfb.py:class SetColourMapEntries(dpkt.Packet):
rfb.py:class CutText(dpkt.Packet):
rip.py:class RIP(dpkt.Packet):
rip.py:class RTE(dpkt.Packet):
rip.py:class Auth(dpkt.Packet):
rpc.py:class RPC(dpkt.Packet):
rpc.py: class Auth(dpkt.Packet):
rpc.py: class Call(dpkt.Packet):
rpc.py: class Reply(dpkt.Packet):
rpc.py: class Accept(dpkt.Packet):
rpc.py: class Reject(dpkt.Packet):
rx.py:class Rx(dpkt.Packet):
sccp.py:class ActivateCallPlane(dpkt.Packet):
sccp.py:class CallInfo(dpkt.Packet):
sccp.py:class CallState(dpkt.Packet):
sccp.py:class ClearPromptStatus(dpkt.Packet):
sccp.py:class CloseReceiveChannel(dpkt.Packet):
sccp.py:class DisplayPromptStatus(dpkt.Packet):
sccp.py:class DisplayText(dpkt.Packet):
sccp.py:class KeypadButton(dpkt.Packet):
sccp.py:class OpenReceiveChannel(dpkt.Packet):
sccp.py:class OpenReceiveChannelAck(dpkt.Packet):
sccp.py:class SelectStartKeys(dpkt.Packet):
sccp.py:class SetLamp(dpkt.Packet):
sccp.py:class SetSpeakerMode(dpkt.Packet):
sccp.py:class StartMediaTransmission(dpkt.Packet):
sccp.py:class StartTone(dpkt.Packet):
sccp.py:class StopMediaTransmission(dpkt.Packet):
sccp.py:class SCCP(dpkt.Packet):
sctp.py:class SCTP(dpkt.Packet):
sctp.py:class Chunk(dpkt.Packet):
sll.py:class SLL(dpkt.Packet):
smb.py:class SMB(dpkt.Packet):
ssl.py:class SSL2(dpkt.Packet):
ssl.py:class SSL3(dpkt.Packet):
stp.py:class STP(dpkt.Packet):
stun.py:class STUN(dpkt.Packet):
tcp.py:class TCP(dpkt.Packet):
tftp.py:class TFTP(dpkt.Packet):
tns.py:class TNS(dpkt.Packet):
tpkt.py:class TPKT(dpkt.Packet):
udp.py:class UDP(dpkt.Packet):
vrrp.py:class VRRP(dpkt.Packet):
yahoo.py:class YHOO(dpkt.Packet):
yahoo.py:class YMSG(dpkt.Packet):