#include <stdio.h>
#include <stdlib.h>
#include <linux/bpf.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <linux/perf_event.h>
#include <argp.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <net/if.h> /* if_nametoindex */
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <arpa/inet.h>

#include "common.h"

static struct arguments arguments;
static int running = 1;

struct arguments {
    char *filename;
    int to_output;
};

/*
 * Functions for arguments
 * https://www.gnu.org/software/libc/manual/html_node/Argp-Example-3.html
 */

static char doc[] = "DNS Trace usage:";
static char args_doc[] = "ARG1 ARG2";

static error_t parse_opts(int key, char *arg, struct argp_state *state){
    struct arguments *arguments = state->input;
    switch(key){
        case 'f':
            arguments->filename = arg;
            arguments->to_output = 0;
            break;
        case 'o':
            arguments->to_output = 1;
            break;
        case ARGP_KEY_ARG:
            break;
        case ARGP_KEY_END:
            break;
        default:
            return ARGP_ERR_UNKNOWN;

    }
    return 0;
}

struct arguments parse_args(int argc, char *argv[]){
    static const struct argp_option opts[] = {
        {"filename", 'f', "FILENAME", 0, "Save result to logs"},
        {"to-output", 'o', NULL, 0, "Print to output"},
        {NULL, 'h', NULL, OPTION_HIDDEN, "help"},
        {},
    };
    struct arguments arguments;
    arguments.filename = NULL;
    arguments.to_output = 1;
    static struct argp argp = {opts, parse_opts, args_doc, doc};

    argp_parse(&argp, argc, argv, 0, 0, &arguments);
    return arguments;
}

/* End functions arguments */

static void signalHandler(int signum){
    running = 0;
}

static int open_raw_sock(const char *name)
{
	struct sockaddr_ll sll;
	int sock;

	sock = socket(PF_PACKET, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, htons(ETH_P_ALL));
	if (sock < 0) {
		printf("cannot create raw socket\n");
		return -1;
	}

	memset(&sll, 0, sizeof(sll));
	sll.sll_family = AF_PACKET;
	sll.sll_ifindex = if_nametoindex(name);
	sll.sll_protocol = htons(ETH_P_ALL);
	if (bind(sock, (struct sockaddr *)&sll, sizeof(sll)) < 0) {
		//printf("bind to %s: %s\n", name, strerror(errno));
        printf("Failed to bind the interface %s\n", name);
		close(sock);
		return -1;
	}

	return sock;
}

static char *mapReqType(const int req){
    char *tmp = malloc(8);
    if (tmp == NULL)
        return NULL;

    switch(req){
        case 0x00:
            strncpy(tmp, "Query", 6);
            break;
        case 0x01:
            strncpy(tmp, "Answer", 7);
            break;
        default:
            strncpy(tmp, "Unknown", 8);
    };
    return tmp;
}
static char *mapClass(const int class){
    char *tmp = malloc(8);
    if (tmp == NULL)
        return NULL;

    memset(tmp, 0, 8);
    switch(class){
        case 1:
           strncpy(tmp, "IN", 3);
           break;
        case 2:
           strncpy(tmp, "CS", 3);
           break;
        case 3:
           strncpy(tmp, "CH", 3);
           break;
        case 4:
           strncpy(tmp, "HS", 3);
           break;
        default:
           strncpy(tmp, "Unknown", 8);
           break;
    }
    return tmp;
}
static char *mapType(const int type){
    char *tmp = malloc(8);
    if (tmp == NULL)
        return NULL;

    /*
     * type DNS defined in RFC:
     * https://datatracker.ietf.org/doc/html/rfc1035#section-3.2.2
     * https://datatracker.ietf.org/doc/html/rfc3596
     */
    switch(type){
        case 1:
           strncpy(tmp, "A", 2);
           break;
        case 2:
           strncpy(tmp, "NS", 3);
           break;
        case 3:
           strncpy(tmp, "MD", 3);
           break;
        case 4:
           strncpy(tmp, "MF", 3);
           break;
        case 5:
           strncpy(tmp, "CNAME", 6);
           break;
        case 6:
           strncpy(tmp, "SOA", 4);
           break;
        case 7:
           strncpy(tmp, "MB", 3);
           break;
        case 8:
           strncpy(tmp, "MG", 3);
           break;
        case 9:
           strncpy(tmp, "MR", 3);
           break;
        case 10:
           strncpy(tmp, "NULL", 5);
           break;
        case 11:
           strncpy(tmp, "WKS", 4);
           break;
        case 12:
           strncpy(tmp, "PTR", 4);
           break;
        case 13:
           strncpy(tmp, "HINFO", 6);
           break;
        case 14:
           strncpy(tmp, "MINFO", 6);
           break;
        case 15:
           strncpy(tmp, "MX", 3);
           break;
        case 16:
           strncpy(tmp, "TXT", 4);
           break;
        case 28:
            strncpy(tmp, "AAAA", 5);
            break;
        default:
           strncpy(tmp, "Unknown", 8);
           break;
    }
    return tmp;
}

static void print_query(struct event *s_event){
    char *req_type, *class, *type;
    printf("%s:%-10d", inet_ntoa(*(struct in_addr*)&s_event->client), s_event->dport);
    printf("%-5x", s_event->tid);

    req_type = mapReqType(s_event->req_type);
    printf("%-10s", req_type);
    free(req_type);

    printf("%-30s", s_event->qname);

    class = mapClass(s_event->class);
    printf("%-5s", class);
    free(class);

    type = mapType(s_event->type);
    printf("%-5s", type);
    free(type);

}
int handle_event(void *ctx, void *data, size_t data_sz){
    struct event *s_event = (struct event*)data;
    if (s_event->req_type == REQ_QUERY){
        print_query(s_event);
    }
    if (s_event->req_type == REQ_ANSWER){
        int pos = 0;
        //for(int i = 0; i < 32; i++)
        //    printf("%d ", s_event->buf[i]);
        //printf("\n");
        for (int i = 0; i < s_event->numAns; i++){
            print_query(s_event);
            uint16_t type2 = (s_event->buf[pos++]) + (s_event->buf[pos++] << 8);
            uint16_t class2 = (s_event->buf[pos++]) + (s_event->buf[pos++] << 8);
            uint32_t ttl2 = (s_event->buf[pos++]) + (s_event->buf[pos++] << 8) + (s_event->buf[pos++] << 16) + (s_event->buf[pos++] << 24);
            uint16_t size2 = (s_event->buf[pos++]) + (s_event->buf[pos++] << 8);
            type2 = ntohs(type2);
            class2 = ntohs(class2);
	    ttl2 = ntohl(ttl2);
	    size2 = ntohs(size2);
            if (type2 == 1) {// -> A
                uint32_t ip = s_event->buf[pos++] + (s_event->buf[pos++] << 8) + (s_event->buf[pos++] << 16) + (s_event->buf[pos++] << 24);
                printf("%s (%d)%5d", inet_ntoa(*(struct in_addr*)&ip), type2, ttl2);
            }
            if (type2 == 28){ // -> AAAA

            }
            printf("\n");
            printf("%d\n", pos);
        }
    }

    printf("\n");
    
    return 0;
}
int main(int argc, char *argv[]){
    const char *fileObj = "src/dns-trace.ebpf.o";
    struct bpf_object *obj;
    struct bpf_program *programSkb;
    struct ring_buffer *rb;
    int err;
    int fd_map_data;

    arguments = parse_args(argc, argv); // Parsing arguments

    signal(SIGINT, signalHandler);

    /* Open and load our eBPF object */
    obj = bpf_object__open_file(fileObj, NULL);
    if (!obj){
        printf("Failed to open the file\n");
        return -1;
    }

    err = bpf_object__load(obj);
    if (err){
        printf("Failed to load object\n");
        return -1;
    }

    /* Retrieving fd of maps */
    fd_map_data = bpf_object__find_map_fd_by_name(obj, "m_data");
    if (!fd_map_data){
        printf("Failed to find the fd map data\n");
        bpf_object__close(obj);
        return -1;
    }

    /* Retrieving our programs */
    programSkb = bpf_object__find_program_by_name(obj, "detect_dns");

    if (!programSkb){
        printf("Failed to find program\n");
        bpf_object__close(obj);
        return -1;
    }

    bpf_program__attach(programSkb);
    // int sock = open_raw_sock("wlp0s20f3");
    //int sock = open_raw_sock("enx98e743c667fc");
    int sock = open_raw_sock("lo");
    printf("Socket: %d\n", sock);
    int prog_fd = bpf_program__fd(programSkb);
    printf("Program fd: %d\n", prog_fd);
    setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd, sizeof(int));

    /* Start the ringbuffer */
    rb = ring_buffer__new(fd_map_data, handle_event, NULL, NULL);
    if (!rb){
        printf("Failed to create the ringbuf\n");
        bpf_object__close(obj);
        return -1;
    }

    while(running){
        err = ring_buffer__poll(rb, 100 /* timeout, ms */);
        if (err == -EINTR){
            printf("Failed to get the ringbuf\n");
            running = 0;
            break;
         }
    }

    ring_buffer__free(rb);
    bpf_object__close(obj);

    return 0;
}
