基于隧道技术的HTTPS代理实现疑问及C语言开发需求
HTTPS代理隧道实现详解(基于C语言)
核心疑问解答
代理如何获取HTTPS目标服务器的IP?
HTTPS隧道建立的第一步是客户端向代理发送明文的HTTP CONNECT请求,格式类似:CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\n\r\n。代理可以从这个明文请求里提取目标主机和端口,再通过DNS解析获取IP,不需要解密后续的HTTPS流量。无法控制客户端的情况下,代理如何适配?
标准HTTPS客户端(比如浏览器、curl)在使用HTTP代理时,会自动发送CONNECT请求来建立隧道,你只需要在代理中增加对CONNECT方法的处理逻辑即可,不需要修改客户端。加密的HTTPS数据包如何转发?
隧道建立完成后,代理只需要做透明的双向数据转发:把客户端发来的加密数据直接转发给目标服务器,把目标服务器返回的加密数据直接转发给客户端,全程不需要解密HTTPS流量。
修改后的完整C语言代码
#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 <netdb.h> #include <pthread.h> #define PROXY_PORT 8080 #define BLACKLISTED_URL "example.com" // 双向转发数据的线程函数 void* forward_data(void* args) { int* fds = (int*)args; int from_fd = fds[0]; int to_fd = fds[1]; char buffer[4096]; ssize_t bytes_read, bytes_written; while ((bytes_read = recv(from_fd, buffer, sizeof(buffer), 0)) > 0) { bytes_written = send(to_fd, buffer, bytes_read, 0); if (bytes_written < 0) { perror("Failed to send data"); break; } } close(from_fd); close(to_fd); free(args); return NULL; } void handle_client(int client_socket) { char request[4096]; ssize_t bytes_received = recv(client_socket, request, sizeof(request)-1, 0); if (bytes_received <= 0) { perror("Failed to receive request"); close(client_socket); return; } request[bytes_received] = '\0'; printf("Received request:\n%s\n", request); // 检查是否是CONNECT请求 if (strncmp(request, "CONNECT ", 8) == 0) { // 提取目标主机和端口 char* target_start = request + 8; char* target_end = strstr(target_start, " HTTP/"); if (target_end == NULL) { const char* bad_response = "HTTP/1.1 400 Bad Request\r\n\r\n"; send(client_socket, bad_response, strlen(bad_response), 0); close(client_socket); return; } size_t target_len = target_end - target_start; char* target = malloc(target_len + 1); if (target == NULL) { perror("Failed to allocate memory"); close(client_socket); return; } strncpy(target, target_start, target_len); target[target_len] = '\0'; // 分割主机和端口,默认443 char* port_str = strchr(target, ':'); char* host = target; int port = 443; if (port_str != NULL) { *port_str = '\0'; port = atoi(port_str + 1); if (port <= 0 || port > 65535) { port = 443; } } // 黑名单检查 if (strstr(host, BLACKLISTED_URL) != NULL) { const char* forbidden_response = "HTTP/1.1 403 Forbidden\r\nContent-Length: 19\r\n\r\nAccess Denied: URL blocked\r\n"; send(client_socket, forbidden_response, strlen(forbidden_response), 0); free(target); close(client_socket); return; } // 解析目标主机IP struct addrinfo hints = {0}; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; struct addrinfo* addr = NULL; int ret = getaddrinfo(host, NULL, &hints, &addr); if (ret != 0) { perror("Failed to resolve host"); const char* error_response = "HTTP/1.1 502 Bad Gateway\r\n\r\n"; send(client_socket, error_response, strlen(error_response), 0); free(target); close(client_socket); return; } // 建立到目标服务器的连接 int target_socket = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in target_addr = *(struct sockaddr_in*)addr->ai_addr; target_addr.sin_port = htons(port); freeaddrinfo(addr); if (connect(target_socket, (struct sockaddr*)&target_addr, sizeof(target_addr)) < 0) { perror("Failed to connect to target server"); const char* error_response = "HTTP/1.1 502 Bad Gateway\r\n\r\n"; send(client_socket, error_response, strlen(error_response), 0); free(target); close(target_socket); close(client_socket); return; } // 返回连接建立成功的响应 const char* connect_response = "HTTP/1.1 200 Connection Established\r\n\r\n"; send(client_socket, connect_response, strlen(connect_response), 0); // 创建两个线程分别转发客户端<->目标服务器的数据 pthread_t client_to_target, target_to_client; int* args1 = malloc(2 * sizeof(int)); int* args2 = malloc(2 * sizeof(int)); args1[0] = client_socket; args1[1] = target_socket; args2[0] = target_socket; args2[1] = client_socket; pthread_create(&client_to_target, NULL, forward_data, args1); pthread_create(&target_to_client, NULL, forward_data, args2); // 等待线程结束 pthread_join(client_to_target, NULL); pthread_join(target_to_client, NULL); free(target); return; } else { // 原有的HTTP代理逻辑 char* hostStart = strstr(request, "Host:"); if (hostStart == NULL) { printf("No Host field found in the request\n"); close(client_socket); return; } hostStart += strlen("Host: "); char* hostEnd = strchr(hostStart, '\n'); if (hostEnd == NULL) { printf("No end of line found after the Host field\n"); close(client_socket); return; } size_t urlLength = hostEnd - hostStart; char* url = malloc(urlLength + 1); if (url == NULL) { perror("Error allocating memory for URL"); close(client_socket); return; } strncpy(url, hostStart, urlLength); url[urlLength] = '\0'; url[strcspn(url, "\r\n")] = '\0'; printf("Extracted URL: %s\n", url); if (strstr(url, BLACKLISTED_URL) != NULL) { printf("URL blocked: %s\n", BLACKLISTED_URL); const char* response = "HTTP/1.1 403 Forbidden\r\nContent-Length: 19\r\n\r\nAccess Denied: URL blocked\r\n"; send(client_socket, response, strlen(response), 0); free(url); close(client_socket); return; } struct addrinfo hints = {0}; hints.ai_flags = AI_NUMERICHOST; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; struct addrinfo *addr = NULL; struct sockaddr_in target; int ret = getaddrinfo(url, NULL, &hints, &addr); if (ret == EAI_NONAME) { hints.ai_flags = 0; ret = getaddrinfo(url, NULL, &hints, &addr); } if (ret != 0) { perror("Failed to resolve host"); const char* error_response = "HTTP/1.1 502 Bad Gateway\r\n\r\n"; send(client_socket, error_response, strlen(error_response), 0); free(url); close(client_socket); return; } target = *(struct sockaddr_in*)(addr->ai_addr); freeaddrinfo(addr); int target_socket = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in target_addr; target_addr.sin_family = AF_INET; target_addr.sin_port = htons(80); target_addr.sin_addr.s_addr = target.sin_addr.s_addr; if (connect(target_socket, (struct sockaddr*)&target_addr, sizeof(target_addr)) < 0) { perror("Error connecting to the target server"); free(url); close(target_socket); close(client_socket); return; } send(target_socket, request, bytes_received, 0); char response_buffer[4096]; ssize_t bytes_sent; while ((bytes_received = recv(target_socket, response_buffer, sizeof(response_buffer), 0)) > 0) { bytes_sent = send(client_socket, response_buffer, bytes_received, 0); if (bytes_sent < 0) { perror("Error sending response to the client"); break; } } free(url); close(client_socket); close(target_socket); } } int main() { int proxy_socket = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in proxy_addr; proxy_addr.sin_family = AF_INET; proxy_addr.sin_port = htons(PROXY_PORT); proxy_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡,不止127.0.0.1 int opt = 1; if (setsockopt(proxy_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { perror("Failed to set socket options"); return 1; } if (bind(proxy_socket, (struct sockaddr*)&proxy_addr, sizeof(proxy_addr)) < 0) { perror("Error binding to the proxy port"); return 1; } if (listen(proxy_socket, 10) < 0) { perror("Error listening on the proxy socket"); return 1; } printf("Proxy server listening on port %d...\n", PROXY_PORT); while (1) { struct sockaddr_in client_addr; socklen_t client_addr_len = sizeof(client_addr); int client_socket = accept(proxy_socket, (struct sockaddr*)&client_addr, &client_addr_len); if (client_socket < 0) { perror("Error accepting client connection"); continue; } printf("Connection accepted from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); // 创建线程处理客户端请求,避免阻塞主循环 pthread_t client_thread; int* socket_ptr = malloc(sizeof(int)); *socket_ptr = client_socket; pthread_create(&client_thread, NULL, (void*)handle_client, socket_ptr); pthread_detach(client_thread); // 分离线程,自动释放资源 } close(proxy_socket); return 0; }
代码关键修改说明
- 新增CONNECT请求处理逻辑:解析目标主机和端口,建立到目标服务器的连接,返回
200 Connection Established响应。 - 实现双向数据转发:使用线程分别处理客户端到目标服务器、目标服务器到客户端的数据流,避免单线程阻塞。
- 优化主循环:用线程处理每个客户端连接,主循环可以继续接受新连接。
- 保留原有HTTP代理逻辑:兼容原有的HTTP请求转发功能。
内容的提问来源于stack exchange,提问作者MT 16
相关产品推荐
相关产品推荐

