/*
 * Written by Pejman Moghadam / 1401-02-30
 * Public domain.
 *
 */

/* gcc src.c -lpcap */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <pcap/pcap.h>
#include <netinet/ether.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <stdint.h>

// The size of datalink header is constant in pcap file
// so the variable is global and will be user for all packets
int datalink_header_len;

struct byte {
        unsigned int a:1;
        unsigned int b:1;
        unsigned int c:1;
        unsigned int d:1;
        unsigned int e:1;
        unsigned int f:1;
        unsigned int g:1;
        unsigned int h:1;
};

char *ether_ntoa_padded(const struct ether_addr *addr, char *buf)
{
        sprintf (buf, "%02x:%02x:%02x:%02x:%02x:%02x",
                addr->ether_addr_octet[0], addr->ether_addr_octet[1],
                addr->ether_addr_octet[2], addr->ether_addr_octet[3],
                addr->ether_addr_octet[4], addr->ether_addr_octet[5]);
        return buf;
}

void show_ethernet_header(char *packet, int len)
{
        /* man 3 ether_aton */
        /* /usr/include/linux/if_ether.h */

        struct ethhdr *h;
        h = (struct ethhdr *)packet;

        struct ether_addr *dst;
        dst = (struct ether_addr *)(h->h_dest);

        struct ether_addr *src;
        src = (struct ether_addr *)(h->h_source);

        char addr_str[INET6_ADDRSTRLEN];
        printf("Ethernet header info:\n");
        printf("\tdst: %s (%s)\n", ether_ntoa(dst),
                ether_ntoa_padded(dst, addr_str));
        printf("\tsrc: %s (%s)\n", ether_ntoa(src),
                ether_ntoa_padded(src, addr_str));
        printf("\ttype: 0x%04X\n", ntohs(h->h_proto));
        printf("\n");
}

void show_endianness()
{
        #if __BYTE_ORDER == __LITTLE_ENDIAN
                printf("Endianness : Little Endian\n");
        #elif __BYTE_ORDER == __BIG_ENDIAN
                printf("Endianness : Big Endian\n");
        #else
                printf("Endianness : Unknown\n");
        #endif
}

void show_ip_header(const u_char *packet, int len)
{
        /* /usr/include/netinet/ip.h */

        struct ip *h;
        //h = (struct ip *)(packet + sizeof(struct ethhdr));
        h = (struct ip *)(packet + datalink_header_len);

        printf("\nIP Header\n");

        printf("\tver   : %X\n",h->ip_v);
        printf("\tihl   : %X\n", h->ip_hl);
        printf("\ttos   : 0x%X\n", h->ip_tos);
        printf("\tlen   : %u\n", ntohs(h->ip_len));
        printf("\tid    : %u\n", ntohs(h->ip_id));

        printf("\tfrag  : %X ", ntohs(h->ip_off));
        if(ntohs(h->ip_off) == 0x4000)
                printf("(DF)");
        if(ntohs(h->ip_off) == 0x2000)
                printf("(MF)");
        printf("\n");

        printf("\tttl   : %u\n", h->ip_ttl);
        printf("\tproto : %u\n", h->ip_p);
        printf("\tsum   : 0x%X\n", ntohs(h->ip_sum));

        char ipv4str[INET_ADDRSTRLEN];

        inet_ntop(AF_INET, &h->ip_src, ipv4str, INET_ADDRSTRLEN);
        printf("\tsaddr : %X (%s)\n", ntohl(*(uint32_t *)(&h->ip_src)),
                ipv4str);

        inet_ntop(AF_INET, &h->ip_dst, ipv4str, INET_ADDRSTRLEN);
        printf("\tdaddr : %X (%s)\n", ntohl(*(uint32_t *)(&h->ip_dst)),
                ipv4str);

        printf("\n");

}

void show_ports(const u_char *packet, int len)
{
        /* Source and Destination ports are in same place */
        /* /usr/include/netinet/tcp.h */
        /* /usr/include/netinet/udp.h */

        struct udphdr *h;
        h = (struct udphdr *)(packet + datalink_header_len +
                sizeof(struct ip));

        printf("TCP/UDP Ports:\n");

        printf("\tsrc port : 0x%X (%u)\n",
                ntohs(h->source), ntohs(h->source));
        printf("\tdst port : 0x%X (%u)\n",
                ntohs(h->dest), ntohs(h->dest));

        printf("\n");

}

// DNS Header Structure (RFC 1035)
struct dnshdr {
    uint16_t id;        // Identification number

    // Flags fields (bit fields)
#if __BYTE_ORDER == __LITTLE_ENDIAN
    uint8_t  rd :1;     // Recursion desired
    uint8_t  tc :1;     // Truncated
    uint8_t  aa :1;     // Authoritative answer
    uint8_t  opcode :4; // Operation code
    uint8_t  qr :1;     // Query/response flag

    uint8_t  rcode :4;  // Response code
    uint8_t  z :3;      // Reserved (zero)
    uint8_t  ra :1;     // Recursion available
#else
    uint8_t  qr :1;     // Query/response flag
    uint8_t  opcode :4; // Operation code
    uint8_t  aa :1;     // Authoritative answer
    uint8_t  tc :1;     // Truncated
    uint8_t  rd :1;     // Recursion desired

    uint8_t  ra :1;     // Recursion available
    uint8_t  z :3;      // Reserved (zero)
    uint8_t  rcode :4;  // Response code
#endif

    uint16_t qdcount;   // Question count
    uint16_t ancount;   // Answer count
    uint16_t nscount;   // Authority count
    uint16_t arcount;   // Additional count
} __attribute__((packed));

void show_datalink_type_name(pcap_t *p)
{
    int datalink_type = pcap_datalink(p);

    printf("datalink type: %d\n", datalink_type);

    printf("datalink name: %s\n",
            pcap_datalink_val_to_name(datalink_type));

    printf("datalink description: %s\n",
            pcap_datalink_val_to_description_or_dlt(datalink_type));

}

int get_datalink_header_len(pcap_t *p)
{
    int datalink_type = pcap_datalink(p);
    int datalink_header_len;

    switch(datalink_type) {
        case DLT_EN10MB:
            // Ethernet
            datalink_header_len = 14;
            break;
        case DLT_LINUX_SLL:
            // Linux cooked-mode capture sockaddr_ll
            datalink_header_len = 16;
            break;
        case DLT_LINUX_SLL2:
            // Linux cooked-mode capture sockaddr_ll Ver2
            datalink_header_len = 20;
            break;
        case DLT_NULL:
            // Loopback
            datalink_header_len = 4;
            break;
        default:
            printf("Unknown header size for DLT %d\n", datalink_type);
            printf("Check pcap-linktype(7) man page"
                   "and https://www.tcpdump.org/linktypes.html"
                   "then update get_datalink_header_len() fuinction\n");
            exit(EXIT_FAILURE);
            break;
    }
    return datalink_header_len;
}

void show_packet(const u_char *packet, int len)
{
    int j = 0;
    char s[17] = {0};

    printf("\n");
    for(int i=0; i<len; i++) {
        if(j==0)
            printf("0x%04x:  ", i);

        printf("%02x", *(packet+i));
        if(*(packet+i) >= 32 && *(packet+i) <=127)
            s[j] = *(packet+i);
        else
            s[j] = '.';

        if((j%2)!=0)
            printf(" ");

        if(j==15) {
            s[16] = '\0';
            printf(" %s\n", s);
            j = 0;
        }
        else
            j++;
    }
    s[j] = '\0';
    int space = 39-(j*2)+(j/2)-1;
    printf(" %*s\n", space+2, s);
}


void show_dns_header(const u_char *packet, int len)
{
    struct dnshdr *h;
    h = (struct dnshdr *)(packet + datalink_header_len +
            sizeof(struct ip) + sizeof(struct udphdr));

    printf("\nDNS Header\n");

    printf("\tID                    : ");
    printf("0x%x (%d)\n", htons(h->id), htons(h->id));

    printf("\tMessage type          : ");
    if(h->qr == 0) puts("Query");
    if(h->qr == 1) puts("Response");

    printf("\tQuery type            : ");
    if(h->opcode == 0) puts("Standard query (QUERY)");
    if(h->opcode == 1) puts("Inverse query (IQUERY)");
    if(h->opcode == 2) puts("Server status request (STATUS)");

    printf("\tAuthorization         : ");
    if(h->qr == 1) {
        // Response
        if(h->aa == 1)
            puts("Authoritative answer");
        if(h->aa == 0)
            puts("Non-authoritative answer");
    } else {
        // Query
        if(h->aa == 1)
            puts("ANOMALY (AA bit set in query message)");
        if(h->aa == 0)
            puts("Not set");
    }

    printf("\tTrunCation            : ");
    if(h->tc == 1) puts("Truncated");
    if(h->tc == 0) puts("Not truncated");

    printf("\tRecursion Desired     : ");
    if(h->rd == 1) puts("Yes");
    if(h->rd == 0) puts("No");

    printf("\tRecursion Available   : ");
    if(h->qr == 1) {
        // Response
        if(h->ra == 1)
            puts("Yes");
        if(h->ra == 0)
            puts("No");
    } else {
        // Query
        if(h->aa == 1)
            puts("ANOMALY (RA bit set in query message)");
        if(h->aa == 0)
            puts("Not set");
    }

    printf("\tReserved              : ");
    if(h->z == 0) puts("Not set");
    if(h->z != 0) puts("ANOMALY (Z bit not zero)");

    printf("\tResponse code         : ");
    if(h->qr == 1) {
        // Response
        if(h->rcode == 0) puts("No error");
        if(h->rcode == 1) puts("Format error");
        if(h->rcode == 2) puts("Server failure");
        if(h->rcode == 3) {
            if(h->aa == 1) // Authoritative answer
                puts("Name error");
            if(h->aa == 0)
                puts("ANOMALY (RCODE bit set in Non-authoritative answer)");
        }
        if(h->rcode == 4) puts("Not Implemented");
        if(h->rcode == 5) puts("Refused");
        if((h->rcode >= 6) && (h->rcode <= 15)) puts("Reserved");
    } else {
        // Query
        if(h->rcode == 0)
            puts("Not set");
        if(h->rcode != 0)
            puts("ANOMALY (RCODE bit set in query message)");
    }

    printf("\tQuestion entries      : %d\n", ntohs(h->qdcount));
    printf("\tAnswer RRs            : %d\n", ntohs(h->ancount));
    printf("\tAuthority RRs         : %d\n", ntohs(h->nscount));
    printf("\tAdditional RRs        : %d\n", ntohs(h->arcount));
}

void packet_handler(u_char *user, const struct pcap_pkthdr *h,
        const u_char *bytes)
{
    static int packet_count = 0;
    printf("--------------------------------------"
           "--------------------------------------\n");
    printf("# Packet: %d, Size: %d\n", ++packet_count, h->len);
    show_packet(bytes, h->len);
    //show_ethernet_header((char *)bytes, h->len);
    show_ip_header(bytes, h->len);
    show_ports(bytes, h->len);
    show_dns_header(bytes, h->len);
}

int main(int argc, char *argv[]) {

    if (argc != 2) {
        fprintf(stderr, "\nUsage: %s <pcap_file>\n", argv[0]);
        return 1;
    }

    char errbuf[PCAP_ERRBUF_SIZE];
    pcap_t *p = pcap_open_offline(argv[1], errbuf);
    if (p == NULL) {
        fprintf(stderr, "\npcap_open_offline() failed: %s\n", errbuf);
        exit(EXIT_FAILURE);
    }

    show_endianness();
    show_datalink_type_name(p);
    datalink_header_len = get_datalink_header_len(p);

    if (pcap_loop(p, 0, packet_handler, NULL) < 0) {
        fprintf(stderr, "\npcap_loop() failed: %s\n", pcap_geterr(p));
        exit(EXIT_FAILURE);
    }

    return 0;
}