From eda0a865895d00f9a1d72bcb9a378d34c456afab Mon Sep 17 00:00:00 2001 From: basil00 Date: Mon, 11 Jan 2016 10:36:10 +0800 Subject: [PATCH] Add a new sample program: streamdump Streamdump demonstrates how to handle streams using WinDivert. This is a WIP. --- examples/streamdump/streamdump.c | 512 +++++++++++++++++++++++++++++++ 1 file changed, 512 insertions(+) create mode 100644 examples/streamdump/streamdump.c diff --git a/examples/streamdump/streamdump.c b/examples/streamdump/streamdump.c new file mode 100644 index 0000000..b32e4f3 --- /dev/null +++ b/examples/streamdump/streamdump.c @@ -0,0 +1,512 @@ +/* + * streamdump.c + * (C) 2016 basil, 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 . + */ + +/* + * DESCRIPTION: + * + * This program demonstrates how to handle streams using WinDivert. + * + * The program works by "reflecting" outbound TCP connections into inbound + * TCP connections that are handled by a simple proxy server. + * + * This program also demonstrates WinDivert asynchronous I/O. + * + * usage: streamdump.exe port + */ + +#include +#include +#include +#include +#include + +#include "windivert.h" + +#define MAXBUF 0xFFFF +#define PROXY_PORT 34010 +#define ALT_PORT 43010 +#define MAX_LINE 65 + +/* + * Proxy server configuration. + */ +typedef struct +{ + int proxy_port; + int alt_port; +} PROXY_CONFIG, *PPROXY_CONFIG; + +typedef struct +{ + SOCKET s; + int alt_port; + struct in_addr dest; +} PROXY_CONNECTION_CONFIG, *PPROXY_CONNECTION_CONFIG; + +typedef struct +{ + BOOL inbound; + SOCKET s; + SOCKET t; +} PROXY_TRANSFER_CONFIG, *PPROXY_TRANSFER_CONFIG; + +/* + * Lock to sync output. + */ +static HANDLE lock; + +/* + * Prototypes. + */ +static DWORD proxy(LPVOID arg); +static DWORD proxy_connection_handler(LPVOID arg); +static DWORD proxy_transfer_handler(LPVOID arg); + +/* + * Error handling. + */ +static void message(const char *msg, ...) +{ + va_list args; + va_start(args, msg); + WaitForSingleObject(lock, INFINITE); + vfprintf(stderr, msg, args); + putc('\n', stderr); + ReleaseMutex(lock); + va_end(args); +} +#define error(msg, ...) \ + do { \ + message("error: " msg, ## __VA_ARGS__); \ + exit(EXIT_FAILURE); \ + } while (FALSE) +#define warning(msg, ...) \ + message("warning: " msg, ## __VA_ARGS__) + +/* + * Cleanup completed I/O requests. + */ +static void cleanup(HANDLE ioport, OVERLAPPED *ignore) +{ + OVERLAPPED *overlapped; + DWORD iolen; + ULONG_PTR iokey = 0; + + while (GetQueuedCompletionStatus(ioport, &iolen, &iokey, &overlapped, 0)) + { + if (overlapped != ignore) + { + free(overlapped); + } + } +} + +/* + * Entry. + */ +int __cdecl main(int argc, char **argv) +{ + HANDLE handle, thread; + int port, proxy_port, alt_port, r; + char filter[256]; + INT16 priority = 123; // Arbitrary. + PPROXY_CONFIG config; + unsigned char packet[MAXBUF]; + UINT packet_len; + WINDIVERT_ADDRESS addr; + PWINDIVERT_IPHDR ip_header; + PWINDIVERT_TCPHDR tcp_header; + OVERLAPPED *poverlapped; + OVERLAPPED overlapped; + HANDLE ioport, event; + DWORD len; + + // Init. + if (argc != 2) + { + fprintf(stderr, "usage: %s dest-port\n", argv[0]); + } + port = atoi(argv[1]); + if (port < 0 || port > 0xFFFF) + { + fprintf(stderr, "error: invalid port number (%d)\n", port); + exit(EXIT_FAILURE); + } + proxy_port = (port == PROXY_PORT? PROXY_PORT+1: PROXY_PORT); + alt_port = (port == ALT_PORT? ALT_PORT+1: ALT_PORT); + lock = CreateMutex(NULL, FALSE, NULL); + if (lock == NULL) + { + fprintf(stderr, "error: failed to create mutex (%d)\n", + GetLastError()); + exit(EXIT_FAILURE); + } + ioport = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); + if (ioport == NULL) + { + error("failed to create I/O completion port (%d)", GetLastError()); + } + event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (event == NULL) + { + error("failed to create event (%d)", GetLastError()); + } + + // Divert all traffic to/from `port', `proxy_port' and `alt_port'. + r = sprintf_s(filter, sizeof(filter), + "tcp and " + "(tcp.DstPort == %d or tcp.DstPort == %d or tcp.DstPort == %d or " + "tcp.SrcPort == %d or tcp.SrcPort == %d or tcp.SrcPort == %d)", + port, proxy_port, alt_port, port, proxy_port, alt_port); + if (r < 0 || r >= sizeof(filter)) + { + error("failed to create filter string"); + } + handle = WinDivertOpen(filter, WINDIVERT_LAYER_NETWORK, priority, 0); + if (handle == INVALID_HANDLE_VALUE) + { + error("failed to open the WinDivert device (%d)", GetLastError()); + } + if (CreateIoCompletionPort(handle, ioport, 0, 0) == NULL) + { + error("failed to associate I/O completion port (%d)", GetLastError()); + } + + // Spawn proxy thread, + config = (PPROXY_CONFIG)malloc(sizeof(PROXY_CONFIG)); + if (config == NULL) + { + error("failed to allocate memory"); + } + config->proxy_port = proxy_port; + config->alt_port = alt_port; + thread = CreateThread(NULL, 1, (LPTHREAD_START_ROUTINE)proxy, + (LPVOID)config, 0, NULL); + if (thread == NULL) + { + error("failed to create thread (%d)", GetLastError()); + } + CloseHandle(thread); + + // Main loop: + while (TRUE) + { + memset(&overlapped, 0, sizeof(overlapped)); + ResetEvent(event); + overlapped.hEvent = event; + if (!WinDivertRecvEx(handle, packet, sizeof(packet), 0, &addr, + &packet_len, &overlapped)) + { + if (GetLastError() != ERROR_IO_PENDING) + { +read_failed: + warning("failed to read packet (%d)", GetLastError()); + continue; + } + + // Timeout = 1s + while (WaitForSingleObject(event, 1000) == WAIT_TIMEOUT) + { + cleanup(ioport, &overlapped); + } + if (!GetOverlappedResult(handle, &overlapped, &len, FALSE)) + { + goto read_failed; + } + packet_len = len; + } + cleanup(ioport, &overlapped); + + if (!WinDivertHelperParsePacket(packet, packet_len, &ip_header, NULL, + NULL, NULL, &tcp_header, NULL, NULL, NULL)) + { + warning("failed to parse packet (%d)", GetLastError()); + continue; + } + + switch (addr.Direction) + { + case WINDIVERT_DIRECTION_OUTBOUND: + if (tcp_header->DstPort == htons(port)) + { + // Reflect: PORT ---> PROXY + UINT32 dst_addr = ip_header->DstAddr; + tcp_header->DstPort = htons(proxy_port); + ip_header->DstAddr = ip_header->SrcAddr; + ip_header->SrcAddr = dst_addr; + addr.Direction = WINDIVERT_DIRECTION_INBOUND; + } + else if (tcp_header->SrcPort == htons(proxy_port)) + { + // Reflect: PROXY ---> PORT + UINT32 dst_addr = ip_header->DstAddr; + tcp_header->SrcPort = htons(port); + ip_header->DstAddr = ip_header->SrcAddr; + ip_header->SrcAddr = dst_addr; + addr.Direction = WINDIVERT_DIRECTION_INBOUND; + } + else if (tcp_header->DstPort == htons(alt_port)) + { + // Redirect: ALT ---> PORT + tcp_header->DstPort = htons(port); + } + break; + + case WINDIVERT_DIRECTION_INBOUND: + if (tcp_header->SrcPort == htons(port)) + { + // Redirect: PORT ---> ALT + tcp_header->SrcPort = htons(alt_port); + } + break; + } + + WinDivertHelperCalcChecksums(packet, packet_len, 0); + poverlapped = (OVERLAPPED *)malloc(sizeof(OVERLAPPED)); + if (poverlapped == NULL) + { + error("failed to allocate memory"); + } + memset(poverlapped, 0, sizeof(OVERLAPPED)); + if (WinDivertSendEx(handle, packet, packet_len, 0, &addr, NULL, + poverlapped)) + { + continue; + } + if (GetLastError() != ERROR_IO_PENDING) + { + warning("failed to send packet (%d)", GetLastError()); + continue; + } + } + + return 0; +} + +/* + * Proxy server thread. + */ +static DWORD proxy(LPVOID arg) +{ + PPROXY_CONFIG config = (PPROXY_CONFIG)arg; + int proxy_port = config->proxy_port; + int alt_port = config->alt_port; + int on = 1; + WSADATA wsa_data; + WORD wsa_version = MAKEWORD(2, 2); + struct sockaddr_in addr; + SOCKET s; + + free(config); + + if (WSAStartup(wsa_version, &wsa_data) != 0) + { + error("failed to start WSA (%d)", GetLastError()); + } + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s == INVALID_SOCKET) + { + error("failed to create socket (%d)", WSAGetLastError()); + } + + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(int)) + == SOCKET_ERROR) + { + error("failed to re-use address (%d)", GetLastError()); + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(proxy_port); + if (bind(s, (SOCKADDR *)&addr, sizeof(addr)) == SOCKET_ERROR) + { + error("failed to bind socket (%d)", WSAGetLastError()); + } + + if (listen(s, 16) == SOCKET_ERROR) + { + error("failed to listen socket (%d)", WSAGetLastError()); + } + + while (TRUE) + { + // Wait for a new connection. + PPROXY_CONNECTION_CONFIG config; + int size = sizeof(addr); + SOCKET t = accept(s, (SOCKADDR *)&addr, &size); + if (t == INVALID_SOCKET) + { + warning("failed to accept socket (%d)", WSAGetLastError()); + continue; + } + + // Spawn proxy connection handler thread. + config = (PPROXY_CONNECTION_CONFIG) + malloc(sizeof(PROXY_CONNECTION_CONFIG)); + if (config == NULL) + { + error("failed to allocate memory"); + } + config->s = t; + config->alt_port = alt_port; + config->dest = addr.sin_addr; + HANDLE thread = CreateThread(NULL, 1, + (LPTHREAD_START_ROUTINE)proxy_connection_handler, + (LPVOID)config, 0, NULL); + if (thread == NULL) + { + warning("failed to create thread (%d)", GetLastError()); + closesocket(t); + free(config); + continue; + } + CloseHandle(thread); + } +} + +/* + * Proxy connection handler thread. + */ +static DWORD proxy_connection_handler(LPVOID arg) +{ + PPROXY_TRANSFER_CONFIG config1, config2; + HANDLE thread; + PPROXY_CONNECTION_CONFIG config = (PPROXY_CONNECTION_CONFIG)arg; + SOCKET s = config->s, t; + int alt_port = config->alt_port; + struct in_addr dest = config->dest; + struct sockaddr_in addr; + + free(config); + + t = socket(AF_INET, SOCK_STREAM, 0); + if (t == INVALID_SOCKET) + { + warning("failed to create socket (%d)", WSAGetLastError()); + closesocket(s); + return 0; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(alt_port); + addr.sin_addr = dest; + if (connect(t, (SOCKADDR *)&addr, sizeof(addr)) == SOCKET_ERROR) + { + warning("failed to connect socket (%d)", WSAGetLastError()); + closesocket(s); + closesocket(t); + return 0; + } + + config1 = (PPROXY_TRANSFER_CONFIG)malloc(sizeof(PROXY_TRANSFER_CONFIG)); + config2 = (PPROXY_TRANSFER_CONFIG)malloc(sizeof(PROXY_TRANSFER_CONFIG)); + if (config1 == NULL || config2 == NULL) + { + error("failed to allocate memory"); + } + config1->inbound = FALSE; + config2->inbound = TRUE; + config2->t = config1->s = s; + config2->s = config1->t = t; + thread = CreateThread(NULL, 1, + (LPTHREAD_START_ROUTINE)proxy_transfer_handler, (LPVOID)config1, 0, + NULL); + if (thread == NULL) + { + warning("failed to create thread (%d)", GetLastError()); + closesocket(s); + closesocket(t); + free(config1); + free(config2); + return 0; + } + proxy_transfer_handler((LPVOID)config2); + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + closesocket(s); + closesocket(t); + return 0; +} + +/* + * Handle the transfer of data from one socket to another. + */ +static DWORD proxy_transfer_handler(LPVOID arg) +{ + PPROXY_TRANSFER_CONFIG config = (PPROXY_TRANSFER_CONFIG)arg; + BOOL inbound = config->inbound; + SOCKET s = config->s, t = config->t; + char buf[8192]; + int len, len2, i; + HANDLE console; + + free(config); + + while (TRUE) + { + // Read data from s. + len = recv(s, buf, sizeof(buf), 0); + if (len == SOCKET_ERROR) + { + warning("failed to recv from socket (%d)", WSAGetLastError()); + shutdown(s, SD_BOTH); + shutdown(t, SD_BOTH); + return 0; + } + if (len == 0) + { + shutdown(s, SD_RECEIVE); + shutdown(t, SD_SEND); + return 0; + } + + // Dump stream information to the screen. + console = GetStdHandle(STD_OUTPUT_HANDLE); + WaitForSingleObject(lock, INFINITE); + printf("[%.4d] ", len); + SetConsoleTextAttribute(console, + (inbound? FOREGROUND_RED: FOREGROUND_GREEN)); + for (i = 0; i < len && i < MAX_LINE; i++) + { + putchar((isprint(buf[i])? buf[i]: '.')); + } + SetConsoleTextAttribute(console, + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + printf("%s\n", (len > MAX_LINE? "...": "")); + ReleaseMutex(lock); + + // Send data to t. + for (i = 0; i < len; ) + { + len2 = send(t, buf+i, len-i, 0); + if (len2 == SOCKET_ERROR) + { + warning("failed to send to socket (%d)", WSAGetLastError()); + shutdown(s, SD_BOTH); + shutdown(t, SD_BOTH); + return 0; + } + i += len2; + } + } + + return 0; +} +