Skip to content

Commit 795c980

Browse files
committed
feat: Node.js 支持 DoT; DoH/DoT 支持不验证服务器证书; Surfboard 支持 Hysteria 2 Salamander 混淆; Node.js Undici 自动使用 HTTP/2
1 parent 3118d14 commit 795c980

8 files changed

Lines changed: 453 additions & 56 deletions

File tree

backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sub-store",
3-
"version": "2.31.6",
3+
"version": "2.32.0",
44
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.",
55
"main": "src/main.js",
66
"packageManager": "pnpm@11.0.9",

backend/src/core/proxy-utils/processors/index.js

Lines changed: 97 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -638,10 +638,20 @@ async function resolveDomainsWithConcurrency(
638638
await Promise.all(workers);
639639
}
640640

641-
function getDomainResolverCacheId(provider, domain, type, url) {
641+
function getDomainResolverCacheId(
642+
provider,
643+
domain,
644+
type,
645+
url,
646+
tlsSkipCertVerify,
647+
) {
642648
switch (provider) {
643649
case 'Custom':
644-
return hex_md5(`CUSTOM:${url}:${domain}:${type}`);
650+
return hex_md5(
651+
`CUSTOM:${
652+
tlsSkipCertVerify ? 'INSECURE:' : ''
653+
}${url}:${domain}:${type}`,
654+
);
645655
case 'Google':
646656
return hex_md5(`GOOGLE:${domain}:${type}`);
647657
case 'IP-API':
@@ -655,15 +665,45 @@ function getDomainResolverCacheId(provider, domain, type, url) {
655665
}
656666
}
657667

658-
function getCachedDomainResolverResult(provider, domain, type, cache, url) {
668+
function getCachedDomainResolverResult(
669+
provider,
670+
domain,
671+
type,
672+
cache,
673+
url,
674+
tlsSkipCertVerify,
675+
) {
659676
if (cache === 'disabled') return null;
660-
const id = getDomainResolverCacheId(provider, domain, type, url);
677+
const id = getDomainResolverCacheId(
678+
provider,
679+
domain,
680+
type,
681+
url,
682+
tlsSkipCertVerify,
683+
);
661684
return id ? resourceCache.get(id) : null;
662685
}
663686

687+
function formatResolverUrlLog(provider, resolverUrl) {
688+
if (provider !== 'Custom' || !resolverUrl) return '';
689+
return ` via ${resolverUrl}`;
690+
}
691+
664692
const DOMAIN_RESOLVERS = {
665-
Custom: async function (domain, type, noCache, timeout, edns, url) {
666-
const id = hex_md5(`CUSTOM:${url}:${domain}:${type}`);
693+
Custom: async function (
694+
domain,
695+
type,
696+
noCache,
697+
timeout,
698+
edns,
699+
url,
700+
tlsSkipCertVerify,
701+
) {
702+
const id = hex_md5(
703+
`CUSTOM:${
704+
tlsSkipCertVerify ? 'INSECURE:' : ''
705+
}${url}:${domain}:${type}`,
706+
);
667707
const cached = resourceCache.get(id);
668708
if (!noCache && cached) return cached;
669709
const answerType = type === 'IPv6' ? 'AAAA' : 'A';
@@ -673,6 +713,7 @@ const DOMAIN_RESOLVERS = {
673713
type: answerType,
674714
timeout,
675715
edns,
716+
skipCertVerify: tlsSkipCertVerify,
676717
});
677718

678719
const { answers } = res;
@@ -828,6 +869,7 @@ function ResolveDomainOperator({
828869
filter,
829870
cache,
830871
url,
872+
tlsSkipCertVerify,
831873
timeout,
832874
edns: _edns,
833875
concurrency: _concurrency,
@@ -846,14 +888,17 @@ function ResolveDomainOperator({
846888
let edns = _edns || '223.6.6.6';
847889
if (!isIP(edns)) throw new Error(`域名解析 EDNS 应为 IP`);
848890
const concurrency = normalizeResolveDomainConcurrency(_concurrency);
891+
const customDnsTlsSkipCertVerify =
892+
provider === 'Custom' &&
893+
(tlsSkipCertVerify === true || tlsSkipCertVerify === 'enabled');
849894
if (concurrency > RESOLVE_DOMAIN_CONCURRENCY_WARN_THRESHOLD) {
850895
$.warn(
851896
`域名解析并发数 ${concurrency} 超过建议值 ${RESOLVE_DOMAIN_CONCURRENCY_WARN_THRESHOLD}, 可能导致代理 App TCP 连接数激增`,
852897
);
853898
}
854899
$.info(
855-
`Domain Resolver: [${_type}] ${provider} ${edns || ''} ${
856-
url || ''
900+
`Domain Resolver: [${_type}] ${provider} ${edns || ''} ${url || ''}${
901+
customDnsTlsSkipCertVerify ? ' tlsSkipCertVerify=enabled' : ''
857902
} concurrency=${concurrency}`,
858903
);
859904
return {
@@ -866,34 +911,47 @@ function ResolveDomainOperator({
866911
});
867912
const results = {};
868913
const domains = [
869-
...new Set(
914+
...new Map(
870915
proxies
871916
.filter((p) => !isIP(p.server) && !p['_no-resolve'])
872-
.map((c) => c.server),
873-
),
917+
.map((p) => {
918+
const id = getDomainResolverCacheId(
919+
provider,
920+
p.server,
921+
type,
922+
url,
923+
customDnsTlsSkipCertVerify,
924+
);
925+
return [id, { id, domain: p.server }];
926+
}),
927+
).values(),
874928
];
875929
const domainsToResolve = [];
876-
domains.forEach((domain) => {
930+
domains.forEach(({ id, domain }) => {
877931
const cached = getCachedDomainResolverResult(
878932
provider,
879933
domain,
880934
type,
881935
cache,
882936
url,
937+
customDnsTlsSkipCertVerify,
883938
);
884939
if (cached) {
885-
results[domain] = cached;
940+
results[id] = cached;
886941
$.info(
887-
`Using cached resolved domain: ${domain}${cached}`,
942+
`Using cached resolved domain: ${domain}${formatResolverUrlLog(
943+
provider,
944+
url,
945+
)}${cached}`,
888946
);
889947
} else {
890-
domainsToResolve.push(domain);
948+
domainsToResolve.push({ id, domain });
891949
}
892950
});
893951
await resolveDomainsWithConcurrency(
894952
domainsToResolve,
895953
concurrency,
896-
async (domain) => {
954+
async ({ id, domain }) => {
897955
try {
898956
const ip = await resolver(
899957
domain,
@@ -902,29 +960,41 @@ function ResolveDomainOperator({
902960
requestTimeout,
903961
edns,
904962
url,
963+
customDnsTlsSkipCertVerify,
905964
);
906-
results[domain] = ip;
965+
results[id] = ip;
907966
$.info(
908-
`Successfully resolved domain: ${domain}${ip}`,
967+
`Successfully resolved domain: ${domain}${formatResolverUrlLog(
968+
provider,
969+
url,
970+
)}${ip}`,
909971
);
910972
} catch (err) {
911973
$.error(
912-
`Failed to resolve domain: ${domain} with resolver [${provider}]: ${err}`,
974+
`Failed to resolve domain: ${domain}${formatResolverUrlLog(
975+
provider,
976+
url,
977+
)} with resolver [${provider}]: ${err}`,
913978
);
914979
}
915980
},
916981
);
917982
proxies.forEach((p) => {
918983
if (!p['_no-resolve']) {
919-
if (results[p.server]) {
920-
p._resolved_ips = results[p.server];
921-
let ip = Array.isArray(results[p.server])
922-
? results[p.server][
923-
Math.floor(
924-
Math.random() * results[p.server].length,
925-
)
984+
const id = getDomainResolverCacheId(
985+
provider,
986+
p.server,
987+
type,
988+
url,
989+
customDnsTlsSkipCertVerify,
990+
);
991+
if (id && results[id]) {
992+
p._resolved_ips = results[id];
993+
let ip = Array.isArray(results[id])
994+
? results[id][
995+
Math.floor(Math.random() * results[id].length)
926996
]
927-
: results[p.server];
997+
: results[id];
928998
if (type === 'IPv6' && isIPv6(ip)) {
929999
try {
9301000
ip = new ipAddress.Address6(ip).correctForm();

backend/src/core/proxy-utils/producers/surfboard.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,11 @@ export default function Surfboard_Producer() {
7373
return { produce };
7474
}
7575
function hysteria2(proxy) {
76-
if (proxy.obfs || proxy['obfs-password']) {
77-
throw new Error(`Surfboard Hysteria2 does not support obfs`);
76+
if (
77+
(proxy.obfs && proxy.obfs !== 'salamander') ||
78+
(proxy['obfs-password'] && proxy.obfs !== 'salamander')
79+
) {
80+
throw new Error(`Surfboard Hysteria2 only supports salamander obfs`);
7881
}
7982

8083
const result = new Result(proxy);
@@ -92,6 +95,10 @@ function hysteria2(proxy) {
9295
result.append(`,port-hopping-interval=${proxy['hop-interval']}`);
9396
}
9497

98+
if (proxy['obfs-password']) {
99+
result.append(`,salamander-password="${proxy['obfs-password']}"`);
100+
}
101+
95102
// tls verification
96103
appendTlsProxyParams(result, proxy);
97104

0 commit comments

Comments
 (0)