/*
 * GoodbyeDPI — Passive DPI blocker and Active DPI circumvention utility.
 */

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <winsock2.h>
#include "windivert.h"

#define die() do { printf("Something went wrong!\n" \
    "Make sure you're running this program with administrator privileges\n"); \
    sleep(10); exit(EXIT_FAILURE); } while (0)

#define MAX_FILTERS 4
#define MAX_PACKET_SIZE 1516
#define IPV4_HDR_LEN 20
#define TCP_HDR_LEN 20
#define IPV4_TOTALLEN_OFFSET 2
#define TCP_WINDOWSIZE_OFFSET 14
    
static HANDLE filters[MAX_FILTERS];
static int filter_num = 0;
static const char *http10_redirect_302 = "HTTP/1.0 302 ";
static const char *http11_redirect_302 = "HTTP/1.1 302 ";
static const char *http_host_find = "\r\nHost: ";
static const char *http_host_replace = "\r\nhoSt: ";
static const char *location_http = "\r\nLocation: http://";

static char* dumb_memmem(const char* haystack, int hlen, const char* needle, int nlen) {
    // naive implementation
    if (nlen > hlen) return 0;
    int i;
    for (i=0; i<hlen-nlen+1; i++) {
        if (memcmp(haystack+i,needle,nlen)==0) {
            return (char*)(haystack+i);
        }
    }
    return NULL;
}

static HANDLE init(char *filter, UINT64 flags) {
    filter = WinDivertOpen(filter, WINDIVERT_LAYER_NETWORK, 0, flags);
    if (filter != INVALID_HANDLE_VALUE)
        return filter;
    return NULL;
}

static int deinit(HANDLE handle) {
    if (handle) {
        WinDivertClose(handle);
        return 1;
    }
    return 0;
}

static void deinit_all() {
    for (int i = 0; i < filter_num; i++) {
        deinit(filters[i]);
    }
}

static void sigint_handler(int sig) {
    deinit_all();
    exit(EXIT_SUCCESS);
}

static int is_passivedpi_redirect(const char *pktdata, int pktlen) {
    /* First check if this is HTTP 302 redirect */
    if (memcmp(pktdata, http11_redirect_302, strlen(http11_redirect_302)) == 0 ||
        memcmp(pktdata, http10_redirect_302, strlen(http10_redirect_302)) == 0)
    {
        /* Then check if this is a redirect to new http site */
        if (dumb_memmem(pktdata, pktlen, location_http, strlen(location_http))) {
            return 1;
        }
    }
    return 0;
}

/* Finds Host header with \r\n before it */
static PVOID find_host_header(const char *pktdata, int pktlen) {
    return dumb_memmem(pktdata, pktlen,
                http_host_find, strlen(http_host_find));
}

static void change_window_size(char *pkt, int size) {
    *(uint16_t*)(pkt + IPV4_HDR_LEN + TCP_WINDOWSIZE_OFFSET) = htons(size);
}

int main(int argc, char *argv[]) {
    static const char fragment_size_message[] =
                "Fragment size should be in range [0 - 65535]\n";
    int i, should_reinject = 0;
    int opt;
    HANDLE w_filter = NULL;
    WINDIVERT_ADDRESS addr;
    char packet[MAX_PACKET_SIZE];
    PVOID packet_data;
    UINT packetLen;
    UINT packet_dataLen;
    PWINDIVERT_IPHDR ppIpHdr;
    PWINDIVERT_TCPHDR ppTcpHdr;

    int do_passivedpi = 0, do_fragment_http = 0,
        do_fragment_https = 0, do_host = 0,
        do_host_removespace = 0;
    int http_fragment_size = 2;
    int https_fragment_size = 2;
    char *data_addr, *data_addr_rn, *host_addr;
    int host_len, fromhost_uptoend_len;

    printf("GoodbyeDPI: Passive DPI blocker and Active DPI circumvention utility\n");

    if (argc == 1) {
        /* enable mode -1 by default */
        do_passivedpi = do_host = do_host_removespace \
            = do_fragment_http = do_fragment_https = 1;
    }

    while ((opt = getopt(argc, argv, "1234prsf:e:")) != -1) {
        switch (opt) {
            case '1':
                do_passivedpi = do_host = do_host_removespace \
                = do_fragment_http = do_fragment_https = 1;
                break;
            case '2':
                do_passivedpi = do_host = do_host_removespace \
                = do_fragment_http = do_fragment_https = 1;
                https_fragment_size = 40;
                break;
            case '3':
                do_passivedpi = do_host = do_host_removespace \
                = do_fragment_https = 1;
                https_fragment_size = 40;
            case '4':
                do_passivedpi = do_host = do_host_removespace = 1;
                break;
            case 'p':
                do_passivedpi = 1;
                break;
            case 'r':
                do_host = 1;
                break;
            case 's':
                do_host_removespace = 1;
                break;
            case 'f':
                do_fragment_http = 1;
                http_fragment_size = atoi(optarg);
                if (http_fragment_size <= 0 || http_fragment_size > 65535) {
                    printf(fragment_size_message);
                    exit(EXIT_FAILURE);
                }
                break;
            case 'e':
                do_fragment_https = 1;
                https_fragment_size = atoi(optarg);
                if (https_fragment_size <= 0 || https_fragment_size > 65535) {
                    printf(fragment_size_message);
                    exit(EXIT_FAILURE);
                }
                break;
            default:
                printf("Usage: goodbyedpi.exe [OPTION...]\n"
                " -p          block passive DPI\n"
                " -r          replace Host with hoSt\n"
                " -s          remove space between host header and its value\n"
                " -f [value]  set HTTP fragmentation to value\n"
                " -e [value]  set HTTPS fragmentation to value\n"
                "\n"
                " -1          enables all options, -f 2 -e 2 (most compatible mode, default)\n"
                " -2          enables all options, -f 2 -e 40 (better speed yet still compatible)\n"
                " -3          all options except HTTP fragmentation, -e 40 (even better speed)\n"
                " -4          all options except fragmentation (best speed)\n");
                exit(EXIT_FAILURE);
        }
    }

    printf("Block passive: %d, Fragment HTTP: %d, Fragment HTTPS: %d, "
           "hoSt: %d, Host no space: %d\n",
           do_passivedpi, (do_fragment_http ? http_fragment_size : 0),
           (do_fragment_https ? https_fragment_size : 0),
           do_host, do_host_removespace);

    printf("\nOpening filter\n");
    filter_num = 0;

    if (do_passivedpi) {
        /* Filter for inbound RST packets with ID = 0 or 1 */
        filters[filter_num] = init("inbound and (ip.Id == 0x0001 or ip.Id == 0x0000) and "
                        "(tcp.SrcPort == 443 or tcp.SrcPort == 80) and tcp.Rst",
                        WINDIVERT_FLAG_DROP);
        filter_num++;
    }

    /* 
     * Filter for inbound HTTP redirection packets and
     * active DPI circumvention
     */
    filters[filter_num] = init("(inbound and (ip.Id == 0x0001 or ip.Id == 0x0000) and tcp.SrcPort == 80 and tcp.Ack) "
                      "or (inbound and (tcp.SrcPort == 80 or tcp.SrcPort == 443) and tcp.Ack and tcp.Syn) "
                      "or (outbound and (tcp.DstPort == 80 or tcp.DstPort == 443) and tcp.Ack)",
                      0);

    w_filter = filters[filter_num];
    filter_num++;

    for (i = 0; i < filter_num; i++) {
        if (filters[i] == NULL)
            die();
    }

    printf("Filter activated!\n");
    signal(SIGINT, sigint_handler);

    while (1) {
        if (WinDivertRecv(w_filter, packet, sizeof(packet), &addr, &packetLen)) {
            //printf("Got %s packet, len=%d!\n", addr.Direction ? "inbound" : "outbound",
            //       packetLen);
            should_reinject = 1;

            if (WinDivertHelperParsePacket(packet, packetLen, &ppIpHdr,
                NULL, NULL, NULL, &ppTcpHdr, NULL, &packet_data, &packet_dataLen)) {
                //printf("Got parsed packet, len=%d!\n", packet_dataLen);
                /* Got a packet WITH DATA */

                /* Handle INBOUND packet with data and find HTTP REDIRECT in there */
                if (addr.Direction == WINDIVERT_DIRECTION_INBOUND && packet_dataLen > 16) {
                    /* If INBOUND packet with DATA (tcp.Ack) */

                    /* Drop packets from filter with HTTP 30x Redirect */
                    if (do_passivedpi && is_passivedpi_redirect(packet_data, packet_dataLen)) {
                        //printf("Dropping HTTP Redirect packet!\n");
                        should_reinject = 0;
                    }
                }
                /* Handle OUTBOUND packet, search for Host header */
                else if (addr.Direction == WINDIVERT_DIRECTION_OUTBOUND && 
                         packet_dataLen > 16 && ppTcpHdr->DstPort == htons(80) &&
                        (do_host || do_host_removespace)) {

                    data_addr = find_host_header(packet_data, packet_dataLen);

                    if (do_host && data_addr) {
                        /* Replace "Host: " with "hoSt: " */
                        memcpy(data_addr, http_host_replace, strlen(http_host_replace));
                        //printf("Replaced Host header!\n");
                    }

                    if (do_host_removespace && data_addr) {
                        host_addr = data_addr + strlen(http_host_find);

                        fromhost_uptoend_len = packet_dataLen - ((PVOID)host_addr - packet_data);
                        data_addr_rn = dumb_memmem(host_addr,
                                                    fromhost_uptoend_len,
                                                    "\r\n", 2);
                        if (data_addr_rn) {
                            host_len = data_addr_rn - host_addr;
                            if (host_len <= 64) {
                                /* Move memory left by 1 byte and reduce packet size for 1 byte */
                                memmove(host_addr - 1, host_addr, fromhost_uptoend_len);
                                /* Reduce "Total Length" in IP header by 1 byte */
                                *(uint16_t*)(packet + IPV4_TOTALLEN_OFFSET) = ntohs(
                                     htons(*(uint16_t*)(packet + IPV4_TOTALLEN_OFFSET)) - 1);
                                /* Reduce packetLen by 1 byte */
                                packetLen--;
                                //printf("Replaced Host header!\n");
                            }
                        }
                    }
                    WinDivertHelperCalcChecksums(packet, packetLen, 0);
                }
            }
            /* Else if we got TCP packet without data */
            else if (WinDivertHelperParsePacket(packet, packetLen, &ppIpHdr,
                NULL, NULL, NULL, &ppTcpHdr, NULL, NULL, NULL)) {
                /* If we got SYN+ACK packet */
                if (addr.Direction == WINDIVERT_DIRECTION_INBOUND && 
                    ppTcpHdr->Syn == 1) {
                    //printf("Changing Window Size!\n");
                    if (do_fragment_http && ppTcpHdr->DstPort == htons(80)) {
                        change_window_size(packet, http_fragment_size);
                        WinDivertHelperCalcChecksums(packet, packetLen, 0);
                    }
                    else if (do_fragment_https && ppTcpHdr->DstPort != htons(80)) {
                        change_window_size(packet, https_fragment_size);
                        WinDivertHelperCalcChecksums(packet, packetLen, 0);
                    }
                }
            }

            if (should_reinject) {
                //printf("Re-injecting!\n");
                WinDivertSend(w_filter, packet, packetLen, &addr, NULL);
            }
        }
        else {
            // error, ignore
            printf("Error receiving packet!\n");
            break;
        }
    }
}