Skip to content

Commit 649412d

Browse files
committed
feat: Node.js 域名解析支持 TCP/UDP DNS
1 parent e6cb6c9 commit 649412d

4 files changed

Lines changed: 458 additions & 24 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.25.0",
3+
"version": "2.26.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: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import scriptResourceCache from '@/utils/script-resource-cache';
33
import { isIPv4, isIPv6, ipAddress, isPlainObject } from '@/utils';
44
import { FULL } from '@/utils/logical';
55
import { getFlag, removeFlag } from '@/utils/geo';
6-
import { doh } from '@/utils/dns';
6+
import { resolveDns } from '@/utils/dns';
77
import lodash from 'lodash';
88
import $ from '@/core/app';
99
import { hex_md5 } from '@/vendor/md5';
@@ -597,7 +597,7 @@ const DOMAIN_RESOLVERS = {
597597
const cached = resourceCache.get(id);
598598
if (!noCache && cached) return cached;
599599
const answerType = type === 'IPv6' ? 'AAAA' : 'A';
600-
const res = await doh({
600+
const res = await resolveDns({
601601
url,
602602
domain,
603603
type: answerType,
@@ -624,7 +624,7 @@ const DOMAIN_RESOLVERS = {
624624
const cached = resourceCache.get(id);
625625
if (!noCache && cached) return cached;
626626
const answerType = type === 'IPv6' ? 'AAAA' : 'A';
627-
const res = await doh({
627+
const res = await resolveDns({
628628
url: 'https://8.8.4.4/dns-query',
629629
domain,
630630
type: answerType,
@@ -678,7 +678,7 @@ const DOMAIN_RESOLVERS = {
678678
const cached = resourceCache.get(id);
679679
if (!noCache && cached) return cached;
680680
const answerType = type === 'IPv6' ? 'AAAA' : 'A';
681-
const res = await doh({
681+
const res = await resolveDns({
682682
url: 'https://1.0.0.1/dns-query',
683683
domain,
684684
type: answerType,

backend/src/test/proxy-processors/resolve-domain.spec.js

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,104 @@
11
import { expect } from 'chai';
22
import { describe, it, beforeEach, afterEach } from 'mocha';
3+
import dnsPacket from 'dns-packet';
34

45
import $ from '@/core/app';
56
import PROCESSORS, { ApplyProcessor } from '@/core/proxy-utils/processors';
67
import { SETTINGS_KEY } from '@/constants';
78
import resourceCache from '@/utils/resource-cache';
9+
import { parseDnsResolver } from '@/utils/dns';
810
import { hex_md5 } from '@/vendor/md5';
911

1012
const ResolveDomainOperator = PROCESSORS['Resolve Domain Operator'];
13+
const dgram = eval("require('dgram')");
14+
const net = eval("require('net')");
1115

1216
function sleep(ms) {
1317
return new Promise((resolve) => setTimeout(resolve, ms));
1418
}
1519

20+
function createDnsResponse(query, data = '192.0.2.55') {
21+
const question = query.questions[0];
22+
return dnsPacket.encode({
23+
type: 'response',
24+
id: query.id,
25+
flags: dnsPacket.RECURSION_DESIRED,
26+
questions: query.questions,
27+
answers: [
28+
{
29+
type: question.type,
30+
class: 'IN',
31+
name: question.name,
32+
ttl: 60,
33+
data,
34+
},
35+
],
36+
});
37+
}
38+
39+
function getEdnsClientSubnet(query) {
40+
return query.additionals
41+
?.find((item) => item.type === 'OPT')
42+
?.options?.find(
43+
(option) =>
44+
option.code === 'CLIENT_SUBNET' || option.code === 8,
45+
)?.ip;
46+
}
47+
48+
function startUdpDnsServer(onQuery) {
49+
return new Promise((resolve, reject) => {
50+
const server = dgram.createSocket('udp4');
51+
server.once('error', reject);
52+
server.on('message', (message, rinfo) => {
53+
const query = dnsPacket.decode(message);
54+
onQuery(query);
55+
server.send(createDnsResponse(query), rinfo.port, rinfo.address);
56+
});
57+
server.bind(0, '127.0.0.1', () => {
58+
server.removeListener('error', reject);
59+
resolve({
60+
host: '127.0.0.1',
61+
port: server.address().port,
62+
close: () =>
63+
new Promise((resolveClose) => server.close(resolveClose)),
64+
});
65+
});
66+
});
67+
}
68+
69+
function startTcpDnsServer(onQuery) {
70+
return new Promise((resolve, reject) => {
71+
const server = net.createServer((socket) => {
72+
let request = Buffer.alloc(0);
73+
socket.on('data', (chunk) => {
74+
request = Buffer.concat([request, chunk]);
75+
if (request.length < 2) return;
76+
const requestLength = request.readUInt16BE(0);
77+
if (request.length < requestLength + 2) return;
78+
79+
const query = dnsPacket.decode(
80+
request.slice(2, 2 + requestLength),
81+
);
82+
onQuery(query);
83+
const response = createDnsResponse(query, '192.0.2.56');
84+
const responseLength = Buffer.alloc(2);
85+
responseLength.writeUInt16BE(response.length, 0);
86+
socket.end(Buffer.concat([responseLength, response]));
87+
});
88+
});
89+
server.once('error', reject);
90+
server.listen(0, '127.0.0.1', () => {
91+
server.removeListener('error', reject);
92+
resolve({
93+
host: '127.0.0.1',
94+
port: server.address().port,
95+
close: () =>
96+
new Promise((resolveClose) => server.close(resolveClose)),
97+
});
98+
});
99+
});
100+
}
101+
16102
describe('Resolve Domain Operator', function () {
17103
let originalGoogleResolver;
18104
let originalRead;
@@ -248,4 +334,96 @@ describe('Resolve Domain Operator', function () {
248334
$.warn = originalWarn;
249335
}
250336
});
337+
338+
it('parses Custom DNS resolver formats', function () {
339+
expect(parseDnsResolver('https://1.1.1.1/dns-query')).to.deep.equal({
340+
protocol: 'doh',
341+
url: 'https://1.1.1.1/dns-query',
342+
});
343+
expect(parseDnsResolver('1.1.1.1:5353')).to.deep.equal({
344+
protocol: 'udp',
345+
host: '1.1.1.1',
346+
port: 5353,
347+
});
348+
expect(parseDnsResolver('1.1.1.1')).to.deep.equal({
349+
protocol: 'udp',
350+
host: '1.1.1.1',
351+
port: 53,
352+
});
353+
expect(parseDnsResolver('udp://1.1.1.1')).to.deep.equal({
354+
protocol: 'udp',
355+
host: '1.1.1.1',
356+
port: 53,
357+
});
358+
expect(parseDnsResolver('tcp://1.1.1.1')).to.deep.equal({
359+
protocol: 'tcp',
360+
host: '1.1.1.1',
361+
port: 53,
362+
});
363+
expect(parseDnsResolver('tcp://1.1.1.1:5353')).to.deep.equal({
364+
protocol: 'tcp',
365+
host: '1.1.1.1',
366+
port: 5353,
367+
});
368+
});
369+
370+
it('resolves Custom UDP DNS and sends EDNS', async function () {
371+
const queries = [];
372+
const server = await startUdpDnsServer((query) => queries.push(query));
373+
const url = `${server.host}:${server.port}`;
374+
cacheKeys.push(hex_md5(`CUSTOM:${url}:udp.example.com:IPv4`));
375+
376+
try {
377+
const processor = ResolveDomainOperator({
378+
provider: 'Custom',
379+
type: 'IPv4',
380+
url,
381+
edns: '198.51.100.1',
382+
cache: 'disabled',
383+
});
384+
const output = await ApplyProcessor(processor, [
385+
{ name: 'UDP Custom', server: 'udp.example.com', port: 443 },
386+
]);
387+
388+
expect(output[0].server).to.equal('192.0.2.55');
389+
expect(queries).to.have.length(1);
390+
expect(queries[0].questions[0]).to.include({
391+
type: 'A',
392+
name: 'udp.example.com',
393+
});
394+
expect(getEdnsClientSubnet(queries[0])).to.equal('198.51.100.0');
395+
} finally {
396+
await server.close();
397+
}
398+
});
399+
400+
it('resolves Custom TCP DNS and sends EDNS', async function () {
401+
const queries = [];
402+
const server = await startTcpDnsServer((query) => queries.push(query));
403+
const url = `tcp://${server.host}:${server.port}`;
404+
cacheKeys.push(hex_md5(`CUSTOM:${url}:tcp.example.com:IPv4`));
405+
406+
try {
407+
const processor = ResolveDomainOperator({
408+
provider: 'Custom',
409+
type: 'IPv4',
410+
url,
411+
edns: '198.51.101.2',
412+
cache: 'disabled',
413+
});
414+
const output = await ApplyProcessor(processor, [
415+
{ name: 'TCP Custom', server: 'tcp.example.com', port: 443 },
416+
]);
417+
418+
expect(output[0].server).to.equal('192.0.2.56');
419+
expect(queries).to.have.length(1);
420+
expect(queries[0].questions[0]).to.include({
421+
type: 'A',
422+
name: 'tcp.example.com',
423+
});
424+
expect(getEdnsClientSubnet(queries[0])).to.equal('198.51.101.0');
425+
} finally {
426+
await server.close();
427+
}
428+
});
251429
});

0 commit comments

Comments
 (0)