mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-10 02:59:01 +08:00
feat: 域名解析支持自定义 DoH(需新版前端)
This commit is contained in:
parent
a5d77c39c8
commit
32dcca4a26
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sub-store",
|
||||
"version": "2.14.342",
|
||||
"version": "2.14.343",
|
||||
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
@ -20,8 +20,10 @@
|
||||
"@maxmind/geoip2-node": "^5.0.0",
|
||||
"automerge": "1.0.1-preview.7",
|
||||
"body-parser": "^1.19.0",
|
||||
"buffer": "^6.0.3",
|
||||
"connect-history-api-fallback": "^2.0.0",
|
||||
"cron": "^3.1.6",
|
||||
"dns-packet": "^5.6.1",
|
||||
"express": "^4.17.1",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"js-base64": "^3.7.2",
|
||||
|
32
backend/pnpm-lock.yaml
generated
32
backend/pnpm-lock.yaml
generated
@ -14,12 +14,18 @@ dependencies:
|
||||
body-parser:
|
||||
specifier: ^1.19.0
|
||||
version: registry.npmmirror.com/body-parser@1.19.0
|
||||
buffer:
|
||||
specifier: ^6.0.3
|
||||
version: registry.npmmirror.com/buffer@6.0.3
|
||||
connect-history-api-fallback:
|
||||
specifier: ^2.0.0
|
||||
version: registry.npmmirror.com/connect-history-api-fallback@2.0.0
|
||||
cron:
|
||||
specifier: ^3.1.6
|
||||
version: registry.npmmirror.com/cron@3.1.6
|
||||
dns-packet:
|
||||
specifier: ^5.6.1
|
||||
version: registry.npmmirror.com/dns-packet@5.6.1
|
||||
express:
|
||||
specifier: ^4.17.1
|
||||
version: registry.npmmirror.com/express@4.17.1
|
||||
@ -1973,6 +1979,12 @@ packages:
|
||||
'@jridgewell/sourcemap-codec': registry.npmmirror.com/@jridgewell/sourcemap-codec@1.4.13
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/@leichtgewicht/ip-codec@2.0.5:
|
||||
resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz}
|
||||
name: '@leichtgewicht/ip-codec'
|
||||
version: 2.0.5
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@maxmind/geoip2-node@5.0.0:
|
||||
resolution: {integrity: sha512-ki+q5//oU4tZ3BAhegZJcB5czoZyic5JSTEKbrUAQB/BzAoAiGyLW0immEmQvVVyy2SMlvBTJ3zqyRj8K9BbwQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@maxmind/geoip2-node/-/geoip2-node-5.0.0.tgz}
|
||||
name: '@maxmind/geoip2-node'
|
||||
@ -2707,7 +2719,6 @@ packages:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz}
|
||||
name: base64-js
|
||||
version: 1.5.1
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/base@0.11.2:
|
||||
resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/base/-/base-0.11.2.tgz}
|
||||
@ -3078,6 +3089,15 @@ packages:
|
||||
ieee754: registry.npmmirror.com/ieee754@1.2.1
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/buffer@6.0.3:
|
||||
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz}
|
||||
name: buffer
|
||||
version: 6.0.3
|
||||
dependencies:
|
||||
base64-js: registry.npmmirror.com/base64-js@1.5.1
|
||||
ieee754: registry.npmmirror.com/ieee754@1.2.1
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/builtin-status-codes@3.0.0:
|
||||
resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz}
|
||||
name: builtin-status-codes
|
||||
@ -4047,6 +4067,15 @@ packages:
|
||||
randombytes: registry.npmmirror.com/randombytes@2.1.0
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/dns-packet@5.6.1:
|
||||
resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/dns-packet/-/dns-packet-5.6.1.tgz}
|
||||
name: dns-packet
|
||||
version: 5.6.1
|
||||
engines: {node: '>=6'}
|
||||
dependencies:
|
||||
'@leichtgewicht/ip-codec': registry.npmmirror.com/@leichtgewicht/ip-codec@2.0.5
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/doctrine@3.0.0:
|
||||
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz}
|
||||
name: doctrine
|
||||
@ -5874,7 +5903,6 @@ packages:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz}
|
||||
name: ieee754
|
||||
version: 1.2.1
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/ignore-by-default@1.0.1:
|
||||
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz}
|
||||
|
@ -1,13 +1,8 @@
|
||||
import { Buffer } from 'buffer';
|
||||
import rs from '@/utils/rs';
|
||||
import YAML from '@/utils/yaml';
|
||||
import download from '@/utils/download';
|
||||
import {
|
||||
isIPv4,
|
||||
isIPv6,
|
||||
isValidPortNumber,
|
||||
isNotBlank,
|
||||
utf8ArrayToStr,
|
||||
} from '@/utils';
|
||||
import { isIPv4, isIPv6, isValidPortNumber, isNotBlank } from '@/utils';
|
||||
import PROXY_PROCESSORS, { ApplyProcessor } from './processors';
|
||||
import PROXY_PREPROCESSORS from './preprocessors';
|
||||
import PROXY_PRODUCERS from './producers';
|
||||
@ -427,6 +422,7 @@ function lastParse(proxy) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof proxy.name !== 'string') {
|
||||
if (/^\d+$/.test(proxy.name)) {
|
||||
proxy.name = `${proxy.name}`;
|
||||
@ -435,7 +431,7 @@ function lastParse(proxy) {
|
||||
if (proxy.name?.data) {
|
||||
proxy.name = Buffer.from(proxy.name.data).toString('utf8');
|
||||
} else {
|
||||
proxy.name = utf8ArrayToStr(proxy.name);
|
||||
proxy.name = Buffer.from(proxy.name).toString('utf8');
|
||||
}
|
||||
} catch (e) {
|
||||
$.error(`proxy.name decode failed\nReason: ${e}`);
|
||||
|
@ -3,6 +3,7 @@ import scriptResourceCache from '@/utils/script-resource-cache';
|
||||
import { isIPv4, isIPv6 } from '@/utils';
|
||||
import { FULL } from '@/utils/logical';
|
||||
import { getFlag, removeFlag } from '@/utils/geo';
|
||||
import { doh } from '@/utils/dns';
|
||||
import lodash from 'lodash';
|
||||
import $ from '@/core/app';
|
||||
import { hex_md5 } from '@/vendor/md5';
|
||||
@ -390,6 +391,27 @@ function parseIP4P(IP4P) {
|
||||
}
|
||||
|
||||
const DOMAIN_RESOLVERS = {
|
||||
Custom: async function (domain, type, noCache, timeout, url) {
|
||||
const id = hex_md5(`CUSTOM:${url}:${domain}:${type}`);
|
||||
const cached = resourceCache.get(id);
|
||||
if (!noCache && cached) return cached;
|
||||
const res = await doh({
|
||||
url,
|
||||
domain,
|
||||
type: type === 'IPv6' ? 'AAAA' : 'A',
|
||||
timeout,
|
||||
});
|
||||
const { answers } = res;
|
||||
if (!Array.isArray(answers) || answers.length === 0) {
|
||||
throw new Error('No answers');
|
||||
}
|
||||
const result = answers.map((i) => i?.data).filter((i) => i);
|
||||
if (result.length === 0) {
|
||||
throw new Error('No answers');
|
||||
}
|
||||
resourceCache.set(id, result);
|
||||
return result;
|
||||
},
|
||||
Google: async function (domain, type, noCache, timeout) {
|
||||
const id = hex_md5(`GOOGLE:${domain}:${type}`);
|
||||
const cached = resourceCache.get(id);
|
||||
@ -528,6 +550,7 @@ function ResolveDomainOperator({
|
||||
type: _type,
|
||||
filter,
|
||||
cache,
|
||||
url,
|
||||
timeout,
|
||||
}) {
|
||||
if (['IPv6', 'IP4P'].includes(_type) && ['IP-API'].includes(provider)) {
|
||||
@ -541,6 +564,7 @@ function ResolveDomainOperator({
|
||||
if (!resolver) {
|
||||
throw new Error(`找不到域名解析服务提供方: ${provider}`);
|
||||
}
|
||||
$.info(`Domain Resolver: [${_type}] ${provider} ${url || ''}`);
|
||||
return {
|
||||
name: 'Resolve Domain Operator',
|
||||
func: async (proxies) => {
|
||||
@ -568,6 +592,7 @@ function ResolveDomainOperator({
|
||||
type,
|
||||
cache === 'disabled',
|
||||
requestTimeout,
|
||||
url,
|
||||
)
|
||||
.then((ip) => {
|
||||
results[domain] = ip;
|
||||
|
55
backend/src/utils/dns.js
Normal file
55
backend/src/utils/dns.js
Normal file
@ -0,0 +1,55 @@
|
||||
import $ from '@/core/app';
|
||||
import dnsPacket from 'dns-packet';
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
export async function doh({
|
||||
url,
|
||||
domain,
|
||||
type = 'A',
|
||||
timeout,
|
||||
ip = '223.6.6.6',
|
||||
}) {
|
||||
const buf = dnsPacket.encode({
|
||||
type: 'query',
|
||||
id: 0,
|
||||
flags: dnsPacket.RECURSION_DESIRED,
|
||||
questions: [
|
||||
{
|
||||
type,
|
||||
name: domain,
|
||||
},
|
||||
],
|
||||
additionals: [
|
||||
{
|
||||
type: 'OPT',
|
||||
name: '.',
|
||||
udpPayloadSize: 4096,
|
||||
flags: 0,
|
||||
options: [
|
||||
{
|
||||
code: 'CLIENT_SUBNET',
|
||||
ip,
|
||||
sourcePrefixLength: 24,
|
||||
scopePrefixLength: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
const res = await $.http.get({
|
||||
url: `${url}?dns=${buf
|
||||
.toString('base64')
|
||||
.toString('utf-8')
|
||||
.replace(/=/g, '')}`,
|
||||
headers: {
|
||||
Accept: 'application/dns-message',
|
||||
// 'Content-Type': 'application/dns-message',
|
||||
},
|
||||
// body: buf,
|
||||
'binary-mode': true,
|
||||
encoding: null, // 使用 null 编码以确保响应是原始二进制数据
|
||||
timeout,
|
||||
});
|
||||
|
||||
return dnsPacket.decode(Buffer.from($.env.isQX ? res.bodyBytes : res.body));
|
||||
}
|
@ -46,52 +46,52 @@ function getPolicyDescriptor(str) {
|
||||
};
|
||||
}
|
||||
|
||||
const utf8ArrayToStr =
|
||||
typeof TextDecoder !== 'undefined'
|
||||
? (v) => new TextDecoder().decode(new Uint8Array(v))
|
||||
: (function () {
|
||||
var charCache = new Array(128); // Preallocate the cache for the common single byte chars
|
||||
var charFromCodePt = String.fromCodePoint || String.fromCharCode;
|
||||
var result = [];
|
||||
// const utf8ArrayToStr =
|
||||
// typeof TextDecoder !== 'undefined'
|
||||
// ? (v) => new TextDecoder().decode(new Uint8Array(v))
|
||||
// : (function () {
|
||||
// var charCache = new Array(128); // Preallocate the cache for the common single byte chars
|
||||
// var charFromCodePt = String.fromCodePoint || String.fromCharCode;
|
||||
// var result = [];
|
||||
|
||||
return function (array) {
|
||||
var codePt, byte1;
|
||||
var buffLen = array.length;
|
||||
// return function (array) {
|
||||
// var codePt, byte1;
|
||||
// var buffLen = array.length;
|
||||
|
||||
result.length = 0;
|
||||
// result.length = 0;
|
||||
|
||||
for (var i = 0; i < buffLen; ) {
|
||||
byte1 = array[i++];
|
||||
// for (var i = 0; i < buffLen; ) {
|
||||
// byte1 = array[i++];
|
||||
|
||||
if (byte1 <= 0x7f) {
|
||||
codePt = byte1;
|
||||
} else if (byte1 <= 0xdf) {
|
||||
codePt = ((byte1 & 0x1f) << 6) | (array[i++] & 0x3f);
|
||||
} else if (byte1 <= 0xef) {
|
||||
codePt =
|
||||
((byte1 & 0x0f) << 12) |
|
||||
((array[i++] & 0x3f) << 6) |
|
||||
(array[i++] & 0x3f);
|
||||
} else if (String.fromCodePoint) {
|
||||
codePt =
|
||||
((byte1 & 0x07) << 18) |
|
||||
((array[i++] & 0x3f) << 12) |
|
||||
((array[i++] & 0x3f) << 6) |
|
||||
(array[i++] & 0x3f);
|
||||
} else {
|
||||
codePt = 63; // Cannot convert four byte code points, so use "?" instead
|
||||
i += 3;
|
||||
}
|
||||
// if (byte1 <= 0x7f) {
|
||||
// codePt = byte1;
|
||||
// } else if (byte1 <= 0xdf) {
|
||||
// codePt = ((byte1 & 0x1f) << 6) | (array[i++] & 0x3f);
|
||||
// } else if (byte1 <= 0xef) {
|
||||
// codePt =
|
||||
// ((byte1 & 0x0f) << 12) |
|
||||
// ((array[i++] & 0x3f) << 6) |
|
||||
// (array[i++] & 0x3f);
|
||||
// } else if (String.fromCodePoint) {
|
||||
// codePt =
|
||||
// ((byte1 & 0x07) << 18) |
|
||||
// ((array[i++] & 0x3f) << 12) |
|
||||
// ((array[i++] & 0x3f) << 6) |
|
||||
// (array[i++] & 0x3f);
|
||||
// } else {
|
||||
// codePt = 63; // Cannot convert four byte code points, so use "?" instead
|
||||
// i += 3;
|
||||
// }
|
||||
|
||||
result.push(
|
||||
charCache[codePt] ||
|
||||
(charCache[codePt] = charFromCodePt(codePt)),
|
||||
);
|
||||
}
|
||||
// result.push(
|
||||
// charCache[codePt] ||
|
||||
// (charCache[codePt] = charFromCodePt(codePt)),
|
||||
// );
|
||||
// }
|
||||
|
||||
return result.join('');
|
||||
};
|
||||
})();
|
||||
// return result.join('');
|
||||
// };
|
||||
// })();
|
||||
|
||||
export {
|
||||
isIPv4,
|
||||
@ -101,6 +101,6 @@ export {
|
||||
getIfNotBlank,
|
||||
isPresent,
|
||||
getIfPresent,
|
||||
utf8ArrayToStr,
|
||||
// utf8ArrayToStr,
|
||||
getPolicyDescriptor,
|
||||
};
|
||||
|
3
backend/src/vendor/open-api.js
vendored
3
backend/src/vendor/open-api.js
vendored
@ -341,7 +341,10 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
|
||||
const request = isNode
|
||||
? eval("require('request')")
|
||||
: $httpClient;
|
||||
const body = options.body;
|
||||
const opts = JSON.parse(JSON.stringify(options));
|
||||
opts.body = body;
|
||||
|
||||
if (!isNode && opts.timeout) {
|
||||
opts.timeout++;
|
||||
let unit = 'ms';
|
||||
|
Loading…
x
Reference in New Issue
Block a user