From 2cb85cbc3dd27b011638a3182ea75c815459052e Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 2 Jul 2026 22:04:37 +0300 Subject: [PATCH] =?UTF-8?q?socks=5Fproxy:=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D0=B6=D0=BA=D0=B0=20HTTP-=D0=BF=D1=80=D0=BE=D0=BA=D1=81?= =?UTF-8?q?=D0=B8=20(GET/POST/HEAD/OPTIONS/...)=20=E2=80=94=20=D0=BD=D0=B5?= =?UTF-8?q?=20=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20CONNECT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit process_http_request() теперь обрабатывает любые HTTP-методы: - CONNECT — туннель как раньше, без изменений - GET/POST/PUT/HEAD/OPTIONS/... — парсинг абсолютного URL, DNS, send_connect + пересборка строки с относительным путём + тело DATA test_socks_http_proxy: +3-й worker (http_proxy через -x без --proxytunnel), +проверки POST (echo), HEAD (200 OK), OPTIONS (Allow). Все 78 проверок — PASS. --- src/proxy/socks_proxy.c | 133 ++++++++++++++++++++++++++++------ tests/test_socks_http_proxy.c | 73 +++++++++++++++++-- 2 files changed, 179 insertions(+), 27 deletions(-) diff --git a/src/proxy/socks_proxy.c b/src/proxy/socks_proxy.c index 86fb6b4c..6bac606a 100644 --- a/src/proxy/socks_proxy.c +++ b/src/proxy/socks_proxy.c @@ -186,48 +186,137 @@ error: { } // ==================================================================== -// HTTP CONNECT handshake +// HTTP CONNECT / HTTP proxy handshake // ==================================================================== static void process_http_request(struct socks_proxy_conn* c) { - // Ищем "\r\n\r\n" или первую "\r\n" для строки CONNECT - char* end = memmem(c->buf, c->buf_len, "\r\n\r\n", 4); - if (!end) { end = memmem(c->buf, c->buf_len, "\r\n", 2); if (!end) return; } + char* line_end = memmem(c->buf, c->buf_len, "\r\n", 2); + if (!line_end) return; - uint16_t line_len = (uint16_t)((uint8_t*)end - c->buf); + uint16_t line_len = (uint16_t)((uint8_t*)line_end - c->buf); if (line_len < 8) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: http request too short"); goto error; } - // Парсим "CONNECT host:port HTTP/1.1" char line[512]; if (line_len > sizeof(line) - 1) line_len = sizeof(line) - 1; memcpy(line, c->buf, line_len); line[line_len] = '\0'; - char host_port[256]; - if (sscanf(line, "CONNECT %255s HTTP/", host_port) != 1) { - DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad http connect: %s", line); + // ===== CONNECT ===== + if (strncmp(line, "CONNECT ", 8) == 0) { + char host_port[256]; + if (sscanf(line, "CONNECT %255s HTTP/", host_port) != 1) { + DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad http connect: %s", line); + goto error; + } + + char* colon = strrchr(host_port, ':'); + if (!colon) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: no port in %s", host_port); goto error; } + *colon = '\0'; int port = atoi(colon + 1); + if (port <= 0 || port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad port %d", port); goto error; } + + struct hostent* he = gethostbyname(host_port); + if (!he || he->h_addrtype != AF_INET) { + DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: DNS failed for %s", host_port); + uint8_t resp[] = "HTTP/1.1 502 Bad Gateway\r\n\r\n"; + write_to_client(c, resp, (uint16_t)strlen((char*)resp)); tcp_conn_push_close(c->tc); return; + } + memcpy(c->dest_ip, he->h_addr, 4); c->dest_port = htons((uint16_t)port); + DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: HTTP CONNECT %s → %d.%d.%d.%d:%d", + host_port, c->dest_ip[0], c->dest_ip[1], c->dest_ip[2], c->dest_ip[3], port); + + c->buf_len = 0; + c->state = HTTP_STATE_RELAY; + uint8_t resp[] = "HTTP/1.1 200 Connection Established\r\n\r\n"; + write_to_client(c, resp, (uint16_t)strlen((char*)resp)); + if (send_connect(c) < 0) { tcp_conn_push_close(c->tc); } + return; + } + + // ===== Не-CONNECT: HTTP-прокси (GET, POST, PUT, HEAD, OPTIONS, ...) ===== + char* hdr_end = memmem(c->buf, c->buf_len, "\r\n\r\n", 4); + if (!hdr_end) return; + + uint16_t headers_len = (uint16_t)((uint8_t*)hdr_end - c->buf) + 4; + + char method[16] = {0}, url[512] = {0}; + if (sscanf(line, "%15s %511s", method, url) < 2) { + DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad http request line: %s", line); + goto error; + } + + if (strncmp(url, "http://", 7) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: not an absolute http URL: %s", url); goto error; } - char* colon = strrchr(host_port, ':'); - if (!colon) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: no port in %s", host_port); goto error; } - *colon = '\0'; int port = atoi(colon + 1); - if (port <= 0 || port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad port %d", port); goto error; } + char host[256]; + int port = 80; + const char* host_start = url + 7; + const char* path_start = strchr(host_start, '/'); + const char* col = NULL; + for (const char* p = host_start; (path_start ? p < path_start : *p); p++) { + if (*p == ':') { col = p; break; } + } - struct hostent* he = gethostbyname(host_port); + if (col && (!path_start || col < path_start)) { + size_t hlen = (size_t)(col - host_start); + if (hlen >= sizeof(host)) goto error; + memcpy(host, host_start, hlen); host[hlen] = '\0'; + port = atoi(col + 1); + if (port <= 0 || port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad port %d in %s", port, url); goto error; } + } else if (path_start) { + size_t hlen = (size_t)(path_start - host_start); + if (hlen >= sizeof(host)) goto error; + memcpy(host, host_start, hlen); host[hlen] = '\0'; + } else { + size_t hlen = strlen(host_start); + if (hlen >= sizeof(host)) goto error; + memcpy(host, host_start, hlen); host[hlen] = '\0'; + } + + if (host[0] == '\0') { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: empty host in %s", url); goto error; } + if (!path_start || *path_start == '\0') path_start = "/"; + + struct hostent* he = gethostbyname(host); if (!he || he->h_addrtype != AF_INET) { - DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: DNS failed for %s", host_port); + DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: DNS failed for %s", host); uint8_t resp[] = "HTTP/1.1 502 Bad Gateway\r\n\r\n"; write_to_client(c, resp, (uint16_t)strlen((char*)resp)); tcp_conn_push_close(c->tc); return; } memcpy(c->dest_ip, he->h_addr, 4); c->dest_port = htons((uint16_t)port); - DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: HTTP CONNECT %s → %d.%d.%d.%d:%d", - host_port, c->dest_ip[0], c->dest_ip[1], c->dest_ip[2], c->dest_ip[3], port); - c->buf_len = 0; - c->state = HTTP_STATE_RELAY; // reply immediately, no waiting - uint8_t resp[] = "HTTP/1.1 200 Connection Established\r\n\r\n"; - write_to_client(c, resp, (uint16_t)strlen((char*)resp)); + DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: HTTP %s %s:%d%s → %d.%d.%d.%d:%d", + method, host, port, path_start, + c->dest_ip[0], c->dest_ip[1], c->dest_ip[2], c->dest_ip[3], port); + if (send_connect(c) < 0) { - tcp_conn_push_close(c->tc); + DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: send_connect failed for %s", host); + uint8_t resp[] = "HTTP/1.1 502 Bad Gateway\r\n\r\n"; + write_to_client(c, resp, (uint16_t)strlen((char*)resp)); tcp_conn_push_close(c->tc); return; + } + + const char* version = strstr(line, " HTTP/"); + char version_str[16] = " HTTP/1.1"; + if (version) { + size_t vlen = strlen(version); + if (vlen >= sizeof(version_str)) vlen = sizeof(version_str) - 1; + memcpy(version_str, version, vlen + 1); + } + + uint16_t rest_off = line_len + 2; + uint16_t rest_len = headers_len - rest_off; + uint8_t hdr_buf[2048]; + int hdr_n = snprintf((char*)hdr_buf, sizeof(hdr_buf), "%s %s%s\r\n", method, path_start, version_str); + if (hdr_n < 0 || (size_t)hdr_n + rest_len > sizeof(hdr_buf)) { + DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: header reconstruction overflow hdr_n=%d rest=%u", hdr_n, rest_len); + goto error; } + memcpy(hdr_buf + hdr_n, c->buf + rest_off, rest_len); + send_data(c, hdr_buf, (uint16_t)(hdr_n + rest_len)); + + if (headers_len < c->buf_len) { + send_data(c, c->buf + headers_len, c->buf_len - headers_len); + } + + c->buf_len = 0; + c->state = HTTP_STATE_RELAY; return; error: { diff --git a/tests/test_socks_http_proxy.c b/tests/test_socks_http_proxy.c index 928d8c17..6ee3a493 100644 --- a/tests/test_socks_http_proxy.c +++ b/tests/test_socks_http_proxy.c @@ -29,7 +29,7 @@ #include "../src/tun_if.h" #define TIMEOUT_MS 120000 -#define WORKERS 2 +#define WORKERS 3 #define REQS_PER_WORKER 25 static const int file_sizes[] = { 1024, 10*1024, 50*1024, 100*1024 }; @@ -68,11 +68,38 @@ static void gen_file(const char* path, int size, int seed) { } static int start_http_server(int port, const char* dir) { + char script[512]; snprintf(script, sizeof(script), "%s/_srv.py", dir); + FILE* sf = fopen(script, "w"); + if (!sf) return -1; + fprintf(sf, + "import sys,os\n" + "from http.server import HTTPServer,SimpleHTTPRequestHandler\n" + "class H(SimpleHTTPRequestHandler):\n" + " def do_POST(self):\n" + " if self.path=='/echo':\n" + " n=int(self.headers.get('Content-Length',0))\n" + " b=self.rfile.read(n)\n" + " self.send_response(200)\n" + " self.send_header('Content-Type','application/octet-stream')\n" + " self.send_header('Content-Length',str(len(b)))\n" + " self.end_headers();self.wfile.write(b)\n" + " else:self.send_response(405);self.end_headers()\n" + " def do_OPTIONS(self):\n" + " self.send_response(200)\n" + " self.send_header('Allow','GET,HEAD,POST,OPTIONS')\n" + " self.send_header('Content-Length','0')\n" + " self.end_headers()\n" + "port=int(sys.argv[1])\n" + "os.chdir(sys.argv[2])\n" + "HTTPServer(('',port),H).serve_forever()\n" + ); + fclose(sf); + pid_t pid = fork(); if (pid < 0) return -1; if (pid == 0) { char port_str[16]; snprintf(port_str, sizeof(port_str), "%d", port); - execlp("python3", "python3", "-m", "http.server", port_str, "--directory", dir, (char*)NULL); + execlp("python3", "python3", script, port_str, dir, (char*)NULL); _exit(1); } g_http_pid = pid; @@ -134,8 +161,10 @@ static int worker_main(const char* type, const char* proxy, const char* url_base snprintf(url, sizeof(url), "%s/%s", url_base, file_names[fi]); if (strcmp(type, "socks") == 0) snprintf(cmd, sizeof(cmd), "curl -s --connect-timeout 10 --max-time 30 --socks5-hostname %s -o %s %s 2>/dev/null", proxy, out, url); - else + else if (strcmp(type, "http_connect") == 0) snprintf(cmd, sizeof(cmd), "curl -s --connect-timeout 10 --max-time 30 --proxytunnel -x %s -o %s %s 2>/dev/null", proxy, out, url); + else + snprintf(cmd, sizeof(cmd), "curl -s --connect-timeout 10 --max-time 30 -x %s -o %s %s 2>/dev/null", proxy, out, url); int rc = system(cmd); if (rc != 0) { fprintf(stderr, "[FAIL] worker %s %s iter %d curl exit %d\n", type, file_names[fi], i, WEXITSTATUS(rc)); return 1; } @@ -146,6 +175,38 @@ static int worker_main(const char* type, const char* proxy, const char* url_base unlink(out); } } + if (strcmp(type, "http_proxy") == 0) { + char cmd[1024], out[512], f1[512]; + int seed = (int)getpid() ^ 0x55; + + // POST /echo — echo body back, compare + snprintf(f1, sizeof(f1), "%s/post_%d.bin", tmpdir, (int)getpid()); + gen_file(f1, 512, seed); + snprintf(out, sizeof(out), "%s/post_out_%d.bin", tmpdir, (int)getpid()); + snprintf(cmd, sizeof(cmd), + "curl -s --connect-timeout 10 --max-time 10 -x %s --data-binary @%s -o %s %s/echo 2>/dev/null", + proxy, f1, out, url_base); + if (system(cmd) != 0) { fprintf(stderr, "[FAIL] http_proxy POST curl\n"); unlink(f1); return 1; } + snprintf(cmd, sizeof(cmd), "cmp -s %s %s", out, f1); + if (system(cmd) != 0) { fprintf(stderr, "[FAIL] http_proxy POST cmp\n"); unlink(f1); unlink(out); return 1; } + unlink(f1); unlink(out); + + // HEAD /f_1k.bin — check status 200 + snprintf(cmd, sizeof(cmd), + "curl -s -I --connect-timeout 10 --max-time 10 -x %s %s/f_1k.bin 2>/dev/null | head -1 | grep -q '200 OK'", + proxy, url_base); + if (system(cmd) != 0) { fprintf(stderr, "[FAIL] http_proxy HEAD 200\n"); return 1; } + + // OPTIONS /f_1k.bin — check Allow header + snprintf(out, sizeof(out), "%s/opt_%d.txt", tmpdir, (int)getpid()); + snprintf(cmd, sizeof(cmd), + "curl -s -i --connect-timeout 10 --max-time 10 -x %s -X OPTIONS -o %s %s/f_1k.bin 2>/dev/null", + proxy, out, url_base); + if (system(cmd) != 0) { fprintf(stderr, "[FAIL] http_proxy OPTIONS curl\n"); return 1; } + snprintf(cmd, sizeof(cmd), "grep -q 'Allow:' %s", out); + if (system(cmd) != 0) { fprintf(stderr, "[FAIL] http_proxy OPTIONS Allow\n"); unlink(out); return 1; } + unlink(out); + } return 0; } @@ -161,11 +222,13 @@ static void monitor(void* arg) { char socks_proxy[64]; snprintf(socks_proxy, sizeof(socks_proxy), "127.0.0.1:%d", g_socks_port); char http_proxy[64]; snprintf(http_proxy, sizeof(http_proxy), "http://127.0.0.1:%d", g_http_proxy_port); + const char* worker_types[] = { "socks", "http_connect", "http_proxy" }; + const char* worker_proxies[] = { socks_proxy, http_proxy, http_proxy }; for (int i = 0; i < WORKERS; i++) { pid_t pid = fork(); if (pid < 0) { printf("[FAIL] fork worker %d: %s\n", i, strerror(errno)); g_done = -1; return; } if (pid == 0) { - int rc = worker_main(i < WORKERS/2 ? "socks" : "http", i < WORKERS/2 ? socks_proxy : http_proxy, url, g_tmpdir); + int rc = worker_main(worker_types[i], worker_proxies[i], url, g_tmpdir); _exit(rc); } g_workers[i] = pid; @@ -242,7 +305,7 @@ int main(void) { if (to_id) uasync_cancel_timeout(g_ua, to_id); - if (g_ok) printf("[PASS] test_socks_http_proxy — %d reqs, %d workers, up to 100KB\n", WORKERS * REQS_PER_WORKER, WORKERS); + if (g_ok) printf("[PASS] test_socks_http_proxy — %d reqs, %d workers (socks/http_connect/http_proxy), up to 100KB\n", WORKERS * REQS_PER_WORKER, WORKERS); else printf("[FAIL] test_socks_http_proxy\n"); cleanup: