feat: Initial code

This commit is contained in:
2025-04-23 17:22:51 +10:00
commit aa3da9d5c3
9 changed files with 640 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
obj/
fireproxy

28
Makefile Normal file
View File

@@ -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

34
README.md Normal file
View File

@@ -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

97
TESTING.md Normal file
View File

@@ -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

226
src/doh.c Normal file
View File

@@ -0,0 +1,226 @@
#include "doh.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#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;
}

16
src/doh.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef DOH_H
#define DOH_H
#include <stddef.h> // 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

28
src/main.c Normal file
View File

@@ -0,0 +1,28 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

197
src/proxy.c Normal file
View File

@@ -0,0 +1,197 @@
#include "proxy.h"
#include "doh.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <netdb.h>
#include <ctype.h>
#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;
}

12
src/proxy.h Normal file
View File

@@ -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