feat: Add https proxying
This commit is contained in:
47
TESTING.md
47
TESTING.md
@@ -52,6 +52,34 @@ curl -v --proxy http://localhost:8080 http://example.com/
|
|||||||
curl -v --proxy http://localhost:8080 https://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
|
## Verifying DoH Functionality
|
||||||
|
|
||||||
To verify that your proxy is using the DoH server for DNS resolution:
|
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
|
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
|
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
|
### Using Network Monitoring Tools
|
||||||
|
|
||||||
You can use tools like Wireshark to monitor the traffic:
|
You can use tools like Wireshark to monitor the traffic:
|
||||||
|
|||||||
265
src/proxy.c
265
src/proxy.c
@@ -10,6 +10,7 @@
|
|||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
#define MAX_REQUEST_SIZE 8192
|
#define MAX_REQUEST_SIZE 8192
|
||||||
#define MAX_URL_LENGTH 2048
|
#define MAX_URL_LENGTH 2048
|
||||||
@@ -22,12 +23,29 @@ typedef struct {
|
|||||||
// Extract hostname from HTTP request
|
// Extract hostname from HTTP request
|
||||||
char* extract_host(const char* request) {
|
char* extract_host(const char* request) {
|
||||||
static char host[MAX_URL_LENGTH];
|
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) {
|
if (host_header) {
|
||||||
host_header += 6; // Skip "Host: "
|
host_header += 6; // Skip "Host: "
|
||||||
int i = 0;
|
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];
|
host[i] = host_header[i];
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
@@ -38,6 +56,139 @@ char* extract_host(const char* request) {
|
|||||||
return NULL;
|
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
|
// Handle client connection in a separate thread
|
||||||
void* handle_client(void* arg) {
|
void* handle_client(void* arg) {
|
||||||
thread_arg_t* thread_arg = (thread_arg_t*)arg;
|
thread_arg_t* thread_arg = (thread_arg_t*)arg;
|
||||||
@@ -55,6 +206,9 @@ void* handle_client(void* arg) {
|
|||||||
|
|
||||||
request[bytes_received] = '\0';
|
request[bytes_received] = '\0';
|
||||||
|
|
||||||
|
// Check if this is an HTTPS CONNECT request
|
||||||
|
int is_connect = (strncmp(request, "CONNECT ", 8) == 0);
|
||||||
|
|
||||||
// Extract host from request
|
// Extract host from request
|
||||||
char* host = extract_host(request);
|
char* host = extract_host(request);
|
||||||
if (!host) {
|
if (!host) {
|
||||||
@@ -63,7 +217,17 @@ void* handle_client(void* arg) {
|
|||||||
return NULL;
|
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
|
// Resolve hostname using DoH
|
||||||
char ip_addr[INET6_ADDRSTRLEN];
|
char ip_addr[INET6_ADDRSTRLEN];
|
||||||
@@ -87,7 +251,7 @@ void* handle_client(void* arg) {
|
|||||||
memset(&server_addr, 0, sizeof(server_addr));
|
memset(&server_addr, 0, sizeof(server_addr));
|
||||||
server_addr.sin_family = AF_INET;
|
server_addr.sin_family = AF_INET;
|
||||||
server_addr.sin_addr.s_addr = inet_addr(ip_addr);
|
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) {
|
if (connect(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
|
||||||
perror("Cannot connect to server");
|
perror("Cannot connect to server");
|
||||||
@@ -96,22 +260,77 @@ void* handle_client(void* arg) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward the request to the server
|
if (is_connect) {
|
||||||
if (send(server_sock, request, bytes_received, 0) < 0) {
|
// HTTPS: Handle CONNECT tunnel
|
||||||
perror("Failed to send request to server");
|
handle_https_tunnel(client_sock, server_sock);
|
||||||
close(server_sock);
|
} else {
|
||||||
close(client_sock);
|
// HTTP: Rewrite the request and forward it
|
||||||
return NULL;
|
printf("HTTP request received, rewriting for server...\n");
|
||||||
}
|
|
||||||
|
size_t new_length = 0;
|
||||||
// Receive response from server and forward to client
|
char* modified_request = rewrite_http_request(request, &new_length);
|
||||||
while ((bytes_received = recv(server_sock, buffer, sizeof(buffer), 0)) > 0) {
|
|
||||||
if (send(client_sock, buffer, bytes_received, 0) < 0) {
|
if (!modified_request) {
|
||||||
perror("Failed to send response to client");
|
printf("Failed to rewrite HTTP request\n");
|
||||||
break;
|
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(server_sock);
|
||||||
close(client_sock);
|
close(client_sock);
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -157,7 +376,11 @@ int start_proxy_server(int port) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up signal handler for graceful termination
|
||||||
|
signal(SIGINT, handle_signal);
|
||||||
|
|
||||||
printf("Proxy server listening on port %d\n", port);
|
printf("Proxy server listening on port %d\n", port);
|
||||||
|
printf("Press Ctrl+C to stop the server\n");
|
||||||
|
|
||||||
// Accept and handle client connections
|
// Accept and handle client connections
|
||||||
while (1) {
|
while (1) {
|
||||||
@@ -195,3 +418,11 @@ int start_proxy_server(int port) {
|
|||||||
close(server_sock);
|
close(server_sock);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Signal handler for graceful termination
|
||||||
|
void handle_signal(int sig) {
|
||||||
|
if (sig == SIGINT) {
|
||||||
|
printf("\nShutting down proxy server...\n");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#ifndef PROXY_H
|
#ifndef PROXY_H
|
||||||
#define PROXY_H
|
#define PROXY_H
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the proxy server on the specified port
|
* Starts the proxy server on the specified port
|
||||||
*
|
*
|
||||||
@@ -9,4 +12,9 @@
|
|||||||
*/
|
*/
|
||||||
int start_proxy_server(int port);
|
int start_proxy_server(int port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal handler for graceful termination
|
||||||
|
*/
|
||||||
|
void handle_signal(int sig);
|
||||||
|
|
||||||
#endif // PROXY_H
|
#endif // PROXY_H
|
||||||
|
|||||||
Reference in New Issue
Block a user