/*
 * A tool to check checksum integrity and gather simple traffic statistics.
 *
 * James Bulpin, October 2004.
 *
 * (C) University of Cambridge 2004.
 *
 * $Id: cksumck.c,v 1.1 2004/11/09 16:15:47 jrb44 Exp $
 *
 * $Log: cksumck.c,v $
 * Revision 1.1  2004/11/09 16:15:47  jrb44
 * Initial revision
 *
 *
 */

#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netdb.h>
#include <time.h>

#include "pcap.h"
#include "pcap-bpf.h"
#include "headers.h"
#include "cksumck.h"
#include "frag.h"

//#define DEBUG

/*
 * We can summarise the data according to the different properties of each
 * packet. These are recorded as a bitmask. Ensure that packet_summary
 * is large enough for the bits used.
 */
#define SUMMARY_IP_CHKSUM_FAIL   0x0001
#define SUMMARY_PROTO_OTHER      0x0000
#define SUMMARY_PROTO_TCP        0x0002
#define SUMMARY_PROTO_UDP        0x0004
#define SUMMARY_PROTO_ICMP       0x0006
#define SUMMARY_L4_NO_CHKSUM     0x0008
#define SUMMARY_L4_CHKSUM_FAIL   0x0010
#define SUMMARY_MULTICAST        0x0020
#define SUMMARY_IP_FRAG          0x0040
#define SUMMARY_IP_ANY_OPTIONS   0x0080
#define SUMMARY_L4_ANY_OPTIONS   0x0100
#define SUMMARY_UDP_LEN_MISMATCH 0x0200

#define SUMMARY_GET_PROTO(_s)  ((_s)&0x0006)

int debug = 0;


/* Accumulating counters.
 */
static long long int cnt_ip_packets        = 0;
static long long int cnt_tcp_packets       = 0;
static long long int cnt_udp_packets       = 0;
static long long int cnt_ip_chk_err        = 0;
static long long int cnt_ip_options        = 0;
static long long int cnt_tcp_options       = 0;
static long long int cnt_errs              = 0;
static long long int cnt_nonip             = 0;
static long long int cnt_udp_no_chk        = 0;
static long long int cnt_udp_chk_err       = 0;
static long long int cnt_tcp_chk_err       = 0;
static long long int cnt_ip_frags          = 0;
static long long int cnt_icmp_packets      = 0;
static long long int cnt_multicast         = 0;
static long long int cnt_udp_size_tooshort = 0;
static long long int cnt_udp_size_toolong  = 0;
static long long int cnt_ip_bytes          = 0;
static long long int cnt_tcp_bytes         = 0;
static long long int cnt_udp_bytes         = 0;
static long long int cnt_icmp_bytes        = 0;

/* This is set by the main function when it opens a PCAP file. It is used
 * by the packet handler callback.
 */
static int current_link_type = -1;

/* Perform a checksum check on a UDP/TCP pseudo header
 */
unsigned int pseudo_hdr_pcsum(ipheader_t *ipp, unsigned int len)
{
    unsigned int c = 0;

    c += ntohs(((unsigned short *)ipp)[6]);
    c += ntohs(((unsigned short *)ipp)[7]);
    c += ntohs(((unsigned short *)ipp)[8]);
    c += ntohs(((unsigned short *)ipp)[9]);
    c += (unsigned int)(ipp->protocol);
    c += len;
    
    return c;
}

/* Calculate a checksum on the supplied buffer
 */
unsigned int buffer_pcsum(unsigned char *buffer, unsigned int len)
{
    unsigned int i, c = 0;

    for (i = 0; (i+1) < len; i+=2) {
        c += ntohs(*((unsigned short *)(&(buffer[i]))));
    }
    if ((len & 1))
        c += buffer[len-1] << 8;
    
    return c;
}

unsigned int fold_csum(unsigned int c)
{
    while (c > 0xffff)
        c = (c & 0xffff) + (c >> 16);
    c = ~c & 0xffff;
    return c;
}

int tcp_csum(ipheader_t *ipp, tcp_t *tcpp)
{
    unsigned int c = 0;
    unsigned int ip_payload_len =
        (unsigned int)(ntohs(ipp->total_length) - IP_HDR_LEN(ipp));
        
    c += buffer_pcsum((unsigned char *)tcpp, ip_payload_len);
    c += pseudo_hdr_pcsum(ipp, ip_payload_len);
    c = fold_csum(c);

    return c;
}

int udp_csum(ipheader_t *ipp, udp_t *udpp)
{
    unsigned int c = 0;

#ifdef DEBUG_NOT_NEEDED_NOW // Needs csum_partial from linux i386-csum.S
    fprintf(stderr, "Oops: %08x/%u:%08x/%u len=%u\n",
            ntohl(ipp->src),
            ntohs(udpp->src_port),
            ntohl(ipp->dst),
            ntohs(udpp->dst_port),
            ntohs(udpp->msg_len));
    fprintf(stderr,
            "  UDP Checksum: me=0x%04x claimed=0x%04x datagram=0x%04x (%d)\n",
            actual,
            claimed,
            csum_fold(csum_partial(udpp, ntohs(udpp->msg_len), 0)),
            ntohs(claimed) - ntohs(actual));
    fprintf(stderr,
            "  %02x%02x%02x%02x %02x%02x%02x%02x\n",
            (int)(((unsigned char *)udpp)[0]),
            (int)(((unsigned char *)udpp)[1]),
            (int)(((unsigned char *)udpp)[2]),
            (int)(((unsigned char *)udpp)[3]),
            (int)(((unsigned char *)udpp)[4]),
            (int)(((unsigned char *)udpp)[5]),
            (int)(((unsigned char *)udpp)[6]),
            (int)(((unsigned char *)udpp)[7]));
#endif

    c += buffer_pcsum((unsigned char *)udpp, ntohs(udpp->msg_len));
    c += pseudo_hdr_pcsum(ipp, ntohs(udpp->msg_len));
    c = fold_csum(c);

#ifdef DEBUG
    if (c)
        fprintf(stderr, "  %08x\n", c);
#endif

    return c;
}

/* Error logging - outputs a formatted error to fh and optionally dumps the
 * error packet to the current dump file.
 */
pcap_dumper_t *dumper = NULL;
void log_error(FILE                     *fh,
               const struct pcap_pkthdr *h,
               const u_char             *sp,
               char                     *fmt, ...)
{
    va_list va;
    int s = (h)->ts.tv_sec % 86400;  

    fprintf(fh, "%02d:%02d:%02d.%06u ",
            s / 3600, (s % 3600) / 60, s % 60,
            (unsigned)(h)->ts.tv_usec);

    va_start(va, fmt);
    vfprintf(fh, fmt, va);
    va_end(va);

    if (dumper)
        pcap_dump((u_char *)dumper, h, sp);
}
#define LOG_ERROR log_error

/* Dump a packet of particular interest. Both the dump file and the type of
 * packet required must be specified on the command line.
 */
static unsigned char dumpitem = '\0';
pcap_dumper_t *dumpspecial = NULL;
#define DUMP_PACKET(_h, _sp) {if (dumpspecial) \
    pcap_dump((u_char *)dumpspecial, _h, _sp);}

/* Distribution counting arrays.
 */
static unsigned long long pktsizes[65536];
static unsigned long long ipsizes[65536];
static unsigned long long ttls[256];
static unsigned long long ecns[4];
static unsigned long long ipoptions[256];
static unsigned long long tcpoptions[256];
static unsigned long long msss[65536];
static unsigned long long mtus[65536];
static unsigned long long packet_summary[65536];
static unsigned long long chksum_iphdr[65536];
static unsigned long long chksum_tcp[65536];
static unsigned long long chksum_udp[65536];
static unsigned long long icmps[65536];
static unsigned long long icmp_types[256];

/* The PCAP packet handler - this is where it's all done.
 */
void cksumck_callback(u_char                   *user,
                      const struct pcap_pkthdr *h,
                      const u_char             *sp)
{
    ipheader_t     *ip;
    udp_t          *udp;
    tcp_t          *tcp;
    icmp_common_t  *icmp;
    unsigned short  s;
    unsigned int    len;
    unsigned int    summary = 0;
    unsigned int    ll_hdr_len = 0;
    
    /* Link layer header
     */
    switch (current_link_type) {
    case DLT_EN10MB:
        if (h->caplen < sizeof(ethernet_t)) {
            cnt_errs++;
            LOG_ERROR(stderr, h, sp,
                      "Capture (%u) too short for ethernet header (%u)\n",
                      h->caplen, sizeof(ethernet_t));
            goto leave_callback;
        }
        {
            ethernet_t *ether = (ethernet_t *)sp;
            switch (ntohs(ether->proto)) {
            case ETHERTYPE_IP:
                ll_hdr_len = sizeof(ethernet_t);
                break;
            case ETHERTYPE_8021Q:
                {
                    vlan_t *v = (vlan_t *)(sp + sizeof(ethernet_t));
                    ll_hdr_len = sizeof(ethernet_t) + 4;
                    if (ntohs(v->proto) != ETHERTYPE_IP) {
                        LOG_ERROR(stderr, h, sp, "VLAN %u, proto 0x%04x\n",
                                  ntohs(v->vlan) & 0xfff,
                                  ntohs(v->proto));
                        cnt_nonip++;
                        goto leave_callback;
                    }
                }
                break;
            default:
                cnt_nonip++;
                LOG_ERROR(stderr, h, sp, "Unknown ethernet protocol 0x%04x\n",
                          ntohs(ether->proto));
                goto leave_callback;
            }
        }
        break;
    case DLT_RAW:
        /* We have to assume IP :-( */
        ll_hdr_len = 0;
        break;
    default:
        fprintf(stderr, "ERROR: unknown link type %u.\n", current_link_type);
        exit(1);
        break;
    }


    cnt_ip_packets++;

    ip = EXTRACT_IPHDR(sp);
    len = (unsigned int)IP_HDR_LEN(ip);
    if ((len + LL_HDR_LEN) > h->caplen) {
        cnt_errs++;
        LOG_ERROR(stderr, h, sp, "Capture (%u) too short for IP header (%u)\n",
                  h->caplen, len);
        goto leave_callback;
    }

    /* IP Header checksum.
     */
    s = ip_fast_csum((unsigned char *)ip, len>>2);
    if (s != 0) {
        cnt_ip_chk_err++;
        LOG_ERROR(stderr, h, sp, "IP header checksum failed %04x (len = %u)\n",
                  (unsigned int)s, len);
        summary |= SUMMARY_IP_CHKSUM_FAIL;
#ifdef STOP_ON_CHKSUM_FAIL
        goto leave_callback;
#endif
    }
    chksum_iphdr[ntohs(ip->hdr_chksum)]++;

    /* Address type
     */
    if (IS_MULTICAST(ip->src) || IS_MULTICAST(ip->dst)) {
        summary |= SUMMARY_MULTICAST;
        cnt_multicast++;
    }

    /* Packet sizes
     */
    if ((unsigned int)user != REASSEMBLED_PACKET)
        pktsizes[ntohs(ip->total_length)]++;

    /* Frag?
     */
    if (((ntohs(ip->flags_frag) & IP_OFFSET)) ||
        ((ntohs(ip->flags_frag) & IP_MF))) {
        cnt_ip_frags++;
        LOG_ERROR(stderr, h, sp, "IP fragment received\n");
        summary |= SUMMARY_IP_FRAG;
        handle_frag(user, h, sp, ll_hdr_len);

        /* For non-initial frags, stop further processing           
         */
#ifdef USE_REASSEMBLED_DATAGRAMS
        goto leave_callback;
#else
        if ((ntohs(ip->flags_frag) & IP_OFFSET))
            goto leave_callback;
#endif
    }

    /* IP datagram sizes
     */
    if (!((ntohs(ip->flags_frag) & IP_OFFSET))) {
        ipsizes[ntohs(ip->total_length)]++;
    }
    cnt_ip_bytes += ntohs(ip->total_length);

    /* TTL and options
     */
    ttls[ip->ttl]++;
    if (IP_HDR_LEN(ip) != sizeof(ipheader_t)) {
        int i, skip = 0;
        cnt_ip_options++;
        summary |= SUMMARY_IP_ANY_OPTIONS;
        if (debug)
            LOG_ERROR(stderr, h, sp, "IPOPTIONS");
        for (i = sizeof(ipheader_t); i < IP_HDR_LEN(ip); i++) {
            unsigned char octet = ((unsigned char *)ip)[i];
            if (!skip) {
                octet &= 0x3f; // ignore copy bit
                ipoptions[(unsigned int)octet]++;
                switch (octet) {
                case IP_OPTION_EOL:
                    if (debug)
                        fprintf(stderr, " EOL");
                    break;
                case IP_OPTION_NOP:
                    if (debug)
                        fprintf(stderr, " NOP");
                    break;
                case IP_OPTION_REST:
                    i++;
                    octet = ((unsigned char *)ip)[i];
                    if (debug)
                        fprintf(stderr, " REST(%u):", (unsigned int)octet);
                    skip = (unsigned int)octet - 2;
                    break;
                case IP_OPTION_LSR:
                    i++;
                    octet = ((unsigned char *)ip)[i];
                    if (debug)
                        fprintf(stderr, " LOOSE_SR(%u)", (unsigned int)octet);
                    octet -= 2;
                    while (octet) {
                        octet--;
                        i++;
                    }
                    break;
                case IP_OPTION_SSR:
                    i++;
                    octet = ((unsigned char *)ip)[i];
                    if (debug)
                        fprintf(stderr, " STRICT_SR(%u)", (unsigned int)octet);
                    octet -= 2;
                    while (octet) {
                        octet--;
                        i++;
                    }
                    break;
                case IP_OPTION_RR:
                    i++;
                    octet = ((unsigned char *)ip)[i];
                    if (debug)
                        fprintf(stderr, " RR(%u)", (unsigned int)octet);
                    octet -= 2;
                    while (octet) {
                        octet--;
                        i++;
                    }
                    break;
                case IP_OPTION_SID:
                    i++;
                    octet = ((unsigned char *)ip)[i];
                    if (debug)
                        fprintf(stderr, " SATNET(%u)", (unsigned int)octet);
                    octet -= 2;
                    while (octet) {
                        octet--;
                        i++;
                    }
                    break;
                case IP_OPTION_MTUP:
                    i++;
                    octet = ((unsigned char *)ip)[i];
                    if (octet > 4) {
                        if (debug)
                            fprintf(stderr, " MTU_PROBE(len=%u) ->",
                                    (unsigned int)octet);
                        skip = (unsigned int)octet - 2;
                    }
                    else {
                        unsigned int mssh, mssl;
                        i++;
                        mssh = (unsigned int)((unsigned char *)ip)[i];
                        i++;
                        mssl = (unsigned int)((unsigned char *)ip)[i];
                        if (debug)
                            fprintf(stderr, " MTU_PROBE:%u",
                                    (mssh << 8) + mssl);
                    }
                    break;
                case IP_OPTION_MTUR:
                    i++;
                    octet = ((unsigned char *)ip)[i];
                    if (octet > 4) {
                        if (debug)
                            fprintf(stderr, " MTU_REPLY(len=%u) ->",
                                    (unsigned int)octet);
                        skip = (unsigned int)octet - 2;
                    }
                    else {
                        unsigned int mssh, mssl;
                        i++;
                        mssh = (unsigned int)((unsigned char *)ip)[i];
                        i++;
                        mssl = (unsigned int)((unsigned char *)ip)[i];
                        if (debug)
                            fprintf(stderr, " MTU_REPLY:%u",
                                    (mssh << 8) + mssl);
                        mtus[(mssh << 8) + mssl]++;
                    }
                    break;
                case IP_OPTION_RA:
                    i++;
                    octet = ((unsigned char *)ip)[i];
                    if (octet > 4) {
                        if (debug)
                            fprintf(stderr, " ALERT(len=%u) ->",
                                    (unsigned int)octet);
                        skip = (unsigned int)octet - 2;
                    }
                    else {
                        unsigned int mssh, mssl;
                        i++;
                        mssh = (unsigned int)((unsigned char *)ip)[i];
                        i++;
                        mssl = (unsigned int)((unsigned char *)ip)[i];
                        if (debug)
                            fprintf(stderr, " ALERT:%u", (mssh << 8) + mssl);
                    }
                    LOG_ERROR(stderr, h, sp, "ALERT\n");
                    //DUMP_PACKET(h, sp);
                    break;
                case IP_OPTION_TS:
                    i++;
                    octet = ((unsigned char *)ip)[i];
                    if (debug)
                        fprintf(stderr, " TS(%u)", (unsigned int)octet);
                    octet -= 2;
                    while (octet) {
                        octet--;
                        i++;
                    }
                    break;
                case IP_OPTION_TR:
                    i++;
                    octet = ((unsigned char *)ip)[i];
                    if (debug)
                        fprintf(stderr, " TR(%u)", (unsigned int)octet);
                    octet -= 2;
                    while (octet) {
                        octet--;
                        i++;
                    }
                    break;
                default:
                    if (debug)
                        fprintf(stderr, " ?%02x", (unsigned int)octet);
                    break;
                }
            }
            else {
                if (debug)
                    fprintf(stderr, " %02x", (unsigned int)octet);
                skip--;
            }
        }
        if (debug)
            fprintf(stderr, "\n");
    }
    ecns[(int)IP_HDR_ECN(ip)]++;

    /* Layer 4
     */
    switch (ip->protocol) {
    case PROTOCOL_TCP:
        summary |= SUMMARY_PROTO_TCP;
        cnt_tcp_packets++;
        tcp = (tcp_t *)(((unsigned char *)ip) + (unsigned int)IP_HDR_LEN(ip));

        if ((((unsigned char *)tcp) + TCP_HDR_LEN(tcp)) > (sp + h->caplen)) {
            cnt_errs++;
            LOG_ERROR(stderr, h, sp,
                      "Capture (%u) too short for TCP header (%u+%u+%u)\n",
                      h->caplen,
                      14,
                      IP_HDR_LEN(ip),
                      TCP_HDR_LEN(tcp));
            goto leave_callback;
        }
        cnt_tcp_bytes += ntohs(ip->total_length);
        
        {
            unsigned short ip_payload_len =
                ntohs(ip->total_length) - IP_HDR_LEN(ip);
            if ((((unsigned char *)ip) + (int)ip_payload_len) >
                (sp+h->caplen)) {
#ifdef DEBUG
                fprintf(stderr, "Oops: %u:%u TCP_len=%u caplen=%u\n",
                        ntohs(tcp->src_port),
                        ntohs(tcp->dst_port),
                        (int)ip_payload_len,
                        h->caplen);
#endif
                cnt_errs++;
                LOG_ERROR(stderr, h, sp,
                          "Capture (%u) too short for TCP payload "
                          "(datagram len=%u)\n",
                          h->caplen,
                          ip_payload_len);
#ifdef STOP_ON_CHKSUM_FAIL
                goto leave_callback;
#endif
            }
            else if (tcp_csum(ip, tcp)) {
                cnt_tcp_chk_err++;
                LOG_ERROR(stderr, h, sp, "TCP checksum failed\n");
                summary |= SUMMARY_L4_CHKSUM_FAIL;
                if (dumpitem == 't')
                    DUMP_PACKET(h, sp);
#ifdef STOP_ON_CHKSUM_FAIL
                goto leave_callback;
#endif
            }
        }
        chksum_tcp[ntohs(tcp->checksum)]++;

        /* TCP options
         */
        if (TCP_HDR_LEN(tcp) != sizeof(tcp_t)) {
            int i, skip = 0;
            cnt_tcp_options++;
            summary |= SUMMARY_L4_ANY_OPTIONS;
            if (debug)
                LOG_ERROR(stderr, h, sp, "TCPOPTIONS");
            for (i = sizeof(tcp_t); i < TCP_HDR_LEN(tcp); i++) {
                unsigned char octet = ((unsigned char *)tcp)[i];
                if (!skip) {
                    tcpoptions[(unsigned int)octet]++;
                    switch (octet) {
                    case TCP_OPTION_EOL:
                        if (debug)
                            fprintf(stderr, " EOL");
                        break;
                    case TCP_OPTION_NOP:
                        if (debug)
                            fprintf(stderr, " NOP");
                        break;
                    case TCP_OPTION_MSS:
                        i++;
                        octet = ((unsigned char *)tcp)[i];
                        if (octet > 4) {
                            if (debug)
                                fprintf(stderr, " MSS(len=%u) ->",
                                        (unsigned int)octet);
                            skip = (unsigned int)octet - 2;
                        }
                        else {
                            unsigned int mssh, mssl;
                            i++;
                            mssh = (unsigned int)((unsigned char *)tcp)[i];
                            i++;
                            mssl = (unsigned int)((unsigned char *)tcp)[i];
                            msss[(mssh << 8) + mssl]++;
                            if (debug)
                                fprintf(stderr, " MSS:%u", (mssh << 8) + mssl);
                        }
                        break;
                    case TCP_OPTION_WS:
                        i++;
                        octet = ((unsigned char *)tcp)[i];
                        if (octet != 3) {
                            if (debug)
                                fprintf(stderr, " WS(len=%u) ->",
                                        (unsigned int)octet);
                            skip = (unsigned int)octet - 2;
                        }
                        else {
                            i++;
                            octet = ((unsigned char *)tcp)[i];
                            if (debug)
                                fprintf(stderr, " WS:%u",
                                        (unsigned int)octet);
                        }
                        break;
                    case TCP_OPTION_SACKOK:
                        i++;
                        octet = ((unsigned char *)tcp)[i];
                        if (octet > 2) {
                            if (debug)
                                fprintf(stderr, " SACKOK(len=%u) ->",
                                        (unsigned int)octet);
                            skip = (unsigned int)octet - 2;
                        }
                        else {
                            if (debug)
                                fprintf(stderr, " SACKOK");
                        }
                        break;
                    case TCP_OPTION_SACK:
                        i++;
                        octet = ((unsigned char *)tcp)[i];
                        if (debug)
                            fprintf(stderr, " SACK(len=%u)",
                                    (unsigned int)octet);
                        octet -= 2;
                        while (octet) {
                            octet--;
                            i++;
                        }
                        break;
                    case TCP_OPTION_TS:
                        i++;
                        octet = ((unsigned char *)tcp)[i];
                        if (octet != 10) {
                            if (debug)
                                fprintf(stderr, " TS(len=%u) ->",
                                        (unsigned int)octet);
                        }
                        else {
                            if (debug)
                                fprintf(stderr, " TS");
                        }
                        octet -= 2;
                        while (octet) {
                            octet--;
                            i++;
                        }
                        break;
                    case TCP_OPTION_ALTCHK:
                        i++;
                        octet = ((unsigned char *)tcp)[i];
                        if (octet > 3) {
                            if (debug)
                                fprintf(stderr, " ALTCHK(len=%u) ->",
                                        (unsigned int)octet);
                            skip = (unsigned int)octet - 2;
                        }
                        else {
                            i++;
                            octet = ((unsigned char *)tcp)[i];
                            switch (octet) {
                            case TCP_ALTCHK_TCP:
                                if (debug)
                                    fprintf(stderr, " ALTCHK:TCP");
                                break;
                            case TCP_ALTCHK_FLETCHER8:
                                if (debug)
                                    fprintf(stderr, " ALTCHK:FLETCHER8");
                                break;
                            case TCP_ALTCHK_FLETCHER16:
                                if (debug)
                                    fprintf(stderr, " ALTCHK:FLETCHER16");
                                break;
                            default:
                                if (debug)
                                    fprintf(stderr, " ALTCHK:%u",
                                            (unsigned int)octet);
                                break;
                            }
                        }
                        break;
                    case TCP_OPTION_CC:
                    case TCP_OPTION_CCNEW:
                    case TCP_OPTION_CCECHO:
                    case TCP_OPTION_ECHO:
                    case TCP_OPTION_REPLY:
                    case TCP_OPTION_PO_OK:
                    case TCP_OPTION_PO_PR:
                    case TCP_OPTION_ACDAT:
                    case TCP_OPTION_SKEET:
                    case TCP_OPTION_BUBBA:
                    case TCP_OPTION_TRLCHK:
                    case TCP_OPTION_MD5:
                    case TCP_OPTION_SCPS:
                    case TCP_OPTION_SNACK:
                    case TCP_OPTION_RBND:
                    case TCP_OPTION_CRPT:
                    case TCP_OPTION_SNAP:
                    case TCP_OPTION_X:
                    case TCP_OPTION_COMP:
                        i++;
                        skip = (unsigned int)(((unsigned char *)tcp)[i]);
                        if (debug)
                            fprintf(stderr, " OPT:%02x[%u]",
                                    (unsigned int)octet, skip);
                        skip -= 2;
                        break;
                    default:
                        i++;
                        skip = (unsigned int)(((unsigned char *)tcp)[i]);
                        LOG_ERROR(stderr, h, sp,
                                  "UNKNOWN_TCPOPTION %02x len=%u\n",
                                  (unsigned int)octet, skip);
                        skip -= 2;
                        break;
                    }
                }
                else {
                    if (debug)
                        fprintf(stderr, " %02x", (unsigned int)octet);
                    skip--;
                }
            }
            if (debug)
                fprintf(stderr, "\n");
        }        
        
        break;
    case PROTOCOL_UDP:
        cnt_udp_packets++;        
        summary |= SUMMARY_PROTO_UDP;

        udp = (udp_t *)(((unsigned char *)ip) + (unsigned int)IP_HDR_LEN(ip));
        if ((((unsigned char *)udp) + sizeof(udp_t)) > (sp + h->caplen)) {
            cnt_errs++;
            LOG_ERROR(stderr, h, sp,
                      "Capture (%u) too short for UDP header (%u)\n",
                      h->caplen, sizeof(udp_t));
            goto leave_callback;
        }
        cnt_udp_bytes += ntohs(ip->total_length);

        /* Check UDP packet length is same as IP payload length and no
         * more-frags bit.
         */
        if (((ntohs(ip->total_length) - IP_HDR_LEN(ip)) != ntohs(udp->msg_len))
            && ((ntohs(ip->flags_frag) & IP_MF))) {
            summary |= SUMMARY_UDP_LEN_MISMATCH;
            LOG_ERROR(stderr, h, sp,
                      "UDP_LEN_MISMATCH IP Payload: %u UDP Message %u\n",
                      ntohs(ip->total_length) - IP_HDR_LEN(ip),
                      ntohs(udp->msg_len));
            if ((ntohs(ip->total_length) - IP_HDR_LEN(ip)) >
                ntohs(udp->msg_len))
                cnt_udp_size_tooshort++;
            else
                cnt_udp_size_toolong++;
        }

        if (!udp->checksum) {
            cnt_udp_no_chk++;
            summary |= SUMMARY_L4_NO_CHKSUM;
            if (debug)
                LOG_ERROR(stderr, h, sp,
                          "UDP_NO_CHK %u %u (UDP checksum not used)\n",
                          ntohs(udp->src_port),
                          ntohs(udp->dst_port));
#ifdef STOP_ON_CHKSUM_FAIL
            goto leave_callback;
#endif
        }
        else {
            unsigned short udp_msg_len = ntohs(udp->msg_len);
            if ((((unsigned char *)ip) + (int)udp_msg_len) > (sp+h->caplen)) {
#ifdef DEBUG
                fprintf(stderr, "Oops: %u:%u UDP_len=%u caplen=%u\n",
                        ntohs(udp->src_port),
                        ntohs(udp->dst_port),
                        (int)udp_msg_len,
                        h->caplen);
#endif
                cnt_errs++;
                LOG_ERROR(stderr, h, sp,
                          "Capture (%u) too short for UDP payload "
                          "(datagram len=%u)\n",
                          h->caplen,
                          (int)udp_msg_len);
                goto leave_callback;
            }
            if (udp_csum(ip, udp)) {
                cnt_udp_chk_err++;
                summary |= SUMMARY_L4_CHKSUM_FAIL;
                LOG_ERROR(stderr, h, sp, "UDP checksum failed len=%u\n",
                          (unsigned int)udp_msg_len);
            }
            else
                chksum_udp[ntohs(udp->checksum)]++;
        }
        break;
    case PROTOCOL_ICMP:
        cnt_icmp_packets++;
        cnt_icmp_bytes += ntohs(ip->total_length);
        summary |= SUMMARY_PROTO_ICMP;

        icmp = (icmp_common_t *)(((unsigned char *)ip) +
                                 (unsigned int)IP_HDR_LEN(ip));
        if ((((unsigned char *)icmp) + sizeof(icmp_common_t)) >
            (sp + h->caplen)) {
            cnt_errs++;
            LOG_ERROR(stderr, h, sp,
                      "Capture (%u) too short for ICMP header (%u)\n",
                      h->caplen, sizeof(icmp_common_t));
            goto leave_callback;
        }

        icmps[((unsigned int)icmp->type << 8) | (unsigned int)icmp->code]++;
        icmp_types[(unsigned int)icmp->type]++;
        switch (icmp->type) {
        }

        break;
    }

    leave_callback:
    packet_summary[summary]++;
    return;
}

/* Generic CDF writing function. Used for non-CDF distributions too, just
 * ignore the 2nd column. Output format is space-separated:
 *   <value> <CDF fraction> <number of that value>
 */
void write_cdf(char *filename, unsigned long long *array, int len)
{
    FILE *fh = fopen(filename, "w");
    if (!fh) {
        fprintf(stderr, "Could not open '%s' for writing.\n", filename);
    }
    else {
        unsigned long long cumf = 0, total = 0;
        int i, min = 0;
        
        for (i = 0; i < len; i++) {
            total += array[i];
            if (!min && array[i])
                min = i;
        }
        
        for (i = 0; i < len; i++) {
            cumf += array[i];
            if (array[i] || (i == min))
                fprintf(fh, "%u %f %llu\n",
                        i,
                        (double)cumf/(double)total,
                        array[i]);
        }
        
        fclose(fh);
    }
}

/* Usage
 */
void usage(FILE *fh, const char *us)
{
    fprintf(fh, "%s [options] <list of pcap capture files>\n\n", us);
    fprintf(fh, "    -h                 Usage\n");
    fprintf(fh, "    -d                 Enable debug output\n");
    fprintf(fh, "    -F \"<BPF filter>\"  Filter (in quotes)\n");
    fprintf(fh, "    -i                 IP datagram size CDF output filename\n");
    fprintf(fh, "    -p                 Packet size CDF output filename\n");
    fprintf(fh, "    -T                 IP TTL CDF output filename\n");
    fprintf(fh, "    -M                 TCP MSS CDF output filename\n");
    fprintf(fh, "    -c                 IP header checksum occurrence output filename\n");
    fprintf(fh, "    -t                 TCP checksum occurrence output filename\n");
    fprintf(fh, "    -u                 UDP checksum occurrence output filename\n");
    fprintf(fh, "    -D                 Error packet dump file prefix\n");
    fprintf(fh, "    -S                 Specified packet dump filename\n");
    fprintf(fh, "    -P <character>     Specified packets to dump\n");
    fprintf(fh, "                         't'  TCP checksum failed\n");
}

/* Main program
 */
int main (int argc, char **argv)
{
    pcap_t *pcap;
    char error_buffer[PCAP_ERRBUF_SIZE];
    char *filename = NULL;
    struct bpf_program filter;
    int rc, c, i, ind, j;
    char *ipsizes_file = NULL;
    char *ttls_file = NULL;
    char *msss_file = NULL;
    char *mtus_file = NULL;
    char *filter_string = NULL;
    char *dump_file = NULL;
    char *dumpspecial_file = NULL;
    char *pktsizes_file = NULL;
    char *chksum_iphdr_file = NULL;
    char *chksum_tcp_file = NULL;
    char *chksum_udp_file = NULL;

    /* Read command line options.
     */
    while((c = getopt(argc, argv, "hi:T:F:dM:D:p:c:t:u:m:P:S:")))
    {
        if (c == -1)
            break;

        switch (c)
        {
        case 'h':
            usage(stdout, argv[0]);
            exit(0);
        case 'i':
            ipsizes_file = optarg;
            break;
        case 'p':
            pktsizes_file = optarg;
            break;
        case 'T':
            ttls_file = optarg;
            break;
        case 'F':
            filter_string = optarg;
            break;
        case 'd':
            debug = 1;
            break;
        case 'M':
            msss_file = optarg;
            break;
        case 'm': // Not used
            mtus_file = optarg;
            break;
        case 'D':
            dump_file = optarg;
            break;
        case 'c':
            chksum_iphdr_file = optarg;
            break;
        case 't':
            chksum_tcp_file = optarg;
            break;
        case 'u':
            chksum_udp_file = optarg;
            break;
        case 'P':
            dumpitem = optarg[0];
            break;
        case 'S':
            dumpspecial_file = optarg;
            break;
        default:
            fprintf(stderr, "Bogus option(s). Usage:\n\n");
            usage(stderr, argv[0]);
            exit(1);
        }
    }

    if (optind == argc) {
        fprintf(stderr, "No input files specified.\n");
        exit(1);
    }

    memset(ipsizes, 0, sizeof(unsigned long long) * 65536);
    memset(pktsizes, 0, sizeof(unsigned long long) * 65536);
    memset(ttls, 0, sizeof(unsigned long long) * 256);
    memset(ecns, 0, sizeof(unsigned long long) * 4);
    memset(ipoptions, 0, sizeof(unsigned long long) * 256);
    memset(tcpoptions, 0, sizeof(unsigned long long) * 256);
    memset(msss, 0, sizeof(unsigned long long) * 65536);
    memset(mtus, 0, sizeof(unsigned long long) * 65536);
    memset(packet_summary, 0, sizeof(unsigned long long) * 65536);
    memset(chksum_iphdr, 0, sizeof(unsigned long long) * 65536);
    memset(chksum_tcp, 0, sizeof(unsigned long long) * 65536);
    memset(chksum_udp, 0, sizeof(unsigned long long) * 65536);
    memset(icmps, 0, sizeof(unsigned long long) * 65536);
    memset(icmp_types, 0, sizeof(unsigned long long) * 256);

    /* Loop through all capture files.
     */
    ind = -1;
    for (c = optind; c < argc; c++) {
        ind++;
        filename = argv[c];
    
        /* Open PCAP offline
         */
        pcap = pcap_open_offline(filename, error_buffer);
        if (pcap == NULL) {
            fprintf(stderr, "Error opening libpcap: %s\n", error_buffer);
            return -1;
        }
        
        /* Compile a filter.
         */
        if (filter_string) {
            rc = pcap_compile(pcap, &filter, filter_string, 1, 0);
            if (rc < 0) {
                fprintf(stderr,
                        "Error compiling filter code: %s\n",
                        pcap_geterr(pcap));
                pcap_close(pcap);
                return -1;
            }
            
            /* Install the filter.
             */
            rc = pcap_setfilter(pcap, &filter);
            if (rc < 0) {
                fprintf(stderr,
                        "Error installing filter: %s\n",
                        pcap_geterr(pcap));
                pcap_freecode(&filter);
                pcap_close(pcap);
                return -1;
            }
            pcap_freecode(&filter);
        }

        /* Open dump file if required. One for each input file.
         */
        if (dump_file) {
            unsigned char *str = malloc(strlen(dump_file)+8);
            if (!str) {
                fprintf(stderr,
                        "Could not malloc for dump file name creation.\n");
                exit(1);
            }
            sprintf(str, "%s.%06u", dump_file, ind);
            dumper = pcap_dump_open(pcap, str);
            if (!dumper)
                fprintf(stderr, "Could not open dump file '%s' for writing.\n",
                        str);
            free(str);
        }

        /* Open special-interest dump file if required. (only once)
         */
        if (dumpspecial_file && !dumpspecial) {
            dumpspecial = pcap_dump_open(pcap, dumpspecial_file);
            if (!dumpspecial)
                fprintf(stderr, "Could not open dump file '%s' for writing.\n",
                        dumpspecial_file);
        }

        /* Get the link type.
         */
        current_link_type = pcap_datalink(pcap);
        fprintf(stderr, "New file '%s' linktype %u.\n",
                filename, current_link_type);

        /* Run the capture loop.
         */
        rc = pcap_loop(pcap, -1, cksumck_callback, NULL);
        if (rc < 0) {
            fprintf(stderr, "Error processing packets.\n");
        }
        
        /* Close PCAP.
         */
        if (dumper) {
            pcap_dump_close(dumper);
            dumper = NULL;
        }
        if (dumpspecial && ((c+1) == argc)) {
            pcap_dump_close(dumpspecial);
            dumpspecial = NULL;
        }
        pcap_close(pcap);
    }

    printf("Processing Errors:          %llu\n", cnt_errs);
    printf("Non-IP packets:             %llu\n", cnt_nonip);
    printf("\n");    
    printf("IP packets:                 %llu\n", cnt_ip_packets);
    printf("IP bytes:                   %llu\n", cnt_ip_bytes);
    printf("IP Header Checksum failed:  %llu\n", cnt_ip_chk_err);
    printf("IP frags received:          %llu\n", cnt_ip_frags);
    printf("IP options packets:         %llu\n", cnt_ip_options);    
    printf("IP ECN fields 00 01 10 11   %llu %llu %llu %llu\n",
           ecns[0], ecns[1], ecns[2], ecns[3]);
    printf("\n");    
    printf("UDP packets:                %llu\n", cnt_udp_packets);
    printf("UDP bytes:                  %llu\n", cnt_udp_bytes);
    printf("UDP Checksum not used:      %llu\n", cnt_udp_no_chk);
    printf("UDP Checksum failed:        %llu\n", cnt_udp_chk_err);
    printf("UDP Size mismatch:          %llu (UDP too long=%llu short=%llu)\n",
           cnt_udp_size_tooshort + cnt_udp_size_toolong,
           cnt_udp_size_toolong, cnt_udp_size_tooshort);
    printf("\n");
    printf("TCP packets:                %llu\n", cnt_tcp_packets);
    printf("TCP bytes:                  %llu\n", cnt_tcp_bytes);
    printf("TCP Checksum failed:        %llu\n", cnt_tcp_chk_err);
    printf("TCP options packets:        %llu\n", cnt_tcp_options);    
    printf("\n");
    printf("ICMP packets:               %llu\n", cnt_icmp_packets);
    printf("ICMP bytes:                 %llu\n", cnt_icmp_bytes);
    printf("\n");

    for (i = 0; i < 256; i++) {
        if (ipoptions[i]) {
            switch (i) {
            case IP_OPTION_EOL:
                printf ("IPOPTION:EOL");
                break;
            case IP_OPTION_NOP:
                printf ("IPOPTION:NOP");
                break;
            case IP_OPTION_REST:
                printf ("IPOPTION:REST");
                break;
            case IP_OPTION_LSR:
                printf ("IPOPTION:LOOSE_SR");
                break;
            case IP_OPTION_RR:
                printf ("IPOPTION:RR");
                break;
            case IP_OPTION_SID:
                printf ("IPOPTION:SATNET");
                break;
            case IP_OPTION_SSR:
                printf ("IPOPTION:STRICT_SR");
                break;
            case IP_OPTION_MTUP:
                printf ("IPOPTION:MTU_PROBE");
                break;
            case IP_OPTION_MTUR:
                printf ("IPOPTION:MTU_REPLY");
                break;
            case IP_OPTION_RA:
                printf ("IPOPTION:ALERT");
                break;
            case IP_OPTION_TS:
                printf ("IPOPTION:TS");
                break;
            case IP_OPTION_TR:
                printf ("IPOPTION:TR");
                break;
            default:
                printf ("IPOPTION:%02u", i);
                break;
            }
            printf(" %llu\n", ipoptions[i]);
        }
    }

    for (i = 0; i < 256; i++) {
        if (tcpoptions[i]) {
            switch (i) {
            case TCP_OPTION_EOL:
                printf ("TCPOPTION:EOL");
                break;
            case TCP_OPTION_NOP:
                printf ("TCPOPTION:NOP");
                break;
            case TCP_OPTION_MSS:
                printf ("TCPOPTION:MSS");
                break;
            case TCP_OPTION_WS:
                printf ("TCPOPTION:WS");
                break;
            case TCP_OPTION_SACKOK:
                printf ("TCPOPTION:SACK_OK");
                break;
            case TCP_OPTION_SACK:
                printf ("TCPOPTION:SACK");
                break;
            case TCP_OPTION_TS:
                printf ("TCPOPTION:TS");
                break;
            case TCP_OPTION_CC:
                printf ("TCPOPTION:CC");
                break;
            case TCP_OPTION_CCNEW:
                printf ("TCPOPTION:CC.NEW");
                break;
            case TCP_OPTION_CCECHO:
                printf ("TCPOPTION:CC.ECHO");
                break;
            case TCP_OPTION_ECHO:
                printf ("TCPOPTION:ECHO");
                break;
            case TCP_OPTION_REPLY:
                printf ("TCPOPTION:ECHOREPLY");
                break;
            case TCP_OPTION_PO_OK:
                printf ("TCPOPTION:PO_OK");
                break;
            case TCP_OPTION_PO_PR:
                printf ("TCPOPTION:PO_PROFILE");
                break;
            case TCP_OPTION_ALTCHK:
                printf ("TCPOPTION:ALT_CHECK");
                break;
            case TCP_OPTION_ACDAT:
                printf ("TCPOPTION:ALT_CHECK_DATA");
                break;
            case TCP_OPTION_SKEET:
                printf ("TCPOPTION:SKEETER");
                break;
            case TCP_OPTION_BUBBA:
                printf ("TCPOPTION:BUBBA");
                break;
            case TCP_OPTION_TRLCHK:
                printf ("TCPOPTION:TRAILER_CHECKSUM");
                break;
            case TCP_OPTION_MD5:
                printf ("TCPOPTION:MD5_SIGN");
                break;
            case TCP_OPTION_SCPS:
                printf ("TCPOPTION:SCPS_CAP");
                break;
            case TCP_OPTION_SNACK:
                printf ("TCPOPTION:SEL_NACK");
                break;
            case TCP_OPTION_RBND:
                printf ("TCPOPTION:REC_BOUND");
                break;
            case TCP_OPTION_CRPT:
                printf ("TCPOPTION:CORRUPTION");
                break;
            case TCP_OPTION_SNAP:
                printf ("TCPOPTION:SNAP");
                break;
            case TCP_OPTION_X:
                printf ("TCPOPTION:WITHDRAWN_25");
                break;
            case TCP_OPTION_COMP:
                printf ("TCPOPTION:COMPRESSION");
                break;
            default:
                printf ("TCPOPTION:%02u", i);
                break;
            }
            printf(" %llu\n", tcpoptions[i]);
        }
    }

    for (i = 0; i < 256; i++) {
        if (icmp_types[i]) {
            switch (i) {
            case ICMP_ECHO_REPLY:
                printf("ICMP:ECHO_REPLY");
                break;
            case ICMP_DEST_UR:
                printf("ICMP:DEST_UR");
                break;
            case ICMP_SQ:
                printf("ICMP:SQ");
                break;
            case ICMP_REDIRECT:
                printf("ICMP:REDIRECT");
                break;
            case ICMP_ECHO_REQ:
                printf("ICMP:ECHO_REQ");
                break;
            case ICMP_RTR_AD:
                printf("ICMP:RTR_AD");
                break;
            case ICMP_RTR_SOL:
                printf("ICMP:RTR_SOL");
                break;
            case ICMP_TE:
                printf("ICMP:TE");
                break;
            case ICMP_PP:
                printf("ICMP:PP");
                break;
            case ICMP_TS_REQ:
                printf("ICMP:TS_REQ");
                break;
            case ICMP_TS_REPLY:
                printf("ICMP:TS_REPLY");
                break;
            case ICMP_INF_REQ:
                printf("ICMP:INF_REQ");
                break;
            case ICMP_INF_REPLY:
                printf("ICMP:INF_REPLY");
                break;
            case ICMP_MASK_REQ:
                printf("ICMP:MASK_REQ");
                break;
            case ICMP_MASK_REPLY:
                printf("ICMP:MASK_REPLY");
                break;
            default:
                printf("ICMP:%02x", i);
                break;
            }
            printf(" %llu\n", icmp_types[i]);
            switch (i) {
            case ICMP_DEST_UR:
                for (j = 0; j < 256; j++) {
                    if (icmps[(i << 8) | j]) {
                        printf("  ");
                        switch (j) {
                        case ICMP_UR_NET:
                            printf("NETWORK_UNREACHABLE");
                            break;
                        case ICMP_UR_HOST:
                            printf("HOST_UNREACHABLE");
                            break;
                        case ICMP_UR_PROTO:
                            printf("PROTOCOL_UNREACHABLE");
                            break;
                        case ICMP_UR_PORT:
                            printf("PORT_UNREACHABLE");
                            break;
                        case ICMP_UR_FRAG:
                            printf("FRAGMENTATION_NEEDED_WITH_DF");
                            break;
                        case ICMP_UR_SR_FAIL:
                            printf("SOURCE_ROUTE_FAILED");
                            break;
                        case ICMP_UR_NET_UNK:
                            printf("NETWORK_UNKNOWN");
                            break;
                        case ICMP_UR_HOST_UNK:
                            printf("HOST_UNKNOWN");
                            break;
                        case ICMP_UR_SRC_ISOL:
                            printf("SOURCE_ISLOATED");
                            break;
                        case ICMP_UR_NET_PROHIB:
                            printf("NETWORK_ADMINISTRATIVELY_PROHIBITED");
                            break;
                        case ICMP_UR_HOST_PROHIB:
                            printf("HOST_ADMINISTRATIVELY_PROHIBITED");
                            break;
                        case ICMP_UR_NET_TOS:
                            printf("NETWORK_UNREACHABLE_FOR_TOS");
                            break;
                        case ICMP_UR_HOST_TOS:
                            printf("HOST_UNREACHABLE_FOR_TOS");
                            break;
                        case ICMP_UR_PKT_FILT:
                            printf("PACKET_FILTERED");
                            break;
                        case ICMP_PREC_VIOLATION:
                            printf("PRECEDENCE_VIOLATION");
                            break;
                        case ICMP_PREC_CUTOFF:
                            printf("PRECEDENCE_CUTOFF");
                            break;
                        default:
                            printf("%02x", j);
                            break;
                        }
                        printf(" %llu\n", icmps[(i << 8) | j]);
                    }
                }
                break;
            }
        }
    }

    printf("\n");
    for (i = 0; i < 65536; i++) {
        if (packet_summary[i]) {
            printf("PKTSUMM:%04x %llu", i, packet_summary[i]);
            if ((i & SUMMARY_IP_CHKSUM_FAIL))
                printf(" IP_CHKSUM_FAIL");
            if ((i & SUMMARY_IP_FRAG))
                printf(" IP_FRAG");
            if ((i & SUMMARY_MULTICAST))
                printf(" IP_MULTICAST");
            switch (SUMMARY_GET_PROTO(i)) {
            case SUMMARY_PROTO_OTHER:
                printf(" PROTO_OTHER");
                break;
            case SUMMARY_PROTO_UDP:
                printf(" PROTO_UDP");
                break;
            case SUMMARY_PROTO_TCP:
                printf(" PROTO_TCP");
                break;
            case SUMMARY_PROTO_ICMP:
                printf(" PROTO_ICMP");
                break;
            }
            if ((i & SUMMARY_L4_NO_CHKSUM))
                printf(" L4_NO_CHKSUM");
            if ((i & SUMMARY_L4_CHKSUM_FAIL))
                printf(" L4_CHKSUM_FAIL");
            if ((i & SUMMARY_IP_ANY_OPTIONS))
                printf(" IP_OPTIONS");
            if ((i & SUMMARY_L4_ANY_OPTIONS))
                printf(" L4_OPTIONS");
            if ((i & SUMMARY_UDP_LEN_MISMATCH))
                printf(" UDP_LEN_MISMATCH");

            printf("\n");
        }
    }

    if (ipsizes_file) 
        write_cdf(ipsizes_file, ipsizes, 65536);
    if (pktsizes_file) 
        write_cdf(pktsizes_file, pktsizes, 65536);
    if (ttls_file) 
        write_cdf(ttls_file, ttls, 256);
    if (msss_file)
        write_cdf(msss_file, msss, 65536);
    if (mtus_file)
        write_cdf(mtus_file, mtus, 65536);
    if (chksum_iphdr_file)
        write_cdf(chksum_iphdr_file, chksum_iphdr, 65536);
    if (chksum_tcp_file)
        write_cdf(chksum_tcp_file, chksum_tcp, 65536);
    if (chksum_udp_file)
        write_cdf(chksum_udp_file, chksum_udp, 65536);
           
    return 0;

}


/* End of $RCSfile: cksumck.c,v $ */
