Muxun_programs/cpp/mxiocp/iocp_s5.c
Galaxy 907bd5af0e mx init
the muxun is not operated by git,now init
2025-11-09 20:06:06 +08:00

917 lines
46 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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;
}
// 对取消的 IOERROR_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;
}