917 lines
46 KiB
C
917 lines
46 KiB
C
// iocp_s5.c : SOCKS5 bridge using IOCP + CNG (no libevent/openssl)
|
||
// Build (MSVC): cl /O2 /DNDEBUG /MT /utf-8 iocp_s5.c /link /MACHINE:X64 /OPT:REF /OPT:ICF ws2_32.lib mswsock.lib bcrypt.lib
|
||
|
||
#define _CRT_SECURE_NO_WARNINGS
|
||
#ifndef _WIN32_WINNT
|
||
#define _WIN32_WINNT 0x0A00 // Windows 10+
|
||
#endif
|
||
|
||
#include <winsock2.h>
|
||
#include <mswsock.h>
|
||
#include <ws2tcpip.h>
|
||
#include <bcrypt.h>
|
||
#include <windows.h>
|
||
#include <stdint.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <stdbool.h>
|
||
#include <time.h>
|
||
#include <stdarg.h>
|
||
#include <string.h>
|
||
|
||
#pragma comment(lib, "Ws2_32.lib")
|
||
#pragma comment(lib, "Mswsock.lib")
|
||
#pragma comment(lib, "Bcrypt.lib")
|
||
|
||
// -------------------- logging --------------------
|
||
static volatile LONG g_log_level = 2; // 0=quiet, 1=info, 2=debug
|
||
|
||
static void logf_(const char* lvl, const char* fmt, ...) {
|
||
SYSTEMTIME st; GetLocalTime(&st);
|
||
DWORD tid = GetCurrentThreadId();
|
||
fprintf(stderr, "[%02u:%02u:%02u.%03u][%s][T%lu] ",
|
||
st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, lvl, (unsigned long)tid);
|
||
va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap);
|
||
fputc('\n', stderr);
|
||
fflush(stderr);
|
||
}
|
||
#define LOGI(...) do{ if(g_log_level>=1) logf_("I", __VA_ARGS__); }while(0)
|
||
#define LOGD(...) do{ if(g_log_level>=2) logf_("D", __VA_ARGS__); }while(0)
|
||
#define LOGE(...) do{ if(g_log_level>=0) logf_("E", __VA_ARGS__); }while(0)
|
||
|
||
// -------------------- compatibility --------------------
|
||
#define CLOSESOCK(s) closesocket((s))
|
||
#define WOULD_BLOCK(e) ((e)==WSAEWOULDBLOCK || (e)==WSAEINTR)
|
||
#define strcasecmp _stricmp
|
||
|
||
// -------------------- constants --------------------
|
||
#define RECV_BUF 8192
|
||
#define UDP_MAP_CAP 128
|
||
#define DOMAIN_MAX 255
|
||
#define DNS_CACHE_SIZE 256
|
||
#define MAX_PENDING_ACCEPT 64
|
||
#define MAX_WORKERS 0 // 0 = system default
|
||
|
||
// -------------------- domains list --------------------
|
||
static const char *k_domains[] = {"google.com", "youtube.com", "github.com"};
|
||
static const size_t k_domains_cnt = 3;
|
||
|
||
static bool ends_with(const char *s, const char *suffix) {
|
||
size_t ls = strlen(s), lt = strlen(suffix);
|
||
return lt <= ls && strcasecmp(s + (ls - lt), suffix) == 0;
|
||
}
|
||
static bool host_in_domain_list(const char *host) {
|
||
for (size_t i = 0; i < k_domains_cnt; ++i) if (ends_with(host, k_domains[i])) return true;
|
||
return false;
|
||
}
|
||
|
||
// -------------------- resolve helpers --------------------
|
||
static bool resolve_host_port(const char *host, uint16_t port, struct sockaddr_storage *ss, int *sslen) {
|
||
struct addrinfo hints, *res = NULL;
|
||
char portstr[8]; _snprintf(portstr, sizeof(portstr), "%u", (unsigned)port);
|
||
memset(&hints, 0, sizeof(hints));
|
||
hints.ai_socktype = SOCK_STREAM;
|
||
hints.ai_family = AF_UNSPEC;
|
||
int rc = getaddrinfo(host, portstr, &hints, &res);
|
||
if (rc != 0 || !res) { if (res) freeaddrinfo(res); LOGE("resolve_host_port('%s',%u) failed: %d", host, (unsigned)port, rc); return false; }
|
||
memcpy(ss, res->ai_addr, (int)res->ai_addrlen);
|
||
*sslen = (int)res->ai_addrlen;
|
||
freeaddrinfo(res);
|
||
return true;
|
||
}
|
||
static int host_literal_ip(const char *host, uint8_t *packed16, size_t *packed_len) {
|
||
IN_ADDR a4; IN6_ADDR a6;
|
||
if (InetPtonA(AF_INET, host, &a4) == 1) { memcpy(packed16, &a4, 4); *packed_len = 4; return AF_INET; }
|
||
if (InetPtonA(AF_INET6, host, &a6) == 1) { memcpy(packed16, &a6, 16); *packed_len = 16; return AF_INET6; }
|
||
return 0;
|
||
}
|
||
static void sockaddr_to_string(const SOCKADDR* sa, int salen, char* out, size_t cap) {
|
||
out[0] = 0; if (!sa) return;
|
||
if (sa->sa_family == AF_INET) {
|
||
const SOCKADDR_IN* s = (const SOCKADDR_IN*)sa;
|
||
char ip[64]; InetNtopA(AF_INET, (void*)&s->sin_addr, ip, sizeof(ip));
|
||
_snprintf(out, cap, "%s:%u", ip, (unsigned)ntohs(s->sin_port));
|
||
} else if (sa->sa_family == AF_INET6) {
|
||
const SOCKADDR_IN6* s6 = (const SOCKADDR_IN6*)sa;
|
||
char ip[128]; InetNtopA(AF_INET6, (void*)&s6->sin6_addr, ip, sizeof(ip));
|
||
_snprintf(out, cap, "[%s]:%u", ip, (unsigned)ntohs(s6->sin6_port));
|
||
} else {
|
||
_snprintf(out, cap, "af=%d", sa->sa_family);
|
||
}
|
||
}
|
||
|
||
// -------------------- config --------------------
|
||
typedef struct {
|
||
char remote_host[64];
|
||
int remote_port;
|
||
char password[64];
|
||
char mx_head[128];
|
||
char listen_host[16];
|
||
int listen_port;
|
||
int recv_buf;
|
||
int connect_timeout; // seconds
|
||
int udp_timeout; // seconds
|
||
int verbose; // 0/1
|
||
} config_t;
|
||
|
||
static void config_default(config_t *cfg) {
|
||
memset(cfg, 0, sizeof(*cfg));
|
||
strcpy(cfg->remote_host, "121.14.152.149");
|
||
cfg->remote_port = 10004;
|
||
strcpy(cfg->password, "dwz1GtF7");
|
||
strcpy(cfg->mx_head, "com.win64.oppc.game.common:22021709,102024080020541279");
|
||
strcpy(cfg->listen_host, "0.0.0.0");
|
||
cfg->listen_port = 10807;
|
||
cfg->recv_buf = RECV_BUF;
|
||
cfg->connect_timeout = 10;
|
||
cfg->udp_timeout = 180;
|
||
cfg->verbose = 1;
|
||
}
|
||
// 全局:是否所有域名都走 DNS-over-tunnel
|
||
static int g_dns_always = 0;
|
||
|
||
static void parse_args(int argc, char **argv, config_t *cfg) {
|
||
config_default(cfg);
|
||
for (int i = 1; i < argc; ++i) {
|
||
if (!strcmp(argv[i], "--remote-host") && i+1 < argc) strncpy(cfg->remote_host, argv[++i], sizeof(cfg->remote_host)-1);
|
||
else if (!strcmp(argv[i], "--remote-port") && i+1 < argc) cfg->remote_port = atoi(argv[++i]);
|
||
else if (!strcmp(argv[i], "--password") && i+1 < argc) strncpy(cfg->password, argv[++i], sizeof(cfg->password)-1);
|
||
else if (!strcmp(argv[i], "--mx") && i+1 < argc) strncpy(cfg->mx_head, argv[++i], sizeof(cfg->mx_head)-1);
|
||
else if (!strcmp(argv[i], "--listen") && i+1 < argc) strncpy(cfg->listen_host, argv[++i], sizeof(cfg->listen_host)-1);
|
||
else if (!strcmp(argv[i], "--port") && i+1 < argc) cfg->listen_port = atoi(argv[++i]);
|
||
else if (!strcmp(argv[i], "--dns-always")) g_dns_always = 1;
|
||
else if (!strcmp(argv[i], "--verbose")) { cfg->verbose = 1; g_log_level = 2; }
|
||
else if (!strcmp(argv[i], "--quiet")) { cfg->verbose = 0; g_log_level = 0; }
|
||
else if (!strcmp(argv[i], "--log") && i+1 < argc) { g_log_level = atoi(argv[++i]); cfg->verbose = (g_log_level>0); }
|
||
}
|
||
}
|
||
|
||
// -------------------- small helpers --------------------
|
||
typedef struct { char* data; size_t len, cap; } dynbuf_t;
|
||
static void dbuf_init(dynbuf_t* b) { b->data=NULL; b->len=0; b->cap=0; }
|
||
static void dbuf_free(dynbuf_t* b) { if(b->data) free(b->data); b->data=NULL; b->len=b->cap=0; }
|
||
static bool dbuf_reserve(dynbuf_t* b, size_t need){ if(b->cap-b->len>=need) return true; size_t nc=b->cap?b->cap:1024; while(nc-b->len<need) nc<<=1; char* p=(char*)realloc(b->data,nc); if(!p) return false; b->data=p; b->cap=nc; return true; }
|
||
static bool dbuf_append(dynbuf_t* b, const void* p, size_t n){ if(!dbuf_reserve(b,n)) return false; memcpy(b->data+b->len,p,n); b->len+=n; return true; }
|
||
static void dbuf_consume(dynbuf_t* b, size_t n){ if(n>=b->len){b->len=0;return;} memmove(b->data,b->data+n,b->len-n); b->len-=n; }
|
||
static void log_preview(const char* tag, const uint8_t* buf, size_t n) {
|
||
char out[200]; size_t m = n<80? n:80; size_t j=0;
|
||
for (size_t i=0;i<m && j<sizeof(out)-1; ++i) {
|
||
unsigned char c = buf[i];
|
||
out[j++] = (c>=32 && c<127)? c : '.';
|
||
}
|
||
out[j]=0;
|
||
LOGD("%s [%zu]: %s", tag, n, out);
|
||
}
|
||
|
||
// -------------------- socks5 --------------------
|
||
typedef struct { uint8_t cmd, atyp; char host[128]; uint16_t port; } socks5_req_t;
|
||
|
||
static int parse_socks5_greeting(const uint8_t* p, size_t len, size_t* consumed) {
|
||
if (len < 2) return 0;
|
||
if (p[0] != 5) return -2;
|
||
size_t need = 2 + p[1];
|
||
if (len < need) return 0;
|
||
*consumed = need;
|
||
return 1;
|
||
}
|
||
static int parse_socks5_request(const uint8_t* p, size_t len, socks5_req_t *req, size_t* consumed) {
|
||
if (len < 4) return 0;
|
||
if (p[0] != 5) return -2;
|
||
req->cmd = p[1];
|
||
req->atyp = p[3];
|
||
size_t off = 4;
|
||
if (req->atyp == 0x01) {
|
||
if (len < off + 4 + 2) return 0;
|
||
char ip[INET_ADDRSTRLEN]; InetNtopA(AF_INET,(void*)(p+off),ip,sizeof(ip));
|
||
strncpy(req->host, ip, sizeof(req->host)-1); req->host[127]=0;
|
||
off += 4; req->port = ntohs(*(uint16_t*)(p + off)); off += 2;
|
||
} else if (req->atyp == 0x03) {
|
||
if (len < off + 1) return 0;
|
||
uint8_t l = p[off++]; if (len < off + l + 2) return 0;
|
||
size_t cplen = (l > 127) ? 127 : l;
|
||
memcpy(req->host, p + off, cplen); req->host[cplen]=0;
|
||
off += l; req->port = ntohs(*(uint16_t*)(p + off)); off += 2;
|
||
} else if (req->atyp == 0x04) {
|
||
if (len < off + 16 + 2) return 0;
|
||
char ip6[INET6_ADDRSTRLEN]; InetNtopA(AF_INET6,(void*)(p+off),ip6,sizeof(ip6));
|
||
strncpy(req->host, ip6, sizeof(req->host)-1); req->host[127]=0;
|
||
off += 16; req->port = ntohs(*(uint16_t*)(p + off)); off += 2;
|
||
} else return -3;
|
||
*consumed = off; return 1;
|
||
}
|
||
static void socks5_send_reply(SOCKET s, uint8_t rep, const char *bind_host, uint16_t bind_port) {
|
||
uint8_t buf[4 + 1 + 255 + 2]; size_t off=0;
|
||
buf[off++]=0x05; buf[off++]=rep; buf[off++]=0x00;
|
||
uint8_t packed[16]; size_t plen=0;
|
||
int af = host_literal_ip(bind_host, packed, &plen);
|
||
if (af == AF_INET) { buf[off++]=0x01; memcpy(buf+off,packed,4); off+=4; }
|
||
else if (af==AF_INET6) { buf[off++]=0x04; memcpy(buf+off,packed,16); off+=16; }
|
||
else { size_t len=strlen(bind_host); if(len>255) len=255; buf[off++]=0x03; buf[off++]=(uint8_t)len; memcpy(buf+off,bind_host,len); off+=len; }
|
||
uint16_t p=htons(bind_port); memcpy(buf+off,&p,2); off+=2; send(s,(const char*)buf,(int)off,0);
|
||
}
|
||
static void socks5_send_reply_log(SOCKET s, uint8_t rep, const char* host, uint16_t port){
|
||
LOGI("S5 reply rep=0x%02x bind=%s:%u", rep, host?host:"", (unsigned)port);
|
||
socks5_send_reply(s, rep, host, port);
|
||
}
|
||
|
||
// -------------------- encode_addr --------------------
|
||
static bool encode_addr(uint8_t atyp, const char *host, uint16_t port, dynbuf_t* out, bool tcp) {
|
||
uint8_t packed[16]; size_t plen=0;
|
||
if (atyp == 0x01) {
|
||
if (host_literal_ip(host, packed, &plen) != AF_INET) return false;
|
||
uint8_t t = tcp ? 0x61 : 0x01; dbuf_append(out,&t,1); dbuf_append(out,packed,4);
|
||
} else if (atyp == 0x03) {
|
||
size_t len=strlen(host); if(len>255) return false;
|
||
uint8_t t=tcp?0x63:0x03; dbuf_append(out,&t,1); uint8_t l=(uint8_t)len; dbuf_append(out,&l,1); dbuf_append(out,host,len);
|
||
} else if (atyp == 0x04) {
|
||
if (host_literal_ip(host, packed, &plen) != AF_INET6) return false;
|
||
uint8_t t=tcp?0x64:0x04; dbuf_append(out,&t,1); dbuf_append(out,packed,16);
|
||
} else return false;
|
||
uint16_t nport=htons(port); dbuf_append(out,&nport,2); return true;
|
||
}
|
||
|
||
// -------------------- CNG crypto (CFB128 fixed) --------------------
|
||
typedef struct AES_CFB {
|
||
BCRYPT_ALG_HANDLE hAlg;
|
||
BCRYPT_KEY_HANDLE hKey;
|
||
PUCHAR keyObj;
|
||
DWORD keyObjLen;
|
||
UCHAR iv[16];
|
||
BOOL ready;
|
||
} AES_CFB;
|
||
|
||
static void rand_bytes(uint8_t* b, size_t n) {
|
||
BCryptGenRandom(NULL,(PUCHAR)b,(ULONG)n,BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||
}
|
||
|
||
static BOOL kdf_evp_bytes_to_key_md5(const uint8_t* pw, size_t pwlen, uint8_t out32[32]) {
|
||
BCRYPT_ALG_HANDLE hMd5=NULL; NTSTATUS st=0; BCRYPT_HASH_HANDLE hh=NULL; UCHAR h1[16],h2[16];
|
||
st=BCryptOpenAlgorithmProvider(&hMd5,BCRYPT_MD5_ALGORITHM,NULL,0); if(st) return FALSE;
|
||
st=BCryptCreateHash(hMd5,&hh,NULL,0,NULL,0,0); if(st) goto L;
|
||
st=BCryptHashData(hh,(PUCHAR)pw,(ULONG)pwlen,0); if(st) goto L;
|
||
st=BCryptFinishHash(hh,h1,16,0); BCryptDestroyHash(hh); hh=NULL; if(st) goto L;
|
||
st=BCryptCreateHash(hMd5,&hh,NULL,0,NULL,0,0); if(st) goto L;
|
||
st=BCryptHashData(hh,h1,16,0); if(st) goto L;
|
||
st=BCryptHashData(hh,(PUCHAR)pw,(ULONG)pwlen,0); if(st) goto L;
|
||
st=BCryptFinishHash(hh,h2,16,0); if(st) goto L;
|
||
memcpy(out32,h1,16); memcpy(out32+16,h2,16);
|
||
L: if(hh)BCryptDestroyHash(hh); if(hMd5)BCryptCloseAlgorithmProvider(hMd5,0); return st==0;
|
||
}
|
||
|
||
static BOOL aes_cfb_init(AES_CFB* s, const uint8_t key[32], const uint8_t iv[16]) {
|
||
memset(s,0,sizeof(*s));
|
||
if (BCryptOpenAlgorithmProvider(&s->hAlg,BCRYPT_AES_ALGORITHM,NULL,0)) return FALSE;
|
||
if (BCryptSetProperty(s->hAlg,BCRYPT_CHAINING_MODE,(PUCHAR)BCRYPT_CHAIN_MODE_CFB,(ULONG)sizeof(BCRYPT_CHAIN_MODE_CFB),0)) return FALSE;
|
||
DWORD cb=0; if (BCryptGetProperty(s->hAlg,BCRYPT_OBJECT_LENGTH,(PUCHAR)&s->keyObjLen,sizeof(DWORD),&cb,0)) return FALSE;
|
||
s->keyObj=(PUCHAR)HeapAlloc(GetProcessHeap(),0,s->keyObjLen); if(!s->keyObj) return FALSE;
|
||
if (BCryptGenerateSymmetricKey(s->hAlg,&s->hKey,s->keyObj,s->keyObjLen,(PUCHAR)key,32,0)) return FALSE;
|
||
DWORD fb = 16; // **** CFB128
|
||
if (BCryptSetProperty(s->hKey, BCRYPT_MESSAGE_BLOCK_LENGTH, (PUCHAR)&fb, sizeof(fb), 0)) return FALSE;
|
||
memcpy(s->iv,iv,16); s->ready=TRUE; return TRUE;
|
||
}
|
||
static void aes_cfb_free(AES_CFB* s){
|
||
if(s->hKey) BCryptDestroyKey(s->hKey);
|
||
if(s->hAlg) BCryptCloseAlgorithmProvider(s->hAlg,0);
|
||
if(s->keyObj) HeapFree(GetProcessHeap(),0,s->keyObj);
|
||
memset(s,0,sizeof(*s));
|
||
}
|
||
static BOOL aes_cfb_update(AES_CFB* s, BOOL enc, PUCHAR in, ULONG inlen, PUCHAR out, ULONG* outlen) {
|
||
if (!s->ready) return FALSE;
|
||
NTSTATUS st = enc ? BCryptEncrypt(s->hKey,in,inlen,NULL,s->iv,16,out,inlen,outlen,0)
|
||
: BCryptDecrypt(s->hKey,in,inlen,NULL,s->iv,16,out,inlen,outlen,0);
|
||
return st==0;
|
||
}
|
||
static int aes_cfb_one_shot(const uint8_t key[32], const uint8_t iv[16], BOOL enc,
|
||
const uint8_t* in, size_t inlen, uint8_t* out) {
|
||
BCRYPT_ALG_HANDLE hAlg=NULL; BCRYPT_KEY_HANDLE hKey=NULL; PUCHAR keyObj=NULL;
|
||
DWORD keyObjLen=0,cb=0,olen=0; UCHAR ivtmp[16]; memcpy(ivtmp,iv,16);
|
||
if (BCryptOpenAlgorithmProvider(&hAlg,BCRYPT_AES_ALGORITHM,NULL,0)) goto ERR;
|
||
if (BCryptSetProperty(hAlg,BCRYPT_CHAINING_MODE,(PUCHAR)BCRYPT_CHAIN_MODE_CFB,(ULONG)sizeof(BCRYPT_CHAIN_MODE_CFB),0)) goto ERR;
|
||
if (BCryptGetProperty(hAlg,BCRYPT_OBJECT_LENGTH,(PUCHAR)&keyObjLen,sizeof(DWORD),&cb,0)) goto ERR;
|
||
keyObj=(PUCHAR)HeapAlloc(GetProcessHeap(),0,keyObjLen); if(!keyObj) goto ERR;
|
||
if (BCryptGenerateSymmetricKey(hAlg,&hKey,keyObj,keyObjLen,(PUCHAR)key,32,0)) goto ERR;
|
||
DWORD fb = 16; // **CFB128**
|
||
if (BCryptSetProperty(hKey, BCRYPT_MESSAGE_BLOCK_LENGTH, (PUCHAR)&fb, sizeof(fb), 0)) goto ERR;
|
||
NTSTATUS st = enc ? BCryptEncrypt(hKey,(PUCHAR)in,(ULONG)inlen,NULL,ivtmp,16,out,(ULONG)inlen,&olen,0)
|
||
: BCryptDecrypt(hKey,(PUCHAR)in,(ULONG)inlen,NULL,ivtmp,16,out,(ULONG)inlen,&olen,0);
|
||
if (st) goto ERR;
|
||
if (hKey) BCryptDestroyKey(hKey); if (hAlg) BCryptCloseAlgorithmProvider(hAlg,0); if (keyObj) HeapFree(GetProcessHeap(),0,keyObj);
|
||
return (int)olen;
|
||
ERR:
|
||
if (hKey) BCryptDestroyKey(hKey); if (hAlg) BCryptCloseAlgorithmProvider(hAlg,0); if (keyObj) HeapFree(GetProcessHeap(),0,keyObj);
|
||
return -1;
|
||
}
|
||
|
||
// -------------------- UDP map --------------------
|
||
typedef struct {
|
||
bool used; time_t ts; uint8_t key[128]; size_t key_len; struct sockaddr_storage client_ss; int client_len;
|
||
} udp_map_entry_t;
|
||
typedef struct udp_assoc_s {
|
||
struct bridge_s* owner; SOCKET fd; struct sockaddr_storage remote_ss; int remote_len; uint8_t key32[32];
|
||
udp_map_entry_t map[UDP_MAP_CAP]; struct sockaddr_storage last_client; int last_client_len; bool has_last; HANDLE timer;
|
||
} udp_assoc_t;
|
||
static int udp_map_find(udp_assoc_t* ua, const uint8_t* k, size_t klen) {
|
||
for (int i=0;i<UDP_MAP_CAP;++i) if (ua->map[i].used && ua->map[i].key_len==klen && memcmp(ua->map[i].key,k,klen)==0) return i; return -1;
|
||
}
|
||
static int udp_map_alloc_slot(udp_assoc_t* ua) {
|
||
for (int i=0;i<UDP_MAP_CAP;++i) if (!ua->map[i].used) return i;
|
||
time_t oldest=time(NULL); int idx=0; for(int i=0;i<UDP_MAP_CAP;++i){ if(ua->map[i].ts<oldest){oldest=ua->map[i].ts; idx=i; } } return idx;
|
||
}
|
||
|
||
// -------------------- IOCP infra --------------------
|
||
typedef enum { OP_ACCEPT, OP_CLI_RECV, OP_CLI_SEND, OP_SRV_RECV, OP_SRV_SEND, OP_CONNECT } OPKIND;
|
||
typedef enum { ST_GREETING=0, ST_REQUEST=1, ST_CONNECTING_REMOTE=2, ST_STREAM=3, ST_UDP_ASSOC=4, ST_CLOSED=5 } bridge_state_t;
|
||
|
||
typedef struct IO_OP { OVERLAPPED ol; OPKIND kind; WSABUF buf; char* storage; DWORD cap; } IO_OP;
|
||
static IO_OP* op_alloc(OPKIND k, DWORD cap){ IO_OP* op=(IO_OP*)calloc(1,sizeof(IO_OP)); if(!op) return NULL; op->kind=k; op->cap=cap; if(cap){ op->storage=(char*)_aligned_malloc(cap,64); op->buf.buf=op->storage; op->buf.len=cap; } return op; }
|
||
static void op_free(IO_OP* op){ if(!op) return; if(op->storage) _aligned_free(op->storage); free(op); }
|
||
|
||
// Extension functions
|
||
static LPFN_ACCEPTEX pAcceptEx = NULL;
|
||
static LPFN_CONNECTEX pConnectEx = NULL;
|
||
static LPFN_GETACCEPTEXSOCKADDRS pGetAcceptExSockaddrs = NULL;
|
||
|
||
static BOOL get_ext_fns(SOCKET s) {
|
||
DWORD r=0; GUID g1=WSAID_ACCEPTEX, g2=WSAID_CONNECTEX, g3=WSAID_GETACCEPTEXSOCKADDRS;
|
||
if (WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &g1, sizeof(g1), &pAcceptEx, sizeof pAcceptEx, &r, NULL, NULL)) return FALSE;
|
||
if (WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &g2, sizeof(g2), &pConnectEx, sizeof pConnectEx, &r, NULL, NULL)) return FALSE;
|
||
if (WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &g3, sizeof(g3), &pGetAcceptExSockaddrs, sizeof pGetAcceptExSockaddrs, &r, NULL, NULL)) return FALSE;
|
||
return TRUE;
|
||
}
|
||
|
||
// -------------------- DNS cache & codec --------------------
|
||
typedef struct { bool used; time_t expire; char host[64]; char ip[16]; } dns_cache_entry_t;
|
||
typedef struct { dns_cache_entry_t tab[DNS_CACHE_SIZE]; } dns_cache_t;
|
||
static void dns_cache_init(dns_cache_t* c){ memset(c,0,sizeof(*c)); }
|
||
static const char* dns_cache_get(dns_cache_t *c, const char *host) {
|
||
time_t now=time(NULL);
|
||
for (size_t i=0;i<DNS_CACHE_SIZE;++i) if (c->tab[i].used && strcmp(c->tab[i].host,host)==0) {
|
||
if (c->tab[i].expire>now) return c->tab[i].ip; c->tab[i].used=false;
|
||
}
|
||
return NULL;
|
||
}
|
||
static void dns_cache_put(dns_cache_t *c, const char *host, const char *ip, int ttl_sec) {
|
||
time_t now=time(NULL); size_t slot=DNS_CACHE_SIZE; time_t oldest=now;
|
||
for (size_t i=0;i<DNS_CACHE_SIZE;++i){ if(!c->tab[i].used){slot=i;break;} if(c->tab[i].expire<oldest){oldest=c->tab[i].expire; slot=i;} }
|
||
if (slot>=DNS_CACHE_SIZE) slot=0;
|
||
dns_cache_entry_t* e=&c->tab[slot];
|
||
e->used=true; e->expire=now+ttl_sec;
|
||
strncpy(e->host,host,sizeof(e->host)-1); e->host[sizeof(e->host)-1]=0;
|
||
strncpy(e->ip,ip,sizeof(e->ip)-1); e->ip[sizeof(e->ip)-1]=0;
|
||
}
|
||
|
||
// DNS build/parse
|
||
static int dns_build_query(const char *host, uint8_t *out, size_t cap, size_t *outlen, uint16_t *out_id) {
|
||
if (!host || !out || cap < 12) return -1;
|
||
uint16_t id; BCryptGenRandom(NULL,(PUCHAR)&id,sizeof(id),BCRYPT_USE_SYSTEM_PREFERRED_RNG); *out_id=id;
|
||
size_t off=0; uint16_t flags=htons(0x0100); uint16_t qd=htons(1),an=0,ns=0,ar=0;
|
||
*(uint16_t*)(out+off)=htons(id); off+=2;
|
||
*(uint16_t*)(out+off)=flags; off+=2;
|
||
*(uint16_t*)(out+off)=qd; off+=2;
|
||
*(uint16_t*)(out+off)=an; off+=2;
|
||
*(uint16_t*)(out+off)=ns; off+=2;
|
||
*(uint16_t*)(out+off)=ar; off+=2;
|
||
const char *p=host, *dot=NULL;
|
||
while (*p) {
|
||
dot=strchr(p,'.'); size_t lab_len= dot ? (size_t)(dot-p) : strlen(p);
|
||
if (lab_len==0 || lab_len>63) return -1;
|
||
if (off+1+lab_len>=cap) return -1;
|
||
out[off++]=(uint8_t)lab_len; memcpy(out+off,p,lab_len); off+=lab_len;
|
||
if (!dot) break; p=dot+1;
|
||
}
|
||
if (off+1+4>cap) return -1;
|
||
out[off++]=0; *(uint16_t*)(out+off)=htons(1); off+=2; *(uint16_t*)(out+off)=htons(1); off+=2;
|
||
*outlen=off; return 0;
|
||
}
|
||
static int dns_skip_name(const uint8_t *buf, size_t len, size_t *off) {
|
||
size_t i=*off;
|
||
while (i<len) {
|
||
uint8_t c=buf[i++]; if ((c&0xC0)==0xC0){ if(i>=len) return -1; i++; break; }
|
||
else if (c==0) break; else { if (i+c>len) return -1; i+=c; }
|
||
}
|
||
*off=i; return 0;
|
||
}
|
||
static int dns_parse_a(const uint8_t *buf, size_t len, char *ip_str, size_t ip_cap) {
|
||
if (len<12) return -1;
|
||
uint16_t qd = ntohs(*(const uint16_t*)(buf+4));
|
||
uint16_t an = ntohs(*(const uint16_t*)(buf+6));
|
||
size_t off=12;
|
||
for (uint16_t i=0;i<qd;++i){ if(dns_skip_name(buf,len,&off)!=0) return -1; if(off+4>len) return -1; off+=4; }
|
||
for (uint16_t i=0;i<an;++i){
|
||
if (dns_skip_name(buf,len,&off)!=0) return -1;
|
||
if (off+10>len) return -1;
|
||
uint16_t type=ntohs(*(const uint16_t*)(buf+off)); off+=2;
|
||
uint16_t class_=ntohs(*(const uint16_t*)(buf+off)); off+=2;
|
||
off+=4; uint16_t rdlen=ntohs(*(const uint16_t*)(buf+off)); off+=2;
|
||
if (off+rdlen>len) return -1;
|
||
if (type==1 && class_==1 && rdlen==4) {
|
||
IN_ADDR a4; memcpy(&a4,buf+off,4);
|
||
const char* ret = InetNtopA(AF_INET,&a4,ip_str,(socklen_t)ip_cap);
|
||
return ret ? 0 : -1;
|
||
}
|
||
off+=rdlen;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
// -------------------- Bridge (per-connection) --------------------
|
||
typedef struct bridge_s {
|
||
config_t* cfg;
|
||
SOCKET cli_fd, srv_fd;
|
||
bridge_state_t state;
|
||
dynbuf_t cli_in, srv_in;
|
||
socks5_req_t req;
|
||
uint8_t key32[32];
|
||
AES_CFB up_enc; BOOL up_ready;
|
||
AES_CFB down_dec; BOOL down_ready;
|
||
uint8_t iv_server[16]; size_t iv_have;
|
||
struct udp_assoc_s* udp_assoc;
|
||
|
||
dns_cache_t dns_cache;
|
||
|
||
volatile LONG closing; // 0=open, 1=closing
|
||
volatile LONG pending_ops; // outstanding I/O counter
|
||
volatile LONG freed; // 0->1 ensure free once
|
||
} bridge_t;
|
||
|
||
static void bridge_free_crypto(bridge_t* b){ if(b->up_ready){aes_cfb_free(&b->up_enc); b->up_ready=FALSE;} if(b->down_ready){aes_cfb_free(&b->down_dec); b->down_ready=FALSE;} }
|
||
|
||
static void bridge_really_free(bridge_t* b){
|
||
if(!b) return;
|
||
if (InterlockedCompareExchange(&b->freed, 1, 0) != 0) return; // ensure once
|
||
LOGI("bridge %p free", b);
|
||
if(b->udp_assoc){
|
||
if (b->udp_assoc->timer) DeleteTimerQueueTimer(NULL,b->udp_assoc->timer,INVALID_HANDLE_VALUE);
|
||
if (b->udp_assoc->fd!=INVALID_SOCKET) CLOSESOCK(b->udp_assoc->fd);
|
||
free(b->udp_assoc); b->udp_assoc=NULL;
|
||
}
|
||
if(b->cli_fd!=INVALID_SOCKET){CLOSESOCK(b->cli_fd); b->cli_fd=INVALID_SOCKET;}
|
||
if(b->srv_fd!=INVALID_SOCKET){CLOSESOCK(b->srv_fd); b->srv_fd=INVALID_SOCKET;}
|
||
bridge_free_crypto(b);
|
||
dbuf_free(&b->cli_in); dbuf_free(&b->srv_in);
|
||
free(b);
|
||
}
|
||
static void bridge_try_free(bridge_t* b){
|
||
if (!b) return;
|
||
if (b->closing && InterlockedCompareExchange(&b->pending_ops, 0, 0)==0){
|
||
bridge_really_free(b);
|
||
}
|
||
}
|
||
static void bridge_start_close(bridge_t* b){
|
||
if (!b) return;
|
||
if (InterlockedCompareExchange(&b->closing, 1, 0)!=0) return; // only once
|
||
LOGI("bridge %p start_close (pending_ops=%ld)", b, InterlockedCompareExchange(&b->pending_ops,0,0));
|
||
if (b->cli_fd!=INVALID_SOCKET){ CancelIoEx((HANDLE)b->cli_fd, NULL); shutdown(b->cli_fd, SD_BOTH); }
|
||
if (b->srv_fd!=INVALID_SOCKET){ CancelIoEx((HANDLE)b->srv_fd, NULL); shutdown(b->srv_fd, SD_BOTH); }
|
||
bridge_try_free(b);
|
||
}
|
||
|
||
// -------------------- UDP assoc helpers --------------------
|
||
typedef struct udp_assoc_s udp_assoc_t;
|
||
static VOID CALLBACK udp_map_cleaner_cb(PVOID param, BOOLEAN fired) {
|
||
(void)fired;
|
||
udp_assoc_t* ua=(udp_assoc_t*)param; if(!ua) return;
|
||
time_t now=time(NULL); int cleared=0;
|
||
for (int i=0;i<UDP_MAP_CAP;++i) if (ua->map[i].used && now - ua->map[i].ts > ua->owner->cfg->udp_timeout) { ua->map[i].used=false; ++cleared; }
|
||
if (cleared && g_log_level>=2) LOGD("udp_assoc %p expired %d map entries", ua, cleared);
|
||
}
|
||
static udp_assoc_t* udp_assoc_create(bridge_t* b) {
|
||
udp_assoc_t* ua=(udp_assoc_t*)calloc(1,sizeof(*ua)); if(!ua) return NULL;
|
||
ua->owner=b; ua->fd=WSASocket(AF_INET,SOCK_DGRAM,IPPROTO_UDP,NULL,0,0); if(ua->fd==INVALID_SOCKET){free(ua); return NULL;}
|
||
SOCKADDR_IN sin; ZeroMemory(&sin,sizeof(sin)); sin.sin_family=AF_INET; sin.sin_port=htons(0); InetPtonA(AF_INET,b->cfg->listen_host,&sin.sin_addr);
|
||
if (bind(ua->fd,(SOCKADDR*)&sin,sizeof(sin))!=0){CLOSESOCK(ua->fd); free(ua); return NULL;}
|
||
struct sockaddr_storage ss; int sslen=0; if(!resolve_host_port(b->cfg->remote_host,(uint16_t)b->cfg->remote_port,&ss,&sslen)){CLOSESOCK(ua->fd); free(ua); return NULL;}
|
||
ua->remote_ss=ss; ua->remote_len=sslen; memcpy(ua->key32,b->key32,32);
|
||
CreateTimerQueueTimer(&ua->timer,NULL,udp_map_cleaner_cb,ua,5000,5000,WT_EXECUTEDEFAULT);
|
||
LOGI("UDP_ASSOC created socket=%llu", (unsigned long long)ua->fd);
|
||
return ua;
|
||
}
|
||
static bool udp_parse_client_packet(const uint8_t* buf,size_t len,size_t *addr_off,size_t *payload_off){
|
||
if(len<3) return false; if(buf[0]!=0||buf[1]!=0||buf[2]!=0) return false; size_t off=3;
|
||
if(len<off+1) return false; uint8_t atyp=buf[off++];
|
||
if(atyp==0x01){ if(len<off+4+2) return false; off+=4+2; }
|
||
else if(atyp==0x03){ if(len<off+1) return false; uint8_t l=buf[off++]; if(len<off+l+2) return false; off+=l+2; }
|
||
else if(atyp==0x04){ if(len<off+16+2) return false; off+=16+2; }
|
||
else return false; *addr_off=3; *payload_off=off; return true;
|
||
}
|
||
static bool udp_clone_addr_block(const uint8_t* buf,size_t addr_off,size_t payload_off,uint8_t* out,size_t* outlen){
|
||
size_t n=payload_off-addr_off; if(n>128) return false; memcpy(out,buf+addr_off,n); *outlen=n; return true;
|
||
}
|
||
static void udp_sendto_remote(udp_assoc_t* ua,const void* data,size_t len){
|
||
sendto(ua->fd,(const char*)data,(int)len,0,(SOCKADDR*)&ua->remote_ss,ua->remote_len);
|
||
}
|
||
static void udp_poll_once(udp_assoc_t* ua) {
|
||
if (!ua) return;
|
||
u_long nb=1; ioctlsocket(ua->fd, FIONBIO, &nb);
|
||
for (;;) {
|
||
uint8_t buf[8192];
|
||
SOCKADDR_STORAGE from; int fromlen = sizeof(from);
|
||
int n = recvfrom(ua->fd, (char*)buf, (int)sizeof(buf), 0, (SOCKADDR*)&from, &fromlen);
|
||
if (n < 0) { int e=WSAGetLastError(); if (e==WSAEWOULDBLOCK) break; else { LOGE("udp recvfrom error=%d", e); break; } }
|
||
bool from_remote = false;
|
||
if (from.ss_family == ua->remote_ss.ss_family) {
|
||
if (from.ss_family == AF_INET) {
|
||
SOCKADDR_IN *a=(SOCKADDR_IN*)&from, *b=(SOCKADDR_IN*)&ua->remote_ss;
|
||
from_remote = (a->sin_port==b->sin_port && a->sin_addr.S_un.S_addr==b->sin_addr.S_un.S_addr);
|
||
} else if (from.ss_family == AF_INET6) {
|
||
SOCKADDR_IN6 *a=(SOCKADDR_IN6*)&from, *b=(SOCKADDR_IN6*)&ua->remote_ss;
|
||
from_remote = (a->sin6_port==b->sin6_port && memcmp(&a->sin6_addr,&b->sin6_addr,sizeof(a->sin6_addr))==0);
|
||
}
|
||
}
|
||
if (from_remote) {
|
||
if (n < 16) continue;
|
||
const uint8_t* iv = buf;
|
||
const uint8_t* ct = buf + 16; size_t ctlen = (size_t)n - 16;
|
||
uint8_t plain[8192];
|
||
int m = aes_cfb_one_shot(ua->key32, iv, FALSE, ct, ctlen, plain);
|
||
if (m <= 0) continue;
|
||
|
||
size_t off = 0; if (m < 1) continue;
|
||
uint8_t atyp = plain[off++];
|
||
if (atyp == 0x01) off += 4 + 2;
|
||
else if (atyp == 0x03) { if (off >= (size_t)m) continue; uint8_t l=plain[off++]; off += l + 2; }
|
||
else if (atyp == 0x04) off += 16 + 2;
|
||
else continue;
|
||
if ((size_t)m < off) continue;
|
||
size_t addrlen = off; size_t payload_len = (size_t)m - off;
|
||
|
||
int idx = udp_map_find(ua, plain, addrlen);
|
||
SOCKADDR_STORAGE dst; int dstlen = 0;
|
||
if (idx >= 0) { dst = ua->map[idx].client_ss; dstlen = ua->map[idx].client_len; }
|
||
else if (ua->has_last) { dst = ua->last_client; dstlen = ua->last_client_len; }
|
||
else continue;
|
||
|
||
uint8_t out[8192];
|
||
size_t outlen = 3 + addrlen + payload_len;
|
||
if (outlen > sizeof(out)) continue;
|
||
out[0] = 0; out[1] = 0; out[2] = 0;
|
||
memcpy(out + 3, plain, addrlen);
|
||
memcpy(out + 3 + addrlen, plain + addrlen, payload_len);
|
||
sendto(ua->fd, (const char*)out, (int)outlen, 0, (SOCKADDR*)&dst, dstlen);
|
||
} else {
|
||
size_t addr_off=0, payload_off=0;
|
||
if (!udp_parse_client_packet(buf, (size_t)n, &addr_off, &payload_off)) continue;
|
||
|
||
uint8_t keybuf[128]; size_t klen=0;
|
||
if (!udp_clone_addr_block(buf, addr_off, payload_off, keybuf, &klen)) continue;
|
||
int idx = udp_map_find(ua, keybuf, klen);
|
||
if (idx < 0) idx = udp_map_alloc_slot(ua);
|
||
ua->map[idx].used = true; ua->map[idx].ts = time(NULL);
|
||
memcpy(ua->map[idx].key, keybuf, klen); ua->map[idx].key_len = klen;
|
||
ua->map[idx].client_ss = from; ua->map[idx].client_len = fromlen;
|
||
ua->last_client = from; ua->last_client_len = fromlen; ua->has_last = true;
|
||
|
||
uint8_t iv[16]; rand_bytes(iv, 16);
|
||
const uint8_t* plain = buf + 3;
|
||
size_t plain_len = (size_t)n - 3;
|
||
uint8_t ct[8192];
|
||
int m = aes_cfb_one_shot(ua->key32, iv, TRUE, plain, plain_len, ct);
|
||
if (m <= 0) continue;
|
||
|
||
uint8_t out[8192];
|
||
if ((size_t)m + 16 > sizeof(out)) continue;
|
||
memcpy(out, iv, 16);
|
||
memcpy(out + 16, ct, m);
|
||
udp_sendto_remote(ua, out, (size_t)m + 16);
|
||
}
|
||
}
|
||
}
|
||
|
||
// -------------------- server env --------------------
|
||
typedef struct { HANDLE iocp; SOCKET listen_fd; config_t cfg; } server_env_t;
|
||
static server_env_t g_env;
|
||
|
||
// -------------------- TCP helpers + post wrappers --------------------
|
||
typedef struct IO_OP IO_OP;
|
||
static void set_tcp_opts(SOCKET s){ int on=1,sz=1<<18; setsockopt(s,IPPROTO_TCP,TCP_NODELAY,(char*)&on,sizeof(on)); setsockopt(s,SOL_SOCKET,SO_SNDBUF,(char*)&sz,sizeof(sz)); setsockopt(s,SOL_SOCKET,SO_RCVBUF,(char*)&sz,sizeof(sz)); }
|
||
|
||
static IO_OP* post_recv_(bridge_t* b, SOCKET s, OPKIND kind, DWORD cap){
|
||
if (!b || b->closing) return NULL; // 关闭中不再投递
|
||
IO_OP* op=op_alloc(kind,cap);
|
||
DWORD flags=0,recvd=0;
|
||
int r=WSARecv(s,&op->buf,1,&recvd,&flags,&op->ol,NULL);
|
||
int e=(r!=0)?WSAGetLastError():0;
|
||
if(r!=0 && e!=WSA_IO_PENDING){ LOGD("WSARecv kind=%d err=%d", (int)kind, e); op_free(op); return NULL; }
|
||
InterlockedIncrement(&b->pending_ops);
|
||
LOGD("post RECV kind=%d cap=%lu (pending_ops=%ld)", (int)kind, (unsigned long)cap, InterlockedCompareExchange(&b->pending_ops,0,0));
|
||
return op;
|
||
}
|
||
static IO_OP* post_send_(bridge_t* b, SOCKET s, OPKIND kind, const void* data, size_t len){
|
||
if (!b || b->closing) return NULL; // 关闭中不再投递
|
||
IO_OP* op=op_alloc(kind,(DWORD)len); memcpy(op->storage,data,len);
|
||
DWORD sent=0;
|
||
int r=WSASend(s,&op->buf,1,&sent,0,&op->ol,NULL);
|
||
int e=(r!=0)?WSAGetLastError():0;
|
||
if(r!=0 && e!=WSA_IO_PENDING){ LOGD("WSASend kind=%d err=%d", (int)kind, e); op_free(op); return NULL; }
|
||
InterlockedIncrement(&b->pending_ops);
|
||
LOGD("post SEND kind=%d len=%zu (pending_ops=%ld)", (int)kind, len, InterlockedCompareExchange(&b->pending_ops,0,0));
|
||
return op;
|
||
}
|
||
|
||
// forward decl
|
||
static bool connect_remote_async(bridge_t* b);
|
||
static void after_remote_connected(bridge_t* b);
|
||
|
||
// -------------------- CONNECT & STREAM --------------------
|
||
static void after_remote_connected(bridge_t* b){
|
||
LOGI("remote connected, sending IV + addr+mx");
|
||
|
||
dynbuf_t ab; dbuf_init(&ab);
|
||
if(!encode_addr(b->req.atyp,b->req.host,b->req.port,&ab,true)){ socks5_send_reply_log(b->cli_fd,0x04,"0.0.0.0",0); dbuf_free(&ab); bridge_start_close(b); return; }
|
||
size_t mx_len=strlen(b->cfg->mx_head); if(mx_len>255){ dbuf_free(&ab); bridge_start_close(b); return; }
|
||
uint8_t ml=(uint8_t)mx_len; dbuf_append(&ab,&ml,1); dbuf_append(&ab,b->cfg->mx_head,mx_len);
|
||
|
||
uint8_t iv[16]; rand_bytes(iv,16); aes_cfb_init(&b->up_enc,b->key32,iv); b->up_ready=TRUE;
|
||
|
||
uint8_t* ct=(uint8_t*)malloc(ab.len);
|
||
ULONG m=0; aes_cfb_update(&b->up_enc,TRUE,(PUCHAR)ab.data,(ULONG)ab.len,ct,&m);
|
||
uint8_t* pkt=(uint8_t*)malloc(16+m); memcpy(pkt,iv,16); memcpy(pkt+16,ct,m);
|
||
post_send_(b, b->srv_fd, OP_SRV_SEND, pkt, 16+m);
|
||
|
||
socks5_send_reply_log(b->cli_fd,0x00,"0.0.0.0",0);
|
||
|
||
free(ct); free(pkt); dbuf_free(&ab);
|
||
b->state=ST_STREAM;
|
||
post_recv_(b, b->cli_fd, OP_CLI_RECV, b->cfg->recv_buf);
|
||
post_recv_(b, b->srv_fd, OP_SRV_RECV, b->cfg->recv_buf);
|
||
LOGI("enter STREAM (+posted cli/srv recv)");
|
||
}
|
||
|
||
static bool connect_remote_async(bridge_t* b){
|
||
b->srv_fd=WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,0,WSA_FLAG_OVERLAPPED); if(b->srv_fd==INVALID_SOCKET){ LOGE("WSASocket for remote failed"); return false; }
|
||
CreateIoCompletionPort((HANDLE)b->srv_fd,g_env.iocp,(ULONG_PTR)b,0);
|
||
SOCKADDR_IN l; ZeroMemory(&l,sizeof(l)); l.sin_family=AF_INET; bind(b->srv_fd,(SOCKADDR*)&l,sizeof(l));
|
||
struct sockaddr_storage ss; int sslen=0; if(!resolve_host_port(b->cfg->remote_host,(uint16_t)b->cfg->remote_port,&ss,&sslen)) return false;
|
||
IO_OP* op=op_alloc(OP_CONNECT,0);
|
||
DWORD bytes=0;
|
||
LOGI("ConnectEx to remote server %s:%u", b->cfg->remote_host, (unsigned)b->cfg->remote_port);
|
||
BOOL ok=pConnectEx(b->srv_fd,(SOCKADDR*)&ss,sslen,NULL,0,&bytes,&op->ol);
|
||
int e = ok?0:WSAGetLastError();
|
||
if(!ok && e!=ERROR_IO_PENDING){ LOGE("ConnectEx err=%d", e); op_free(op); return false; }
|
||
InterlockedIncrement(&b->pending_ops);
|
||
b->state=ST_CONNECTING_REMOTE; return true;
|
||
}
|
||
|
||
// -------------------- DNS over tunnel (sync) --------------------
|
||
static bool start_dns_over_tunnel_sync(bridge_t* b, const char* host) {
|
||
const char* cached = dns_cache_get(&b->dns_cache, host);
|
||
if (cached) {
|
||
LOGI("DNS cache hit: %s -> %s", host, cached);
|
||
strncpy(b->req.host, cached, sizeof(b->req.host)-1); b->req.atyp=0x01;
|
||
return connect_remote_async(b);
|
||
}
|
||
uint8_t q[512]; size_t qlen=0; uint16_t qid=0;
|
||
if (dns_build_query(host, q, sizeof(q), &qlen, &qid) != 0) { LOGE("dns_build_query failed for %s", host); return false; }
|
||
|
||
dynbuf_t ab; dbuf_init(&ab);
|
||
if (!encode_addr(0x01, "8.8.8.8", 53, &ab, false)) { dbuf_free(&ab); LOGE("encode_addr for DNS failed"); return false; }
|
||
size_t plain_len = ab.len + qlen; uint8_t* plain = (uint8_t*)malloc(plain_len);
|
||
memcpy(plain, ab.data, ab.len); memcpy(plain+ab.len, q, qlen);
|
||
|
||
uint8_t iv[16]; rand_bytes(iv,16);
|
||
uint8_t ct[1024]; int m = aes_cfb_one_shot(b->key32, iv, TRUE, plain, plain_len, ct);
|
||
if (m <= 0) { free(plain); dbuf_free(&ab); LOGE("dns encrypt failed"); return false; }
|
||
uint8_t out[1024]; memcpy(out, iv, 16); memcpy(out+16, ct, m);
|
||
|
||
SOCKET us = WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, 0); if (us==INVALID_SOCKET){ free(plain); dbuf_free(&ab); LOGE("udp socket for DNS failed"); return false; }
|
||
struct sockaddr_storage r; int rlen=0; if(!resolve_host_port(b->cfg->remote_host,(uint16_t)b->cfg->remote_port,&r,&rlen)){ CLOSESOCK(us); free(plain); dbuf_free(&ab); return false; }
|
||
int to_ms = b->cfg->connect_timeout*1000; setsockopt(us,SOL_SOCKET,SO_RCVTIMEO,(char*)&to_ms,sizeof(to_ms));
|
||
|
||
LOGI("DNS over tunnel: %s -> 8.8.8.8:53 (qid=0x%04x), sending %d bytes", host, qid, 16+m);
|
||
int sret = sendto(us,(const char*)out,16+m,0,(SOCKADDR*)&r,rlen);
|
||
if (sret<0){ int e=WSAGetLastError(); LOGE("DNS sendto err=%d", e); CLOSESOCK(us); free(plain); dbuf_free(&ab); return false; }
|
||
|
||
uint8_t rbuf[2048]; SOCKADDR_STORAGE from; int fromlen=sizeof(from);
|
||
int n = recvfrom(us,(char*)rbuf,(int)sizeof(rbuf),0,(SOCKADDR*)&from,&fromlen);
|
||
if (n <= 0) { int e=WSAGetLastError(); LOGE("DNS recvfrom timeout/err=%d", e); CLOSESOCK(us); free(plain); dbuf_free(&ab); return false; }
|
||
|
||
LOGI("DNS over tunnel: received %d bytes", n);
|
||
if (n < 16) { CLOSESOCK(us); free(plain); dbuf_free(&ab); LOGE("DNS resp too short"); return false; }
|
||
uint8_t plain2[2048];
|
||
int m2 = aes_cfb_one_shot(b->key32, rbuf, FALSE, rbuf+16, (size_t)n-16, plain2);
|
||
if (m2 <= 0) { CLOSESOCK(us); free(plain); dbuf_free(&ab); LOGE("DNS decrypt failed"); return false; }
|
||
|
||
size_t off = 0; if (m2 < 1) { CLOSESOCK(us); free(plain); dbuf_free(&ab); return false; }
|
||
uint8_t atyp = plain2[off++];
|
||
if (atyp == 0x01) off += 4 + 2;
|
||
else if (atyp == 0x03) { if (off >= (size_t)m2) { CLOSESOCK(us); free(plain); dbuf_free(&ab); return false; } uint8_t l=plain2[off++]; off += l + 2; }
|
||
else if (atyp == 0x04) off += 16 + 2;
|
||
else { CLOSESOCK(us); free(plain); dbuf_free(&ab); return false; }
|
||
if ((size_t)m2 <= off) { CLOSESOCK(us); free(plain); dbuf_free(&ab); return false; }
|
||
|
||
char ip[INET_ADDRSTRLEN];
|
||
if (dns_parse_a(plain2 + off, (size_t)m2 - off, ip, sizeof(ip)) != 0) {
|
||
LOGE("DNS parse A failed for %s", host);
|
||
CLOSESOCK(us); free(plain); dbuf_free(&ab); return false;
|
||
}
|
||
LOGI("DNS over tunnel OK: %s -> %s", host, ip);
|
||
dns_cache_put(&b->dns_cache, host, ip, 300);
|
||
strncpy(b->req.host, ip, sizeof(b->req.host)-1); b->req.atyp=0x01;
|
||
|
||
CLOSESOCK(us); free(plain); dbuf_free(&ab);
|
||
return connect_remote_async(b);
|
||
}
|
||
|
||
// -------------------- handlers --------------------
|
||
static void handle_greeting(bridge_t* b, const uint8_t* p, size_t n) {
|
||
size_t consumed=0;
|
||
int r = parse_socks5_greeting(p, n, &consumed);
|
||
if (r < 0) { LOGE("S5 greeting invalid"); bridge_start_close(b); return; }
|
||
if (r == 0) return;
|
||
dbuf_consume(&b->cli_in, consumed);
|
||
uint8_t resp[2]={0x05,0x00};
|
||
post_send_(b, b->cli_fd, OP_CLI_SEND, resp, 2);
|
||
b->state=ST_REQUEST;
|
||
LOGI("S5 greeting ok");
|
||
}
|
||
static void handle_request(bridge_t* b, const uint8_t* p, size_t n) {
|
||
size_t consumed=0; socks5_req_t rq; int r=parse_socks5_request(p,n,&rq,&consumed);
|
||
if (r < 0) { LOGE("S5 request invalid"); bridge_start_close(b); return; }
|
||
if (r == 0) return;
|
||
dbuf_consume(&b->cli_in, consumed); b->req=rq;
|
||
LOGI("S5 request cmd=0x%02x atyp=0x%02x host=%s port=%u", rq.cmd, rq.atyp, rq.host, (unsigned)rq.port);
|
||
|
||
if (b->req.cmd == 0x01) {
|
||
if (b->req.atyp==0x03 && (g_dns_always || host_in_domain_list(b->req.host))) {
|
||
LOGI("%s DNS-over-tunnel for %s", g_dns_always ? "force" : "match list, use", b->req.host);
|
||
if (!start_dns_over_tunnel_sync(b, b->req.host)) {
|
||
socks5_send_reply_log(b->cli_fd, 0x04, "0.0.0.0", 0); bridge_start_close(b); return;
|
||
}
|
||
return;
|
||
}
|
||
if (!connect_remote_async(b)) { socks5_send_reply_log(b->cli_fd,0x05,"0.0.0.0",0); bridge_start_close(b); return; }
|
||
} else if (b->req.cmd == 0x03) {
|
||
b->udp_assoc = udp_assoc_create(b);
|
||
if (!b->udp_assoc) { socks5_send_reply_log(b->cli_fd,0x01,"0.0.0.0",0); bridge_start_close(b); return; }
|
||
SOCKADDR_IN sin; int slen=sizeof(sin); getsockname(b->udp_assoc->fd,(SOCKADDR*)&sin,&slen);
|
||
char bind_ip[INET_ADDRSTRLEN]; InetNtopA(AF_INET,&sin.sin_addr,bind_ip,sizeof(bind_ip));
|
||
socks5_send_reply_log(b->cli_fd,0x00,bind_ip,ntohs(sin.sin_port));
|
||
b->state=ST_UDP_ASSOC;
|
||
LOGI("enter UDP_ASSOC bind=%s:%u", bind_ip, (unsigned)ntohs(sin.sin_port));
|
||
} else {
|
||
socks5_send_reply_log(b->cli_fd,0x07,"0.0.0.0",0); bridge_start_close(b); return;
|
||
}
|
||
}
|
||
|
||
// -------------------- accept path --------------------
|
||
static void post_accept(SOCKET listen_fd){
|
||
IO_OP* op=op_alloc(OP_ACCEPT,2*(sizeof(SOCKADDR_STORAGE)+16));
|
||
SOCKET as=WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,0,WSA_FLAG_OVERLAPPED);
|
||
DWORD bytes=0; BOOL ok=pAcceptEx(listen_fd,as,op->storage,0,sizeof(SOCKADDR_STORAGE)+16,sizeof(SOCKADDR_STORAGE)+16,&bytes,&op->ol);
|
||
if(!ok){ int e=WSAGetLastError(); if(e!=ERROR_IO_PENDING){ closesocket(as); op_free(op); LOGE("AcceptEx err=%d", e);} }
|
||
op->buf.buf=(char*)(UINT_PTR)as; // stash accepted socket handle
|
||
}
|
||
|
||
// -------------------- worker thread --------------------
|
||
static DWORD WINAPI worker_thread(LPVOID arg){
|
||
(void)arg;
|
||
for(;;){
|
||
DWORD bytes=0; ULONG_PTR key=0; OVERLAPPED* pol=NULL; BOOL ok=GetQueuedCompletionStatus(g_env.iocp,&bytes,&key,&pol,INFINITE);
|
||
DWORD gle = ok ? 0 : GetLastError();
|
||
if(!pol){
|
||
LOGI("worker exit signal");
|
||
break;
|
||
}
|
||
bridge_t* b=(bridge_t*)key; IO_OP* op=(IO_OP*)pol;
|
||
|
||
if(op->kind==OP_ACCEPT){
|
||
SOCKET as=(SOCKET)(UINT_PTR)op->buf.buf;
|
||
setsockopt(as,SOL_SOCKET,SO_UPDATE_ACCEPT_CONTEXT,(char*)&g_env.listen_fd,sizeof(g_env.listen_fd));
|
||
set_tcp_opts(as);
|
||
|
||
SOCKADDR *lpLocal=NULL,*lpRemote=NULL; int lLocal=0,lRemote=0;
|
||
pGetAcceptExSockaddrs(op->storage,0,sizeof(SOCKADDR_STORAGE)+16,sizeof(SOCKADDR_STORAGE)+16, &lpLocal,&lLocal,&lpRemote,&lRemote);
|
||
char peer[128]; sockaddr_to_string(lpRemote,lRemote,peer,sizeof(peer));
|
||
LOGI("accepted socket=%llu from %s", (unsigned long long)as, peer[0]?peer:"?");
|
||
|
||
bridge_t* nb=(bridge_t*)calloc(1,sizeof(*nb));
|
||
nb->cfg=&g_env.cfg; nb->cli_fd=as; nb->srv_fd=INVALID_SOCKET; nb->state=ST_GREETING; dbuf_init(&nb->cli_in); dbuf_init(&nb->srv_in);
|
||
kdf_evp_bytes_to_key_md5((const uint8_t*)g_env.cfg.password, strlen(g_env.cfg.password), nb->key32);
|
||
dns_cache_init(&nb->dns_cache);
|
||
nb->closing=0; nb->pending_ops=0; nb->freed=0;
|
||
|
||
if (!CreateIoCompletionPort((HANDLE)as, g_env.iocp, (ULONG_PTR)nb, 0)) {
|
||
LOGE("CreateIoCompletionPort(as) failed gle=%lu", GetLastError());
|
||
closesocket(as); op_free(op); free(nb); continue;
|
||
}
|
||
|
||
post_recv_(nb, as, OP_CLI_RECV, nb->cfg->recv_buf);
|
||
post_accept(g_env.listen_fd);
|
||
op_free(op);
|
||
continue;
|
||
}
|
||
|
||
if(!b){
|
||
LOGE("completion key is NULL (gle=%lu), drop op kind=%d", gle, (int)op->kind);
|
||
op_free(op);
|
||
continue;
|
||
}
|
||
|
||
// 对取消的 IO(ERROR_OPERATION_ABORTED)做容错
|
||
if (!ok && gle==ERROR_OPERATION_ABORTED) {
|
||
LOGD("GQCS: op kind=%d canceled", (int)op->kind);
|
||
op_free(op);
|
||
LONG left = InterlockedDecrement(&b->pending_ops);
|
||
bridge_try_free(b);
|
||
continue;
|
||
}
|
||
|
||
switch(op->kind){
|
||
case OP_CLI_RECV: {
|
||
LOGD("GQCS: CLI_RECV %lu bytes", bytes);
|
||
if (bytes==0){ op_free(op); bridge_start_close(b); break; }
|
||
dbuf_append(&b->cli_in, op->storage, bytes); op_free(op);
|
||
if (b->state==ST_GREETING){ handle_greeting(b,(uint8_t*)b->cli_in.data,b->cli_in.len); }
|
||
if (b->state==ST_REQUEST){ handle_request (b,(uint8_t*)b->cli_in.data,b->cli_in.len); }
|
||
if (b->state==ST_STREAM){
|
||
if (!b->up_ready){ bridge_start_close(b); break; }
|
||
if (b->cli_in.len){
|
||
uint8_t* ct=(uint8_t*)malloc(b->cli_in.len); ULONG m=0;
|
||
aes_cfb_update(&b->up_enc,TRUE,(PUCHAR)b->cli_in.data,(ULONG)b->cli_in.len,ct,&m);
|
||
LOGD("STREAM up: in=%zu enc=%lu", b->cli_in.len, (unsigned long)m);
|
||
dbuf_consume(&b->cli_in,b->cli_in.len); if(m) post_send_(b,b->srv_fd,OP_SRV_SEND,ct,m); else free(ct);
|
||
}
|
||
} else if (b->state==ST_UDP_ASSOC){
|
||
dbuf_consume(&b->cli_in,b->cli_in.len);
|
||
if (b->udp_assoc) udp_poll_once(b->udp_assoc);
|
||
}
|
||
if (!b->closing && b->cli_fd!=INVALID_SOCKET) post_recv_(b, b->cli_fd, OP_CLI_RECV, b->cfg->recv_buf);
|
||
} break;
|
||
|
||
case OP_SRV_RECV: {
|
||
LOGD("GQCS: SRV_RECV %lu bytes", bytes);
|
||
if (bytes==0){ op_free(op); bridge_start_close(b); break; }
|
||
dbuf_append(&b->srv_in, op->storage, bytes); op_free(op);
|
||
if(!b->down_ready){
|
||
if(b->srv_in.len<16){ if(!b->closing) post_recv_(b,b->srv_fd,OP_SRV_RECV,b->cfg->recv_buf); break; }
|
||
memcpy(b->iv_server,b->srv_in.data,16); dbuf_consume(&b->srv_in,16);
|
||
aes_cfb_init(&b->down_dec,b->key32,b->iv_server); b->down_ready=TRUE;
|
||
LOGI("downstream IV received");
|
||
}
|
||
if(b->srv_in.len){
|
||
uint8_t* pt=(uint8_t*)malloc(b->srv_in.len); ULONG m=0;
|
||
aes_cfb_update(&b->down_dec,FALSE,(PUCHAR)b->srv_in.data,(ULONG)b->srv_in.len,pt,&m);
|
||
LOGD("STREAM down: in=%zu dec=%lu", b->srv_in.len, (unsigned long)m);
|
||
log_preview("HTTP preview", pt, (size_t)m);
|
||
dbuf_consume(&b->srv_in,b->srv_in.len); if(m) post_send_(b,b->cli_fd,OP_CLI_SEND,pt,m); else free(pt);
|
||
}
|
||
if (!b->closing && b->srv_fd!=INVALID_SOCKET) post_recv_(b,b->srv_fd,OP_SRV_RECV,b->cfg->recv_buf);
|
||
} break;
|
||
|
||
case OP_CLI_SEND:
|
||
case OP_SRV_SEND:
|
||
LOGD("GQCS: %s_SEND %lu bytes", (op->kind==OP_CLI_SEND)?"CLI":"SRV", bytes);
|
||
op_free(op);
|
||
break;
|
||
|
||
case OP_CONNECT:
|
||
LOGI("ConnectEx completed ok=%d gle=%lu", ok, (unsigned long)gle);
|
||
op_free(op);
|
||
if(!ok){ socks5_send_reply_log(b->cli_fd,0x05,"0.0.0.0",0); bridge_start_close(b); break; }
|
||
setsockopt(b->srv_fd,SOL_SOCKET,SO_UPDATE_CONNECT_CONTEXT,NULL,0); set_tcp_opts(b->srv_fd);
|
||
after_remote_connected(b);
|
||
break;
|
||
|
||
default:
|
||
LOGE("unknown op kind=%d", (int)op->kind);
|
||
op_free(op);
|
||
break;
|
||
}
|
||
|
||
if (op->kind!=OP_ACCEPT) {
|
||
LONG left = InterlockedDecrement(&b->pending_ops);
|
||
LOGD("op done kind=%d, pending_ops=%ld", (int)op->kind, left);
|
||
bridge_try_free(b);
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
// -------------------- main --------------------
|
||
int main(int argc, char** argv){
|
||
WSADATA w; WSAStartup(MAKEWORD(2,2), &w);
|
||
config_t cfg; parse_args(argc, argv, &cfg);
|
||
g_env.cfg = cfg;
|
||
|
||
LOGI("=== mx iocp socks5 ===");
|
||
LOGI("listen %s:%d remote %s:%d verbose=%d", cfg.listen_host, cfg.listen_port, cfg.remote_host, cfg.remote_port, cfg.verbose);
|
||
LOGI("udp_timeout=%ds connect_timeout=%ds", cfg.udp_timeout, cfg.connect_timeout);
|
||
|
||
SOCKET ls=WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,0,WSA_FLAG_OVERLAPPED); if(ls==INVALID_SOCKET){ LOGE("WSASocket listen failed"); return 1; }
|
||
SOCKADDR_IN sin; ZeroMemory(&sin,sizeof(sin)); sin.sin_family=AF_INET; sin.sin_port=htons((uint16_t)cfg.listen_port); InetPtonA(AF_INET,cfg.listen_host,&sin.sin_addr);
|
||
if(bind(ls,(SOCKADDR*)&sin,sizeof(sin))!=0){ LOGE("bind failed"); return 1; }
|
||
if(listen(ls,SOMAXCONN)!=0){ LOGE("listen failed"); return 1; }
|
||
if(!get_ext_fns(ls)){ LOGE("get_ext_fns failed"); return 1; }
|
||
|
||
g_env.iocp=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,MAX_WORKERS); g_env.listen_fd=ls;
|
||
CreateIoCompletionPort((HANDLE)ls,g_env.iocp,0,0);
|
||
|
||
SYSTEM_INFO si; GetSystemInfo(&si); int nthreads=(int)si.dwNumberOfProcessors; if(nthreads<2) nthreads=2;
|
||
for(int i=0;i<nthreads;i++){ HANDLE th=CreateThread(NULL,0,worker_thread,NULL,0,NULL); CloseHandle(th); }
|
||
for (int i=0;i<MAX_PENDING_ACCEPT;i++) post_accept(ls);
|
||
|
||
LOGI("ready.");
|
||
Sleep(INFINITE);
|
||
WSACleanup(); return 0;
|
||
}
|