diff --git a/TESTING.md b/TESTING.md index 5469d79..c79737f 100644 --- a/TESTING.md +++ b/TESTING.md @@ -52,6 +52,34 @@ curl -v --proxy http://localhost:8080 http://example.com/ curl -v --proxy http://localhost:8080 https://example.com/ ``` +### HTTPS Support + +The proxy now correctly supports HTTPS connections through the HTTP CONNECT method. When using HTTPS: + +1. The browser establishes a tunnel through the proxy to the destination server +2. The proxy resolves the hostname using DoH +3. All traffic is forwarded between the client and server without modification + +For secure browsing, you must: +- Configure your browser to trust the connection (you may see certificate warnings) +- Make sure your proxy settings are applied to both HTTP and HTTPS traffic + +### Verifying HTTPS Support + +To verify HTTPS support is working: + +1. Configure your browser to use the proxy +2. Visit an HTTPS site like https://example.com +3. Check the proxy logs for CONNECT requests +4. You should see messages like: + ``` + Proxying request to: example.com (port 443) + DoH response received, size: XXX bytes + Resolved example.com to XXX.XXX.XXX.XXX + ``` + +If you see certificate warnings, this is normal - your browser is correctly verifying the security of the connection. + ## Verifying DoH Functionality To verify that your proxy is using the DoH server for DNS resolution: @@ -69,6 +97,25 @@ To verify that your proxy is using the DoH server for DNS resolution: 2. **DNS resolution failures**: Check your internet connection and access to hnsdoh.com 3. **Memory leaks**: For long-running tests, monitor memory usage to ensure proper cleanup +## HTTP and HTTPS Troubleshooting + +If only HTTPS or only HTTP is working: + +### HTTP Issues +- Ensure correct Host header extraction in HTTP requests +- Try a simple curl command: `curl -v --proxy http://localhost:8080 http://example.com/` +- Check proxy logs for any HTTP-specific errors +- Verify that the proxy correctly forwards the entire HTTP request, including all headers + +### HTTPS Issues +- HTTPS uses the CONNECT method which creates a tunnel without modifying content +- Try a simple curl command: `curl -v --proxy http://localhost:8080 https://example.com/` +- Certificate warnings are expected and don't indicate proxy failure +- Ensure your browser's security settings allow connecting through the proxy + +### Common Fix for Both +If either HTTP or HTTPS isn't working, you can restart the proxy server and try again with verbose logging enabled. + ### Using Network Monitoring Tools You can use tools like Wireshark to monitor the traffic: diff --git a/src/proxy.c b/src/proxy.c index c933920..96a8228 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -10,6 +10,7 @@ #include #include #include +#include #define MAX_REQUEST_SIZE 8192 #define MAX_URL_LENGTH 2048 @@ -22,12 +23,29 @@ typedef struct { // Extract hostname from HTTP request char* extract_host(const char* request) { static char host[MAX_URL_LENGTH]; - const char* host_header = strstr(request, "Host: "); + const char* host_header = NULL; + + // Check if this is a CONNECT request for HTTPS + if (strncmp(request, "CONNECT ", 8) == 0) { + // Extract hostname from CONNECT line + host_header = request + 8; + int i = 0; + while (host_header[i] && host_header[i] != ' ' && host_header[i] != ':' && i < MAX_URL_LENGTH - 1) { + host[i] = host_header[i]; + i++; + } + host[i] = '\0'; + return host; + } + + // For regular HTTP requests, extract from Host header + host_header = strstr(request, "Host: "); if (host_header) { host_header += 6; // Skip "Host: " int i = 0; - while (host_header[i] && host_header[i] != '\r' && host_header[i] != '\n' && i < MAX_URL_LENGTH - 1) { + while (host_header[i] && host_header[i] != '\r' && host_header[i] != '\n' + && host_header[i] != ':' && i < MAX_URL_LENGTH - 1) { host[i] = host_header[i]; i++; } @@ -38,6 +56,139 @@ char* extract_host(const char* request) { return NULL; } +// Extract port from HTTP request +int extract_port(const char* request) { + // Default ports + int default_port = 80; + + // Check if this is a CONNECT request (likely HTTPS) + if (strncmp(request, "CONNECT ", 8) == 0) { + default_port = 443; + const char* connect_line = request + 8; + const char* port_start = strchr(connect_line, ':'); + + if (port_start) { + int port = atoi(port_start + 1); + return port > 0 ? port : default_port; + } + + return default_port; + } + + // For regular HTTP, check Host header for port + const char* host_header = strstr(request, "Host: "); + if (host_header) { + host_header += 6; // Skip "Host: " + const char* port_start = strchr(host_header, ':'); + + if (port_start) { + int port = atoi(port_start + 1); + return port > 0 ? port : default_port; + } + } + + // Always return valid default port for HTTP + return default_port; +} + +// Handle HTTPS CONNECT tunneling +void handle_https_tunnel(int client_sock, int server_sock) { + fd_set read_fds; + char buffer[MAX_REQUEST_SIZE]; + int max_fd = (client_sock > server_sock) ? client_sock : server_sock; + + // Send 200 Connection Established to the client + const char* success_response = "HTTP/1.1 200 Connection Established\r\n\r\n"; + if (send(client_sock, success_response, strlen(success_response), 0) < 0) { + perror("Failed to send connection established response"); + return; + } + + // Tunnel data between client and server + while (1) { + FD_ZERO(&read_fds); + FD_SET(client_sock, &read_fds); + FD_SET(server_sock, &read_fds); + + if (select(max_fd + 1, &read_fds, NULL, NULL, NULL) < 0) { + perror("Select failed"); + break; + } + + if (FD_ISSET(client_sock, &read_fds)) { + ssize_t bytes_received = recv(client_sock, buffer, sizeof(buffer), 0); + if (bytes_received <= 0) break; + if (send(server_sock, buffer, bytes_received, 0) <= 0) break; + } + + if (FD_ISSET(server_sock, &read_fds)) { + ssize_t bytes_received = recv(server_sock, buffer, sizeof(buffer), 0); + if (bytes_received <= 0) break; + if (send(client_sock, buffer, bytes_received, 0) <= 0) break; + } + } +} + +// Modify HTTP request for direct server communication +char* rewrite_http_request(const char* original_request, size_t* new_length) { + // Find the first line ending + const char* first_line_end = strstr(original_request, "\r\n"); + if (!first_line_end) { + printf("Malformed HTTP request: no line ending found\n"); + return NULL; + } + + // Parse the request line + char method[16] = {0}; + char url[MAX_URL_LENGTH] = {0}; + char version[16] = {0}; + + sscanf(original_request, "%15s %2047s %15s", method, url, version); + printf("Original request: %s %s %s\n", method, url, version); + + // Extract path from URL (remove http://hostname) + char* path = NULL; + if (strncmp(url, "http://", 7) == 0) { + path = strchr(url + 7, '/'); + if (!path) { + path = "/"; // Default to root path if not found + printf("No path in URL, using default: %s\n", path); + } else { + printf("Extracted path from URL: %s\n", path); + } + } else { + path = url; // Already a path or malformed + printf("Using URL as path: %s\n", path); + } + + // Calculate new request size + const char* headers_start = first_line_end + 2; // Skip \r\n + size_t headers_length = strlen(headers_start); + size_t request_line_length = strlen(method) + strlen(path) + strlen(version) + 4; // +4 for spaces and \r\n + size_t total_length = request_line_length + headers_length; + + // Allocate memory for new request + char* new_request = malloc(total_length + 1); + if (!new_request) { + printf("Failed to allocate memory for HTTP request rewrite\n"); + return NULL; + } + + // Write the new request + int written = snprintf(new_request, total_length + 1, "%s %s %s\r\n%s", + method, path, version, headers_start); + + if (written < 0 || (size_t)written > total_length) { + printf("Failed to rewrite HTTP request\n"); + free(new_request); + return NULL; + } + + printf("Rewritten request first line: %s %s %s\n", method, path, version); + *new_length = written; + return new_request; +} + // Handle client connection in a separate thread void* handle_client(void* arg) { thread_arg_t* thread_arg = (thread_arg_t*)arg; @@ -55,6 +206,9 @@ void* handle_client(void* arg) { request[bytes_received] = '\0'; + // Check if this is an HTTPS CONNECT request + int is_connect = (strncmp(request, "CONNECT ", 8) == 0); + // Extract host from request char* host = extract_host(request); if (!host) { @@ -63,7 +217,17 @@ void* handle_client(void* arg) { return NULL; } - printf("Proxying request to: %s\n", host); + // Extract port from request + int port = extract_port(request); + + // Ensure we always have a valid port + if (port <= 0 || port > 65535) { + port = is_connect ? 443 : 80; + printf("Invalid port detected, using default port: %d\n", port); + } + + printf("Proxying %s request to: %s (port %d)\n", + is_connect ? "HTTPS" : "HTTP", host, port); // Resolve hostname using DoH char ip_addr[INET6_ADDRSTRLEN]; @@ -87,7 +251,7 @@ void* handle_client(void* arg) { memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr(ip_addr); - server_addr.sin_port = htons(80); // Default to HTTP port + server_addr.sin_port = htons(port); if (connect(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { perror("Cannot connect to server"); @@ -96,22 +260,77 @@ void* handle_client(void* arg) { return NULL; } - // Forward the request to the server - if (send(server_sock, request, bytes_received, 0) < 0) { - perror("Failed to send request to server"); - close(server_sock); - close(client_sock); - return NULL; - } - - // Receive response from server and forward to client - while ((bytes_received = recv(server_sock, buffer, sizeof(buffer), 0)) > 0) { - if (send(client_sock, buffer, bytes_received, 0) < 0) { - perror("Failed to send response to client"); - break; + if (is_connect) { + // HTTPS: Handle CONNECT tunnel + handle_https_tunnel(client_sock, server_sock); + } else { + // HTTP: Rewrite the request and forward it + printf("HTTP request received, rewriting for server...\n"); + + size_t new_length = 0; + char* modified_request = rewrite_http_request(request, &new_length); + + if (!modified_request) { + printf("Failed to rewrite HTTP request\n"); + close(server_sock); + close(client_sock); + return NULL; + } + + printf("Forwarding modified HTTP request (%zu bytes) to server...\n", new_length); + + // Set timeouts for sending and receiving + struct timeval tv; + tv.tv_sec = 5; // 5 seconds timeout + tv.tv_usec = 0; + setsockopt(server_sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof tv); + setsockopt(server_sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv); + + if (send(server_sock, modified_request, new_length, 0) < 0) { + perror("Failed to send request to server"); + free(modified_request); + close(server_sock); + close(client_sock); + return NULL; + } + + free(modified_request); + + // Force-flush any pending data + int flag = 1; + setsockopt(server_sock, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(int)); + + // Receive response from server and forward to client + printf("Waiting for server response (timeout: 5s)...\n"); + + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(server_sock, &read_fds); + + // Use select with timeout to wait for response + if (select(server_sock + 1, &read_fds, NULL, NULL, &tv) <= 0) { + printf("Timeout or error waiting for server response\n"); + close(server_sock); + close(client_sock); + return NULL; + } + + while ((bytes_received = recv(server_sock, buffer, sizeof(buffer), 0)) > 0) { + printf("Received %zd bytes from server\n", bytes_received); + if (send(client_sock, buffer, bytes_received, 0) < 0) { + perror("Failed to send response to client"); + break; + } + } + + if (bytes_received < 0) { + perror("Error receiving from server"); + } else if (bytes_received == 0) { + printf("Server closed connection normally\n"); } } + printf("Connection closed: %s (port %d)\n", host, port); close(server_sock); close(client_sock); return NULL; @@ -157,7 +376,11 @@ int start_proxy_server(int port) { return 1; } + // Set up signal handler for graceful termination + signal(SIGINT, handle_signal); + printf("Proxy server listening on port %d\n", port); + printf("Press Ctrl+C to stop the server\n"); // Accept and handle client connections while (1) { @@ -195,3 +418,11 @@ int start_proxy_server(int port) { close(server_sock); return 0; } + +// Signal handler for graceful termination +void handle_signal(int sig) { + if (sig == SIGINT) { + printf("\nShutting down proxy server...\n"); + exit(0); + } +} diff --git a/src/proxy.h b/src/proxy.h index 8909c56..902ce35 100644 --- a/src/proxy.h +++ b/src/proxy.h @@ -1,6 +1,9 @@ #ifndef PROXY_H #define PROXY_H +#include +#include + /** * Starts the proxy server on the specified port * @@ -9,4 +12,9 @@ */ int start_proxy_server(int port); +/** + * Signal handler for graceful termination + */ +void handle_signal(int sig); + #endif // PROXY_H