/*
 * Identify unique source hosts in PCAP traces.
 *
 * WARNING: this was a quick lash-up - netblocks are hardcoded.
 *
 * James Bulpin, October 2004.
 *
 * (C) University of Cambridge 2004.
 *
 * $Id: uniqhost.c,v 1.1 2004/11/09 16:15:47 jrb44 Exp $
 *
 * $Log: uniqhost.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"

//#define DEBUG

#define TRACE_B

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

int debug = 0;
static int current_link_type = -1;

unsigned long long cnt_hosts = 0;

typedef struct net_desc_t_struct {
    addr_t network;
    addr_t mask;
} net_desc_t;

#define SLASH00 MAKEADDR_NE(0,0,0,0)
#define SLASH01 MAKEADDR_NE(128,0,0,0)
#define SLASH02 MAKEADDR_NE(192,0,0,0)
#define SLASH03 MAKEADDR_NE(224,0,0,0)
#define SLASH04 MAKEADDR_NE(240,0,0,0)
#define SLASH05 MAKEADDR_NE(248,0,0,0)
#define SLASH06 MAKEADDR_NE(252,0,0,0)
#define SLASH07 MAKEADDR_NE(254,0,0,0)
#define SLASH08 MAKEADDR_NE(255,0,0,0)
#define SLASH09 MAKEADDR_NE(255,128,0,0)
#define SLASH10 MAKEADDR_NE(255,192,0,0)
#define SLASH11 MAKEADDR_NE(255,224,0,0)
#define SLASH12 MAKEADDR_NE(255,240,0,0)
#define SLASH13 MAKEADDR_NE(255,248,0,0)
#define SLASH14 MAKEADDR_NE(255,252,0,0)
#define SLASH15 MAKEADDR_NE(255,254,0,0)
#define SLASH16 MAKEADDR_NE(255,255,0,0)
#define SLASH17 MAKEADDR_NE(255,255,128,0)
#define SLASH18 MAKEADDR_NE(255,255,192,0)
#define SLASH19 MAKEADDR_NE(255,255,224,0)
#define SLASH20 MAKEADDR_NE(255,255,240,0)
#define SLASH21 MAKEADDR_NE(255,255,248,0)
#define SLASH22 MAKEADDR_NE(255,255,252,0)
#define SLASH23 MAKEADDR_NE(255,255,254,0)
#define SLASH24 MAKEADDR_NE(255,255,255,0)
#define SLASH25 MAKEADDR_NE(255,255,255,128)
#define SLASH26 MAKEADDR_NE(255,255,255,192)
#define SLASH27 MAKEADDR_NE(255,255,255,224)
#define SLASH28 MAKEADDR_NE(255,255,255,240)
#define SLASH29 MAKEADDR_NE(255,255,255,248)
#define SLASH30 MAKEADDR_NE(255,255,255,252)
#define SLASH31 MAKEADDR_NE(255,255,255,254)
#define SLASH32 MAKEADDR_NE(255,255,255,255)

#include "localonly/netblocks.h"


/* Netblock definitions look like this. Note the use of commas.
 *   
 * net_desc_t localnets[] = {
 * {MAKEADDR_NE(10,232,0,0), SLASH17},
 * {MAKEADDR_NE(192,168,99,96), SLASH27},
 * {0, 0}
 * };
 *
 */


typedef struct hashent_t_struct {
    struct hashent_t_struct *next;
    addr_t addr_ne;
} hashent_t;

#define HASHADDR(_ane) ((unsigned int)((_ane)&0xff)^\
                        (unsigned int)(((_ane)>>8)&0xff)^\
                        (unsigned int)(((_ane)>>16)&0xff)^\
                        (unsigned int)(((_ane)>>24)&0xff))

hashent_t *htable[256];

int new_host(addr_t addr_ne)
{
    int found = 0;
    int i = 0, hv;
    hashent_t *h;
    
    while(1) {
        if (localnets[i].network == 0)
            break;
        if ((addr_ne & localnets[i].mask) == localnets[i].network) {
            found = 1;
            break;
        }
        i++;
    }

    if (!found)
        return 0;

    found = 0;
    
    hv = HASHADDR(addr_ne);
    for (h = htable[hv]; h; h = h->next) {
        if (h->addr_ne == addr_ne)
            return 0;
    }

    printf("New host: %u.%u.%u.%u\n", ADDR_PRINTF_LIST(addr_ne));
    
    h = (hashent_t *)malloc(sizeof(hashent_t));
    if (!h) {
        fprintf(stderr, "BARF!\n");
        exit(1);
    }
    h->addr_ne = addr_ne;
    h->next = htable[hv];
    htable[hv] = h;

    return 1;
}

void uniqhost_callback(u_char                   *user,
                       const struct pcap_pkthdr *h,
                       const u_char             *sp)
{
    ipheader_t     *ip;
    unsigned int    len;
    unsigned int    ll_hdr_len = 0;
    
    /* Link layer header
     */
    switch (current_link_type) {
    case DLT_EN10MB:
        if (h->caplen < sizeof(ethernet_t)) {
            goto leave_callback;
        }
        else {
            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) {
                        goto leave_callback;
                    }
                }
                break;
            default:
                goto leave_callback;
            }
        }
        break;
    case DLT_RAW:
        ll_hdr_len = 0;
        break;
    default:
        fprintf(stderr, "ERROR: unknown link type %u.\n", current_link_type);
        exit(1);
        break;
    }


    ip = EXTRACT_IPHDR(sp);
    len = (unsigned int)IP_HDR_LEN(ip);
    if ((len + LL_HDR_LEN) > h->caplen) {
        goto leave_callback;
    }

    if (new_host(ip->src))
        cnt_hosts++;
    //if (new_host(ip->dst)) ---- only counting source to avoid port scanning
    //  cnt_hosts++;         ---- influencing the count.

    leave_callback:
    return;
}

int main (int argc, char **argv)
{
    pcap_t *pcap;
    char error_buffer[PCAP_ERRBUF_SIZE];
    char *filename = NULL;
    struct bpf_program filter;
    char *filter_string = NULL;
    int rc, c, ind;

    /* Read command line options.
     */
    while((c = getopt(argc, argv, "hd")))
    {
        if (c == -1)
            break;

        switch (c)
        {
        case 'h':
                fprintf(stdout, "Usage: %s <list of PCAP capture files>\n",
                    argv[0]);
            exit(0);
        case 'd':
            debug = 1;
            break;
        default:
            fprintf(stderr, "Bogus option(s).\n");
            exit(1);
        }
    }

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

    memset((void *)htable, 0, sizeof(hashent_t *) * 256);

    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);
        }

        /* 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, uniqhost_callback, NULL);
        if (rc < 0) {
            fprintf(stderr, "Error processing packets.\n");
        }
        
        /* Close PCAP.
         */
        pcap_close(pcap);
    }

    printf("%llu\n", cnt_hosts);

    return 0;

}

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