#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <err.h>

#include <unistd.h>
#include <sys/types.h>

#include <netinet/in.h>
#include <net/if.h>

#include <errno.h>

#include <sys/ioctl.h>
#include <net/bpf.h>

#include <net/ethernet.h>


int open_dev(void);
int check_dlt(int fd);
int set_options(int fd, char *iface);
int installFilter(int fd ,unsigned char Protocol, unsigned short Port);
void read_packets(int fd);


    int
main(int argc, char *argv[])
{
    int fd = 0;
    char *iface = NULL;


    iface = strdup(argc < 2 ? "en0" : argv[1]);
    if (iface == NULL)
        err(EXIT_FAILURE, "strdup");

    fd = open_dev();
    if (fd < 0)
        err(EXIT_FAILURE, "open_dev");

    if (set_options(fd, iface) < 0)
        err(EXIT_FAILURE, "set_options");

    if (check_dlt(fd) < 0)
        err(EXIT_FAILURE, "check_dlt");

    if (installFilter(fd, IPPROTO_TCP, 80) < 0)
        err(EXIT_FAILURE, "installFilter");

    read_packets(fd);

    err(EXIT_FAILURE, "read_packets");
}


    int
open_dev()
{
    int fd = -1;
    char dev[32];
    int i = 0;


    /* Open the bpf device */
    for (i = 0; i < 255; i++) {
        (void)snprintf(dev, sizeof(dev), "/dev/bpf%u", i);

        (void)printf("Trying to open: %s\n", dev);

        fd = open(dev, O_RDWR);
        if (fd > -1)
            return fd;

        switch (errno) {
            case EBUSY:
                break;
            default:
                return -1;
        }
    }

    errno = ENOENT;
    return -1;
}

    int
check_dlt(int fd)
{
    u_int32_t dlt = 0;


    /* Ensure we are dumping the datalink we expect */
    if(ioctl(fd, BIOCGDLT, &dlt) < 0)
        return -1;

    (void)fprintf(stdout, "datalink type=%u\n", dlt);

    switch (dlt) {
        case DLT_EN10MB:
            return 0;
        default:
            (void)fprintf(stderr, "Unsupported datalink type:%u", dlt);
            errno = EINVAL;
            return -1;
    }
}

    int
set_options(int fd, char *iface)
{
    struct ifreq ifr;
    u_int32_t enable = 1;


    /* Associate the bpf device with an interface */
    (void)strlcpy(ifr.ifr_name, iface, sizeof(ifr.ifr_name)-1);

    if(ioctl(fd, BIOCSETIF, &ifr) < 0)
        return -1;

    /* Set header complete mode */
   // if(ioctl(fd, BIOCSHDRCMPLT, &enable) < 0)
    //    return -1;

    /* Monitor packets sent from our interface */
    if(ioctl(fd, BIOCSSEESENT, &enable) < 0)
        return -1;

    /* Return immediately when a packet received */
    if(ioctl(fd, BIOCIMMEDIATE, &enable) < 0)
        return -1;

    return 0;
}

int installFilter(int   fd, 
         unsigned char  Protocol, 
	     unsigned short Port)
{
    struct bpf_program bpfProgram = {0};

    /* dump IPv4 packets matching Protocol and Port only */
    /* @param: fd - Open /dev/bpfX handle.               */
    
    /* As an exercise, you might want to extend this to IPv6, as well */
     
    const int IPHeaderOffset = 14;
    
    /* Assuming Ethernet II frames, We have: 
     *
     *    Ethernet header = 14 = 6 (dest) + 6 (src) + 2 (ethertype)
     *    Ethertype is 8-bits (BFP_P) at offset 12
     *    IP header len is at offset 14 of frame (lower 4 bytes). We use BPF_MSH to isolate field and multiply by 4
     *    IP fragment data is 16-bits (BFP_H) at offset  6 of IP header, 20 from frame
     *    IP protocol field is 8-bts (BFP_B) at offset 9 of IP header, 23 from frame 
     *    TCP source port is right after IP header (HLEN*4 bytes from IP header)
     *    TCP destination port is two bytes later)
     */

    struct bpf_insn insns[] = {
     BPF_STMT(BPF_LD  + BPF_H   + BPF_ABS, 6+6),                 // Load ethertype 16-bits (12 (6+6) bytes from beginning)
     BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K  , ETHERTYPE_IP, 0, 10), // Compare to requested Ethertype or jump(10) to reject
     BPF_STMT(BPF_LD  + BPF_B   + BPF_ABS, 23),                  // Load protocol (=14 + 9 (bytes from IP)) bytes from beginning 
     BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K  , Protocol, 0, 8),      // Compare current to requested Protocol or jump(8) to reject 
     BPF_STMT(BPF_LD  + BPF_H   + BPF_ABS, 20),                  // Move 20 (=14 + 6)  We are now on fragment offset field 
     BPF_JUMP(BPF_JMP + BPF_JSET+ BPF_K  , 0x1fff, 6, 0),        // Bitwise-AND with 0x1FF and jump(6) to reject if true
     BPF_STMT(BPF_LDX + BPF_B   + BPF_MSH, IPHeaderOffset),      // Load IP Header Len (from offset 14) x 4 , into Index register
     BPF_STMT(BPF_LD  + BPF_H   + BPF_IND, IPHeaderOffset),      // Skip past IP header (off: 14 + hlen, in BPF_IND), load TCP src
     BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K  , Port, 2, 0),          // Compare src port to requested Port and jump to "port" if true
     BPF_STMT(BPF_LD  + BPF_H   + BPF_IND, IPHeaderOffset+2),    // Skip two more bytes (off: 14 + hlen + 2), to load TCP dest
/* port */
     BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K  , Port, 0, 1),          // If port matches, ok. Else reject
/* ok: */
     BPF_STMT(BPF_RET + BPF_K, (u_int)-1),                       // Return -1 (packet accepted)
/* reject: */
     BPF_STMT(BPF_RET + BPF_K, 0)                                // Return 0  (packet rejected)
    };

    // Load filter into program 
    bpfProgram.bf_len = sizeof(insns) / sizeof(struct bpf_insn);
    bpfProgram.bf_insns = &insns[0];

    return(ioctl(fd, BIOCSETF, &bpfProgram));
}

    void
read_packets(int fd)
{
    char *buf = NULL;
    char *p = NULL;
    size_t blen = 0;
    ssize_t n = 0;
    struct bpf_hdr *bh = NULL;
    struct ether_header *eh = NULL;


    if(ioctl(fd, BIOCGBLEN, &blen) < 0)
        return;

    if ( (buf = malloc(blen)) == NULL)
        return;

    (void)printf("reading packets ...\n");

    for ( ; ; ) {
        (void)memset(buf, '\0', blen);

        n = read(fd, buf, blen);

        if (n <= 0)
            return;

        p = buf;
        while (p < buf + n) {
            bh = (struct bpf_hdr *)p;

            /* Start of ethernet frame */
            eh = (struct ether_header *)(p + bh->bh_hdrlen);

            (void)printf("%02x:%02x:%02x:%02x:%02x:%02x -> "
                    "%02x:%02x:%02x:%02x:%02x:%02x "
                    "[type=%u]\n",
                    eh->ether_shost[0], eh->ether_shost[1], eh->ether_shost[2],
                    eh->ether_shost[3], eh->ether_shost[4], eh->ether_shost[5],

                    eh->ether_dhost[0], eh->ether_dhost[1], eh->ether_dhost[2],
                    eh->ether_dhost[3], eh->ether_dhost[4], eh->ether_dhost[5],

                    eh->ether_type);

            p += BPF_WORDALIGN(bh->bh_hdrlen + bh->bh_caplen);
        }
    }
}