commit aa3da9d5c304c97eec5442b2eb1e1971e200edaa Author: Nathan Woodburn Date: Wed Apr 23 17:22:51 2025 +1000 feat: Initial code diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c0de7a7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +obj/ +fireproxy diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d9264a7 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +CC = gcc +CFLAGS = -Wall -Wextra -pthread +LDFLAGS = -lcurl -ljansson + +SRC_DIR = src +OBJ_DIR = obj +BIN_DIR = . + +SRCS = $(wildcard $(SRC_DIR)/*.c) +OBJS = $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRCS)) +TARGET = $(BIN_DIR)/fireproxy + +all: directories $(TARGET) + +directories: + mkdir -p $(OBJ_DIR) + mkdir -p $(BIN_DIR) + +$(TARGET): $(OBJS) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -rf $(OBJ_DIR) $(TARGET) + +.PHONY: all clean directories diff --git a/README.md b/README.md new file mode 100644 index 0000000..fe7e5b1 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# FireProxy - C Proxy Server with DoH Support + +FireProxy is a lightweight HTTP proxy server written in C that intercepts web requests and resolves DNS queries using DNS-over-HTTPS (DoH). + +## Features + +- HTTP proxy functionality +- DNS resolution using DoH (DNS-over-HTTPS) +- Uses hnsdoh.com as the DoH provider +- Multithreaded connection handling + +## Building + +```bash +make +``` + +## Usage + +```bash +./fireproxy [port] +``` + +Default port is 8080 if not specified. + +## Configure Your Browser + +Configure your browser's proxy settings to use localhost with the port you specified when running FireProxy. + +## Dependencies + +- libcurl for HTTP requests +- OpenSSL for HTTPS support +- pthread for multi-threading \ No newline at end of file diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..5469d79 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,97 @@ +# Testing FireProxy + +This document provides instructions for testing the FireProxy server. + +## Building the Proxy + +First, build the proxy server: + +```bash +make clean +make +``` + +## Running the Proxy + +Start the proxy server on port 8080 (or another port of your choice): + +```bash +./fireproxy 8080 +``` + +## Testing with a Web Browser + +### Firefox Configuration + +1. Open Firefox and go to Settings +2. Search for "proxy" and click on "Settings" in the Network Settings section +3. Select "Manual proxy configuration" +4. Set HTTP Proxy to "localhost" and Port to "8080" +5. Leave other proxy fields empty +6. Check "Also use this proxy for HTTPS" +7. Click "OK" + +### Chrome Configuration + +1. Open Chrome and go to Settings +2. Search for "proxy" and click on "Open your computer's proxy settings" +3. Enable proxy settings according to your operating system: + - **Windows**: Set the HTTP proxy to "localhost:8080" + - **macOS**: Set the Web Proxy (HTTP) to "localhost" with port "8080" + - **Linux**: Set the HTTP proxy to "localhost" with port "8080" + +## Testing with cURL + +You can use cURL to test your proxy: + +```bash +# Test HTTP request through proxy +curl -v --proxy http://localhost:8080 http://example.com/ + +# Test HTTPS request through proxy (if supported) +curl -v --proxy http://localhost:8080 https://example.com/ +``` + +## Verifying DoH Functionality + +To verify that your proxy is using the DoH server for DNS resolution: + +1. Run the proxy with increased verbosity (if available) +2. In another terminal, monitor the proxy output while making requests +3. You should see messages indicating DoH lookups to hnsdoh.com +4. The proxy should log the resolved IP addresses + +## Troubleshooting + +### Common Issues + +1. **Connection refused**: Make sure the proxy is running and listening on the configured port +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 + +### Using Network Monitoring Tools + +You can use tools like Wireshark to monitor the traffic: + +```bash +# Capture traffic on loopback interface +sudo tcpdump -i lo port 8080 -vv +``` + +## Performance Testing + +For load testing the proxy: + +```bash +# Install Apache Bench (ab) if not already installed +# Then test with multiple concurrent connections +ab -n 1000 -c 10 -X localhost:8080 http://example.com/ +``` + +## Security Testing + +Since your proxy handles web traffic, consider testing for: + +1. Buffer overflow vulnerabilities using oversized requests +2. Handling of malformed HTTP requests +3. Proper handling of connection termination diff --git a/src/doh.c b/src/doh.c new file mode 100644 index 0000000..90578f3 --- /dev/null +++ b/src/doh.c @@ -0,0 +1,226 @@ +#include "doh.h" +#include +#include +#include +#include +#include +#include + +#define DOH_SERVER "https://hnsdoh.com/dns-query" + +// DNS header structure +typedef struct { + unsigned short id; + unsigned char rd :1; + unsigned char tc :1; + unsigned char aa :1; + unsigned char opcode :4; + unsigned char qr :1; + unsigned char rcode :4; + unsigned char z :3; + unsigned char ra :1; + unsigned short qdcount; + unsigned short ancount; + unsigned short nscount; + unsigned short arcount; +} dns_header; + +// Struct to hold response data +typedef struct { + char* data; + size_t size; +} response_data; + +// Callback function for cURL +static size_t write_callback(void* contents, size_t size, size_t nmemb, void* userp) { + size_t realsize = size * nmemb; + response_data* resp = (response_data*)userp; + + char* ptr = realloc(resp->data, resp->size + realsize + 1); + if (!ptr) { + printf("Error: Failed to allocate memory\n"); + return 0; + } + + resp->data = ptr; + memcpy(&(resp->data[resp->size]), contents, realsize); + resp->size += realsize; + resp->data[resp->size] = 0; + + return realsize; +} + +// Create a DNS query in wire format +void create_dns_query(const char* hostname, char** query, int* query_len) { + // Calculate the size of the query + int hostname_len = strlen(hostname); + int packet_size = sizeof(dns_header) + hostname_len + 2 + 4; // +2 for length bytes, +4 for qtype and qclass + + *query = malloc(packet_size); + if (!*query) { + printf("Error: Failed to allocate memory for DNS query\n"); + *query_len = 0; + return; + } + + // Initialize the entire buffer to 0 + memset(*query, 0, packet_size); + + // Setup DNS header + dns_header* header = (dns_header*)*query; + header->id = htons(12345); // Random ID + header->rd = 1; // Recursion desired + header->qdcount = htons(1); // One question + + // Add the query + char* qname = *query + sizeof(dns_header); + char* hostname_copy = strdup(hostname); + + // Convert hostname to DNS format (length byte + name part) + char* token = strtok(hostname_copy, "."); + char* cursor = qname; + while (token) { + size_t len = strlen(token); + *cursor = len; + cursor++; + memcpy(cursor, token, len); + cursor += len; + token = strtok(NULL, "."); + } + free(hostname_copy); + + // Set the query type and class after the name + char* qinfo = qname + hostname_len + 2; + unsigned short qtype = htons(1); // A record + unsigned short qclass = htons(1); // IN class + + memcpy(qinfo, &qtype, sizeof(qtype)); + memcpy(qinfo + sizeof(qtype), &qclass, sizeof(qclass)); + + *query_len = packet_size; +} + +// Extract an IP address from the DNS response +int extract_ip_from_dns_response(const char* response, int response_len, char* ip_buffer, size_t buffer_size) { + if (response_len < sizeof(dns_header)) { + printf("Error: Response too short\n"); + return 1; + } + + // Skip the header + int offset = sizeof(dns_header); + + // Skip the question section + // First find the end of the domain name + while (offset < response_len && response[offset] != 0) { + offset += response[offset] + 1; + } + offset += 5; // Skip the null byte, qtype, and qclass + + // Now parse the answer section + if (offset + 12 > response_len) { // At least 12 bytes for a DNS RR + printf("Error: Response too short for answer section\n"); + return 1; + } + + // Skip the name pointer in the answer + offset += 2; + + // Read the type + unsigned short type; + memcpy(&type, response + offset, sizeof(type)); + type = ntohs(type); + offset += 2; + + // Skip class + offset += 2; + + // Skip TTL + offset += 4; + + // Read the data length + unsigned short rdlength; + memcpy(&rdlength, response + offset, sizeof(rdlength)); + rdlength = ntohs(rdlength); + offset += 2; + + // Check if it's an A record (type 1) and the length is 4 (IPv4 address) + if (type == 1 && rdlength == 4) { + unsigned char ip_bytes[4]; + memcpy(ip_bytes, response + offset, 4); + + // Convert to string representation + snprintf(ip_buffer, buffer_size, "%d.%d.%d.%d", + ip_bytes[0], ip_bytes[1], ip_bytes[2], ip_bytes[3]); + + return 0; + } + + printf("Error: No valid A record found in response\n"); + return 1; +} + +int resolve_doh(const char* hostname, char* ip_buffer, size_t buffer_size) { + CURL* curl; + CURLcode res; + response_data resp; + resp.data = malloc(1); + resp.size = 0; + + curl = curl_easy_init(); + if (!curl) { + printf("Error: Failed to initialize cURL\n"); + free(resp.data); + return 1; + } + + // Create DNS query in wire format + char* dns_query = NULL; + int query_len = 0; + create_dns_query(hostname, &dns_query, &query_len); + + if (!dns_query || query_len == 0) { + printf("Error: Failed to create DNS query\n"); + curl_easy_cleanup(curl); + free(resp.data); + return 1; + } + + // Set up cURL options for DoH wire format + curl_easy_setopt(curl, CURLOPT_URL, DOH_SERVER); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&resp); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, dns_query); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, query_len); + + // Set the content-type header for DNS wireformat + struct curl_slist* headers = NULL; + headers = curl_slist_append(headers, "Content-Type: application/dns-message"); + headers = curl_slist_append(headers, "Accept: application/dns-message"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + // Perform the request + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + printf("Error: cURL request failed: %s\n", curl_easy_strerror(res)); + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + free(dns_query); + free(resp.data); + return 1; + } + + printf("DoH response received, size: %zu bytes\n", resp.size); + + // Parse the DNS wire format response to extract the IP + int result = extract_ip_from_dns_response(resp.data, resp.size, ip_buffer, buffer_size); + + // Clean up + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + free(dns_query); + free(resp.data); + + return result; +} diff --git a/src/doh.h b/src/doh.h new file mode 100644 index 0000000..7a210dd --- /dev/null +++ b/src/doh.h @@ -0,0 +1,16 @@ +#ifndef DOH_H +#define DOH_H + +#include // Add this to define size_t type + +/** + * Resolves a hostname using DNS-over-HTTPS (DoH) + * + * @param hostname The hostname to resolve + * @param ip_buffer Buffer to store the resulting IP address + * @param buffer_size Size of the IP buffer + * @return 0 on success, non-zero on failure + */ +int resolve_doh(const char* hostname, char* ip_buffer, size_t buffer_size); + +#endif // DOH_H diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..d787286 --- /dev/null +++ b/src/main.c @@ -0,0 +1,28 @@ +#include +#include +#include +#include "proxy.h" + +int main(int argc, char *argv[]) { + int port = 8080; // Default port + + // Check if port was provided as command line argument + if (argc > 1) { + port = atoi(argv[1]); + if (port <= 0 || port > 65535) { + fprintf(stderr, "Invalid port number. Using default port 8080.\n"); + port = 8080; + } + } + + printf("Starting FireProxy on port %d...\n", port); + printf("Using DoH server: https://hnsdoh.com/dns-query\n"); + + // Start the proxy server + if (start_proxy_server(port) != 0) { + fprintf(stderr, "Failed to start proxy server.\n"); + return 1; + } + + return 0; +} diff --git a/src/proxy.c b/src/proxy.c new file mode 100644 index 0000000..c933920 --- /dev/null +++ b/src/proxy.c @@ -0,0 +1,197 @@ +#include "proxy.h" +#include "doh.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_REQUEST_SIZE 8192 +#define MAX_URL_LENGTH 2048 +#define THREAD_POOL_SIZE 20 + +typedef struct { + int client_sock; +} thread_arg_t; + +// Extract hostname from HTTP request +char* extract_host(const char* request) { + static char host[MAX_URL_LENGTH]; + const char* 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) { + host[i] = host_header[i]; + i++; + } + host[i] = '\0'; + return host; + } + + return NULL; +} + +// Handle client connection in a separate thread +void* handle_client(void* arg) { + thread_arg_t* thread_arg = (thread_arg_t*)arg; + int client_sock = thread_arg->client_sock; + free(thread_arg); + + char request[MAX_REQUEST_SIZE]; + char buffer[MAX_REQUEST_SIZE]; + ssize_t bytes_received = recv(client_sock, request, sizeof(request) - 1, 0); + + if (bytes_received <= 0) { + close(client_sock); + return NULL; + } + + request[bytes_received] = '\0'; + + // Extract host from request + char* host = extract_host(request); + if (!host) { + printf("Failed to extract host from request\n"); + close(client_sock); + return NULL; + } + + printf("Proxying request to: %s\n", host); + + // Resolve hostname using DoH + char ip_addr[INET6_ADDRSTRLEN]; + if (resolve_doh(host, ip_addr, sizeof(ip_addr)) != 0) { + printf("Failed to resolve hostname using DoH: %s\n", host); + close(client_sock); + return NULL; + } + + printf("Resolved %s to %s\n", host, ip_addr); + + // Connect to the target server + struct sockaddr_in server_addr; + int server_sock = socket(AF_INET, SOCK_STREAM, 0); + if (server_sock < 0) { + perror("Cannot create socket to server"); + close(client_sock); + return NULL; + } + + 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 + + if (connect(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { + perror("Cannot connect to server"); + close(server_sock); + close(client_sock); + 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; + } + } + + close(server_sock); + close(client_sock); + return NULL; +} + +int start_proxy_server(int port) { + int server_sock, client_sock; + struct sockaddr_in server_addr, client_addr; + socklen_t client_len = sizeof(client_addr); + + // Create socket + server_sock = socket(AF_INET, SOCK_STREAM, 0); + if (server_sock < 0) { + perror("Cannot create socket"); + return 1; + } + + // Allow reuse of address + int opt = 1; + if (setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { + perror("setsockopt failed"); + close(server_sock); + return 1; + } + + // Initialize server address + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_port = htons(port); + + // Bind socket + if (bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { + perror("Bind failed"); + close(server_sock); + return 1; + } + + // Listen for connections + if (listen(server_sock, 10) < 0) { + perror("Listen failed"); + close(server_sock); + return 1; + } + + printf("Proxy server listening on port %d\n", port); + + // Accept and handle client connections + while (1) { + client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &client_len); + if (client_sock < 0) { + perror("Accept failed"); + continue; + } + + printf("New connection from %s:%d\n", + inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); + + // Create thread argument + thread_arg_t* thread_arg = malloc(sizeof(thread_arg_t)); + if (!thread_arg) { + perror("Failed to allocate memory for thread argument"); + close(client_sock); + continue; + } + thread_arg->client_sock = client_sock; + + // Create thread to handle client + pthread_t thread_id; + if (pthread_create(&thread_id, NULL, handle_client, thread_arg) != 0) { + perror("Failed to create thread"); + free(thread_arg); + close(client_sock); + continue; + } + + // Detach thread to allow it to clean up automatically + pthread_detach(thread_id); + } + + close(server_sock); + return 0; +} diff --git a/src/proxy.h b/src/proxy.h new file mode 100644 index 0000000..8909c56 --- /dev/null +++ b/src/proxy.h @@ -0,0 +1,12 @@ +#ifndef PROXY_H +#define PROXY_H + +/** + * Starts the proxy server on the specified port + * + * @param port The port to listen on + * @return 0 on success, non-zero on failure + */ +int start_proxy_server(int port); + +#endif // PROXY_H