init: socks5 proxy with tls clienthello fragmentation

This commit is contained in:
ImSavsis 2026-07-19 19:14:43 +05:00
commit a1200d20d1
7 changed files with 349 additions and 0 deletions

219
src/main.cpp Normal file
View file

@ -0,0 +1,219 @@
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <string>
#include <cstdint>
#include "sni_split.h"
#pragma comment(lib, "ws2_32.lib")
struct Socks5Target {
std::string host;
uint16_t port = 0;
};
static void close_sock(SOCKET s) {
if (s != INVALID_SOCKET) closesocket(s);
}
static bool read_exact(SOCKET s, uint8_t* buf, int len) {
int got = 0;
while (got < len) {
int n = recv(s, reinterpret_cast<char*>(buf + got), len - got, 0);
if (n <= 0) return false;
got += n;
}
return true;
}
static bool send_all(SOCKET s, const uint8_t* data, size_t len) {
size_t sent = 0;
while (sent < len) {
int n = send(s, reinterpret_cast<const char*>(data + sent), static_cast<int>(len - sent), 0);
if (n <= 0) return false;
sent += static_cast<size_t>(n);
}
return true;
}
static void relay(SOCKET from, SOCKET to) {
std::vector<char> buf(16384);
while (true) {
int n = recv(from, buf.data(), static_cast<int>(buf.size()), 0);
if (n <= 0) break;
if (!send_all(to, reinterpret_cast<uint8_t*>(buf.data()), static_cast<size_t>(n))) break;
}
shutdown(to, SD_SEND);
}
static bool socks5_handshake(SOCKET client, Socks5Target& target) {
uint8_t hdr[2];
if (!read_exact(client, hdr, 2)) return false;
if (hdr[0] != 0x05) return false;
std::vector<uint8_t> methods(hdr[1]);
if (hdr[1] > 0 && !read_exact(client, methods.data(), hdr[1])) return false;
uint8_t reply[2] = { 0x05, 0x00 };
if (send(client, reinterpret_cast<char*>(reply), 2, 0) != 2) return false;
uint8_t req[4];
if (!read_exact(client, req, 4)) return false;
if (req[0] != 0x05 || req[1] != 0x01) return false;
uint8_t atyp = req[3];
if (atyp == 0x01) {
uint8_t addr[4];
if (!read_exact(client, addr, 4)) return false;
char ipstr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, addr, ipstr, sizeof(ipstr));
target.host = ipstr;
} else if (atyp == 0x03) {
uint8_t len;
if (!read_exact(client, &len, 1)) return false;
std::vector<uint8_t> domain(len);
if (len > 0 && !read_exact(client, domain.data(), len)) return false;
target.host.assign(domain.begin(), domain.end());
} else if (atyp == 0x04) {
uint8_t addr[16];
if (!read_exact(client, addr, 16)) return false;
char ipstr[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, addr, ipstr, sizeof(ipstr));
target.host = ipstr;
} else {
return false;
}
uint8_t portb[2];
if (!read_exact(client, portb, 2)) return false;
target.port = (static_cast<uint16_t>(portb[0]) << 8) | portb[1];
return true;
}
static SOCKET connect_target(const Socks5Target& target) {
addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
addrinfo* res = nullptr;
std::string portstr = std::to_string(target.port);
if (getaddrinfo(target.host.c_str(), portstr.c_str(), &hints, &res) != 0) return INVALID_SOCKET;
SOCKET s = INVALID_SOCKET;
for (addrinfo* p = res; p != nullptr; p = p->ai_next) {
s = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (s == INVALID_SOCKET) continue;
if (connect(s, p->ai_addr, static_cast<int>(p->ai_addrlen)) == 0) break;
closesocket(s);
s = INVALID_SOCKET;
}
freeaddrinfo(res);
return s;
}
static void send_socks5_ok(SOCKET client) {
uint8_t reply[10] = { 0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0 };
send(client, reinterpret_cast<char*>(reply), sizeof(reply), 0);
}
static void send_socks5_fail(SOCKET client) {
uint8_t reply[10] = { 0x05, 0x01, 0x00, 0x01, 0, 0, 0, 0, 0, 0 };
send(client, reinterpret_cast<char*>(reply), sizeof(reply), 0);
}
static void handle_client(SOCKET client) {
Socks5Target target;
if (!socks5_handshake(client, target)) {
close_sock(client);
return;
}
SOCKET upstream = connect_target(target);
if (upstream == INVALID_SOCKET) {
send_socks5_fail(client);
close_sock(client);
return;
}
send_socks5_ok(client);
std::cout << "-> " << target.host << ":" << target.port << std::endl;
std::vector<uint8_t> first(4096);
int n = recv(client, reinterpret_cast<char*>(first.data()), static_cast<int>(first.size()), 0);
if (n > 0) {
size_t split = find_sni_split_point(first.data(), static_cast<size_t>(n));
if (split > 0 && split < static_cast<size_t>(n)) {
send_all(upstream, first.data(), split);
std::this_thread::sleep_for(std::chrono::milliseconds(2));
send_all(upstream, first.data() + split, static_cast<size_t>(n) - split);
} else if (n > 0) {
send_all(upstream, first.data(), static_cast<size_t>(n));
}
}
std::thread t1(relay, client, upstream);
std::thread t2(relay, upstream, client);
t1.join();
t2.join();
close_sock(client);
close_sock(upstream);
}
int main(int argc, char** argv) {
int port = 1080;
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--port" && i + 1 < argc) {
port = std::stoi(argv[++i]);
}
}
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
std::cerr << "WSAStartup failed" << std::endl;
return 1;
}
SOCKET listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listener == INVALID_SOCKET) {
std::cerr << "socket() failed" << std::endl;
WSACleanup();
return 1;
}
sockaddr_in addr{};
addr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
addr.sin_port = htons(static_cast<uint16_t>(port));
if (bind(listener, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == SOCKET_ERROR) {
std::cerr << "bind() failed on port " << port << std::endl;
WSACleanup();
return 1;
}
if (listen(listener, SOMAXCONN) == SOCKET_ERROR) {
std::cerr << "listen() failed" << std::endl;
WSACleanup();
return 1;
}
std::cout << "socks5 proxy on 127.0.0.1:" << port << std::endl;
while (true) {
sockaddr_in client_addr{};
int client_len = sizeof(client_addr);
SOCKET client = accept(listener, reinterpret_cast<sockaddr*>(&client_addr), &client_len);
if (client == INVALID_SOCKET) continue;
std::thread(handle_client, client).detach();
}
closesocket(listener);
WSACleanup();
return 0;
}

66
src/sni_split.cpp Normal file
View file

@ -0,0 +1,66 @@
#include "sni_split.h"
static bool read_u16(const uint8_t* d, size_t len, size_t pos, uint16_t& out) {
if (pos + 2 > len) return false;
out = (uint16_t(d[pos]) << 8) | d[pos + 1];
return true;
}
size_t find_sni_split_point(const uint8_t* data, size_t len) {
if (len < 6 || data[0] != 0x16) return 0;
size_t pos = 5;
if (pos >= len || data[pos] != 0x01) return 0;
pos += 4;
if (pos + 2 + 32 > len) return 0;
pos += 2 + 32;
if (pos >= len) return 0;
uint8_t session_id_len = data[pos];
pos += 1 + session_id_len;
uint16_t cipher_len;
if (!read_u16(data, len, pos, cipher_len)) return 0;
pos += 2 + cipher_len;
if (pos >= len) return 0;
uint8_t comp_len = data[pos];
pos += 1 + comp_len;
uint16_t ext_total_len;
if (!read_u16(data, len, pos, ext_total_len)) return 0;
pos += 2;
size_t ext_end = pos + ext_total_len;
if (ext_end > len) ext_end = len;
while (pos + 4 <= ext_end) {
uint16_t ext_type, ext_len;
if (!read_u16(data, len, pos, ext_type)) break;
if (!read_u16(data, len, pos + 2, ext_len)) break;
size_t ext_data_start = pos + 4;
if (ext_type == 0x0000) {
size_t p = ext_data_start;
uint16_t list_len;
if (!read_u16(data, len, p, list_len)) return 0;
p += 2;
if (p + 3 > len) return 0;
uint8_t name_type = data[p];
uint16_t name_len;
if (name_type != 0 || !read_u16(data, len, p + 1, name_len)) return 0;
size_t name_start = p + 3;
size_t name_end = name_start + name_len;
if (name_end > len || name_len == 0) return 0;
return name_start + (name_len / 2);
}
pos = ext_data_start + ext_len;
}
return 0;
}

5
src/sni_split.h Normal file
View file

@ -0,0 +1,5 @@
#pragma once
#include <cstdint>
#include <cstddef>
size_t find_sni_split_point(const uint8_t* data, size_t len);