/*
 * Written by Pejman Moghadam / 1404-10-30
 * Public domain.
 *
 */

/* gcc analyze.c -lpcap  -o analyze */

#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>

// DNS header as specified by RFC 1035, November 1987.
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));

struct {
    int datalink_type;
    int datalink_header_len;
} pktinfo;

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(const u_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");
}
struct sll_header {
    uint16_t sll_pkttype;    /* packet type */
    uint16_t sll_hatype; /* link-layer address type */
    uint16_t sll_halen;  /* link-layer address length */
    char     sll_addr[8];  /* link-layer address */
    uint16_t sll_protocol;   /* protocol */
};

void show_linuxsll_header(const u_char *packet, int len)
{
    /* man 3 ether_aton */
    /* /usr/include/linux/if_ether.h */

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

    printf("SLL header info:\n");
    printf("\tPacket type  : %d\n", ntohs(h->sll_pkttype));
    printf("\tlink-layer address type : %d\n", ntohs(h->sll_hatype));
    printf("\tlink-layer address length : %d\n", ntohs(h->sll_halen));
    printf("\tprotocol : %d\n", ntohs(h->sll_protocol));
    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 */

    int length = sizeof(struct ip);
    int offset = pktinfo.datalink_header_len;
    struct ip *h;
    h = (struct ip *)(packet + offset);

    printf("IP Header (Offset:%d, Length:%d)\n", offset, length);

    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_udp_header(const u_char *packet, int len)
{
    // UDP header as specified by RFC 768, August 1980.
    // /usr/include/netinet/udp.h

    int length = sizeof(struct udphdr);
    int offset = pktinfo.datalink_header_len + sizeof(struct ip);
    struct udphdr *h;
    h = (struct udphdr *)(packet + offset);

    printf("UDP Header (Offset:%d, Length:%d)\n", offset, length);

    printf("\tSource Port      : 0x%x (%u)\n",
        ntohs(h->source), ntohs(h->source));

    printf("\tDestination Port : 0x%x (%u)\n",
        ntohs(h->dest), ntohs(h->dest));

    printf("\tLength           : %u\n", ntohs(h->len));
    printf("\tChecksum         : 0x%x\n", ntohs(h->check));

    printf("\n");
}



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

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

    printf("\n");
}


void show_dns_header(const u_char *packet, int len)
{
    int length = sizeof(struct dnshdr);

    int offset = pktinfo.datalink_header_len +
                 sizeof(struct ip) +
                 sizeof(struct udphdr);

    struct dnshdr *h;
    h = (struct dnshdr *)(packet + offset);

    printf("DNS Header (Offset:%d, Length:%d)\n", offset, length);

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

    printf("\n");
}

void show_datalink_type_name(pcap_t *p)
{
    printf("datalink type: %d\n", pktinfo.datalink_type);

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

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

int get_datalink_header_len(pcap_t *p)
{
    switch(pktinfo.datalink_type) {
        case DLT_EN10MB:
            // Ethernet
            pktinfo.datalink_header_len = 14;
            break;
        case DLT_LINUX_SLL:
            // Linux cooked-mode capture sockaddr_ll
            pktinfo.datalink_header_len = 16;
            break;
        case DLT_LINUX_SLL2:
            // Linux cooked-mode capture sockaddr_ll Ver2
            pktinfo.datalink_header_len = 20;
            break;
        case DLT_NULL:
            // Loopback
            pktinfo.datalink_header_len = 4;
            break;
        default:
            printf("Unknown header size for DLT %d\n",
                pktinfo.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;
    }
}

void show_datalink_header(const u_char *packet, int len)
{
    // Ethernet
    if(pktinfo.datalink_type == DLT_EN10MB)
        show_ethernet_header(packet, len);
    // Linux SLL
    if(pktinfo.datalink_type == DLT_LINUX_SLL)
        show_linuxsll_header(packet, len);
}

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\n", ++packet_count, h->len);
    show_packet(bytes, h->len);
    show_datalink_header(bytes, h->len);
    show_ip_header(bytes, h->len);
    show_udp_header(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();
    pktinfo.datalink_type = pcap_datalink(p);
    show_datalink_type_name(p);
    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;
}