├── .gitignore ├── LICENSE ├── rtypes.h ├── etc ├── dns_parse.cfg └── dnscapture.conf ├── Makefile ├── strutils.h ├── pkgs └── dns_parse.spec ├── tcp.h ├── README ├── init └── dnscapture ├── network.h ├── dns_parse.h ├── bin └── dns_parse_cron ├── strutils.c ├── rtypes.c ├── network.c ├── tcp.c └── dns_parse.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *.o 4 | test_strutils 5 | bin/dns_parse 6 | samples 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software has been authored by an employee or employees of Los 2 | Alamos National Security, LLC, operator of the Los Alamos National 3 | Laboratory (LANL) under Contract No. DE-AC52-06NA25396 with the 4 | U.S. Department of Energy. The U.S. Government has rights to use, 5 | reproduce, and distribute this software. The public may copy, 6 | distribute, prepare derivative works and publicly display this 7 | software without charge, provided that this Notice and any 8 | statement of authorship are reproduced on all copies. Neither the 9 | Government nor LANS makes any warranty, express or implied, or 10 | assumes any liability or responsibility for the use of this 11 | software. If software is modified to produce derivative works, 12 | such modified software should be clearly marked, so as not to 13 | confuse it with the version available from LANL. 14 | -------------------------------------------------------------------------------- /rtypes.h: -------------------------------------------------------------------------------- 1 | #ifndef __RTYPES_H__ 2 | #define __RTYPES_H__ 3 | 4 | #include 5 | #include 6 | 7 | typedef char * rr_data_parser(const uint8_t*, uint32_t, uint32_t, 8 | uint16_t, uint32_t); 9 | 10 | typedef struct { 11 | uint16_t cls; 12 | uint16_t rtype; 13 | rr_data_parser * parser; 14 | const char * name; 15 | const char * doc; 16 | unsigned long long count; 17 | } rr_parser_container; 18 | 19 | rr_parser_container * find_parser(uint16_t, uint16_t); 20 | 21 | char * read_dns_name(uint8_t *, uint32_t, uint32_t); 22 | 23 | rr_data_parser opts; 24 | rr_data_parser escape; 25 | 26 | // Add them to the list of data parsers in rtypes.c. 27 | extern rr_parser_container rr_parsers[]; 28 | 29 | // This is for handling rr's with errors or an unhandled rtype. 30 | rr_parser_container default_rr_parser; 31 | 32 | void print_parsers(); 33 | void print_parser_usage(); 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /etc/dns_parse.cfg: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | # The parse_dns_pcap python script applies the 'dns_parse' parser to each new 3 | # file in a directory containing dns pcaps. The file names should contain time 4 | # stamps such that a reversed sort of the files by name will put the newest 5 | # files at the front of the list. 6 | PCAP_DIR = /var/dns_pcaps/ 7 | # The NETWORKS list contains the file name prefixes for different sets of 8 | # pcap files in the PCAP directory that need to be parsed. Each prefix will 9 | # be kept track of separately. These should be space separated 10 | NETWORKS = tap1 tap2 11 | # Where to store state information on which file was last read. 12 | STATE_DIR = /var/spool/dns_parse/ 13 | # additional options to pass to DNS parse 14 | # the most common is -m, which sets up multiline mode and the multiline 15 | # delmiter. 16 | #OPTIONS = -m '' 17 | #OPTIONS = -m '' 18 | # Where to output the parsed DNS data. This log file is appended too directly (not via syslog), if 19 | # that matters. 20 | LOG_FILE = /var/log/dns_parsed 21 | 22 | # The syslog facility to send any errors to. 23 | ERROR_LOG_FACILITY = daemon 24 | -------------------------------------------------------------------------------- /etc/dnscapture.conf: -------------------------------------------------------------------------------- 1 | # Configuration file for the dnscapture daemon. 2 | # Use bash syntax 3 | 4 | # Required settings 5 | 6 | # The interface on which to capture. 7 | IFACE="" 8 | 9 | # Where to output the pcaps 10 | OUTPUT_DIR="" 11 | 12 | # The user to switch to after getting access to the interfaces. 13 | # This user must have write permissions for the OUTPUT_DIR. 14 | # Note that for the standard version of tcpdump, the first file written 15 | # will be written as the user that started the daemon. (tcpdump bug) 16 | OUTPUT_USER="nobody" 17 | 18 | # Optional settings. 19 | 20 | # You probably want to specify one of the following, otherwise everything 21 | # will be written to a single file. 22 | 23 | # The max size of each file. 24 | FILE_SIZE_LIMIT="" 25 | # The length of each file in seconds 26 | FILE_TIME_LIMIT="" 27 | 28 | # Whether or not to compress the file using gzip. 29 | COMPRESS="" 30 | 31 | # The output filename. This can include strftime escapes 32 | # The default writes files of the form: dns.2010-10-25_10:32:01.pcap 33 | #FILE_NAME="dns.%F_%T.pcap" 34 | 35 | # The tcpdump filter applied. 36 | #FILTER="ip and port 53" 37 | 38 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -Wall -O2 -mtune=native 3 | 4 | build: bin/dns_parse 5 | 6 | #DEBUG=-g 7 | 8 | install: 9 | mkdir -p ${DESTDIR}/usr/local/sbin/ 10 | cp bin/* ${DESTDIR}/usr/local/sbin/ 11 | mkdir -p ${DESTDIR}/etc/init.d/ 12 | cp init/dnscapture ${DESTDIR}/etc/init.d/ 13 | cp etc/* ${DESTDIR}/etc/ 14 | 15 | tar: clean 16 | if [ -z ${version} ]; then \ 17 | echo "set 'version' env variable first."; \ 18 | false;\ 19 | fi; 20 | mkdir dns_parse-${version} 21 | cp -r README LICENSE Makefile *.c *.h etc bin init Makefile dns_parse-${version}/ 22 | tar -czf dns_parse-${version}.tar.gz dns_parse-${version} 23 | rm -rf dns_parse-${version} 24 | 25 | OBJ=rtypes.o strutils.o network.o tcp.o 26 | 27 | bin/dns_parse: dns_parse.c ${OBJ} 28 | mkdir -p bin 29 | ${CC} ${CFLAGS} ${DEBUG} ${OBJ} -o bin/dns_parse dns_parse.c -lpcap 30 | 31 | rtypes.o: rtypes.c rtypes.h 32 | ${CC} ${CFLAGS} ${DEBUG} -c rtypes.c 33 | 34 | strutils.o: strutils.h strutils.c 35 | ${CC} ${CFLAGS} ${DEBUG} -c strutils.c 36 | 37 | tcp.o: tcp.h tcp.c dns_parse.h 38 | ${CC} ${CFLAGS} ${DEBUG} -c tcp.c 39 | 40 | network.o: network.h network.c dns_parse.h 41 | ${CC} ${CFLAGS} ${DEBUG} -c network.c 42 | 43 | clean: 44 | rm -f *.o 45 | rm -rf bin/dns_parse dns_parse-* 46 | 47 | test_strutils: strutils.c 48 | mkdir bin 49 | ${CC} -o bin/test_strutils strutils.c 50 | ./bin/test_strutils 51 | -------------------------------------------------------------------------------- /strutils.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifndef __DNS_STR_UTILS__ 4 | #define __DNS_STR_UTILS__ 5 | 6 | // Encodes the data into plaintext (minus newlines and delimiters). Escaped 7 | // characters are in the format \x33 (an ! in this case). The escaped 8 | // characters are: 9 | // All characters < \x20 10 | // Backslash (\x5c) 11 | // All characters >= \x7f 12 | // Arguments (packet, start, end): 13 | // packet - The uint8_t array of the whole packet. 14 | // start - the position of the first character in the data. 15 | // end - the position + 1 of the last character in the data. 16 | char * escape_data(const uint8_t *, uint32_t, uint32_t); 17 | 18 | // Read a reservation record style name, dealing with any compression. 19 | // A newly allocated string of the read name with length bytes 20 | // converted to periods is placed in the char * argument. 21 | // If there was an error reading the name, NULL is returned and the 22 | // position argument is left with it's passed value. 23 | // Args (packet, pos, id_pos, len, name) 24 | // packet - The uint8_t array of the whole packet. 25 | // pos - the start of the rr name. 26 | // id_pos - the start of the dns packet (id field) 27 | // len - the length of the whole packet 28 | // name - We will return read name via this pointer. 29 | char * read_rr_name(const uint8_t *, uint32_t *, uint32_t, uint32_t); 30 | 31 | char * fail_name(const uint8_t *, uint32_t, uint32_t, const char *); 32 | 33 | char * b64encode(const uint8_t *, uint32_t, uint16_t); 34 | #endif 35 | -------------------------------------------------------------------------------- /pkgs/dns_parse.spec: -------------------------------------------------------------------------------- 1 | Name: dns_parse 2 | Version: 2.0.6 3 | Release: 4%{?dist} 4 | Summary: Converts pcap files of DNS data into something more manageable. 5 | Source: dns_parse-%{version}.tar.gz 6 | Group: Applications/Internet 7 | License: GPLv2+ 8 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) 9 | Requires: python 10 | 11 | BuildRequires: gcc libpcap 12 | 13 | %description 14 | This package provides a command line tool, dns_parse, that converts pcap files 15 | of DNS data (currently UDP only) into a readable and easily parsable format. 16 | This data can then be easily fed into splunk or searched directly using grep. 17 | The raw files are actually slightly larger than the original pcap files, but 18 | compress much more readily. 19 | 20 | It also provides: 21 | A configurable script for processing a directory of files using dns_parse. 22 | A configurable init script for capturing the DNS data. 23 | 24 | %prep 25 | %setup 26 | 27 | %build 28 | make 29 | 30 | %install 31 | rm -rf $RPM_BUILD_ROOT 32 | make install DESTDIR=$RPM_BUILD_ROOT 33 | 34 | 35 | %clean 36 | rm -rf $RPM_BUILD_ROOT 37 | 38 | %files 39 | %defattr(-,root,root,-) 40 | /usr/local/sbin/dns_parse 41 | /usr/local/sbin/dns_parse_cron 42 | /etc/init.d/dnscapture 43 | %config /etc/dnscapture.conf 44 | %config /etc/dns_parse.cfg 45 | 46 | %changelog 47 | * Tue Mar 12 2013 Paul Ferrell - 2.0.0 48 | - Added support for TCP, IPv6, IP fragmentation, and MPLS. 49 | - Added UDP packet deduplication. 50 | 51 | * Thu Oct 28 2010 Paul Ferrell - 0.2.0 52 | - Added a couple of useful scripts to simplify dns capture and parsing, 53 | - and their config files. 54 | 55 | * Wed Aug 19 2010 Paul Ferrell - 0.1.9 56 | - Added -r option, to print the rr names instead of type, class numbers. 57 | 58 | * Tue Aug 17 2010 Paul Ferrell - 0.1.8 59 | - Multi bug fixes, improved error handling, fixed some memory issues. 60 | 61 | * Wed Aug 11 2010 Paul Ferrell - 0.1.0 62 | - Initial Build 63 | 64 | -------------------------------------------------------------------------------- /tcp.h: -------------------------------------------------------------------------------- 1 | #include "dns_parse.h" 2 | #include "network.h" 3 | 4 | // Function declarations for tcp protocol handling. 5 | 6 | #ifndef __DP_TCP 7 | #define __DP_TCP 8 | 9 | // TCP header information. Also contains pointers used to connect to this 10 | // to other TCP streams, and to connect this packet to other packets in 11 | // it's stream. 12 | typedef struct tcp_info { 13 | struct timeval ts; 14 | ip_addr src; 15 | ip_addr dst; 16 | uint16_t srcport; 17 | uint16_t dstport; 18 | uint32_t sequence; 19 | uint32_t ack_num; 20 | // The length of the data portion. 21 | uint32_t len; 22 | uint8_t syn; 23 | uint8_t ack; 24 | uint8_t fin; 25 | uint8_t rst; 26 | uint8_t * data; 27 | // The next item in the list of tcp sessions. 28 | struct tcp_info * next_sess; 29 | // These are for connecting all the packets in a session. The session 30 | // pointers above will always point to the most recent packet. 31 | // next_pkt and prev_pkt make chronological sense (next_pkt is always 32 | // more recent, and prev_pkt is less), we just hold the chain by the tail. 33 | struct tcp_info * next_pkt; 34 | struct tcp_info * prev_pkt; 35 | } tcp_info; 36 | 37 | // Print the TCP header information. Used for debugging. 38 | void tcp_print(tcp_info *); 39 | // Save all the current unresolved TCP streams to file. 40 | void tcp_save_state(config *); 41 | // Load TCP streams saved by tcp_save_state. 42 | tcp_info * tcp_load_state(config *); 43 | // Parse a TCP packet, and attach the data to a TCP stream. 44 | void tcp_parse(uint32_t, struct pcap_pkthdr *, uint8_t *, ip_info *, 45 | config *); 46 | // Assemble the TCP packets in the list with it's head at the given 47 | // tcp_info struct. 48 | tcp_info * tcp_assemble(tcp_info *); 49 | // Expire TCP streams based on the given timeval and the compile time defined 50 | // expiration time limit. The age of a stream is based on the last packet 51 | // it received. A timeval of NULL will expire all streams. 52 | // Expired streams are put through tcp reassembly. 53 | // Each reassembled stream is then put through dns parsing. 54 | // Parsed DNS data is then output. 55 | void tcp_expire(config *, const struct timeval *); 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | -- OVERVIEW -- 2 | dns_parse takes as input a pcap of DNS data and produces a complete, trivially 3 | parsable, human readable ASCII version of the same data. It's generally useful 4 | for network monitoring (send the data to Splunk or similar). The most common 5 | carrying protocols are supported, as well as packet deduplication. 6 | 7 | -- SUPPORTED PROTOCOLS -- 8 | Ethernet 9 | MPLS 10 | IPv4 (including fragment reassembly) 11 | IPv6 (including fragment reassembly) 12 | UDP 13 | TCP (with flow state saving and loading between pcaps) 14 | DNS (on any port) 15 | 16 | -- AUTHOR INFO -- 17 | Paul Ferrell 18 | pferrell@lanl.gov 19 | 20 | -- CONTENTS -- 21 | Code to build bin/dns_parse. 22 | init/dnscapture - An init script for running tcpdump on an interface as a 23 | service to generate regular pcap files. 24 | bin/dns_parse_cron - A python cron job script for periodically running dns_parse 25 | on regularly output pcap files (generally from using the -C or -G options in 26 | tcpdump). 27 | pkgs/dns_parse.spec - An RPM spec file, for those dinosaurs that still use these 28 | things (like me). 29 | etc/* - example config files for init/dnscapture and bin/dns_parse_cron 30 | 31 | -- DEPENDENCIES -- 32 | libpcap 33 | 34 | -- OS Dependencies -- 35 | This has been tested primarily on x86_64 linux, but there shouldn't be any typing issues on 32 bit machines. 36 | 37 | -- BUILDING AND INSTALLING -- 38 | make 39 | make install 40 | 41 | -- Running -- 42 | "./bin/dns_parse -h" should tell you everything you need to know. 43 | 44 | A reasonable set of options is: 45 | ./bin/dns_parse -m "" -t -r 46 | This gets you newline separated resource records an empty main record separator, 47 | pretty printed dates, and the shorthand for the record types (ie. A or CNAME). 48 | Printing of additional and name server records is disabled (by default). 49 | The output will look like this: 50 | 51 | 2013-02-13 14:56:06.002102,75.32.12.33,75.32.37.16,61,u,r,AA 52 | ? mass.blah.com A 53 | ! mass.blah.com A 75.32.37.40 54 | 55 | 2013-02-13 14:56:06.002471,33.53.3.59,112.78.117.89,54,u,q,NA 56 | ? getsystemsinc.com MX 57 | 58 | 2013-02-13 14:56:06.005718,75.32.207.52,75.32.12.33,53,u,q,NA 59 | ? 11.207.165.128.in-addr.arpa PTR 60 | 61 | 2013-02-13 14:56:06.006109,75.32.12.33,75.32.207.52,80,u,r,AA 62 | ? 11.207.165.128.in-addr.arpa PTR 63 | ! 11.207.165.128.in-addr.arpa PTR cato.blah.com 64 | 65 | 2013-02-13 14:56:06.006189,75.32.157.134,75.32.12.33,47,u,q,NA 66 | ? blerg-dd.blah.com A 67 | 68 | 2013-02-13 14:56:06.006212,192.12.184.7,33.53.3.1,42,u,q,NA 69 | ? api.flyertown.ca A 70 | 71 | 2013-02-13 14:56:06.006570,75.32.12.33,75.32.157.134,63,u,r,AA 72 | ? blerg-dd.blah.com A 73 | ! blerg-dd.blah.com A 172.16.236.126 74 | 75 | 76 | -------------------------------------------------------------------------------- /init/dnscapture: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Init file for dns capture daemon (just a tcpdump process...) 4 | # This was written for RHEL 5, and will probably need to be modified 5 | # for other systems. 6 | # 7 | # chkconfig: 2345 50 50 8 | # description: DNS capture daemon 9 | # 10 | # processname: tcpdump 11 | # config: /etc/dnscapture 12 | # pidfile: /var/run/dnscapture.pid 13 | 14 | . /etc/init.d/functions 15 | PID_FILE=/var/run/dnscapture.pid 16 | CONFIG_FILE=/etc/dnscapture.conf 17 | 18 | # These variables may all be overridden in the config file. 19 | TCPDUMP=/usr/sbin/tcpdump 20 | IFACE="" 21 | OUTPUT_DIR="" 22 | OUTPUT_USER="nobody" 23 | # The max size of each file. 24 | FILE_SIZE_LIMIT="" 25 | # The length of each file in seconds 26 | FILE_TIME_LIMIT="" 27 | # The output filename. This can include strftime escapes 28 | FILE_NAME="dns.%F_%T.pcap" 29 | FILTER="ip and port 53" 30 | COMPRESS="" 31 | 32 | . $CONFIG_FILE 33 | 34 | # Check the pid in the pidfile to see if the process is running. 35 | # Usage: is_running {pidfile} 36 | # Returns 0 if the pidfile contains the pid of a running process. 37 | is_running() 38 | { 39 | if [ -f $1 ]; then 40 | read pid < $1 41 | if [ -d "/proc/$pid" ]; then 42 | return 0 43 | fi 44 | fi 45 | return -1 46 | } 47 | 48 | start() 49 | { 50 | if [ -z $FILE_NAME -o -z $OUTPUT_DIR -o -z $IFACE ]; then 51 | echo "Missing required config variable - " 52 | echo "FILE_NAME: $FILE_NAME" 53 | echo "OUTPUT_DIR: $OUTPUT_DIR" 54 | echo "IFACE: $IFACE" 55 | echo "\nPlease make sure these are set in the config ($CONFIG_FILE)." 56 | exit -1; 57 | fi 58 | 59 | OPTIONS="-i $IFACE -n -w $OUTPUT_DIR/$FILE_NAME" 60 | 61 | if [ -n "$FILE_SIZE_LIMIT" -a -n "$FILE_TIME_LIMIT" ]; then 62 | echo "You can't set both a file size limit and a file time limit." 63 | exit -1 64 | elif [ -n "$FILE_SIZE_LIMIT" ]; then 65 | OPTIONS="$OPTIONS -C $FILE_SIZE_LIMIT" 66 | elif [ -n "$FILE_TIME_LIMIT" ]; then 67 | OPTIONS="$OPTIONS -G $FILE_TIME_LIMIT" 68 | fi 69 | 70 | if [ -n "$OUTPUT_USER" ]; then 71 | OPTIONS="$OPTIONS -Z $OUTPUT_USER" 72 | fi 73 | 74 | if [ -n "$COMPRESS" ]; then 75 | OPTIONS="$OPTIONS -z gzip" 76 | fi 77 | 78 | OPTIONS="$OPTIONS $FILTER" 79 | 80 | echo -n "Starting dns capture daemon: " 81 | 82 | if is_running $PID_FILE; then 83 | failure; 84 | echo "Already Running" 85 | return -1; 86 | fi 87 | 88 | $TCPDUMP $OPTIONS >/dev/null 2>&1 & 89 | echo $! > $PID_FILE 90 | if is_running $PID_FILE; then 91 | success; 92 | fi 93 | echo 94 | } 95 | 96 | stop() 97 | { 98 | killproc -p $PID_FILE 99 | echo 100 | } 101 | 102 | case "$1" in 103 | start) 104 | start 105 | touch /var/lock/subsys/dnscapture 106 | ;; 107 | stop) 108 | stop 109 | rm -f /var/lock/subsys/dnscapture 110 | ;; 111 | restart) 112 | stop 113 | start 114 | ;; 115 | status) 116 | status -p $PID_FILE dnscapture 117 | ;; 118 | *) 119 | echo "Usage: /etc/init.d/dnscapture start|stop|restart|status" 120 | ;; 121 | esac 122 | -------------------------------------------------------------------------------- /network.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "dns_parse.h" 3 | 4 | #ifndef __BP_NETWORK 5 | #define __BP_NETWORK 6 | 7 | // Ethernet data struct. 8 | typedef struct { 9 | // The MAC's can probably be removed from this, as they aren't used 10 | // outside of the ethernet parser. 11 | uint8_t dstmac[6]; 12 | uint8_t srcmac[6]; 13 | uint16_t ethtype; 14 | } eth_info; 15 | 16 | #define IPv4 0x04 17 | #define IPv6 0x06 18 | 19 | // IP address container that is IP version agnostic. 20 | // The IPvX_MOVE macros handle filling these with packet data correctly. 21 | typedef struct ip_addr { 22 | // Should always be either 4 or 6. 23 | uint8_t vers; 24 | union { 25 | struct in_addr v4; 26 | struct in6_addr v6; 27 | } addr; 28 | } ip_addr; 29 | // Move IPv4 addr at pointer P into ip object D, and set it's type. 30 | #define IPv4_MOVE(D, P) D.addr.v4.s_addr = *(in_addr_t *)(P); \ 31 | D.vers = IPv4; 32 | // Move IPv6 addr at pointer P into ip object D, and set it's type. 33 | #define IPv6_MOVE(D, P) memcpy(D.addr.v6.s6_addr, P, 16); D.vers = IPv6; 34 | 35 | // Convert an ip struct into a str. Like NTOA, this uses a single 36 | // buffer, so freeing it need not be freed, but it can only be used once 37 | // per statement. 38 | char IP_STR_BUFF[INET6_ADDRSTRLEN]; 39 | 40 | // Compare two IP addresses. 41 | #define IP_CMP(ipA, ipB) ((ipA.vers == ipB.vers) &&\ 42 | (ipA.vers == IPv4 ? \ 43 | ipA.addr.v4.s_addr == ipB.addr.v4.s_addr : \ 44 | ((ipA.addr.v6.s6_addr32[0] == ipB.addr.v6.s6_addr32[0]) && \ 45 | (ipA.addr.v6.s6_addr32[1] == ipB.addr.v6.s6_addr32[1]) && \ 46 | (ipA.addr.v6.s6_addr32[2] == ipB.addr.v6.s6_addr32[2]) && \ 47 | (ipA.addr.v6.s6_addr32[3] == ipB.addr.v6.s6_addr32[3])) \ 48 | )) 49 | 50 | // Basic network layer information. 51 | typedef struct { 52 | ip_addr src; 53 | ip_addr dst; 54 | uint32_t length; 55 | uint8_t proto; 56 | } ip_info; 57 | 58 | // IP fragment information. 59 | typedef struct ip_fragment { 60 | uint32_t id; 61 | ip_addr src; 62 | ip_addr dst; 63 | uint32_t start; 64 | uint32_t end; 65 | uint8_t * data; 66 | uint8_t islast; 67 | struct ip_fragment * next; 68 | struct ip_fragment * child; 69 | } ip_fragment; 70 | 71 | #define UDP 0x11 72 | #define TCP 0x06 73 | 74 | // Transport information. 75 | typedef struct { 76 | uint16_t srcport; 77 | uint16_t dstport; 78 | // Length of the payload. 79 | uint16_t length; 80 | uint8_t transport; 81 | } transport_info; 82 | 83 | // Parsers all follow the same basic pattern. They take the position in 84 | // the packet data, the packet data pointer, the header, and an object 85 | // to fill out. They return the position of the first byte of their payload. 86 | // On error, they report the error and return 0. 87 | // Exceptions are noted. 88 | 89 | // No pos is passed, since we always start at 0. 90 | uint32_t eth_parse(struct pcap_pkthdr *, uint8_t *, eth_info *, config *); 91 | // This mucks with the eth data, rather than having data of its own. 92 | uint32_t mpls_parse(uint32_t, struct pcap_pkthdr *, 93 | uint8_t *, eth_info *); 94 | uint32_t udp_parse(uint32_t, struct pcap_pkthdr *, uint8_t *, 95 | transport_info *, config *); 96 | // The ** to the packet data is passed, instead of the data directly. 97 | // They may set the packet pointer to a new data array. 98 | // On error, the packet pointer is set to NULL. 99 | uint32_t ipv4_parse(uint32_t, struct pcap_pkthdr *, 100 | uint8_t **, ip_info *, config *); 101 | uint32_t ipv6_parse(uint32_t, struct pcap_pkthdr *, 102 | uint8_t **, ip_info *, config *); 103 | 104 | // Add the ip_fragment object to our lists of fragments. If a fragment is 105 | // complete, returns the completed fragment object. 106 | ip_fragment * ip_frag_add(ip_fragment *, config *); 107 | 108 | // Frees all fragment objects. 109 | void ip_frag_free(config *); 110 | 111 | // Convert an ip struct to a string. The returned buffer is internal, 112 | // and need not be freed. 113 | char * iptostr(ip_addr *); 114 | #endif 115 | -------------------------------------------------------------------------------- /dns_parse.h: -------------------------------------------------------------------------------- 1 | // Structs and defines common to most of DNS parse. 2 | 3 | #ifndef __DP_MAIN 4 | #define __DP_MAIN 5 | 6 | #include 7 | // For standard int type declarations. 8 | #include 9 | 10 | // Fix missing symbols on OSX 11 | #ifndef s6_addr16 12 | #define s6_addr16 __u6_addr.__u6_addr16 13 | #endif 14 | #ifndef s6_addr32 15 | #define s6_addr32 __u6_addr.__u6_addr32 16 | #endif 17 | 18 | // Verbosity flags. Switch which function is defined to add or remove 19 | // various output printfs from the source. These are all for debugging 20 | // purposes. 21 | //#define VERBOSE(A) A 22 | #define VERBOSE(A) 23 | //#define DBG(A) A fflush(stdout); 24 | #define DBG(A) 25 | //#define SHOW_RAW(A) A 26 | #define SHOW_RAW(A) 27 | // There are a lot of DBG statements in the tcp and ip_fragment sections. 28 | // When debugging those areas, it's really nice to know what's going on 29 | // exactly at each point. 30 | 31 | // Get the value of the BITth bit from byte offset O bytes from base B. 32 | #define GET_BIT(B,O,BIT) (uint8_t)(((*(B+O)) & (1 << (BIT))) >> BIT ) 33 | // Get a two byte little endian u_int at base B and offset O. 34 | #define LE_U_SHORT(B,O) (uint16_t)((B[O]<<8)+B[O+1]) 35 | // Get a four byte little endian u_int at base B and offset O. 36 | #define LE_U_INT(B,O) (uint32_t)((B[O]<<24)+(B[O+1]<<16)+(B[O+2]<<8)+B[O+3]) 37 | // Get the DNS tcp length prepended field. 38 | #define TCP_DNS_LEN(P,O) ((P[O]<<8) + P[O+1]) 39 | 40 | // Pre-declarations. 41 | struct tcp_info; 42 | struct ip_fragment; 43 | 44 | #define MAX_EXCLUDES 100 45 | // Globals passed in via the command line. 46 | // I don't really want these to be globals, but libpcap doesn't really 47 | // have the mechanism I need to pass them to the handler. 48 | typedef struct { 49 | uint16_t EXCLUDED[MAX_EXCLUDES]; 50 | uint16_t EXCLUDES; 51 | char SEP; 52 | char * RECORD_SEP; 53 | int AD_ENABLED; 54 | int NS_ENABLED; 55 | int datalink; 56 | int COUNTS; 57 | int PRETTY_DATE; 58 | int PRINT_RR_NAME; 59 | int MISSING_TYPE_WARNINGS; 60 | char * TCP_STATE_PATH; 61 | uint32_t DEDUPS; 62 | struct tcp_info * tcp_sessions_head; 63 | struct ip_fragment * ip_fragment_head; 64 | unsigned long long * dedup_hashes; 65 | uint32_t dedup_pos; 66 | } config; 67 | 68 | // Holds the information for a dns question. 69 | typedef struct dns_question { 70 | char * name; 71 | uint16_t type; 72 | uint16_t cls; 73 | struct dns_question * next; 74 | } dns_question; 75 | 76 | // Holds the information for a dns resource record. 77 | typedef struct dns_rr { 78 | char * name; 79 | uint16_t type; 80 | uint16_t cls; 81 | const char * rr_name; 82 | uint16_t ttl; 83 | uint16_t rdlength; 84 | uint16_t data_len; 85 | char * data; 86 | struct dns_rr * next; 87 | } dns_rr; 88 | 89 | // Holds general DNS information. 90 | typedef struct { 91 | uint16_t id; 92 | char qr; 93 | char AA; 94 | char TC; 95 | uint8_t rcode; 96 | uint8_t opcode; 97 | uint16_t qdcount; 98 | dns_question * queries; 99 | uint16_t ancount; 100 | dns_rr * answers; 101 | uint16_t nscount; 102 | dns_rr * name_servers; 103 | uint16_t arcount; 104 | dns_rr * additional; 105 | } dns_info; 106 | 107 | // Including these earlier leads to all sorts of circular dependencies. 108 | #include "tcp.h" 109 | #include "network.h" 110 | 111 | #define FORCE 1 112 | 113 | // Parse DNS from from the given 'packet' byte array starting at offset 'pos', 114 | // with libpcap header information in 'header'. 115 | // The parsed information is put in the 'dns' struct, and the 116 | // new pos in the packet is returned. (0 on error). 117 | // The config struct gives needed configuration options. 118 | // force - Force fully parsing the dns data, even if 119 | // configuration parameters mean it isn't necessary. If this is false, 120 | // the returned position may not correspond with the end of the DNS data. 121 | uint32_t dns_parse(uint32_t pos, struct pcap_pkthdr *header, 122 | uint8_t *packet, dns_info * dns, 123 | config * conf, uint8_t force); 124 | // Print the information in the given packet information objects according 125 | // to the settings in the configuration struct. 126 | void print_summary(ip_info * ip, transport_info * trns, dns_info * dns, 127 | struct pcap_pkthdr * header, config * conf); 128 | // Print packet bytes in hex. 129 | // max_len - Maximum packet offset. 130 | // packet - pointer to the packet data. 131 | // start - start offset 132 | // end - end offset (if farther than max_len, printing stops at max_len). 133 | // wrap - How many bytes to print per line. 134 | void print_packet(uint32_t max_len, uint8_t *packet, 135 | uint32_t start, uint32_t end, u_int wrap); 136 | 137 | // Print the given timestamp out on the given file*, as configured. 138 | void print_ts(struct timeval *, config *); 139 | #endif 140 | -------------------------------------------------------------------------------- /bin/dns_parse_cron: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # This script is meant to be set up as a cron job. It uses dns_parse to 4 | # parse a set of gzipped pcaps and writes the result to a single log file. 5 | # There are several config file options available, see the file at CFG_PATH 6 | # below. 7 | 8 | from ConfigParser import SafeConfigParser 9 | import glob 10 | import fcntl 11 | import os 12 | import subprocess 13 | import sys 14 | import syslog 15 | from syslog import LOG_KERN, LOG_USER, LOG_DAEMON, LOG_LOCAL0, LOG_LOCAL1,\ 16 | LOG_LOCAL2, LOG_LOCAL3, LOG_LOCAL4, LOG_LOCAL5, \ 17 | LOG_LOCAL6, LOG_LOCAL7 18 | 19 | CFG_PATH = '/etc/dns_parse.cfg' 20 | CFG_OPTIONS = ['PCAP_DIR', 'NETWORKS', 'STATE_DIR', 'OPTIONS', 21 | 'LOG_FILE', 'ERROR_LOG_FACILITY', 'MOVE_DIR'] 22 | parser = SafeConfigParser() 23 | parser.read(CFG_PATH) 24 | 25 | FACILITIES = {'kern': LOG_KERN, 'user': LOG_USER, 'daemon': LOG_DAEMON, 26 | 'local0': LOG_LOCAL0, 'local1': LOG_LOCAL1, 'local2': LOG_LOCAL2, 27 | 'local3': LOG_LOCAL3, 'local4': LOG_LOCAL4, 'local5': LOG_LOCAL5, 28 | 'local6': LOG_LOCAL6, 'local7': LOG_LOCAL7} 29 | 30 | facility = None 31 | if parser.has_option('DEFAULT', 'ERROR_LOG_FACILITY'): 32 | facility = parser.get('DEFAULT', 'ERROR_LOG_FACILITY') 33 | 34 | if facility in FACILITIES: 35 | syslog.openlog("dns_parse_cron", 0, FACILITIES[facility]) 36 | else: 37 | syslog.openlog("dns_parse_cron", 0, LOG_DAEMON) 38 | syslog.syslog(syslog.LOG_WARNING, "Missing or bad ERROR_LOG_FACILITY " 39 | "option: %s" % facility) 40 | 41 | # Grab all the config options 42 | options = {'OPTIONS':''} 43 | for option in CFG_OPTIONS: 44 | if parser.has_option('DEFAULT', option): 45 | options[option] = parser.get('DEFAULT', option) 46 | 47 | if option not in options: 48 | print "You must set a value for option '%s' in %s" % (option, CFG_PATH) 49 | sys.exit(-1) 50 | 51 | errtmp = os.path.join(options['STATE_DIR'], 'errtmp') 52 | 53 | # Process the files for each network specified. 54 | for network in options['NETWORKS'].split(): 55 | network = network.strip() 56 | if not network: 57 | continue 58 | state_fn = os.path.join(options['STATE_DIR'], network) 59 | if os.path.exists(state_fn): 60 | statefile = open(state_fn) 61 | last = statefile.read() 62 | else: 63 | if not os.path.exists(options['STATE_DIR']): 64 | os.makedirs(options['STATE_DIR']) 65 | statefile = open(state_fn, 'a') 66 | last = None 67 | 68 | try: 69 | # Only allow one copy of this script to run at a time. 70 | fcntl.flock(statefile, fcntl.LOCK_EX | fcntl.LOCK_NB) 71 | 72 | # It's assumed that we're using the COMPRESS option in the 73 | # capture script. It's also assumed that they're named such that 74 | # a reversed sort will put them in order of most to least recent time 75 | # wise. 76 | path = os.path.join(options['PCAP_DIR'], network) + '.*.gz' 77 | files = glob.glob(path) 78 | files.sort(reverse=True) 79 | if len(files) < 2: 80 | continue 81 | 82 | # If we don't have a lock file, start with the most recent completed 83 | # file. 84 | if (last not in files): 85 | i = 1 86 | else: 87 | i = files.index(last) - 1 88 | 89 | # process every file except the most current (it's currently being 90 | # written). 91 | success = True 92 | while i > 0: 93 | path = os.path.join(options['PCAP_DIR'], files[i]) 94 | cmd = 'gunzip -c %s | /usr/local/sbin/dns_parse %s - >> %s 2> %s'%\ 95 | (path, options['OPTIONS'], options['LOG_FILE'], errtmp) 96 | ret = subprocess.call(cmd, shell=True) 97 | if ret != 0: 98 | syslog.syslog(syslog.LOG_ERR, "Error processing file '%s'. " 99 | "Cmd: %s" % (path, cmd)) 100 | 101 | if (options['MOVE_DIR']): 102 | new_path = os.path.join(options['MOVE_DIR'], 103 | files[i].split('/')[-1]) 104 | cmd = ['mv', '-f', path, new_path] 105 | proc = subprocess.Popen(cmd, stderr=subprocess.PIPE) 106 | ret = proc.wait() 107 | if ret != 0: 108 | errs.seek(0) 109 | syslog.syslog(syslog.LOG_ERR, 110 | "Error moving pcap file: %s" % proc.stderr.read()) 111 | 112 | try: 113 | if os.path.exists(errtmp): 114 | errtmp_file = open(errtmp) 115 | for line in errtmp_file.readlines(): 116 | syslog.syslog(syslog.LOG_INFO, line) 117 | errtmp_file.close() 118 | except Exception, msg: 119 | syslog.syslog(syslog.LOG_ERR, 120 | "Error handling error file: %s" % msg) 121 | 122 | 123 | i = i - 1 124 | 125 | try: 126 | wstatefile = open(state_fn, 'w') 127 | wstatefile.write(files[1]) 128 | wstatefile.close() 129 | except Exception, msg: 130 | syslog.syslog(syslog.LOG_ERR, "Statefile saving error: %s" % msg) 131 | finally: 132 | fcntl.flock(statefile, fcntl.LOCK_UN) 133 | statefile.close() 134 | 135 | -------------------------------------------------------------------------------- /strutils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | char * escape_data(const uint8_t * packet, uint32_t start, uint32_t end) { 8 | int i,o; 9 | uint8_t c; 10 | unsigned int length=1; 11 | 12 | char * outstr; 13 | 14 | for (i=start; i= 0x7f) length += 4; 17 | else length += 1; 18 | } 19 | 20 | outstr = (char *)malloc(sizeof(char)*length); 21 | // If the malloc failed then fail. 22 | if (outstr == 0) return (char *)0; 23 | 24 | o=0; 25 | for (i=start; i= 0x7f) { 28 | outstr[o] = '\\'; 29 | outstr[o+1] = 'x'; 30 | outstr[o+2] = c/16 + 0x30; 31 | outstr[o+3] = c%16 + 0x30; 32 | if (outstr[o+2] > 0x39) outstr[o+2] += 0x27; 33 | if (outstr[o+3] > 0x39) outstr[o+3] += 0x27; 34 | o += 4; 35 | } else { 36 | outstr[o] = c; 37 | o++; 38 | } 39 | } 40 | outstr[o] = 0; 41 | return outstr; 42 | } 43 | 44 | char * read_rr_name(const uint8_t * packet, uint32_t * packet_p, 45 | uint32_t id_pos, uint32_t len) { 46 | uint32_t i, next, pos=*packet_p; 47 | uint32_t end_pos = 0; 48 | uint32_t name_len=0; 49 | uint32_t steps = 0; 50 | char * name; 51 | 52 | // Scan through the name, one character at a time. We need to look at 53 | // each character to look for values we can't print in order to allocate 54 | // extra space for escaping them. 'next' is the next position to look 55 | // for a compression jump or name end. 56 | // It's possible that there are endless loops in the name. Our protection 57 | // against this is to make sure we don't read more bytes in this process 58 | // than twice the length of the data. Names that take that many steps to 59 | // read in should be impossible. 60 | next = pos; 61 | while (pos < len && !(next == pos && packet[pos] == 0) 62 | && steps < len*2) { 63 | uint8_t c = packet[pos]; 64 | steps++; 65 | if (next == pos) { 66 | // Handle message compression. 67 | // If the length byte starts with the bits 11, then the rest of 68 | // this byte and the next form the offset from the dns proto start 69 | // to the start of the remainder of the name. 70 | if ((c & 0xc0) == 0xc0) { 71 | if (pos + 1 >= len) return 0; 72 | if (end_pos == 0) end_pos = pos + 1; 73 | pos = id_pos + ((c & 0x3f) << 8) + packet[pos+1]; 74 | next = pos; 75 | } else { 76 | name_len++; 77 | pos++; 78 | next = next + c + 1; 79 | } 80 | } else { 81 | if (c >= '!' && c <= 'z' && c != '\\') name_len++; 82 | else name_len += 4; 83 | pos++; 84 | } 85 | } 86 | if (end_pos == 0) end_pos = pos; 87 | 88 | // Due to the nature of DNS name compression, it's possible to get a 89 | // name that is infinitely long. Return an error in that case. 90 | // We use the len of the packet as the limit, because it shouldn't 91 | // be possible for the name to be that long. 92 | if (steps >= 2*len || pos >= len) return NULL; 93 | 94 | name_len++; 95 | 96 | name = (char *)malloc(sizeof(char) * name_len); 97 | pos = *packet_p; 98 | 99 | //Now actually assemble the name. 100 | //We've already made sure that we don't exceed the packet length, so 101 | // we don't need to make those checks anymore. 102 | // Non-printable and whitespace characters are replaced with a question 103 | // mark. They shouldn't be allowed under any circumstances anyway. 104 | // Other non-allowed characters are kept as is, as they appear sometimes 105 | // regardless. 106 | // This shouldn't interfere with IDNA (international 107 | // domain names), as those are ascii encoded. 108 | next = pos; 109 | i = 0; 110 | while (next != pos || packet[pos] != 0) { 111 | if (pos == next) { 112 | if ((packet[pos] & 0xc0) == 0xc0) { 113 | pos = id_pos + ((packet[pos] & 0x3f) << 8) + packet[pos+1]; 114 | next = pos; 115 | } else { 116 | // Add a period except for the first time. 117 | if (i != 0) name[i++] = '.'; 118 | next = pos + packet[pos] + 1; 119 | pos++; 120 | } 121 | } else { 122 | uint8_t c = packet[pos]; 123 | if (c >= '!' && c <= '~' && c != '\\') { 124 | name[i] = packet[pos]; 125 | i++; pos++; 126 | } else { 127 | name[i] = '\\'; 128 | name[i+1] = 'x'; 129 | name[i+2] = c/16 + 0x30; 130 | name[i+3] = c%16 + 0x30; 131 | if (name[i+2] > 0x39) name[i+2] += 0x27; 132 | if (name[i+3] > 0x39) name[i+3] += 0x27; 133 | i+=4; 134 | pos++; 135 | } 136 | } 137 | } 138 | name[i] = 0; 139 | 140 | *packet_p = end_pos + 1; 141 | 142 | return name; 143 | } 144 | 145 | static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 146 | 147 | char * b64encode(const uint8_t * data, uint32_t pos, uint16_t length) { 148 | char * out; 149 | uint32_t end_pos = pos + length; 150 | uint32_t op = 0; 151 | 152 | // We allocate a little extra here sometimes, but in this application 153 | // these strings are almost immediately de-allocated anyway. 154 | out = malloc(sizeof(char) * ((length/3 + 1)*4 + 1)); 155 | 156 | while (pos + 2 < end_pos) { 157 | out[op] = cb64[ data[pos] >> 2 ]; 158 | out[op+1] = cb64[ ((data[pos] & 0x3) << 4) | 159 | ((data[pos+1] & 0xf0) >> 4) ]; 160 | out[op+2] = cb64[ ((data[pos+1] & 0xf) << 2) | 161 | ((data[pos+2] & 0xc0) >> 6) ]; 162 | out[op+3] = cb64[ data[pos+2] & 0x3f ]; 163 | 164 | op = op + 4; 165 | pos = pos + 3; 166 | } 167 | 168 | if ((end_pos - pos) == 2) { 169 | out[op] = cb64[ data[pos] >> 2 ]; 170 | out[op+1] = cb64[ ((data[pos] & 0x3) << 4) | 171 | ((data[pos+1] & 0xf0) >> 4) ]; 172 | out[op+2] = cb64[ ((data[pos+1] & 0xf) << 2) ]; 173 | out[op+3] = '='; 174 | op = op + 4; 175 | } else if ((end_pos - pos) == 1) { 176 | out[op] = cb64[ data[pos] >> 2 ]; 177 | out[op+1] = cb64[ ((data[pos] & 0x3) << 4) ]; 178 | out[op+2] = out[op+3] = '='; 179 | op = op + 4; 180 | } 181 | out[op] = 0; 182 | 183 | return out; 184 | } 185 | 186 | #ifdef __STRUTILS_TESTING__ 187 | int main() { 188 | 189 | uint8_t * ed_data = " " 190 | "\x00\x0f\x10\x1f\x5c\x7f" // 6 191 | "abcdefghijklmnopqrstuvwxyz" // 26 192 | "1234567890" // 10 193 | "ZYXWVUTSRQPONMLKJIHGFEDCBA" // 26 194 | "+_)(*&^%$#@!~`-=[]{}|;':<>?,./" // 30 195 | "\\ \"" // 3, 101 total 196 | "blahblahblah"; 197 | char * s = escape_data(ed_data, 2, 103); 198 | char * result = "\\x00\\x0f\\x10\\x1f\\x5c\\x7fabcdefghijklmnopqrstuvwxyz1234567890ZYXWVUTSRQPONMLKJIHGFEDCBA+_)(*&^%$#@!~`-=[]{}|;':<>?,./\\x5c \""; 199 | 200 | uint8_t * name_data = "5junk\x03rat\x04\x7f\x00\xe3\\\x03gov\x00tenjunkchr" 201 | "\x05hello\x03the\xc0\x01"; 202 | const char * name_result = "hello.the.rat.\\x7f\\x00\\xe3\\x5c.gov"; 203 | uint32_t pos; 204 | int i; 205 | uint8_t b64data[256]; 206 | char * b64result; 207 | char * b64t1 = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="; 208 | char * b64t2 = "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp7fH1+f4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foKGio6SlpqeoqaqrrK2ur7CxsrO0tba3uLm6u7y9vr/AwcLDxMXGx8jJysvMzc7P0NHS09TV1tfY2drb3N3e3+Dh4uPk5ebn6Onq6+zt7u/w8fLz9PX29/j5+vv8/f7/"; 209 | char * b64t3 = "AgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ent8fX5/gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vL2+v8DBwsPExcbHyMnKy8zNzs/Q0dLT1NXW19jZ2tvc3d7f4OHi4+Tl5ufo6err7O3u7/Dx8vP09fb3+Pn6+/z9/v8="; 210 | if (strcmp(s,result) == 0) printf("escape_data Test ok\n"); 211 | else { 212 | printf("escape_data Test Failed\n"); 213 | int i = 0; 214 | char n = s[0], r=result[0]; 215 | while (n == r) { 216 | printf("%c, %c, %d\n", n, r, n == r); 217 | i++; 218 | n = s[i]; r=result[i]; 219 | } 220 | printf("%c, %c, %d\n", n, r, n == r); 221 | } 222 | 223 | free(s); 224 | s = NULL; 225 | 226 | pos = 29; 227 | s = read_rr_name(name_data, &pos, 4, 41); 228 | 229 | if ((strcmp(s, name_result) == 0) && pos == 41) 230 | printf("name parse test ok.\n"); 231 | else { 232 | printf("pos: %d\n", pos); 233 | printf("name parse test failed: \n%s\n%s\n", s,name_result); 234 | for (i=0; i<35; i++) 235 | printf("%x, %x, %c, %c\n", s[i], name_result[i], 236 | s[i], name_result[i]); 237 | } 238 | 239 | free(s); 240 | 241 | for (i=0; i<256; i++) b64data[i] = i; 242 | 243 | b64result = b64encode(b64data,0,256); 244 | if (strcmp(b64result, b64t1)) 245 | printf("b64 test failed.\n%s\n%s\n", b64result, b64t1); 246 | else printf("b64 test1 passed\n"); 247 | free(b64result); 248 | b64result = b64encode(b64data,1,255); 249 | if (strcmp(b64result, b64t2)) 250 | printf("b64 test failed.\n%s\n%s\n", b64result, b64t2); 251 | else printf("b64 test2 passed\n"); 252 | free(b64result); 253 | b64result = b64encode(b64data,2,254); 254 | if (strcmp(b64result, b64t3)) 255 | printf("b64 test failed.\n%s\n%s\n", b64result, b64t3); 256 | else printf("b64 test3 passed\n"); 257 | return 0; 258 | } 259 | #endif 260 | -------------------------------------------------------------------------------- /rtypes.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "rtypes.h" 5 | #include "strutils.h" 6 | 7 | // Add new parser functions and documentation to the rr_parsers array at 8 | // the bottom of this file. 9 | 10 | // This is used when a parser isn't defined for a given class, rtypes. 11 | // Note: The NULL in the name field is important, and used to distinguish 12 | // this from the rest of the parser containers. 13 | rr_parser_container default_rr_parser = {0, 0, escape, NULL, NULL, 0}; 14 | 15 | char * mk_error(const char * msg, const uint8_t * packet, uint32_t pos, 16 | uint16_t rdlength) { 17 | char * tmp = escape_data(packet, pos, pos+rdlength); 18 | size_t len = strlen(tmp) + strlen(msg) + 1; 19 | char * buffer = malloc(sizeof(char)*len); 20 | sprintf(buffer, "%s%s", msg, tmp); 21 | free(tmp); 22 | return buffer; 23 | } 24 | 25 | #define A_DOC "A (IPv4 address) format\n"\ 26 | "A records are simply an IPv4 address, and are formatted as such." 27 | char * A(const uint8_t * packet, uint32_t pos, uint32_t i, 28 | uint16_t rdlength, uint32_t plen) { 29 | char * data = (char *)malloc(sizeof(char)*16); 30 | 31 | if (rdlength != 4) { 32 | free(data); 33 | return mk_error("Bad A record: ", packet, pos, rdlength); 34 | } 35 | 36 | sprintf(data, "%d.%d.%d.%d", packet[pos], packet[pos+1], 37 | packet[pos+2], packet[pos+3]); 38 | 39 | return data; 40 | } 41 | 42 | #define D_DOC "domain name like format\n"\ 43 | "A DNS like name. This format is used for many record types." 44 | char * domain_name(const uint8_t * packet, uint32_t pos, uint32_t id_pos, 45 | uint16_t rdlength, uint32_t plen) { 46 | char * name = read_rr_name(packet, &pos, id_pos, plen); 47 | if (name == NULL) 48 | name = mk_error("Bad DNS name: ", packet, pos, rdlength); 49 | 50 | return name; 51 | } 52 | 53 | #define SOA_DOC "Start of Authority format\n"\ 54 | "Presented as a series of labeled SOA fields." 55 | char * soa(const uint8_t * packet, uint32_t pos, uint32_t id_pos, 56 | uint16_t rdlength, uint32_t plen) { 57 | char * mname; 58 | char * rname; 59 | char * buffer; 60 | uint32_t serial, refresh, retry, expire, minimum; 61 | const char * format = "mname: %s, rname: %s, serial: %d, " 62 | "refresh: %d, retry: %d, expire: %d, min: %d"; 63 | 64 | mname = read_rr_name(packet, &pos, id_pos, plen); 65 | if (mname == NULL) return mk_error("Bad SOA: ", packet, pos, rdlength); 66 | rname = read_rr_name(packet, &pos, id_pos, plen); 67 | if (rname == NULL) return mk_error("Bad SOA: ", packet, pos, rdlength); 68 | 69 | int i; 70 | serial = refresh = retry = expire = minimum = 0; 71 | for (i = 0; i < 4; i++) { 72 | serial <<= 8; serial |= packet[pos+(i+(4*0))]; 73 | refresh <<= 8; refresh |= packet[pos+(i+(4*1))]; 74 | retry <<= 8; retry |= packet[pos+(i+(4*2))]; 75 | expire <<= 8; expire |= packet[pos+(i+(4*3))]; 76 | minimum <<= 8; minimum |= packet[pos+(i+(4*4))]; 77 | } 78 | 79 | // let snprintf() measure the formatted string 80 | int len = snprintf(0, 0, format, mname, rname, serial, refresh, retry, 81 | expire, minimum); 82 | buffer = malloc(len + 1); 83 | sprintf(buffer, format, mname, rname, serial, refresh, retry, expire, 84 | minimum); 85 | free(mname); 86 | free(rname); 87 | return buffer; 88 | } 89 | 90 | #define MX_DOC "Mail Exchange record format\n"\ 91 | "A standard dns name preceded by a preference number." 92 | char * mx(const uint8_t * packet, uint32_t pos, uint32_t id_pos, 93 | uint16_t rdlength, uint32_t plen) { 94 | 95 | uint16_t pref = (packet[pos] << 8) + packet[pos+1]; 96 | char * name; 97 | char * buffer; 98 | uint32_t spos = pos; 99 | 100 | pos = pos + 2; 101 | name = read_rr_name(packet, &pos, id_pos, plen); 102 | if (name == NULL) 103 | return mk_error("Bad MX: ", packet, spos, rdlength); 104 | 105 | buffer = malloc(sizeof(char)*(5 + 1 + strlen(name) + 1)); 106 | sprintf(buffer, "%d,%s", pref, name); 107 | free(name); 108 | return buffer; 109 | } 110 | 111 | #define OPTS_DOC "EDNS option record format\n"\ 112 | "These records contain a size field for warning about extra large DNS \n"\ 113 | "packets, an extended rcode, and an optional set of dynamic fields.\n"\ 114 | "The size and extended rcode are printed, but the dynamic fields are \n"\ 115 | "simply escaped. Note that the associated format function is non-standard,\n"\ 116 | "as EDNS records modify the basic resourse record protocol (there is no \n"\ 117 | "class field, for instance. RFC 2671" 118 | char * opts(const uint8_t * packet, uint32_t pos, uint32_t id_pos, 119 | uint16_t rdlength, uint32_t plen) { 120 | uint16_t payload_size = (packet[pos] << 8) + packet[pos+1]; 121 | char *buffer; 122 | const char * base_format = "size:%d,rcode:0x%02x%02x%02x%02x,%s"; 123 | char *rdata = escape_data(packet, pos+6, pos + 6 + rdlength); 124 | 125 | buffer = malloc(sizeof(char) * (strlen(base_format) - 20 + 5 + 8 + 126 | strlen(rdata) + 1)); 127 | sprintf(buffer, base_format, payload_size, packet[pos+2], packet[pos+3], 128 | packet[pos+4], packet[pos+5], rdata); 129 | free(rdata); 130 | return buffer; 131 | } 132 | 133 | #define SRV_DOC "Service record format. RFC 2782\n"\ 134 | "Service records are used to identify various network services and ports.\n"\ 135 | "The format is: 'priority,weight,port target'\n"\ 136 | "The target is a somewhat standard DNS name." 137 | char * srv(const uint8_t * packet, uint32_t pos, uint32_t id_pos, 138 | uint16_t rdlength, uint32_t plen) { 139 | uint16_t priority = (packet[pos] << 8) + packet[pos+1]; 140 | uint16_t weight = (packet[pos+2] << 8) + packet[pos+3]; 141 | uint16_t port = (packet[pos+4] << 8) + packet[pos+5]; 142 | char *target, *buffer; 143 | pos = pos + 6; 144 | // Don't read beyond the end of the rr. 145 | target = read_rr_name(packet, &pos, id_pos, pos+rdlength-6); 146 | if (target == NULL) 147 | return mk_error("Bad SRV", packet, pos, rdlength); 148 | 149 | buffer = malloc(sizeof(char) * ((3*5+3) + strlen(target))); 150 | sprintf(buffer, "%d,%d,%d %s", priority, weight, port, target); 151 | free(target); 152 | return buffer; 153 | } 154 | 155 | #define AAAA_DOC "IPv6 record format. RFC 3596\n"\ 156 | "A standard IPv6 address. No attempt is made to abbreviate the address." 157 | char * AAAA(const uint8_t * packet, uint32_t pos, uint32_t id_pos, 158 | uint16_t rdlength, uint32_t plen) { 159 | char *buffer; 160 | uint16_t ipv6[8]; 161 | int i; 162 | 163 | if (rdlength != 16) { 164 | return mk_error("Bad AAAA record", packet, pos, rdlength); 165 | } 166 | 167 | for (i=0; i < 8; i++) 168 | ipv6[i] = (packet[pos+i*2] << 8) + packet[pos+i*2+1]; 169 | buffer = malloc(sizeof(char) * (4*8 + 7 + 1)); 170 | sprintf(buffer, "%x:%x:%x:%x:%x:%x:%x:%x", ipv6[0], ipv6[1], ipv6[2], 171 | ipv6[3], ipv6[4], ipv6[5], 172 | ipv6[6], ipv6[7]); 173 | return buffer; 174 | } 175 | 176 | #define KEY_DOC "dnssec Key format. RFC 4034\n"\ 177 | "format: flags, proto, algorithm, key\n"\ 178 | "All fields except the key are printed as decimal numbers.\n"\ 179 | "The key is given in base64. " 180 | char * dnskey(const uint8_t * packet, uint32_t pos, uint32_t id_pos, 181 | uint16_t rdlength, uint32_t plen) { 182 | uint16_t flags = (packet[pos] << 8) + packet[pos+1]; 183 | uint8_t proto = packet[pos+2]; 184 | uint8_t algorithm = packet[pos+3]; 185 | char *buffer, *key; 186 | 187 | key = b64encode(packet, pos+4, rdlength-4); 188 | buffer = malloc(sizeof(char) * (1 + strlen(key) + 18)); 189 | sprintf(buffer, "%d,%d,%d,%s", flags, proto, algorithm, key); 190 | free(key); 191 | return buffer; 192 | } 193 | 194 | #define RRSIG_DOC "DNS SEC Signature. RFC 4304\n"\ 195 | "format: tc,alg,labels,ottl,expiration,inception,tag signer signature\n"\ 196 | "All fields except the signer and signature are given as decimal numbers.\n"\ 197 | "The signer is a standard DNS name.\n"\ 198 | "The signature is base64 encoded." 199 | char * rrsig(const uint8_t * packet, uint32_t pos, uint32_t id_pos, 200 | uint16_t rdlength, uint32_t plen) { 201 | uint32_t o_pos = pos; 202 | uint16_t tc = (packet[pos] << 8) + packet[pos+1]; 203 | uint8_t alg = packet[pos+2]; 204 | uint8_t labels = packet[pos+3]; 205 | u_int ottl, sig_exp, sig_inc; 206 | uint16_t key_tag = (packet[pos+16] << 8) + packet[pos+17]; 207 | char *signer, *signature, *buffer; 208 | pos = pos + 4; 209 | ottl = (packet[pos] << 24) + (packet[pos+1] << 16) + 210 | (packet[pos+2] << 8) + packet[pos+3]; 211 | pos = pos + 4; 212 | sig_exp = (packet[pos] << 24) + (packet[pos+1] << 16) + 213 | (packet[pos+2] << 8) + packet[pos+3]; 214 | pos = pos + 4; 215 | sig_inc = (packet[pos] << 24) + (packet[pos+1] << 16) + 216 | (packet[pos+2] << 8) + packet[pos+3]; 217 | pos = pos + 6; 218 | signer = read_rr_name(packet, &pos, id_pos, o_pos+rdlength); 219 | if (signer == NULL) 220 | return mk_error("Bad Signer name", packet, pos, rdlength); 221 | 222 | signature = b64encode(packet, pos, o_pos+rdlength-pos); 223 | buffer = malloc(sizeof(char) * (2*5 + // 2 16 bit ints 224 | 3*10 + // 3 32 bit ints 225 | 2*3 + // 2 8 bit ints 226 | 8 + // 8 separator chars 227 | strlen(signer) + 228 | strlen(signature) + 1)); 229 | sprintf(buffer, "%d,%d,%d,%d,%d,%d,%d,%s,%s", tc, alg, labels, ottl, 230 | sig_exp, sig_inc, key_tag, signer, signature); 231 | free(signer); 232 | free(signature); 233 | return buffer; 234 | } 235 | 236 | #define NSEC_DOC "NSEC format. RFC 4034\n"\ 237 | "Format: domain bitmap\n"\ 238 | "domain is a DNS name, bitmap is hex escaped." 239 | char * nsec(const uint8_t * packet, uint32_t pos, uint32_t id_pos, 240 | uint16_t rdlength, uint32_t plen) { 241 | 242 | char *buffer, *domain, *bitmap; 243 | 244 | domain = read_rr_name(packet, &pos, id_pos, pos+rdlength); 245 | if (domain == NULL) 246 | return mk_error("Bad NSEC domain", packet, pos, rdlength); 247 | 248 | bitmap = escape_data(packet, pos, pos+rdlength); 249 | buffer = malloc(sizeof(char) * (strlen(domain)+strlen(bitmap)+2)); 250 | sprintf(buffer, "%s,%s", domain, bitmap); 251 | free(domain); 252 | free(bitmap); 253 | return buffer; 254 | 255 | } 256 | 257 | #define DS_DOC "DS DNS SEC record. RFC 4034\n"\ 258 | "format: key_tag,algorithm,digest_type,digest\n"\ 259 | "The keytag, algorithm, and digest type are given as base 10.\n"\ 260 | "The digest is base64 encoded." 261 | char * ds(const uint8_t * packet, uint32_t pos, uint32_t id_pos, uint16_t rdlength, uint32_t plen) { 262 | uint16_t key_tag = (packet[pos] << 8) + packet[pos+1]; 263 | uint8_t alg = packet[pos+2]; 264 | uint8_t dig_type = packet[pos+3]; 265 | char * digest = b64encode(packet,pos+4,rdlength-4); 266 | char * buffer; 267 | 268 | buffer = malloc(sizeof(char) * (strlen(digest) + 15)); 269 | sprintf(buffer,"%d,%d,%d,%s", key_tag, alg, dig_type, digest); 270 | free(digest); 271 | return buffer; 272 | } 273 | 274 | #define NULL_DOC "This data is simply hex escaped. \n"\ 275 | "Non printable characters are given as a hex value (\\x30), for example." 276 | char * escape(const uint8_t * packet, uint32_t pos, uint32_t i, 277 | uint16_t rdlength, uint32_t plen) { 278 | return escape_data(packet, pos, pos + rdlength); 279 | } 280 | 281 | // Add parser functions here, they should be prototyped in rtypes.h and 282 | // then defined below. 283 | // Some of the rtypes below use the escape parser. This isn't 284 | // because we don't know how to parse them, it's simply because that's 285 | // the right parser for them anyway. 286 | rr_parser_container rr_parsers[] = {{1, 1, A, "A", A_DOC, 0}, 287 | {0, 2, domain_name, "NS", D_DOC, 0}, 288 | {0, 5, domain_name, "CNAME", D_DOC, 0}, 289 | {0, 6, soa, "SOA", SOA_DOC, 0}, 290 | {0, 12, domain_name, "PTR", D_DOC, 0}, 291 | {1, 33, srv, "SRV", SRV_DOC, 0}, 292 | {1, 28, AAAA, "AAAA", AAAA_DOC, 0}, 293 | {0, 15, mx, "MX", MX_DOC, 0}, 294 | {0, 46, rrsig, "RRSIG", RRSIG_DOC, 0}, 295 | {0, 16, escape, "TEXT", NULL_DOC, 0}, 296 | {0, 47, nsec, "NSEC", NSEC_DOC, 0}, 297 | {0, 43, ds, "DS", DS_DOC, 0}, 298 | {0, 10, escape, "NULL", NULL_DOC, 0}, 299 | {0, 48, dnskey, "DNSKEY", KEY_DOC, 0}, 300 | {0, 255, escape, "ANY", NULL_DOC, 0} 301 | }; 302 | 303 | inline int count_parsers() { 304 | return sizeof(rr_parsers)/sizeof(rr_parser_container); 305 | } 306 | 307 | // We occasionally sort the parsers according to how they're used in order 308 | // to speed up lookups. 309 | void sort_parsers() { 310 | int m,n; 311 | int change = 1; 312 | int pcount = count_parsers(); 313 | rr_parser_container tmp; 314 | for (m = 0; m < pcount - 1 && change == 1; m++) { 315 | change = 0; 316 | for (n = 0; n < pcount - 1; n++) { 317 | if (rr_parsers[n].count < rr_parsers[n+1].count) { 318 | tmp = rr_parsers[n]; 319 | rr_parsers[n] = rr_parsers[n+1]; 320 | rr_parsers[n+1] = tmp; 321 | change = 1; 322 | } 323 | } 324 | } 325 | // Reset the counts 326 | for (m = 0; m < pcount - 1; m++) { 327 | rr_parsers[m].count = 0; 328 | } 329 | } 330 | 331 | unsigned int PACKETS_SEEN = 0; 332 | #define REORDER_LIMIT 100000 333 | // Find the parser that corresponds to the given cls and rtype. 334 | rr_parser_container * find_parser(uint16_t cls, uint16_t rtype) { 335 | 336 | unsigned int i=0, pcount = count_parsers(); 337 | rr_parser_container * found = NULL; 338 | 339 | // Re-arrange the order of the parsers according to how often things are 340 | // seen every REORDER_LIMIT packets. 341 | if (PACKETS_SEEN > REORDER_LIMIT) { 342 | PACKETS_SEEN = 0; 343 | sort_parsers(); 344 | } 345 | PACKETS_SEEN++; 346 | 347 | while (i < pcount && found == NULL) { 348 | rr_parser_container pc = rr_parsers[i]; 349 | if ((pc.rtype == rtype || pc.rtype == 0) && 350 | (pc.cls == cls || pc.cls == 0)) { 351 | rr_parsers[i].count++; 352 | found = &rr_parsers[i]; 353 | break; 354 | } 355 | i++; 356 | } 357 | 358 | if (found == NULL) 359 | found = &default_rr_parser; 360 | 361 | found->count++; 362 | return found; 363 | } 364 | 365 | void print_parsers() { 366 | int i; 367 | printf("What follows is a list of handled DNS classes and resource \n" 368 | "record types. \n" 369 | " - The class # may be listed as 'any', though anything \n" 370 | " other than the internet class is rarely seen. \n" 371 | " - Parsers for records other than those in RFC 1035 should \n" 372 | " have their RFC listed. \n" 373 | " - Unhandled resource records are simply string escaped.\n" 374 | " - Some resource records share parsers and documentation.\n\n" 375 | "class, rtype, name: documentation\n"); 376 | for (i=0; i < count_parsers(); i++) { 377 | rr_parser_container cont = rr_parsers[i]; 378 | if (cont.cls == 0) printf("any,"); 379 | else printf("%d,", cont.cls); 380 | 381 | printf(" %d, %s: %s\n\n", cont.rtype, cont.name, cont.doc); 382 | } 383 | } 384 | 385 | void print_parser_usage() { 386 | int i; 387 | rr_parser_container pc; 388 | 389 | fprintf(stderr, "parser usage:\n"); 390 | for (i=0; i < count_parsers(); i++) { 391 | pc = rr_parsers[i]; 392 | fprintf(stderr, " %s - %llu\n", pc.name, pc.count); 393 | } 394 | 395 | fprintf(stderr, " undefined parser - %llu\n", default_rr_parser.count); 396 | } 397 | -------------------------------------------------------------------------------- /network.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "network.h" 6 | 7 | // Parse the ethernet headers, and return the payload position (0 on error). 8 | uint32_t eth_parse(struct pcap_pkthdr *header, uint8_t *packet, 9 | eth_info * eth, config * conf) { 10 | uint32_t pos = 0; 11 | 12 | if (header->len < 14) { 13 | fprintf(stderr, "Truncated Packet(eth)\n"); 14 | return 0; 15 | } 16 | 17 | while (pos < 6) { 18 | eth->dstmac[pos] = packet[pos]; 19 | eth->srcmac[pos] = packet[pos+6]; 20 | pos++; 21 | } 22 | pos = pos + 6; 23 | 24 | // Skip the extra 2 byte field inserted in "Linux Cooked" captures. 25 | if (conf->datalink == DLT_LINUX_SLL) { 26 | pos = pos + 2; 27 | } 28 | 29 | // Skip VLAN tagging 30 | if (packet[pos] == 0x81 && packet[pos+1] == 0) pos = pos + 4; 31 | 32 | eth->ethtype = (packet[pos] << 8) + packet[pos+1]; 33 | pos = pos + 2; 34 | 35 | SHOW_RAW( 36 | printf("\neth "); 37 | print_packet(header->len, packet, 0, pos, 18); 38 | ) 39 | VERBOSE( 40 | printf("dstmac: %02x:%02x:%02x:%02x:%02x:%02x, " 41 | "srcmac: %02x:%02x:%02x:%02x:%02x:%02x\n", 42 | eth->dstmac[0],eth->dstmac[1],eth->dstmac[2], 43 | eth->dstmac[3],eth->dstmac[4],eth->dstmac[5], 44 | eth->srcmac[0],eth->srcmac[1],eth->srcmac[2], 45 | eth->srcmac[3],eth->srcmac[4],eth->srcmac[5]); 46 | ) 47 | return pos; 48 | } 49 | 50 | // Parse MPLS. We don't care about the data in these headers, all we have 51 | // to do is continue parsing them until the 'bottom of stack' flag is set. 52 | uint32_t mpls_parse(uint32_t pos, struct pcap_pkthdr *header, 53 | uint8_t *packet, eth_info * eth) { 54 | // Bottom of stack flag. 55 | uint8_t bos; 56 | do { 57 | VERBOSE(printf("MPLS Layer.\n");) 58 | // Deal with truncated MPLS. 59 | if (header->len < (pos + 4)) { 60 | fprintf(stderr, "Truncated Packet(mpls)\n"); 61 | return 0; 62 | } 63 | 64 | bos = packet[pos + 2] & 0x01; 65 | pos += 4; 66 | DBG(printf("MPLS layer. \n");) 67 | } while (bos == 0); 68 | 69 | if (header->len < pos) { 70 | fprintf(stderr, "Truncated Packet(post mpls)\n"); 71 | return 0; 72 | } 73 | 74 | 75 | // 'Guess' the next protocol. This can result in false positives, but 76 | // generally not. 77 | uint8_t ip_ver = packet[pos] >> 4; 78 | switch (ip_ver) { 79 | case IPv4: 80 | eth->ethtype = 0x0800; break; 81 | case IPv6: 82 | eth->ethtype = 0x86DD; break; 83 | default: 84 | eth->ethtype = 0; 85 | } 86 | 87 | return pos; 88 | } 89 | 90 | // Parse the IPv4 header. May point p_packet to a new packet data array, 91 | // which means zero is a valid return value. Sets p_packet to NULL on error. 92 | // See RFC791 93 | uint32_t ipv4_parse(uint32_t pos, struct pcap_pkthdr *header, 94 | uint8_t ** p_packet, ip_info * ip, config * conf) { 95 | 96 | uint32_t h_len; 97 | ip_fragment * frag = NULL; 98 | uint8_t frag_mf; 99 | uint16_t frag_offset; 100 | 101 | // For convenience and code consistency, dereference the packet **. 102 | uint8_t * packet = *p_packet; 103 | 104 | if (header-> len - pos < 20) { 105 | fprintf(stderr, "Truncated Packet(ipv4)\n"); 106 | *p_packet = NULL; 107 | return 0; 108 | } 109 | 110 | h_len = packet[pos] & 0x0f; 111 | ip->length = (packet[pos+2] << 8) + packet[pos+3] - h_len*4; 112 | ip->proto = packet[pos+9]; 113 | 114 | IPv4_MOVE(ip->src, packet + pos + 12); 115 | IPv4_MOVE(ip->dst, packet + pos + 16); 116 | 117 | // Set if NOT the last fragment. 118 | frag_mf = (packet[pos+6] & 0x20) >> 5; 119 | // Offset for this data in the fragment. 120 | frag_offset = ((packet[pos+6] & 0x1f) << 11) + (packet[pos+7] << 3); 121 | 122 | SHOW_RAW( 123 | printf("\nipv4\n"); 124 | print_packet(header->len, packet, pos, pos + 4*h_len, 4); 125 | ) 126 | VERBOSE( 127 | printf("version: %d, length: %d, proto: %d\n", 128 | IPv4, ip->length, ip->proto); 129 | printf("src ip: %s, ", iptostr(&ip->src)); 130 | printf("dst ip: %s\n", iptostr(&ip->dst)); 131 | ) 132 | 133 | if (frag_mf == 1 || frag_offset != 0) { 134 | VERBOSE(printf("Fragmented IPv4, offset: %u, mf:%u\n", frag_offset, 135 | frag_mf);) 136 | frag = malloc(sizeof(ip_fragment)); 137 | frag->start = frag_offset; 138 | // We don't try to deal with endianness here, since it 139 | // won't matter as long as we're consistent. 140 | frag->islast = !frag_mf; 141 | frag->id = *((uint16_t *)(packet + pos + 4)); 142 | frag->src = ip->src; 143 | frag->dst = ip->dst; 144 | frag->end = frag->start + ip->length; 145 | frag->data = malloc(sizeof(uint8_t) * ip->length); 146 | frag->next = frag->child = NULL; 147 | memcpy(frag->data, packet + pos + 4*h_len, ip->length); 148 | // Add the fragment to the list. 149 | // If this completed the packet, it is returned. 150 | frag = ip_frag_add(frag, conf); 151 | if (frag != NULL) { 152 | // Update the IP info on the reassembled data. 153 | header->len = ip->length = frag->end - frag->start; 154 | *p_packet = frag->data; 155 | free(frag); 156 | return 0; 157 | } 158 | // Signals that there is no more work to do on this packet. 159 | *p_packet = NULL; 160 | return 0; 161 | } 162 | 163 | // move the position up past the options section. 164 | return pos + 4*h_len; 165 | 166 | } 167 | 168 | // Parse the IPv6 header. May point p_packet to a new packet data array, 169 | // which means zero is a valid return value. Sets p_packet to NULL on error. 170 | // See RFC2460 171 | uint32_t ipv6_parse(uint32_t pos, struct pcap_pkthdr *header, 172 | uint8_t ** p_packet, ip_info * ip, config * conf) { 173 | 174 | // For convenience and code consistency, dereference the packet **. 175 | uint8_t * packet = *p_packet; 176 | 177 | // In case the IP packet is a fragment. 178 | ip_fragment * frag = NULL; 179 | uint32_t header_len = 0; 180 | 181 | if (header->len < (pos + 40)) { 182 | fprintf(stderr, "Truncated Packet(ipv6)\n"); 183 | *p_packet=NULL; return 0; 184 | } 185 | ip->length = (packet[pos+4] << 8) + packet[pos+5]; 186 | IPv6_MOVE(ip->src, packet + pos + 8); 187 | IPv6_MOVE(ip->dst, packet + pos + 24); 188 | 189 | // Jumbo grams will have a length of zero. We'll choose to ignore those, 190 | // and any other zero length packets. 191 | if (ip->length == 0) { 192 | fprintf(stderr, "Zero Length IP packet, possible Jumbo Payload.\n"); 193 | *p_packet=NULL; return 0; 194 | } 195 | 196 | uint8_t next_hdr = packet[pos+6]; 197 | VERBOSE(print_packet(header->len, packet, pos, pos+40, 4);) 198 | VERBOSE(printf("IPv6 src: %s, ", iptostr(&ip->src));) 199 | VERBOSE(printf("IPv6 dst: %s\n", iptostr(&ip->dst));) 200 | pos += 40; 201 | 202 | // We pretty much have no choice but to parse all extended sections, 203 | // since there is nothing to tell where the actual data is. 204 | uint8_t done = 0; 205 | while (done == 0) { 206 | VERBOSE(printf("IPv6, next header: %u\n", next_hdr);) 207 | switch (next_hdr) { 208 | // Handle hop-by-hop, dest, and routing options. 209 | // Yay for consistent layouts. 210 | case IPPROTO_HOPOPTS: 211 | case IPPROTO_DSTOPTS: 212 | case IPPROTO_ROUTING: 213 | if (header->len < (pos + 16)) { 214 | fprintf(stderr, "Truncated Packet(ipv6)\n"); 215 | *p_packet = NULL; return 0; 216 | } 217 | next_hdr = packet[pos]; 218 | // The headers are 16 bytes longer. 219 | header_len += 16; 220 | pos += packet[pos+1] + 1; 221 | break; 222 | case 51: // Authentication Header. See RFC4302 223 | if (header->len < (pos + 2)) { 224 | fprintf(stderr, "Truncated Packet(ipv6)\n"); 225 | *p_packet = NULL; return 0; 226 | } 227 | next_hdr = packet[pos]; 228 | header_len += (packet[pos+1] + 2) * 4; 229 | pos += (packet[pos+1] + 2) * 4; 230 | if (header->len < pos) { 231 | fprintf(stderr, "Truncated Packet(ipv6)\n"); 232 | *p_packet = NULL; return 0; 233 | } 234 | break; 235 | case 50: // ESP Protocol. See RFC4303. 236 | // We don't support ESP. 237 | fprintf(stderr, "Unsupported protocol: IPv6 ESP.\n"); 238 | if (frag != NULL) free(frag); 239 | *p_packet = NULL; return 0; 240 | case 135: // IPv6 Mobility See RFC 6275 241 | if (header->len < (pos + 2)) { 242 | fprintf(stderr, "Truncated Packet(ipv6)\n"); 243 | *p_packet = NULL; return 0; 244 | } 245 | next_hdr = packet[pos]; 246 | header_len += packet[pos+1] * 8; 247 | pos += packet[pos+1] * 8; 248 | if (header->len < pos) { 249 | fprintf(stderr, "Truncated Packet(ipv6)\n"); 250 | *p_packet = NULL; return 0; 251 | } 252 | break; 253 | case IPPROTO_FRAGMENT: 254 | // IP fragment. 255 | next_hdr = packet[pos]; 256 | frag = malloc(sizeof(ip_fragment)); 257 | // Get the offset of the data for this fragment. 258 | frag->start = (packet[pos+2] << 8) + (packet[pos+3] & 0xf4); 259 | frag->islast = !(packet[pos+3] & 0x01); 260 | // We don't try to deal with endianness here, since it 261 | // won't matter as long as we're consistent. 262 | frag->id = *(uint32_t *)(packet+pos+4); 263 | // The headers are 8 bytes longer. 264 | header_len += 8; 265 | pos += 8; 266 | break; 267 | case TCP: 268 | case UDP: 269 | done = 1; 270 | break; 271 | default: 272 | fprintf(stderr, "Unsupported IPv6 proto(%u).\n", next_hdr); 273 | *p_packet = NULL; return 0; 274 | } 275 | } 276 | 277 | // check for int overflow 278 | if (header_len > ip->length) { 279 | fprintf(stderr, "Malformed packet(ipv6)\n"); 280 | *p_packet = NULL; 281 | return 0; 282 | } 283 | 284 | ip->proto = next_hdr; 285 | ip->length = ip->length - header_len; 286 | 287 | // Handle fragments. 288 | if (frag != NULL) { 289 | frag->src = ip->src; 290 | frag->dst = ip->dst; 291 | frag->end = frag->start + ip->length; 292 | frag->next = frag->child = NULL; 293 | frag->data = malloc(sizeof(uint8_t) * ip->length); 294 | VERBOSE(printf("IPv6 fragment. offset: %d, m:%u\n", frag->start, 295 | frag->islast);) 296 | memcpy(frag->data, packet+pos, ip->length); 297 | // Add the fragment to the list. 298 | // If this completed the packet, it is returned. 299 | frag = ip_frag_add(frag, conf); 300 | if (frag != NULL) { 301 | header->len = ip->length = frag->end - frag->start; 302 | *p_packet = frag->data; 303 | free(frag); 304 | return 0; 305 | } 306 | // Signals that there is no more work to do on this packet. 307 | *p_packet = NULL; 308 | return 0; 309 | } else { 310 | return pos; 311 | } 312 | 313 | } 314 | 315 | // Add this ip fragment to the our list of fragments. If we complete 316 | // a fragmented packet, return it. 317 | // Limitations - Duplicate packets may end up in the list of fragments. 318 | // - We aren't going to expire fragments, and we aren't going 319 | // to save/load them like with TCP streams either. This may 320 | // mean lost data. 321 | ip_fragment * ip_frag_add(ip_fragment * this, config * conf) { 322 | ip_fragment ** curr = &(conf->ip_fragment_head); 323 | ip_fragment ** found = NULL; 324 | 325 | DBG(printf("Adding fragment at %p\n", this);) 326 | 327 | // Find the matching fragment list. 328 | while (*curr != NULL) { 329 | if ((*curr)->id == this->id && 330 | IP_CMP((*curr)->src, this->src) && 331 | IP_CMP((*curr)->dst, this->dst)) { 332 | found = curr; 333 | DBG(printf("Match found. %p\n", *found);) 334 | break; 335 | } 336 | curr = &(*curr)->next; 337 | } 338 | 339 | // At this point curr will be the head of our matched chain of fragments, 340 | // and found will be the same. We'll use found as our pointer into this 341 | // chain, and curr to remember where it starts. 342 | // 'found' could also be NULL, meaning no match was found. 343 | 344 | // If there wasn't a matching list, then we're done. 345 | if (found == NULL) { 346 | DBG(printf("No matching fragments.\n");) 347 | this->next = conf->ip_fragment_head; 348 | conf->ip_fragment_head = this; 349 | return NULL; 350 | } 351 | 352 | while (*found != NULL) { 353 | DBG(printf("*found: %u-%u, this: %u-%u\n", 354 | (*found)->start, (*found)->end, 355 | this->start, this->end);) 356 | if ((*found)->start >= this->end) { 357 | DBG(printf("It goes in front of %p\n", *found);) 358 | // It goes before, so put it there. 359 | this->child = *found; 360 | this->next = (*found)->next; 361 | *found = this; 362 | break; 363 | } else if ((*found)->child == NULL && 364 | (*found)->end <= this->start) { 365 | DBG(printf("It goes at the end. %p\n", *found);) 366 | // We've reached the end of the line, and that's where it 367 | // goes, so put it there. 368 | (*found)->child = this; 369 | break; 370 | } 371 | DBG(printf("What: %p\n", *found);) 372 | found = &((*found)->child); 373 | } 374 | DBG(printf("What: %p\n", *found);) 375 | 376 | // We found no place for the fragment, which means it's a duplicate 377 | // (or the chain is screwed up...) 378 | if (*found == NULL) { 379 | DBG(printf("No place for fragment: %p\n", *found);) 380 | free(this); 381 | return NULL; 382 | } 383 | 384 | // Now we try to collapse the list. 385 | found = curr; 386 | while ((*found != NULL) && (*found)->child != NULL) { 387 | ip_fragment * child = (*found)->child; 388 | if ((*found)->end == child->start) { 389 | DBG(printf("Merging frag at offset %u-%u with %u-%u\n", 390 | (*found)->start, (*found)->end, 391 | child->start, child->end);) 392 | uint32_t child_len = child->end - child->start; 393 | uint32_t fnd_len = (*found)->end - (*found)->start; 394 | uint8_t * buff = malloc(sizeof(uint8_t) * (fnd_len + child_len)); 395 | memcpy(buff, (*found)->data, fnd_len); 396 | memcpy(buff + fnd_len, child->data, child_len); 397 | (*found)->end = (*found)->end + child_len; 398 | (*found)->islast = child->islast; 399 | (*found)->child = child->child; 400 | // Free the old data and the child, and make the combined buffer 401 | // the new data for the merged fragment. 402 | free((*found)->data); 403 | free(child->data); 404 | free(child); 405 | (*found)->data = buff; 406 | } else { 407 | found = &(*found)->child; 408 | } 409 | } 410 | 411 | DBG(printf("*curr, start: %u, end: %u, islast: %u\n", 412 | (*curr)->start, (*curr)->end, (*curr)->islast);) 413 | // Check to see if we completely collapsed it. 414 | // *curr is the pointer to the first fragment. 415 | if ((*curr)->islast != 0) { 416 | ip_fragment * ret = *curr; 417 | // Remove this from the fragment list. 418 | *curr = (*curr)->next; 419 | DBG(printf("Returning reassembled fragments.\n");) 420 | return ret; 421 | } 422 | // This is what happens when we don't complete a packet. 423 | return NULL; 424 | } 425 | 426 | // Free the lists of IP fragments. 427 | void ip_frag_free(config * conf) { 428 | ip_fragment * curr; 429 | ip_fragment * child; 430 | 431 | while (conf->ip_fragment_head != NULL) { 432 | curr = conf->ip_fragment_head; 433 | conf->ip_fragment_head = curr->next; 434 | while (curr != NULL) { 435 | child = curr->child; 436 | free(curr->data); 437 | free(curr); 438 | curr = child; 439 | } 440 | } 441 | } 442 | 443 | // Parse the udp headers. 444 | uint32_t udp_parse(uint32_t pos, struct pcap_pkthdr *header, 445 | uint8_t *packet, transport_info * udp, 446 | config * conf) { 447 | if (header->len - pos < 8) { 448 | fprintf(stderr, "Truncated Packet(udp)\n"); 449 | return 0; 450 | } 451 | 452 | udp->srcport = (packet[pos] << 8) + packet[pos+1]; 453 | udp->dstport = (packet[pos+2] << 8) + packet[pos+3]; 454 | udp->length = (packet[pos+4] << 8) + packet[pos+5]; 455 | udp->transport = UDP; 456 | VERBOSE(printf("udp\n");) 457 | VERBOSE(printf("srcport: %d, dstport: %d, len: %d\n", udp->srcport, udp->dstport, udp->length);) 458 | SHOW_RAW(print_packet(header->len, packet, pos, pos, 4);) 459 | return pos + 8; 460 | } 461 | 462 | // Convert an ip struct to a string. The returned buffer is internal, 463 | // and need not be freed. 464 | char * iptostr(ip_addr * ip) { 465 | if (ip->vers == IPv4) { 466 | inet_ntop(AF_INET, (const void *) &(ip->addr.v4), 467 | IP_STR_BUFF, INET6_ADDRSTRLEN); 468 | } else { // IPv6 469 | inet_ntop(AF_INET6, (const void *) &(ip->addr.v6), 470 | IP_STR_BUFF, INET6_ADDRSTRLEN); 471 | } 472 | return IP_STR_BUFF; 473 | } 474 | -------------------------------------------------------------------------------- /tcp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "strutils.h" 7 | #include "tcp.h" 8 | 9 | // Perform TCP checksum on the given packet. 10 | // This handles both IPv6 and IPv4 based TCP. 11 | uint16_t tcp_checksum(ip_info *ip, uint8_t *packet, 12 | uint32_t pos, struct pcap_pkthdr *header) { 13 | unsigned int sum = 0; 14 | unsigned int i; 15 | 16 | if (ip->src.vers == IPv4) { 17 | uint32_t srcip = ip->src.addr.v4.s_addr; 18 | uint32_t dstip = ip->dst.addr.v4.s_addr; 19 | 20 | // Put together the psuedo-header preamble for the checksum calculation. 21 | // I handle the IP's in a rather odd manner and save a few cycles. 22 | // Instead of arranging things such that for ip d.c.b.a -> cd + ab 23 | // I do cb + ad, which is equivalent. 24 | sum += (srcip >> 24) + ((srcip & 0xff) << 8); 25 | sum += (srcip >> 8) & 0xffff; 26 | sum += (dstip >> 24) + ((dstip & 0xff) << 8); 27 | sum += (dstip >> 8) & 0xffff; 28 | sum += ip->proto; 29 | sum += ip->length; 30 | } else { 31 | // IPv6 psuedo header construction. 32 | uint16_t * src_v6 = ip->src.addr.v6.s6_addr16; 33 | uint16_t * dst_v6 = ip->dst.addr.v6.s6_addr16; 34 | for (i=0; i<8; i++) { 35 | sum += (src_v6[i] >> 8) + ((src_v6[i] & 0xff) << 8); 36 | sum += (dst_v6[i] >> 8) + ((dst_v6[i] & 0xff) << 8); 37 | } 38 | sum += ip->length; 39 | sum += TCP; 40 | } 41 | 42 | // Add the TCP Header up to the checksum, which we'll skip. 43 | for (i=0; i < 16; i += 2) { 44 | sum += LE_U_SHORT(packet, pos + i); 45 | } 46 | 47 | // Skip the checksum. 48 | pos = pos + i + 2; 49 | 50 | // Add the rest of the packet, stopping short of a final odd byte. 51 | while (pos < header->len - 1) { 52 | sum += LE_U_SHORT(packet, pos); 53 | pos += 2; 54 | } 55 | // Pad the last, odd byte if present. 56 | if (pos < header->len) 57 | sum += packet[pos] << 8; 58 | 59 | // All the overflow bits should be added to the lower 16, including the 60 | // overflow from adding the overflow. 61 | while (sum > 0xffff) { 62 | sum = (sum & 0xffff) + (sum >> 16); 63 | } 64 | // Take the one's compliment (logical not) and we're done. 65 | return ~sum; 66 | } 67 | 68 | // The one-half second expiration time is chosen simply because it's the 69 | // shortest time that consistently works. Shorter and you may miss some 70 | // late arriving packets (.1 seconds misses quite a few). Longer and 71 | // it's more likely that continuous sessions will never expire. 72 | #define TCP_EXPIRE_USECS 500000 73 | #define __USEC_RES 1000000 74 | #define is_expired(now, old) (\ 75 | ((long long) (now).tv_sec*__USEC_RES + (now).tv_usec) - \ 76 | ((long long) (old).tv_sec*__USEC_RES + (old).tv_usec)) > \ 77 | TCP_EXPIRE_USECS 78 | 79 | // Parse the tcp data, and put it in our lists of streams to be reassembled 80 | // later. 81 | void tcp_parse(uint32_t pos, struct pcap_pkthdr *header, 82 | uint8_t *packet, ip_info *ip, config * conf) { 83 | // This packet. 84 | tcp_info * tcp; 85 | // For traversing the session list. 86 | tcp_info ** next; 87 | // Will hold the matching session when we look. 88 | tcp_info * sess = NULL; 89 | unsigned int offset; 90 | uint16_t checksum; 91 | uint16_t actual_checksum; 92 | 93 | tcp = malloc(sizeof(tcp_info)); 94 | 95 | // Get basic TCP header information. 96 | tcp->next_sess = NULL; 97 | tcp->next_pkt = NULL; 98 | tcp->prev_pkt = NULL; 99 | tcp->ts = header->ts; 100 | tcp->src = ip->src; 101 | tcp->dst = ip->dst; 102 | tcp->srcport = LE_U_SHORT(packet, pos); 103 | tcp->dstport = LE_U_SHORT(packet, pos+2); 104 | tcp->sequence = LE_U_INT(packet, pos + 4); 105 | tcp->ack_num = LE_U_INT(packet, pos + 8); 106 | tcp->ack = GET_BIT(packet, pos + 13, 5); 107 | tcp->syn = GET_BIT(packet, pos + 13, 1); 108 | tcp->fin = GET_BIT(packet, pos + 13, 0); 109 | tcp->rst = GET_BIT(packet, pos + 13, 2); 110 | offset = packet[pos + 12] >> 4; 111 | 112 | if ((pos + offset*4) > header->len) { 113 | fprintf(stderr, "Truncated TCP packet: %d, %d\n", offset, header->len); 114 | free(tcp); 115 | return; 116 | } 117 | tcp->len = ip->length - offset*4; 118 | 119 | // Ignore packets with a bad checksum 120 | checksum = LE_U_SHORT(packet, pos + 16); 121 | 122 | actual_checksum = tcp_checksum(ip, packet, pos, header); 123 | if (checksum != actual_checksum || 124 | // 0xffff and 0x0000 are both equal to zero in one's compliment, 125 | // so these are actually the same. 126 | (checksum == 0xffff && actual_checksum == 0x0000) || 127 | (checksum == 0x0000 && actual_checksum == 0xffff) ) { 128 | // Do Bad Checksum stuff 129 | DBG(printf("Bad checksum.");) 130 | free(tcp); 131 | return; 132 | } else 133 | 134 | // Only allocated space for the TCP data if there is any. 135 | if (tcp->len > 0) { 136 | tcp->data = malloc(sizeof(char) * (tcp->len)); 137 | memcpy(tcp->data, packet + pos + (offset*4), tcp->len); 138 | } else 139 | tcp->data = NULL; 140 | 141 | DBG(printf("This pkt - %p: ", tcp);) 142 | DBG(tcp_print(tcp);) 143 | DBG(printf("The head - %p: ", conf->tcp_sessions_head);) 144 | DBG(tcp_print(conf->tcp_sessions_head);) 145 | 146 | DBG(printf("Finding the matching session.\n");) 147 | // Keep in mind 'next' is a pointer to the pointer to the next item. 148 | // Find a matching session, if we have one. 149 | // We treat sessions as 1-way communications. The other direction 150 | // is handled as a separate stream. 151 | next = &(conf->tcp_sessions_head); 152 | while (*next != NULL) { 153 | DBG(printf("Checking: ");) 154 | DBG(tcp_print(*next);) 155 | if ( IP_CMP((*next)->src, tcp->src) && 156 | IP_CMP((*next)->dst, tcp->dst) && 157 | (*next)->srcport == tcp->srcport && 158 | (*next)->dstport == tcp->dstport) { 159 | 160 | DBG(printf("Match found:\n ");) 161 | DBG(tcp_print(*next);) 162 | 163 | // This is the matching session. 164 | sess = *next; 165 | 166 | // Assign this to the packet chain. 167 | sess->next_pkt = tcp; 168 | tcp->prev_pkt = sess; 169 | // Since this will be the head, it needs to know where 170 | // the next session is. 171 | tcp->next_sess = sess->next_sess; 172 | // The current packet is now the head packet of this session. 173 | sess = tcp; 174 | 175 | // The pointer to the next object should now be set to skip one. 176 | *next = sess->next_sess; 177 | // Set sess's next pointer to the old head. 178 | sess->next_sess = conf->tcp_sessions_head; 179 | // Then stick our sess back in as the head of the list. 180 | conf->tcp_sessions_head = sess; 181 | // We found our session, we're done. 182 | break; 183 | } 184 | // It doesn't belong to this session, move on to the next. 185 | next = &(*next)->next_sess; 186 | } 187 | 188 | // No matching session found. 189 | if (sess == NULL) { 190 | DBG(printf("No match found.\n");) 191 | tcp->next_sess = conf->tcp_sessions_head; 192 | conf->tcp_sessions_head = tcp; 193 | } 194 | 195 | return; 196 | } 197 | 198 | // Go through the list of tcp sessions and expire any old ones. 199 | // (Old is defined by TCP_EXPIRE_USECS). 200 | // The expired sessions are reassembled (or at least an attempt is made). 201 | // The reassembled data is handed of the the dns parser, and we 202 | // output the results. 203 | // Now should be the timeval that came with the most recent packet. 204 | // Now can also be NULL, which will expire everything. 205 | void tcp_expire(config * conf, const struct timeval * now ) { 206 | tcp_info * head = NULL; 207 | tcp_info ** ptr = &head; 208 | tcp_info ** next = &(conf->tcp_sessions_head); 209 | 210 | while (*next != NULL) { 211 | // Check to see if this session is expired based on the time given. 212 | if (now == NULL || is_expired(*now, (*next)->ts)) { 213 | // We need this because we'll probably end up free the 214 | // first packet of the session. 215 | tcp_info * next_sess = (*next)->next_sess; 216 | // Add this session to the list of of returned sessions 217 | 218 | *ptr = tcp_assemble(*next); 219 | // *next is probably freed now, unless it was returned as *ptr. 220 | 221 | // Remove this session from the main session list. 222 | *next = next_sess; 223 | 224 | // If the assembled stream was empty, skip to the next one. 225 | DBG(printf("*ptr %p\n", *ptr);) 226 | if (*ptr == NULL) { 227 | continue; 228 | } 229 | 230 | // Set ptr to point to the where the next expired session 231 | // should be added to the list. 232 | ptr = &(*ptr)->next_sess; 233 | // Clear that pointer. 234 | *ptr = NULL; 235 | } else { 236 | // Skip this session, it isn't expired. 237 | next = &(*next)->next_sess; 238 | } 239 | } 240 | 241 | // Step through all the assembled sessions, dns parse the data, and 242 | // output it. 243 | // 244 | // The madness you're about to experience stems from the fact that a 245 | // session may contain multiple DNS requests. Additionally, we might 246 | // just have junk, and need a rough way of telling the difference. 247 | // With TCP DNS, the DNS data is prepended with a two byte length, 248 | // so we at least know how long it is. 249 | while (head != NULL) { 250 | // There is a possiblity that this session won't start at the 251 | // the beginning of the data; that we've caught a session mid-stream. 252 | // Assuming we have expired it at a reasonable end, we can use the 253 | // length bytes to test our start position. If our length bytes allow 254 | // us to correctly jump the length of the packet, then we're good. 255 | // (Probably) 256 | unsigned long long offset; 257 | unsigned long long dns_len; 258 | char offset_found = 0; 259 | for (offset=0; offset < head->len-1; offset++) { 260 | unsigned long long pos = offset; 261 | while (pos + 1 < head->len) { 262 | dns_len = TCP_DNS_LEN(head->data, pos); 263 | // We shouldn't ever have an offset of 0. 264 | if (dns_len == 0) break; 265 | pos += 2 + dns_len; 266 | } 267 | // We've found the right offset (probably 0). 268 | if (pos == head->len) { 269 | offset_found = 1; 270 | break; 271 | } 272 | } 273 | 274 | // If we couldn't find the right offset, just try an offset of 275 | // zero as long as that offset isn't longer than all of our data. 276 | if (offset_found == 0) { 277 | if (head->len > 2 && 278 | TCP_DNS_LEN(head->data, 0) < head->len && 279 | // We should have more data than fits in a DNS header. 280 | // (12 bytes). 281 | TCP_DNS_LEN(head->data, 0) > 12 ) { 282 | offset = 0; 283 | } else { 284 | char * bad_data = escape_data(head->data, 0, head->len); 285 | print_ts(&(head->ts), conf); 286 | printf(", Bad TCP stream: %s\n", bad_data); 287 | free(bad_data); 288 | } 289 | } 290 | 291 | // Go through the stream offset by offset, create a fake packet 292 | // header (and packet data), and hand both off to the DNS parser. 293 | // The results are output. 294 | if (offset + 1 < head->len) { 295 | dns_len = TCP_DNS_LEN(head->data, offset); 296 | } else { 297 | // Skip trying to parse this. 298 | dns_len = head->len; 299 | } 300 | while (offset + dns_len < head->len) { 301 | dns_info dns; 302 | ip_info ip; 303 | transport_info trns; 304 | struct pcap_pkthdr header; 305 | uint32_t pos; 306 | 307 | // Create a fake packet header, transport and IP structs. 308 | header.ts = head->ts; 309 | header.caplen = head->len; 310 | header.len = head->len; 311 | trns.srcport = head->srcport; 312 | trns.dstport = head->dstport; 313 | trns.length = head->len; 314 | trns.transport = TCP; 315 | ip.src = head->src; 316 | ip.dst = head->dst; 317 | ip.proto = 0x06; 318 | DBG(printf("Parsing DNS (TCP).\n");) 319 | // Parse the DNS data from the point after the prepended len. 320 | // We must parse the whole packet, otherwise we get off in our 321 | // stream, so we set the parse_all flag. 322 | pos = dns_parse(offset+2, &header, head->data, &dns, conf, FORCE); 323 | if (pos != 0) { 324 | // Print the data if there wasn't an error. 325 | print_summary(&ip, &trns, &dns, &header, conf); 326 | } 327 | 328 | if (pos != offset + 2 + dns_len) { 329 | // If these don't match up, then there is no point in 330 | // continuing for this session. 331 | DBG( 332 | fprintf(stderr, "Mismatched TCP lengths: %u, %llu.\n", 333 | pos, (offset + 2 + dns_len)); 334 | fflush(stderr); 335 | ) 336 | break; 337 | } 338 | // Move on to the next DNS header in the stream. 339 | offset += 2 + dns_len; 340 | if (offset + 1 < head->len) { 341 | // We don't want to try to parse the length if we're past 342 | // the end of the packet. 343 | dns_len = TCP_DNS_LEN(head->data, offset); 344 | } 345 | } 346 | 347 | // Free this TCP stream and it's data. 348 | tcp_info * tmp; 349 | tmp = head; 350 | head = head->next_sess; 351 | free(tmp->data); 352 | free(tmp); 353 | } 354 | } 355 | 356 | // Go through the tcp starting at 'base'. Hopefully it will all be there. 357 | // Otherwise assemble as much as you can. 358 | // In doing this all child packets are freed (and their data chunks), 359 | // and a allocation is made. This is attached to the 'base' tcp_info object. 360 | // That tcp_info object has all its point sess and packet pointers set to 361 | // NULL. 362 | // It is assumed that the total data portion will fit in memory (twice actually, 363 | // since the original allocations will be freed after assembly is complete). 364 | tcp_info * tcp_assemble(tcp_info * base) { 365 | tcp_info **curr; 366 | tcp_info *origin = NULL; 367 | uint32_t curr_seq; 368 | // We'll keep track of the total size of data to copy. 369 | long long total_length = 0; 370 | // Where we are in the copying. 371 | long long pos = 0; 372 | // The actual data pointer for the final data. 373 | uint8_t * final_data; 374 | 375 | // All the pieces of data to reassemble. 376 | char ** data_chain; 377 | // The sizes of each piece. 378 | uint32_t * data_lengths; 379 | size_t dc_i = 0; 380 | uint32_t i; 381 | 382 | DBG(printf("In TCP_assembly.\n");) 383 | DBG(printf("Assembling:\n");) 384 | DBG(tcp_print(base);) 385 | 386 | // Figure out the max length of the data chain. 387 | // Move base along to be the oldest packet, so we can work on this 388 | // from the start rather than the end. 389 | for (curr=&base; *curr != NULL; curr = &(*curr)->prev_pkt) { 390 | dc_i++; 391 | base = *curr; 392 | } 393 | DBG(printf("Making the data_chain vars.\n");) 394 | data_chain = calloc(dc_i, sizeof(char *)); 395 | data_lengths = calloc(dc_i, sizeof(uint32_t)); 396 | 397 | // Find the first syn packet 398 | curr = &base; 399 | while (*curr != NULL) { 400 | DBG(tcp_print(*curr);) 401 | if ((*curr)->syn) { 402 | // Make note of this packet, it's the object we'll return. 403 | origin = *curr; 404 | curr_seq = (*curr)->sequence; 405 | DBG(printf("Found first sequence #: %x\n", curr_seq);) 406 | break; 407 | } 408 | curr = &(*curr)->next_pkt; 409 | } 410 | 411 | if (origin == NULL) { 412 | // If we fail to find the syn packet, use the earliest packet. 413 | // This means we might jump in in the middle of a session, but 414 | // we may still be able to pull out some DNS data if we're lucky. 415 | origin = base; 416 | curr_seq = base->sequence; 417 | } 418 | 419 | // Gather all the bits of data, in order. 420 | // The chain is destroyed bit by bit, except for the last tcp object. 421 | // Skip all this if the origin is NULL, since we don't have a starting 422 | // point anyway. 423 | dc_i = 0; 424 | while (base != NULL && origin != NULL) { 425 | // Search for the packet with the next sequence number that has 426 | // non-zero length. 427 | tcp_info ** next_best = NULL; 428 | for (curr = &base; *curr != NULL; curr = &(*curr)->next_pkt) { 429 | if ((*curr)->sequence == curr_seq) { 430 | if ((*curr)->len > 0) { 431 | // We found a packet at that sequence with data, it 432 | // should be what we want. 433 | break; 434 | } else if (next_best == NULL) { 435 | // A zero length packet will do if we can't find anything 436 | // better. 437 | next_best = curr; 438 | } 439 | } 440 | } 441 | // If we didn't find a matching packet with data, use the least 442 | // recent zero length packet. If that should be the origin, but 443 | // isn't, adjust the origin packet. 444 | if (*curr == NULL && next_best != NULL) { 445 | if (*next_best != NULL) { 446 | curr = next_best; 447 | } 448 | } 449 | 450 | // Set the origin to this packet if they have the same sequence. 451 | // Guarantees that the origin will be a packet removed from the 452 | // packet list (and thus not thrown away later). 453 | // This will only occur for the first sequence number. 454 | if (*curr != NULL && (origin->sequence == (*curr)->sequence)) { 455 | origin = *curr; 456 | } 457 | 458 | if (*curr != NULL) { 459 | DBG(printf("Current assembly packet: ");) 460 | DBG(tcp_print(*curr);) 461 | tcp_info * tmp; 462 | //DBG(print_packet((*curr)->len, (*curr)->data, 0, (*curr)->len, 8);) 463 | // We found a match. 464 | // Save the data and it's length. 465 | data_chain[dc_i] = (char*) (*curr)->data; 466 | data_lengths[dc_i] = (*curr)->len; 467 | total_length += (*curr)->len; 468 | dc_i++; 469 | 470 | // Look for the next sequence number. 471 | DBG(printf("curr_seq, seq: %x, %x\n", curr_seq, (*curr)->sequence);) 472 | if ((*curr)->len == 0) { 473 | curr_seq++; 474 | } else { 475 | curr_seq += (*curr)->len; 476 | } 477 | 478 | // Remove this packet from the list. 479 | tmp = *curr; 480 | *curr = (*curr)->next_pkt; 481 | // Free that packet object as long as it isn't the origin. 482 | if (tmp != origin) { 483 | // The data part will be freed separately in a bit. 484 | DBG(printf("Freeing: %p\n", tmp);) 485 | free(tmp); 486 | } 487 | 488 | } else { 489 | // We didn't find a match. We're probably done now. 490 | break; 491 | } 492 | // Start over from the beginning of the list every time. 493 | curr = &base; 494 | } 495 | 496 | // Free any remaining packet objects and their data. 497 | while (base != NULL) { 498 | tcp_info * next = base->next_pkt; 499 | DBG(printf("Free unused packet:\n");) 500 | DBG(tcp_print(base);) 501 | free(base->data); 502 | free(base); 503 | base = next; 504 | } 505 | 506 | DBG(printf("Total_length: %lld\n", total_length);) 507 | 508 | // Make the final data struct. 509 | // This could be seriously freaking huge. We'll ignore that for now. 510 | // It should be fine, in theory, thanks to virtual memory and big disks, 511 | // but it's good this is only DNS data, right? 512 | // Combine the data. 513 | // We'll skip combining the data, and just free the chain, if there 514 | // isn't any data to deal with. 515 | if (total_length > 0) { 516 | final_data = malloc(sizeof(uint8_t) * total_length); 517 | for(i=0; i < dc_i; i++) { 518 | if (data_chain[i] != NULL) { 519 | memcpy(final_data + pos, data_chain[i], data_lengths[i]); 520 | pos += data_lengths[i]; 521 | DBG(printf("data_chain[%d] free: ", i);) 522 | free(data_chain[i]); 523 | } 524 | } 525 | } 526 | 527 | DBG(printf("data_chain, lengths, free.\n");) 528 | free(data_chain); 529 | free(data_lengths); 530 | 531 | if (total_length == 0) { 532 | // There was no data in the session to return. 533 | DBG(printf("Empty session:%p.\n", origin);) 534 | if (origin != NULL) { 535 | DBG(printf("Bleh\n");) 536 | free(origin); 537 | } 538 | return NULL; 539 | } 540 | 541 | // Set the the first packet in the session as our return value. 542 | origin->data = final_data; 543 | origin->len = total_length; 544 | 545 | DBG(printf("TCP assembly finished.\n");) 546 | DBG(printf("origin - ");) 547 | DBG(tcp_print(origin);) 548 | 549 | return origin; 550 | } 551 | 552 | // Save all unresolved sessions to disk. The path to the save file can be 553 | // set with the -s option at runtime. 554 | // File format: 555 | // Each tcp_info object and it's data are saved in turn, starting with 556 | // conf->tcp_sessions_head. All the packets of each session are saved 557 | // before moving to the next session. 558 | // When saving a session, the tcp_info object has all it's pointers 559 | // set to NULL. The prev_pkt pointer will be 1 if there is another 560 | // packet in the session after this one. 561 | void tcp_save_state(config * conf) { 562 | FILE * outfile = fopen(conf->TCP_STATE_PATH,"w"); 563 | tcp_info * next = conf->tcp_sessions_head; 564 | tcp_info * curr_pkt; 565 | 566 | if (outfile == NULL) { 567 | fprintf(stderr, "Could not open tcp state file.\n"); 568 | fclose(outfile); 569 | return; 570 | } 571 | 572 | while (next != NULL) { 573 | curr_pkt = next; 574 | next = next->next_sess; 575 | while (curr_pkt != NULL) { 576 | tcp_info * prev_pkt = curr_pkt->prev_pkt; 577 | uint8_t * data = curr_pkt->data; 578 | uint32_t len = curr_pkt->len; 579 | size_t written; 580 | // Clear all or pointers, or turn them into flags. 581 | curr_pkt->next_sess = NULL; 582 | curr_pkt->next_pkt = NULL; 583 | // All we need to know is whether there is a prev. packet. 584 | curr_pkt->prev_pkt = (prev_pkt == NULL) ? (NULL+1) : NULL; 585 | curr_pkt->data = NULL; 586 | written = fwrite(curr_pkt, sizeof(tcp_info), 1, outfile); 587 | if (written != 1) { 588 | fprintf(stderr, "Could not write to tcp state file.\n"); 589 | fclose(outfile); 590 | return; 591 | } 592 | free(curr_pkt); 593 | written = fwrite(data, sizeof(uint8_t), len, outfile); 594 | if (written != len) { 595 | fprintf(stderr, "Could not write to tcp state file(data).\n"); 596 | fclose(outfile); 597 | free(data); 598 | return; 599 | } 600 | free(data); 601 | curr_pkt = prev_pkt; 602 | } 603 | } 604 | fclose(outfile); 605 | } 606 | 607 | // Look for a saved TCP state data file, and try to load the data from it. 608 | tcp_info * tcp_load_state(config * conf) { 609 | FILE * infile; 610 | struct stat i_stat; 611 | int ret = stat(conf->TCP_STATE_PATH, &i_stat); 612 | size_t read; 613 | tcp_info * pkt; 614 | tcp_info * prev = NULL; 615 | tcp_info * first_sess = NULL; 616 | tcp_info ** sess = &first_sess; 617 | int has_prev = 0; 618 | 619 | if (ret != 0) { 620 | // No prior state file. 621 | fprintf(stderr, "No prior tcp state file.\n"); 622 | return NULL; 623 | } 624 | 625 | infile = fopen(conf->TCP_STATE_PATH, "r"); 626 | if (infile == NULL) { 627 | fprintf(stderr, "Could not open existing tcp state file.\n"); 628 | return NULL; 629 | } 630 | 631 | pkt = malloc(sizeof(tcp_info)); 632 | read = fread(pkt, sizeof(tcp_info), 1, infile); 633 | while (read != 0) { 634 | // If the last packet had a another packet in the session, 635 | // then point it to this one and vice versa. 636 | // Note: Don't forget the packets are in most recent first order. 637 | if (has_prev == 1) { 638 | prev->prev_pkt = pkt; 639 | pkt->next_pkt = prev; 640 | } else { 641 | // The last packet was the last in a session. 642 | // Start a new session. 643 | *sess = pkt; 644 | sess = &(pkt->next_sess); 645 | } 646 | has_prev = (pkt->prev_pkt == NULL); 647 | pkt->prev_pkt = NULL; 648 | 649 | pkt->data = malloc(sizeof(uint8_t) * pkt->len); 650 | read = fread(pkt->data, sizeof(uint8_t), pkt->len, infile); 651 | if (read != pkt->len) { 652 | // We are failing to free the memory of anything read in so far. 653 | // It's probably not a big deal. 654 | fprintf(stderr, "Tcp state file read error (data).\n"); 655 | return NULL; 656 | } 657 | 658 | prev = pkt; 659 | pkt = malloc(sizeof(tcp_info)); 660 | read = fread(pkt, sizeof(tcp_info), 1, infile); 661 | } 662 | 663 | // Since the last read was of length zero, (all other cases return or 664 | // continue) go ahead and free our last allocated object. 665 | free(pkt); 666 | fclose(infile); 667 | 668 | return first_sess; 669 | } 670 | 671 | // Print a tcp_info object. For debugging. 672 | void tcp_print(tcp_info * tcp) { 673 | if (tcp == NULL) { 674 | printf("NULL tcp object\n"); 675 | } else { 676 | printf("%p %s:%d ", tcp, iptostr(&tcp->src), tcp->srcport); 677 | printf("-> %s:%d, seq: %x, safr: %d%d%d%d, len: %u\n", 678 | iptostr(&tcp->dst), tcp->dstport, 679 | tcp->sequence, tcp->syn, tcp->ack, 680 | tcp->fin, tcp->rst, tcp->len); 681 | } 682 | } 683 | -------------------------------------------------------------------------------- /dns_parse.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "network.h" 10 | #include "tcp.h" 11 | #include "rtypes.h" 12 | #include "strutils.h" 13 | 14 | // If you want a reasonable place to start walking through the code, 15 | // go to the 'handler' function. 16 | 17 | #define DEFAULT_TCP_STATE_PATH "/tmp/dnsparse_tcp.state" 18 | void handler(uint8_t *, const struct pcap_pkthdr *, const uint8_t *); 19 | void dns_rr_free(dns_rr *); 20 | void dns_question_free(dns_question *); 21 | uint32_t parse_rr(uint32_t, uint32_t, struct pcap_pkthdr *, 22 | uint8_t *, dns_rr *, config *); 23 | void print_rr_section(dns_rr *, char *, config *); 24 | void print_packet(uint32_t, uint8_t *, uint32_t, uint32_t, u_int); 25 | int dedup(uint32_t, struct pcap_pkthdr *, uint8_t *, 26 | ip_info *, transport_info *, config *); 27 | 28 | int main(int argc, char **argv) { 29 | pcap_t * pcap_file; 30 | char errbuf[PCAP_ERRBUF_SIZE]; 31 | int read; 32 | config conf; 33 | 34 | int c; 35 | int arg_failure = 0; 36 | 37 | const char * OPTIONS = "cdfhm:MnrtD:x:s:S"; 38 | 39 | // Setting configuration defaults. 40 | uint8_t TCP_SAVE_STATE = 1; 41 | conf.COUNTS = 0; 42 | conf.EXCLUDES = 0; 43 | conf.RECORD_SEP = ""; 44 | conf.SEP = '\t'; 45 | conf.AD_ENABLED = 0; 46 | conf.NS_ENABLED = 0; 47 | conf.PRETTY_DATE = 0; 48 | conf.PRINT_RR_NAME = 0; 49 | conf.MISSING_TYPE_WARNINGS = 0; 50 | conf.TCP_STATE_PATH = NULL; 51 | conf.DEDUPS = 10; 52 | conf.dedup_pos = 0; 53 | 54 | c = getopt(argc, argv, OPTIONS); 55 | while (c != -1) { 56 | switch (c) { 57 | case 'c': 58 | conf.COUNTS = 1; 59 | break; 60 | case 'd': 61 | conf.AD_ENABLED = 1; 62 | break; 63 | case 'f': 64 | print_parsers(); 65 | return 0; 66 | case 'm': 67 | conf.RECORD_SEP = optarg; 68 | conf.SEP = '\n'; 69 | break; 70 | case 'M': 71 | conf.MISSING_TYPE_WARNINGS = 1; 72 | break; 73 | case 'n': 74 | conf.NS_ENABLED = 1; 75 | break; 76 | case 'r': 77 | conf.PRINT_RR_NAME = 1; 78 | break; 79 | case 's': 80 | conf.TCP_STATE_PATH = optarg; 81 | break; 82 | case 'S': 83 | TCP_SAVE_STATE = 0; 84 | break; 85 | case 't': 86 | conf.PRETTY_DATE = 1; 87 | break; 88 | case 'D': 89 | conf.DEDUPS = strtoul(optarg, NULL, 10); 90 | if (conf.DEDUPS > 10000) { 91 | conf.DEDUPS = 10000; 92 | } 93 | break; 94 | case 'x': 95 | if (conf.EXCLUDES < MAX_EXCLUDES) { 96 | int ival = atoi(optarg); 97 | if (ival == 0 || ival >= 65536) { 98 | fprintf(stderr, "Invalid excluded rtype value. " 99 | "Value must be a short int.\n"); 100 | arg_failure = 1; 101 | } else { 102 | conf.EXCLUDED[conf.EXCLUDES] = ival; 103 | conf.EXCLUDES++; 104 | } 105 | } else { 106 | fprintf(stderr, "Too many excluded rtypes. " 107 | "If this limit is an issue, then recompile with " 108 | "the MAX_EXCLUDES define set higher.\n"); 109 | arg_failure = 1; 110 | } 111 | break; 112 | case '?': 113 | if (optopt == 'x') 114 | fprintf(stderr, "Option -x requires an rtype number.\n"); 115 | else if (optopt == 'm') 116 | fprintf(stderr, "Option -m needs a delimiter string.\n"); 117 | else if (isprint(optopt)) 118 | fprintf(stderr, "Unknown option -%c.\n",optopt); 119 | else 120 | fprintf(stderr, "Invalid option char: 0x%x.\n", optopt); 121 | case 'h': 122 | default: 123 | arg_failure = 1; 124 | } 125 | c = getopt(argc, argv, OPTIONS); 126 | } 127 | 128 | if (conf.TCP_STATE_PATH == NULL) { 129 | conf.TCP_STATE_PATH = DEFAULT_TCP_STATE_PATH; 130 | } 131 | 132 | if (optind == argc - 1) { 133 | pcap_file = pcap_open_offline(argv[optind], errbuf); 134 | if (pcap_file == NULL) { 135 | fprintf(stderr, "Could not open pcapfile.\n%s\n", errbuf); 136 | return -1; 137 | } 138 | conf.datalink = pcap_datalink(pcap_file); 139 | if (conf.datalink != DLT_EN10MB && conf.datalink != DLT_LINUX_SLL) { 140 | fprintf(stderr, "Unsupported data link layer: %d\n", conf.datalink); 141 | arg_failure = 1; 142 | pcap_close(pcap_file); 143 | } 144 | } else if (optind >= argc) { 145 | fprintf(stderr, "No input file specified.\n"); 146 | arg_failure = 1; 147 | } else { 148 | fprintf(stderr, "Multiple input files or bad arguments."); 149 | arg_failure = 1; 150 | } 151 | 152 | if (arg_failure) { 153 | fprintf(stderr, 154 | "Usage: dns_parse [-dnthf] [-m] [-x] [-s]\n" 155 | " \n" 156 | "dns_parse parses a pcap file and gives a nicely " 157 | "formatted ascii string for each dns request.\n" 158 | "By default the reservation records are tab separated " 159 | "and the entire record is ended with a newline.\n\n" 160 | "The comma separated fields printed for each request are:\n" 161 | " time - The time of the request relative to the \n" 162 | " capture source clock.\n" 163 | " srcip, dstip - the source and dest ipv4 addresses.\n" 164 | " ipv6 support is not present.\n" 165 | " size - the size of the dns portion of the message.\n" 166 | " proto - udp (u) or tcp(t)\n" 167 | " query/response - is it a query(q) or response(r)\n" 168 | " authoritative - marked with AA if authoritative\n\n" 169 | "The resource records are printed after these fields, separated by\n" 170 | "a tab (a newline in multiline mode). \n" 171 | "By default the resource record format is:\n" 172 | "
\n\n" 173 | "
is a symbol denoting record type.\n" 174 | " ? - Questions (No rdata is included or printed).\n" 175 | " ! - Answers\n" 176 | " $ - Name Servers\n" 177 | " + - Additional\n" 178 | "The rdata is parsed by a custom parser that depends on the\n" 179 | "record type and class. Use the -f option to get a list of\n" 180 | "the supported record types and documentation on the parsers.\n\n" 181 | "Args:\n" 182 | " - The pcapfile to parse. Use a '-' for stdin\n" 183 | "-c\n" 184 | " Append a list of counts for each record type (Questions, \n" 185 | " Answers, etc) to record fields. Each type is followed by it's\n" 186 | " record type symbol.\n" 187 | "-d\n" 188 | " Enable the parsing and output of the Additional\n" 189 | " Records section. Disabled by default.\n" 190 | "-D \n" 191 | " Keep hashes of the last packets for de-duplication\n" 192 | " purposes (max 10,000). A of zero turns off \n" 193 | " de-duplication. \n" 194 | " Default 10.\n" 195 | "-f\n" 196 | " Print out documentation on the various resource \n" 197 | " record parsers.\n" 198 | "-n\n" 199 | " Enable the parsing and output of the Name Server\n" 200 | " Records section. Disabled by default.\n" 201 | "-m \n" 202 | " Multiline mode. Reservation records are newline\n" 203 | " separated, and the whole record ends with the\n" 204 | " separator given.\n" 205 | "-M \n" 206 | " Print a message for each occurance of a missing class,type\n" 207 | " parser.\n" 208 | "-r \n" 209 | " Changes the resource record format to: \n" 210 | "
\n" 211 | " If the record type isn't known, 'UNKNOWN(,)' is given\n" 212 | " The query record format is the similar, but missing the rdata.\n" 213 | "-s \n" 214 | " Path to the tcp state save file. \n" 215 | " This will be loaded (and overwritten) every time dns_parse \n" 216 | " is run. \n" 217 | " Default is: %s \n" 218 | "-S \n" 219 | " Disable TCP state saving/loading.\n" 220 | "-t \n" 221 | " Print the time/date as in Y-m-d H:M:S (ISO 8601) format.\n" 222 | " The time will be in the local timezone.\n" 223 | "-x\n" 224 | " Exclude the given reservation record types by \n" 225 | " number. This option can be given multiple times.\n" 226 | "\n" 227 | "Supported protocols:\n" 228 | "DNS can ride on a number of protocols, and dns_parse supports\n" 229 | "a fair number of them, including:\n" 230 | "Ethernet, MPLS, IPv4, IPv6, UDP and TCP.\n" 231 | "IPv4 and IPv6 fragments - fragments are reassembled, but data\n" 232 | " may be lost if the fragments are split across multiple pcaps.\n" 233 | "TCP reassembly - TCP packets are reassembled, but the resulting\n" 234 | " data may be offset from their time of occurance. Partial flow\n" 235 | " reassembly is supported; long flows are printed whenever a \n" 236 | " a lull in that flow occurs (500 ms since the last packet, \n" 237 | " this can only be changed at compile time).\n" 238 | " TCP flow state is saved at the end of execution, and loaded\n" 239 | " at the beginning. See the -S option to disable.\n", 240 | DEFAULT_TCP_STATE_PATH); 241 | return -1; 242 | } 243 | 244 | conf.ip_fragment_head = NULL; 245 | 246 | // Load and prior TCP session info 247 | conf.tcp_sessions_head = NULL; 248 | if (TCP_SAVE_STATE == 1) { 249 | tcp_load_state(&conf); 250 | } 251 | 252 | conf.dedup_hashes = calloc(conf.DEDUPS, sizeof(uint64_t)); 253 | 254 | // need to check this for overflow. 255 | read = pcap_dispatch(pcap_file, -1, (pcap_handler)handler, 256 | (uint8_t *) &conf); 257 | 258 | if (TCP_SAVE_STATE == 1) { 259 | tcp_save_state(&conf); 260 | } else { 261 | tcp_expire(&conf, NULL); 262 | } 263 | 264 | free(conf.dedup_hashes); 265 | ip_frag_free(&conf); 266 | 267 | if (read == -1) { 268 | fprintf(stderr, "pcap_dispatch: %s\n", pcap_geterr(pcap_file)); 269 | } else if (read == -2) { 270 | fprintf(stderr, "pcap_dispatch: break!\n"); 271 | } VERBOSE( else fprintf(stderr, "pcap_dispatch: %d packets processed\n", read); ) 272 | 273 | pcap_close(pcap_file); 274 | 275 | return 0; 276 | } 277 | 278 | void handler(uint8_t * args, const struct pcap_pkthdr *orig_header, 279 | const uint8_t *orig_packet) { 280 | int pos; 281 | eth_info eth; 282 | ip_info ip; 283 | config * conf = (config *) args; 284 | 285 | // The way we handle IP fragments means we may have to replace 286 | // the original data and correct the header info, so a const won't work. 287 | uint8_t * packet = (uint8_t *) orig_packet; 288 | struct pcap_pkthdr header; 289 | header.ts = orig_header->ts; 290 | header.caplen = orig_header->caplen; 291 | header.len = orig_header->len; 292 | 293 | VERBOSE(printf("\nPacket %llu.%llu\n", 294 | (uint64_t)header.ts.tv_sec, 295 | (uint64_t)header.ts.tv_usec);) 296 | 297 | // Parse the ethernet frame. Errors are typically handled in the parser 298 | // functions. The functions generally return 0 on error. 299 | pos = eth_parse(&header, packet, ð, conf); 300 | if (pos == 0) return; 301 | 302 | // MPLS parsing is simple, but leaves us to guess the next protocol. 303 | // We make our guess in the MPLS parser, and set the ethtype accordingly. 304 | if (eth.ethtype == 0x8847) { 305 | pos = mpls_parse(pos, &header, packet, ð); 306 | } 307 | 308 | // IP v4 and v6 parsing. These may replace the packet byte array with 309 | // one from reconstructed packet fragments. Zero is a reasonable return 310 | // value, so they set the packet pointer to NULL on failure. 311 | if (eth.ethtype == 0x0800) { 312 | pos = ipv4_parse(pos, &header, &packet, &ip, conf); 313 | } else if (eth.ethtype == 0x86DD) { 314 | pos = ipv6_parse(pos, &header, &packet, &ip, conf); 315 | } else { 316 | fprintf(stderr, "Unsupported EtherType: %04x\n", eth.ethtype); 317 | return; 318 | } 319 | if (packet == NULL) return; 320 | 321 | // Transport layer parsing. 322 | if (ip.proto == 17) { 323 | // Parse the udp and this single bit of DNS, and output it. 324 | dns_info dns; 325 | transport_info udp; 326 | pos = udp_parse(pos, &header, packet, &udp, conf); 327 | if ( pos == 0 ) return; 328 | // Only do deduplication if DEDUPS > 0. 329 | if (conf->DEDUPS != 0 ) { 330 | if (dedup(pos, &header, packet, &ip, &udp, conf) == 1) { 331 | // A duplicate packet. 332 | return; 333 | } 334 | } 335 | pos = dns_parse(pos, &header, packet, &dns, conf, !FORCE); 336 | print_summary(&ip, &udp, &dns, &header, conf); 337 | } else if (ip.proto == 6) { 338 | // Hand the tcp packet over for later reconstruction. 339 | tcp_parse(pos, &header, packet, &ip, conf); 340 | } else { 341 | fprintf(stderr, "Unsupported Transport Protocol(%d)\n", ip.proto); 342 | return; 343 | } 344 | 345 | if (packet != orig_packet) { 346 | // Free data from artificially constructed packets. 347 | free(packet); 348 | } 349 | 350 | // Expire tcp sessions, and output them if possible. 351 | DBG(printf("Expiring TCP.\n");) 352 | tcp_expire(conf, &header.ts); 353 | } 354 | 355 | // Output the DNS data. 356 | void print_summary(ip_info * ip, transport_info * trns, dns_info * dns, 357 | struct pcap_pkthdr * header, config * conf) { 358 | char proto; 359 | 360 | uint32_t dnslength; 361 | dns_question *qnext; 362 | 363 | print_ts(&(header->ts), conf); 364 | 365 | // Print the transport protocol indicator. 366 | if (ip->proto == 17) { 367 | proto = 'u'; 368 | } else if (ip->proto == 6) { 369 | proto = 't'; 370 | } else { 371 | return; 372 | } 373 | dnslength = trns->length; 374 | 375 | // Print the IP addresses and the basic query information. 376 | printf(",%s,", iptostr(&ip->src)); 377 | printf("%s,%d,%c,%c,%s", iptostr(&ip->dst), 378 | dnslength, proto, dns->qr ? 'r':'q', dns->AA?"AA":"NA"); 379 | 380 | if (conf->COUNTS) { 381 | printf(",%u?,%u!,%u$,%u+", dns->qdcount, dns->ancount, 382 | dns->nscount, dns->arcount); 383 | } 384 | 385 | // Go through the list of queries, and print each one. 386 | qnext = dns->queries; 387 | while (qnext != NULL) { 388 | printf("%c? ", conf->SEP); 389 | if (conf->PRINT_RR_NAME) { 390 | rr_parser_container * parser; 391 | parser = find_parser(qnext->cls, qnext->type); 392 | if (parser->name == NULL) 393 | printf("%s UNKNOWN(%s,%d)", qnext->name, parser->name, qnext->type); 394 | else 395 | printf("%s %s", qnext->name, parser->name); 396 | } else 397 | printf("%s %d %d", qnext->name, qnext->type, qnext->cls); 398 | qnext = qnext->next; 399 | } 400 | 401 | // Print it resource record type in turn (for those enabled). 402 | print_rr_section(dns->answers, "!", conf); 403 | if (conf->NS_ENABLED) 404 | print_rr_section(dns->name_servers, "$", conf); 405 | if (conf->AD_ENABLED) 406 | print_rr_section(dns->additional, "+", conf); 407 | printf("%c%s\n", conf->SEP, conf->RECORD_SEP); 408 | 409 | dns_question_free(dns->queries); 410 | dns_rr_free(dns->answers); 411 | dns_rr_free(dns->name_servers); 412 | dns_rr_free(dns->additional); 413 | fflush(stdout); 414 | fflush(stderr); 415 | } 416 | 417 | // Print all resource records in the given section. 418 | void print_rr_section(dns_rr * next, char * name, config * conf) { 419 | int skip; 420 | int i; 421 | while (next != NULL) { 422 | // Print the rr seperator and rr section name. 423 | printf("%c%s", conf->SEP, name); 424 | skip = 0; 425 | // Search the excludes list to see if we should not print this 426 | // rtype. 427 | for (i=0; i < conf->EXCLUDES && skip == 0; i++) 428 | if (next->type == conf->EXCLUDED[i]) skip = 1; 429 | if (!skip) { 430 | char *name, *data; 431 | name = (next->name == NULL) ? "*empty*" : next->name; 432 | data = (next->data == NULL) ? "*empty*" : next->data; 433 | if (conf->PRINT_RR_NAME) { 434 | if (next->rr_name == NULL) 435 | // Handle bad records. 436 | printf(" %s UNKNOWN(%d,%d) %s", name, next->type, 437 | next->cls, data); 438 | else 439 | // Print the string rtype name with the rest of the record. 440 | printf(" %s %s %s", name, next->rr_name, data); 441 | } else 442 | // The -r option case. 443 | // Print the rtype and class number with the record. 444 | printf(" %s %d %d %s", name, next->type, next->cls, data); 445 | } 446 | next = next->next; 447 | } 448 | } 449 | 450 | // Print packet bytes in hex. 451 | // See dns_parse.h 452 | void print_packet(uint32_t max_len, uint8_t *packet, 453 | uint32_t start, uint32_t end, u_int wrap) { 454 | int i=0; 455 | while (i < end - start && (i + start) < max_len) { 456 | printf("%02x ", packet[i+start]); 457 | i++; 458 | if ( i % wrap == 0) printf("\n"); 459 | } 460 | if ( i % wrap != 0) printf("\n"); 461 | return; 462 | } 463 | 464 | // Free a dns_rr struct. 465 | void dns_rr_free(dns_rr * rr) { 466 | if (rr == NULL) return; 467 | if (rr->name != NULL) free(rr->name); 468 | if (rr->data != NULL) free(rr->data); 469 | dns_rr_free(rr->next); 470 | free(rr); 471 | } 472 | 473 | // Free a dns_question struct. 474 | void dns_question_free(dns_question * question) { 475 | if (question == NULL) return; 476 | if (question->name != NULL) free(question->name); 477 | dns_question_free(question->next); 478 | free(question); 479 | } 480 | 481 | // Print the time stamp. 482 | void print_ts(struct timeval * ts, config * conf) { 483 | if (conf->PRETTY_DATE) { 484 | struct tm *time; 485 | size_t result; 486 | char t_date[200]; 487 | const char * format = "%F %T"; 488 | time = localtime(&(ts->tv_sec)); 489 | result = strftime(t_date, 200, format, time); 490 | if (result == 0) { 491 | printf("0000-00-00 00:00:00.000000"); 492 | fprintf(stderr, "Date format error\n"); 493 | } 494 | printf("%s.%06d", t_date, (int)ts->tv_usec); 495 | } else { 496 | printf("%d.%06d", (int)ts->tv_sec, (int)ts->tv_usec); 497 | } 498 | } 499 | 500 | 501 | // Parse the questions section of the dns protocol. 502 | // pos - offset to the start of the questions section. 503 | // id_pos - offset set to the id field. Needed to decompress dns data. 504 | // packet, header - the packet location and header data. 505 | // count - Number of question records to expect. 506 | // root - Pointer to where to store the question records. 507 | uint32_t parse_questions(uint32_t pos, uint32_t id_pos, 508 | struct pcap_pkthdr *header, 509 | uint8_t *packet, uint16_t count, 510 | dns_question ** root) { 511 | uint32_t start_pos = pos; 512 | dns_question * last = NULL; 513 | dns_question * current; 514 | uint16_t i; 515 | *root = NULL; 516 | 517 | for (i=0; i < count; i++) { 518 | current = malloc(sizeof(dns_question)); 519 | current->next = NULL; current->name = NULL; 520 | 521 | current->name = read_rr_name(packet, &pos, id_pos, header->len); 522 | if (current->name == NULL || (pos + 2) >= header->len) { 523 | // Handle a bad DNS name. 524 | fprintf(stderr, "DNS question error\n"); 525 | char * buffer = escape_data(packet, start_pos, header->len); 526 | const char * msg = "Bad DNS question: "; 527 | current->name = malloc(sizeof(char) * (strlen(buffer) + 528 | strlen(msg) + 1)); 529 | sprintf(current->name, "%s%s", msg, buffer); 530 | free(buffer); 531 | current->type = 0; 532 | current->cls = 0; 533 | if (last == NULL) *root = current; 534 | else last->next = current; 535 | return 0; 536 | } 537 | current->type = (packet[pos] << 8) + packet[pos+1]; 538 | current->cls = (packet[pos+2] << 8) + packet[pos+3]; 539 | 540 | // Add this question object to the list. 541 | if (last == NULL) *root = current; 542 | else last->next = current; 543 | last = current; 544 | pos = pos + 4; 545 | 546 | VERBOSE(printf("question->name: %s\n", current->name);) 547 | VERBOSE(printf("type %d, cls %d\n", current->type, current->cls);) 548 | } 549 | 550 | return pos; 551 | } 552 | 553 | // Parse an individual resource record, placing the acquired data in 'rr'. 554 | // 'packet', 'pos', and 'id_pos' serve the same uses as in parse_rr_set. 555 | // Return 0 on error, the new 'pos' in the packet otherwise. 556 | uint32_t parse_rr(uint32_t pos, uint32_t id_pos, struct pcap_pkthdr *header, 557 | uint8_t *packet, dns_rr * rr, config * conf) { 558 | int i; 559 | uint32_t rr_start = pos; 560 | rr_parser_container * parser; 561 | rr_parser_container opts_cont = {0,0, opts}; 562 | 563 | rr->name = NULL; 564 | rr->data = NULL; 565 | 566 | rr->name = read_rr_name(packet, &pos, id_pos, header->len); 567 | // Handle a bad rr name. 568 | // We still want to print the rest of the escaped rr data. 569 | if (rr->name == NULL) { 570 | const char * msg = "Bad rr name: "; 571 | rr->name = malloc(sizeof(char) * (strlen(msg) + 1)); 572 | sprintf(rr->name, "%s", "Bad rr name"); 573 | rr->type = 0; 574 | rr->rr_name = NULL; 575 | rr->cls = 0; 576 | rr->ttl = 0; 577 | rr->data = escape_data(packet, pos, header->len); 578 | return 0; 579 | } 580 | 581 | if ((header->len - pos) < 10 ) return 0; 582 | 583 | rr->type = (packet[pos] << 8) + packet[pos+1]; 584 | rr->rdlength = (packet[pos+8] << 8) + packet[pos + 9]; 585 | // Handle edns opt RR's differently. 586 | if (rr->type == 41) { 587 | rr->cls = 0; 588 | rr->ttl = 0; 589 | rr->rr_name = "OPTS"; 590 | parser = &opts_cont; 591 | // We'll leave the parsing of the special EDNS opt fields to 592 | // our opt rdata parser. 593 | pos = pos + 2; 594 | } else { 595 | // The normal case. 596 | rr->cls = (packet[pos+2] << 8) + packet[pos+3]; 597 | rr->ttl = 0; 598 | for (i=0; i<4; i++) 599 | rr->ttl = (rr->ttl << 8) + packet[pos+4+i]; 600 | // Retrieve the correct parser function. 601 | parser = find_parser(rr->cls, rr->type); 602 | rr->rr_name = parser->name; 603 | pos = pos + 10; 604 | } 605 | 606 | VERBOSE(printf("Applying RR parser: %s\n", parser->name);) 607 | 608 | if (conf->MISSING_TYPE_WARNINGS && &default_rr_parser == parser) 609 | fprintf(stderr, "Missing parser for class %d, type %d\n", 610 | rr->cls, rr->type); 611 | 612 | // Make sure the data for the record is actually there. 613 | // If not, escape and print the raw data. 614 | if (header->len < (rr_start + 10 + rr->rdlength)) { 615 | char * buffer; 616 | const char * msg = "Truncated rr: "; 617 | rr->data = escape_data(packet, rr_start, header->len); 618 | buffer = malloc(sizeof(char) * (strlen(rr->data) + strlen(msg) + 1)); 619 | sprintf(buffer, "%s%s", msg, rr->data); 620 | free(rr->data); 621 | rr->data = buffer; 622 | return 0; 623 | } 624 | // Parse the resource record data. 625 | rr->data = parser->parser(packet, pos, id_pos, rr->rdlength, 626 | header->len); 627 | VERBOSE( 628 | printf("rr->name: %s\n", rr->name); 629 | printf("type %d, cls %d, ttl %d, len %d\n", rr->type, rr->cls, rr->ttl, 630 | rr->rdlength); 631 | printf("rr->data %s\n", rr->data); 632 | ) 633 | 634 | return pos + rr->rdlength; 635 | } 636 | 637 | // Parse a set of resource records in the dns protocol in 'packet', starting 638 | // at 'pos'. The 'id_pos' offset is necessary for putting together 639 | // compressed names. 'count' is the expected number of records of this type. 640 | // 'root' is where to assign the parsed list of objects. 641 | // Return 0 on error, the new 'pos' in the packet otherwise. 642 | uint32_t parse_rr_set(uint32_t pos, uint32_t id_pos, 643 | struct pcap_pkthdr *header, 644 | uint8_t *packet, uint16_t count, 645 | dns_rr ** root, config * conf) { 646 | dns_rr * last = NULL; 647 | dns_rr * current; 648 | uint16_t i; 649 | *root = NULL; 650 | for (i=0; i < count; i++) { 651 | // Create and clear the data in a new dns_rr object. 652 | current = malloc(sizeof(dns_rr)); 653 | current->next = NULL; current->name = NULL; current->data = NULL; 654 | 655 | pos = parse_rr(pos, id_pos, header, packet, current, conf); 656 | // If a non-recoverable error occurs when parsing an rr, 657 | // we can only return what we've got and give up. 658 | if (pos == 0) { 659 | if (last == NULL) *root = current; 660 | else last->next = current; 661 | return 0; 662 | } 663 | if (last == NULL) *root = current; 664 | else last->next = current; 665 | last = current; 666 | } 667 | return pos; 668 | } 669 | 670 | // Generates a hash from the current packet. 671 | int dedup(uint32_t pos, struct pcap_pkthdr *header, uint8_t * packet, 672 | ip_info * ip, transport_info * trns, config * conf) { 673 | 674 | uint64_t hash = 0; 675 | uint32_t i; 676 | 677 | // Put the hash of the src address in the upper 32 bits, 678 | // and the dest in the lower 32. 679 | if (ip->src.vers == IPv4) { 680 | hash += ((uint64_t)ip->src.addr.v4.s_addr << 32); 681 | hash += ip->dst.addr.v4.s_addr; 682 | } else { 683 | for (i=0; i<4; i++) { 684 | hash += (uint64_t)ip->src.addr.v6.s6_addr32[i] << 32; 685 | hash += ip->dst.addr.v6.s6_addr32[i]; 686 | } 687 | } 688 | hash += ((uint64_t)trns->srcport << 32) + trns->dstport; 689 | 690 | // Add in the payload. 691 | for (; pos + 8 < header->len; pos+=8) { 692 | hash += *(uint64_t*)(packet + pos); 693 | } 694 | // Add in those last bytes, if any. 695 | // Where doesn't matter, as long as we're consistent. 696 | for (; pos < header->len; pos++) { 697 | hash += packet[pos]; 698 | } 699 | // Without this, all strings of nulls are equivalent. 700 | hash += header->len; 701 | // The hash is now done. 702 | 703 | if (hash == 0) { 704 | // Say it's not a duplicate even though it might be. 705 | // Since we initialize the dedup list to zero's, it's likely that 706 | // this will produce a false positive. 707 | return 0; 708 | } 709 | 710 | for (i=0; i < conf->DEDUPS; i++) { 711 | if (hash == conf->dedup_hashes[i]) { 712 | // Found a match, return the fact. 713 | return 1; 714 | } 715 | } 716 | 717 | // There was no match. Replace the oldest dedup. 718 | conf->dedup_hashes[conf->dedup_pos] = hash; 719 | conf->dedup_pos = (conf->dedup_pos + 1) % conf->DEDUPS; 720 | return 0; 721 | } 722 | 723 | // Parse the dns protocol in 'packet'. 724 | // See RFC1035 725 | // See dns_parse.h for more info. 726 | uint32_t dns_parse(uint32_t pos, struct pcap_pkthdr *header, 727 | uint8_t *packet, dns_info * dns, 728 | config * conf, uint8_t force) { 729 | 730 | uint32_t id_pos = pos; 731 | 732 | if (header->len - pos < 12) { 733 | char * msg = escape_data(packet, id_pos, header->len); 734 | fprintf(stderr, "Truncated Packet(dns): %s\n", msg); 735 | return 0; 736 | } 737 | 738 | dns->id = (packet[pos] << 8) + packet[pos+1]; 739 | dns->qr = packet[pos+2] >> 7; 740 | dns->AA = (packet[pos+2] & 0x04) >> 2; 741 | dns->TC = (packet[pos+2] & 0x02) >> 1; 742 | dns->rcode = packet[pos + 3] & 0x0f; 743 | // rcodes > 5 indicate various protocol errors and redefine most of the 744 | // remaining fields. Parsing this would hurt more than help. 745 | if (dns->rcode > 5) { 746 | dns->qdcount = dns->ancount = dns->nscount = dns->arcount = 0; 747 | dns->queries = NULL; 748 | dns->answers = NULL; 749 | dns->name_servers = NULL; 750 | dns->additional = NULL; 751 | return pos + 12; 752 | } 753 | 754 | // Counts for each of the record types. 755 | dns->qdcount = (packet[pos+4] << 8) + packet[pos+5]; 756 | dns->ancount = (packet[pos+6] << 8) + packet[pos+7]; 757 | dns->nscount = (packet[pos+8] << 8) + packet[pos+9]; 758 | dns->arcount = (packet[pos+10] << 8) + packet[pos+11]; 759 | 760 | SHOW_RAW( 761 | printf("dns\n"); 762 | print_packet(header->len, packet, pos, header->len, 8); 763 | ) 764 | VERBOSE( 765 | printf("DNS id:%d, qr:%d, AA:%d, TC:%d, rcode:%d\n", 766 | dns->id, dns->qr, dns->AA, dns->TC, dns->rcode); 767 | printf("DNS qdcount:%d, ancount:%d, nscount:%d, arcount:%d\n", 768 | dns->qdcount, dns->ancount, dns->nscount, dns->arcount); 769 | ) 770 | 771 | // Parse each type of records in turn. 772 | pos = parse_questions(pos+12, id_pos, header, packet, 773 | dns->qdcount, &(dns->queries)); 774 | if (pos != 0) 775 | pos = parse_rr_set(pos, id_pos, header, packet, 776 | dns->ancount, &(dns->answers), conf); 777 | else dns->answers = NULL; 778 | if (pos != 0 && 779 | (conf->NS_ENABLED || conf->AD_ENABLED || force)) { 780 | pos = parse_rr_set(pos, id_pos, header, packet, 781 | dns->nscount, &(dns->name_servers), conf); 782 | } else dns->name_servers = NULL; 783 | if (pos != 0 && (conf->AD_ENABLED || force)) { 784 | pos = parse_rr_set(pos, id_pos, header, packet, 785 | dns->arcount, &(dns->additional), conf); 786 | } else dns->additional = NULL; 787 | return pos; 788 | } 789 | --------------------------------------------------------------------------------