diff --git a/test/build.sh b/test/build.sh
new file mode 100755
index 0000000..a45b276
--- /dev/null
+++ b/test/build.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# build.sh
+# (C) 2014, all rights reserved,
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+
+# Script for MinGW/Linux cross compilation.
+# NOTE: run wddk-build.bat before this script.
+
+CC=x86_64-w64-mingw32-gcc
+
+$CC -s -O2 -I../include/ test.c -o test.exe -lWinDivert \
+ -L"../install/MINGW/amd64/"
+
diff --git a/test/test.c b/test/test.c
new file mode 100644
index 0000000..727ec6e
--- /dev/null
+++ b/test/test.c
@@ -0,0 +1,338 @@
+/*
+ * test.c
+ * (C) 2014, all rights reserved,
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+/*
+ * WinDivert testing framework.
+ */
+
+#include
+#include
+#include
+
+#include "windivert.h"
+
+#define MAX_PACKET 2048
+
+/*
+ * Packet data.
+ */
+#include "test_data.c"
+
+/*
+ * Test entry.
+ */
+struct packet
+{
+ char *packet;
+ size_t packet_len;
+ char *name;
+};
+
+struct test
+{
+ char *filter;
+ struct packet *packet;
+ BOOL match;
+};
+
+/*
+ * Prototypes.
+ */
+static BOOL run_test(HANDLE inject_handle, const char *filter,
+ const char *packet, const size_t packet_len, BOOL match);
+
+/*
+ * Test data.
+ */
+struct packet pkt_echo_request =
+{
+ echo_request,
+ sizeof(echo_request),
+ "ipv4_icmp_echo_req"
+};
+struct packet pkt_http_request =
+{
+ http_request,
+ sizeof(http_request),
+ "ipv4_tcp_http_req"
+};
+struct packet pkt_ipv6_exthdrs_udp =
+{
+ ipv6_exthdrs_udp,
+ sizeof(ipv6_exthdrs_udp),
+ "ipv6_exthdrs_udp"
+};
+struct test tests[] =
+{
+ {"outbound and icmp", &pkt_echo_request, TRUE},
+ {"outbound", &pkt_echo_request, TRUE},
+ {"icmp", &pkt_echo_request, TRUE},
+ {"not icmp", &pkt_echo_request, FALSE},
+ {"inbound", &pkt_echo_request, FALSE},
+ {"tcp", &pkt_echo_request, FALSE},
+ {"icmp.Type == 8", &pkt_echo_request, TRUE},
+ {"icmp.Type == 9", &pkt_echo_request, FALSE},
+ {"tcp", &pkt_http_request, TRUE},
+ {"outbound and tcp and tcp.DstPort == 80", &pkt_http_request, TRUE},
+ {"outbound and tcp and tcp.DstPort == 81", &pkt_http_request, FALSE},
+ {"outbound and tcp and tcp.DstPort != 80", &pkt_http_request, FALSE},
+ {"inbound and tcp and tcp.DstPort == 80", &pkt_http_request, FALSE},
+ {"tcp.PayloadLength == 469", &pkt_http_request, TRUE},
+ {"tcp.PayloadLength != 469", &pkt_http_request, FALSE},
+ {"tcp.PayloadLength >= 469", &pkt_http_request, TRUE},
+ {"tcp.PayloadLength <= 469", &pkt_http_request, TRUE},
+ {"tcp.PayloadLength > 469", &pkt_http_request, FALSE},
+ {"tcp.PayloadLength < 469", &pkt_http_request, FALSE},
+ {"true", &pkt_ipv6_exthdrs_udp, TRUE},
+ {"udp", &pkt_ipv6_exthdrs_udp, TRUE},
+ {"tcp", &pkt_ipv6_exthdrs_udp, FALSE},
+ {"ipv6.SrcAddr == ::1", &pkt_ipv6_exthdrs_udp, TRUE},
+ {"ipv6.SrcAddr == ::2", &pkt_ipv6_exthdrs_udp, FALSE},
+ {"udp.SrcPort == 4660 and udp.DstPort == 43690",
+ &pkt_ipv6_exthdrs_udp, TRUE},
+ {"udp.SrcPort == 4660 and udp.DstPort == 12345",
+ &pkt_ipv6_exthdrs_udp, FALSE},
+};
+
+/*
+ * Main.
+ */
+int main(void)
+{
+ HANDLE upper_handle, lower_handle;
+ HANDLE console;
+ size_t i;
+
+ // Open handles to:
+ // (1) stop normal traffic from interacting with the tests; and
+ // (2) stop test packets escaping to the Internet or TCP/IP stack.
+ upper_handle = WinDivertOpen("true", WINDIVERT_LAYER_NETWORK, -510,
+ WINDIVERT_FLAG_DROP);
+ lower_handle = WinDivertOpen("true", WINDIVERT_LAYER_NETWORK, 510,
+ WINDIVERT_FLAG_DROP);
+ if (upper_handle == INVALID_HANDLE_VALUE ||
+ lower_handle == INVALID_HANDLE_VALUE)
+ {
+ fprintf(stderr, "error: failed to open WinDivert handle (err = %d)",
+ GetLastError());
+ exit(EXIT_FAILURE);
+ }
+
+ console = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ // Wait for existing packets to flush:
+ Sleep(100);
+
+ // Run tests:
+ size_t num_tests = sizeof(tests) / sizeof(struct test);
+ for (i = 0; i < num_tests; i++)
+ {
+ char *filter = tests[i].filter;
+ char *packet = tests[i].packet->packet;
+ size_t packet_len = tests[i].packet->packet_len;
+ char *name = tests[i].packet->name;
+ BOOL match = tests[i].match;
+
+ // Ensure the correct checksum:
+ WinDivertHelperCalcChecksums(packet, packet_len, 0);
+
+ // Run the test:
+ BOOL res = run_test(upper_handle, filter, packet, packet_len, match);
+
+ printf("%.2u ", i);
+ if (res)
+ {
+ SetConsoleTextAttribute(console, FOREGROUND_GREEN);
+ printf("PASSED");
+ }
+ else
+ {
+ SetConsoleTextAttribute(console, FOREGROUND_RED);
+ printf("FAILED");
+ }
+ SetConsoleTextAttribute(console, FOREGROUND_RED | FOREGROUND_GREEN |
+ FOREGROUND_BLUE);
+ printf(" p=[");
+ SetConsoleTextAttribute(console, FOREGROUND_RED | FOREGROUND_GREEN);
+ printf("%s", name);
+ SetConsoleTextAttribute(console, FOREGROUND_RED | FOREGROUND_GREEN |
+ FOREGROUND_BLUE);
+ printf("] f=[");
+ SetConsoleTextAttribute(console, FOREGROUND_RED | FOREGROUND_GREEN);
+ printf("%s", filter);
+ SetConsoleTextAttribute(console, FOREGROUND_RED | FOREGROUND_GREEN |
+ FOREGROUND_BLUE);
+ printf("]\n");
+ }
+
+ WinDivertClose(upper_handle);
+ WinDivertClose(lower_handle);
+
+ return 0;
+}
+
+/*
+ * Run a test case.
+ */
+static BOOL run_test(HANDLE inject_handle, const char *filter,
+ const char *packet, const size_t packet_len, BOOL match)
+{
+ char buf[MAX_PACKET];
+ UINT buf_len, i;
+ DWORD iolen;
+ WINDIVERT_ADDRESS addr;
+ OVERLAPPED overlapped;
+ HANDLE handle = INVALID_HANDLE_VALUE, handle0 = INVALID_HANDLE_VALUE,
+ event = NULL;
+
+ // (1) Open a WinDivert handle to the given filter:
+ handle = WinDivertOpen(filter, WINDIVERT_LAYER_NETWORK, 0, 0);
+ if (handle == INVALID_HANDLE_VALUE)
+ {
+ fprintf(stderr, "error: failed to open WinDivert handle for filter "
+ "\"%s\" (err = %d)\n", filter, GetLastError());
+ goto failed;
+ }
+
+ if (!match)
+ {
+ // Catch non-matching packets:
+ handle0 = handle;
+ handle = WinDivertOpen("true", WINDIVERT_LAYER_NETWORK, 33, 0);
+ if (handle == INVALID_HANDLE_VALUE)
+ {
+ fprintf(stderr, "error: failed to open WinDivert handle "
+ "(err = %d)\n", GetLastError());
+ goto failed;
+ }
+ }
+
+ // (2) Inject the packet:
+ memset(&addr, 0, sizeof(addr));
+ addr.Direction = WINDIVERT_DIRECTION_OUTBOUND;
+ if (!WinDivertSend(inject_handle, packet, packet_len, &addr, NULL))
+ {
+ fprintf(stderr, "error: failed to inject test packet (err = %d)\n",
+ GetLastError());
+ goto failed;
+ }
+
+ // (3) Wait for the packet to arrive.
+ // NOTE: This may fail, so set a generous time-out of 1 second.
+ memset(&overlapped, 0, sizeof(overlapped));
+ event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (event == NULL)
+ {
+ fprintf(stderr, "error: failed to create event (err = %d)\n",
+ GetLastError());
+ goto failed;
+ }
+ overlapped.hEvent = event;
+ if (!WinDivertRecvEx(handle, buf, sizeof(buf), 0, &addr, &buf_len,
+ &overlapped))
+ {
+ if (GetLastError() != ERROR_IO_PENDING)
+ {
+read_failed:
+ fprintf(stderr, "error: failed to read packet from WinDivert "
+ "handle (err = %d)\n", GetLastError());
+ goto failed;
+ }
+
+ switch (WaitForSingleObject(event, 1000))
+ {
+ case WAIT_OBJECT_0:
+ break;
+ case WAIT_TIMEOUT:
+ fprintf(stderr, "error: failed to read packet from WinDivert "
+ "handle (timeout)\n", GetLastError());
+ goto failed;
+ default:
+ goto read_failed;
+ }
+
+ if (!GetOverlappedResult(handle, &overlapped, &iolen, TRUE))
+ {
+ fprintf(stderr, "error: failed to get the overlapped result from "
+ "WinDivert handle (err = %d)\n", GetLastError());
+ goto failed;
+ }
+ buf_len = (UINT)iolen;
+ }
+
+ // (4) Verify that the packet is the same.
+ if (buf_len != packet_len)
+ {
+ fprintf(stderr, "error: packet length mis-match, expected (%u), got "
+ "(%u)\n", packet_len, buf_len);
+ goto failed;
+ }
+ for (i = 0; i < packet_len; i++)
+ {
+ if (packet[i] != buf[i])
+ {
+ fprintf(stderr, "error: packet data mis-match, expected byte #%u "
+ "to be (0x%.2X), got (0x%.2X)\n", i, (unsigned char)packet[i],
+ (unsigned char)buf[i]);
+ for (i = 0; i < packet_len; i++)
+ {
+ printf("%c", (packet[i] == buf[i]? '.': 'X'));
+ }
+ putchar('\n');
+ goto failed;
+ }
+ }
+
+ // (5) Clean-up:
+ if (!WinDivertClose(handle))
+ {
+ handle = INVALID_HANDLE_VALUE;
+ fprintf(stderr, "error: failed to close WinDivert handle (err = %d)\n",
+ GetLastError());
+ goto failed;
+ }
+ if (handle0 != INVALID_HANDLE_VALUE)
+ {
+ if (!WinDivertClose(handle0))
+ {
+ handle0 = INVALID_HANDLE_VALUE;
+ fprintf(stderr, "error: failed to close WinDivert handle "
+ "(err = %d)\n", GetLastError());
+ goto failed;
+ }
+ }
+ CloseHandle(event);
+
+ return TRUE;
+
+failed:
+ if (handle0 != INVALID_HANDLE_VALUE)
+ {
+ WinDivertClose(handle0);
+ }
+ if (handle != INVALID_HANDLE_VALUE)
+ {
+ WinDivertClose(handle);
+ }
+ if (event != NULL)
+ {
+ CloseHandle(event);
+ }
+ return FALSE;
+}
+
diff --git a/test/test_data.c b/test/test_data.c
new file mode 100644
index 0000000..1258020
--- /dev/null
+++ b/test/test_data.c
@@ -0,0 +1,121 @@
+/*
+ * test_data.c
+ * (C) 2014, all rights reserved,
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+// IPV4 ICMP ECHO REQUEST
+static unsigned char echo_request[] =
+{
+ 0x45, 0x00, 0x00, 0x54, 0x12, 0x34, 0x40, 0x00,
+ 0x40, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x01,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00,
+ 0x0d, 0x56, 0x00, 0x01, 0x8b, 0xa6, 0x60, 0x54,
+ 0x00, 0x00, 0x00, 0x00, 0xf9, 0x08, 0x0a, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33,
+ 0x34, 0x35, 0x36, 0x37
+};
+
+// IPV4 TCP HTTP GET REQUEST
+static unsigned char http_request[] =
+{
+ 0x45, 0x00, 0x02, 0x09, 0x48, 0x2d, 0x40, 0x00,
+ 0x40, 0x06, 0x00, 0x00, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x5d, 0xb8, 0xd8, 0x77, 0xa3, 0x1a, 0x00, 0x50,
+ 0x53, 0x38, 0xcc, 0xc2, 0x56, 0x37, 0xb3, 0x55,
+ 0x80, 0x18, 0x00, 0x73, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x08, 0x0a, 0x00, 0x2c, 0x85, 0x1b,
+ 0x1b, 0x7f, 0x3a, 0x71, 0x47, 0x45, 0x54, 0x20,
+ 0x2f, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31,
+ 0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74,
+ 0x3a, 0x20, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78,
+ 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65,
+ 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6b,
+ 0x65, 0x65, 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76,
+ 0x65, 0x0d, 0x0a, 0x43, 0x61, 0x63, 0x68, 0x65,
+ 0x2d, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
+ 0x3a, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67,
+ 0x65, 0x3d, 0x30, 0x0d, 0x0a, 0x41, 0x63, 0x63,
+ 0x65, 0x70, 0x74, 0x3a, 0x20, 0x74, 0x65, 0x78,
+ 0x74, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x61,
+ 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69,
+ 0x6f, 0x6e, 0x2f, 0x78, 0x68, 0x74, 0x6d, 0x6c,
+ 0x2b, 0x78, 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70,
+ 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x2f, 0x78, 0x6d, 0x6c, 0x3b, 0x71, 0x3d, 0x30,
+ 0x2e, 0x39, 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65,
+ 0x2f, 0x77, 0x65, 0x62, 0x70, 0x2c, 0x2a, 0x2f,
+ 0x2a, 0x3b, 0x71, 0x3d, 0x30, 0x2e, 0x38, 0x0d,
+ 0x0a, 0x55, 0x73, 0x65, 0x72, 0x2d, 0x41, 0x67,
+ 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
+ 0x58, 0x58, 0x0d, 0x0a, 0x41, 0x63, 0x63, 0x65,
+ 0x70, 0x74, 0x2d, 0x45, 0x6e, 0x63, 0x6f, 0x64,
+ 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x67, 0x7a, 0x69,
+ 0x70, 0x2c, 0x64, 0x65, 0x66, 0x6c, 0x61, 0x74,
+ 0x65, 0x2c, 0x73, 0x64, 0x63, 0x68, 0x0d, 0x0a,
+ 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x4c,
+ 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x3a,
+ 0x20, 0x65, 0x6e, 0x2d, 0x55, 0x53, 0x2c, 0x65,
+ 0x6e, 0x3b, 0x71, 0x3d, 0x30, 0x2e, 0x38, 0x0d,
+ 0x0a, 0x49, 0x66, 0x2d, 0x4e, 0x6f, 0x6e, 0x65,
+ 0x2d, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x3a, 0x20,
+ 0x22, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
+ 0x33, 0x33, 0x22, 0x0d, 0x0a, 0x49, 0x66, 0x2d,
+ 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64,
+ 0x2d, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x3a, 0x20,
+ 0x46, 0x72, 0x69, 0x2c, 0x20, 0x30, 0x33, 0x20,
+ 0x41, 0x75, 0x67, 0x20, 0x32, 0x30, 0x31, 0x34,
+ 0x20, 0x31, 0x33, 0x3a, 0x33, 0x33, 0x3a, 0x33,
+ 0x33, 0x20, 0x47, 0x4d, 0x54, 0x0d, 0x0a, 0x0d,
+ 0x0a
+};
+
+// IPV6 EXTENSION HEADERS UDP
+static unsigned char ipv6_exthdrs_udp[] =
+{
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x64,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x12, 0x34, 0xaa, 0xaa, 0x00, 0x15, 0x00, 0x00,
+ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f,
+ 0x72, 0x6c, 0x64, 0x21, 0x01
+};
+