diff --git a/packages/mitmproxy/src/lib/dns/base.js b/packages/mitmproxy/src/lib/dns/base.js index d44b1a5..b7413a9 100644 --- a/packages/mitmproxy/src/lib/dns/base.js +++ b/packages/mitmproxy/src/lib/dns/base.js @@ -1,8 +1,19 @@ const LRUCache = require('lru-cache') const log = require('../../utils/util.log.server') +const matchUtil = require('../../utils/util.match') const { DynamicChoice } = require('../choice/index') -const cacheSize = 1024 +function mapToList (ipMap) { + const ipList = [] + for (const key in ipMap) { + if (ipMap[key]) { // 配置为 ture 时才生效 + ipList.push(key) + } + } + return ipList +} + +const defaultCacheSize = 1024 class IpCache extends DynamicChoice { constructor (hostname) { @@ -22,10 +33,11 @@ class IpCache extends DynamicChoice { } module.exports = class BaseDNS { - constructor (dnsName) { + constructor (dnsName, cacheSize, preSetIpList) { this.dnsName = dnsName + this.preSetIpList = preSetIpList this.cache = new LRUCache({ - maxSize: cacheSize, + maxSize: (cacheSize > 0 ? cacheSize : defaultCacheSize), sizeCalculation: () => { return 1 }, @@ -53,7 +65,7 @@ module.exports = class BaseDNS { } const t = new Date() - let ipList = await this._lookup(hostname) + let ipList = await this._lookupInternal(hostname) if (ipList == null) { // 没有获取到ipv4地址 ipList = [] @@ -69,4 +81,46 @@ module.exports = class BaseDNS { return hostname } } + + async _lookupInternal (hostname) { + // 获取当前域名的预设IP列表 + let hostnamePreSetIpList = matchUtil.matchHostname(this.preSetIpList, hostname, 'matched preSetIpList') + if (hostnamePreSetIpList && (hostnamePreSetIpList.length > 0 || hostnamePreSetIpList.length === undefined)) { + if (hostnamePreSetIpList.length > 0) { + hostnamePreSetIpList = hostnamePreSetIpList.slice() + } else { + hostnamePreSetIpList = mapToList(hostnamePreSetIpList) + } + + if (hostnamePreSetIpList.length > 0) { + hostnamePreSetIpList.isPreSet = true + return hostnamePreSetIpList + } + } + + return await this._lookup(hostname) + } + + async _lookup (hostname) { + const start = Date.now() + try { + const response = await this._doDnsQuery(hostname) + const cost = Date.now() - start + if (response == null || response.answers == null || response.answers.length == null || response.answers.length === 0) { + // 说明没有获取到ip + log.warn(`DNS '${this.dnsName}' 没有该域名的IP地址: ${hostname}, cost: ${cost} ms, response:`, response) + return [] + } + const ret = response.answers.filter(item => item.type === 'A').map(item => item.data) + if (ret.length === 0) { + log.info(`DNS '${this.dnsName}' 没有该域名的IPv4地址: ${hostname}, cost: ${cost} ms`) + } else { + log.info(`DNS '${this.dnsName}' 获取到该域名的IPv4地址: ${hostname} - ${JSON.stringify(ret)}, cost: ${cost} ms`) + } + return ret + } catch (e) { + log.error(`DNS query error: ${hostname}, dns: ${this.dnsName}${this.dnsServer ? (`, dnsServer: ${this.dnsServer}`) : ''}, cost: ${Date.now() - start} ms, error:`, e) + return [] + } + } } diff --git a/packages/mitmproxy/src/lib/dns/https.js b/packages/mitmproxy/src/lib/dns/https.js index 1031af0..95c6606 100644 --- a/packages/mitmproxy/src/lib/dns/https.js +++ b/packages/mitmproxy/src/lib/dns/https.js @@ -1,64 +1,16 @@ const { promisify } = require('node:util') const doh = require('dns-over-http') -const log = require('../../utils/util.log.server') -const matchUtil = require('../../utils/util.match') const BaseDNS = require('./base') const dohQueryAsync = promisify(doh.query) -function mapToList (ipMap) { - const ipList = [] - for (const key in ipMap) { - if (ipMap[key]) { // 配置为 ture 时才生效 - ipList.push(key) - } - } - return ipList -} - module.exports = class DNSOverHTTPS extends BaseDNS { - constructor (dnsName, dnsServer, preSetIpList) { - super(dnsName) + constructor (dnsName, cacheSize, preSetIpList, dnsServer) { + super(dnsName, cacheSize, preSetIpList) this.dnsServer = dnsServer - this.preSetIpList = preSetIpList } - async _lookup (hostname) { - // 获取当前域名的预设IP列表 - let hostnamePreSetIpList = matchUtil.matchHostname(this.preSetIpList, hostname, 'matched preSetIpList') - if (hostnamePreSetIpList && (hostnamePreSetIpList.length > 0 || hostnamePreSetIpList.length === undefined)) { - if (hostnamePreSetIpList.length > 0) { - hostnamePreSetIpList = hostnamePreSetIpList.slice() - } else { - hostnamePreSetIpList = mapToList(hostnamePreSetIpList) - } - - if (hostnamePreSetIpList.length > 0) { - hostnamePreSetIpList.isPreSet = true - return hostnamePreSetIpList - } - } - - // 未预设当前域名的IP列表,则从dns服务器获取 - const start = new Date() - try { - const result = await dohQueryAsync({ url: this.dnsServer }, [{ type: 'A', name: hostname }]) - const cost = new Date() - start - if (result.answers.length === 0) { - // 说明没有获取到ip - log.info(`DNS '${this.dnsName}' 没有该域名的IP地址: ${hostname}, cost: ${cost} ms`) - return [] - } - const ret = result.answers.filter(item => item.type === 'A').map(item => item.data) - if (ret.length === 0) { - log.info(`DNS '${this.dnsName}' 没有该域名的IPv4地址: ${hostname}, cost: ${cost} ms`) - } else { - log.info(`DNS '${this.dnsName}' 获取到该域名的IPv4地址: ${hostname} ${JSON.stringify(ret)}, cost: ${cost} ms`) - } - return ret - } catch (e) { - log.warn(`DNS query error: ${hostname}, dns: ${this.dnsName}, dnsServer: ${this.dnsServer}, cost: ${new Date() - start} ms, error:`, e) - return [] - } + async _doDnsQuery (hostname) { + return await dohQueryAsync({ url: this.dnsServer }, [{ type: 'A', name: hostname }]) } } diff --git a/packages/mitmproxy/src/lib/dns/index.js b/packages/mitmproxy/src/lib/dns/index.js index e153a0c..b931a49 100644 --- a/packages/mitmproxy/src/lib/dns/index.js +++ b/packages/mitmproxy/src/lib/dns/index.js @@ -1,8 +1,10 @@ const matchUtil = require('../../utils/util.match') -const DNSOverHTTPS = require('./https.js') -const DNSOverIpAddress = require('./ipaddress.js') const DNSOverPreSetIpList = require('./preset.js') +const DNSOverIpAddress = require('./ipaddress.js') +const DNSOverHTTPS = require('./https.js') const DNSOverTLS = require('./tls.js') +const DNSOverTCP = require('./tcp.js') +const DNSOverUDP = require('./udp.js') module.exports = { initDNS (dnsProviders, preSetIpList) { @@ -12,12 +14,50 @@ module.exports = { for (const provider in dnsProviders) { const conf = dnsProviders[provider] - if (conf.type === 'ipaddress') { - dnsMap[provider] = new DNSOverIpAddress(provider) - } else if (conf.type === 'https') { - dnsMap[provider] = new DNSOverHTTPS(provider, conf.server, preSetIpList) + let server = conf.server || conf.host + if (server != null) { + server = server.replace(/\s+/, '') + } + if (!server) { + continue + } + + // 获取DNS类型 + if (conf.type == null) { + if (server.startsWith('https://')) { + conf.type = 'https' + } else if (server.startsWith('tls://')) { + conf.type = 'tls' + } else if (server.startsWith('tcp://')) { + conf.type = 'tcp' + } else if (server.includes('://') && !server.startsWith('udp://')) { + throw new Error(`Unknown type DNS: ${server}, provider: ${provider}`) + } else { + conf.type = 'udp' + } } else { - dnsMap[provider] = new DNSOverTLS(provider) + conf.type = conf.type.toLowerCase() + } + + if (conf.type === 'ipaddress') { + dnsMap[provider] = new DNSOverIpAddress(provider, conf.cacheSize, preSetIpList) + } else if (conf.type === 'https') { + dnsMap[provider] = new DNSOverHTTPS(provider, conf.cacheSize, preSetIpList, server) + } else if (conf.type === 'tls') { + if (server.startsWith('tls://')) { + server = server.substring(6) + } + dnsMap[provider] = new DNSOverTLS(provider, conf.cacheSize, preSetIpList, server, conf.port, conf.servername) + } else if (conf.type === 'tcp') { + if (server.startsWith('tcp://')) { + server = server.substring(6) + } + dnsMap[provider] = new DNSOverTCP(provider, conf.cacheSize, preSetIpList, server, conf.port) + } else { // udp + if (server.startsWith('udp://')) { + server = server.substring(6) + } + dnsMap[provider] = new DNSOverUDP(provider, conf.cacheSize, preSetIpList, server, conf.port) } // 设置DNS名称到name属性中 diff --git a/packages/mitmproxy/src/lib/dns/ipaddress.js b/packages/mitmproxy/src/lib/dns/ipaddress.js index ab4d85d..e02e43e 100644 --- a/packages/mitmproxy/src/lib/dns/ipaddress.js +++ b/packages/mitmproxy/src/lib/dns/ipaddress.js @@ -25,14 +25,5 @@ module.exports = class DNSOverIpAddress extends BaseDNS { } log.warn(`[dns] get ${hostname} ipaddress: error`) return null - - // const { answers } = await dnstls.query(hostname) - // - // const answer = answers.find(answer => answer.type === 'A' && answer.class === 'IN') - // - // log.info('dns lookup:', hostname, answer) - // if (answer) { - // return answer.data - // } } } diff --git a/packages/mitmproxy/src/lib/dns/tcp.js b/packages/mitmproxy/src/lib/dns/tcp.js new file mode 100644 index 0000000..98357ee --- /dev/null +++ b/packages/mitmproxy/src/lib/dns/tcp.js @@ -0,0 +1,51 @@ +const net = require('node:net') +const { Buffer } = require('node:buffer') +const dnsPacket = require('dns-packet') +const randi = require('random-int') +const BaseDNS = require('./base') + +const defaultPort = 53 // UDP类型的DNS服务默认端口号 + +module.exports = class DNSOverTCP extends BaseDNS { + constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort) { + super(dnsName, cacheSize, preSetIpList) + this.dnsServer = dnsServer + this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort + } + + _doDnsQuery (hostname) { + return new Promise((resolve, reject) => { + // 构造 DNS 查询报文 + const packet = dnsPacket.encode({ + flags: dnsPacket.RECURSION_DESIRED, + type: 'query', + id: randi(0x0, 0xFFFF), + questions: [{ + type: 'A', + name: hostname, + }], + }) + + // --- TCP 查询 --- + const tcpClient = net.createConnection({ + host: this.dnsServer, + port: this.dnsServerPort, + }, () => { + // TCP DNS 报文前需添加 2 字节长度头 + const lengthBuffer = Buffer.alloc(2) + lengthBuffer.writeUInt16BE(packet.length) + tcpClient.write(Buffer.concat([lengthBuffer, packet])) + }) + + tcpClient.on('data', (data) => { + const length = data.readUInt16BE(0) + const response = dnsPacket.decode(data.subarray(2, 2 + length)) + resolve(response) + }) + + tcpClient.on('error', (err) => { + reject(err) + }) + }) + } +} diff --git a/packages/mitmproxy/src/lib/dns/tls.js b/packages/mitmproxy/src/lib/dns/tls.js index 0247c3e..ba0a55f 100644 --- a/packages/mitmproxy/src/lib/dns/tls.js +++ b/packages/mitmproxy/src/lib/dns/tls.js @@ -1,16 +1,27 @@ const dnstls = require('dns-over-tls') -const log = require('../../utils/util.log.server') const BaseDNS = require('./base') +const defaultPort = 853 + module.exports = class DNSOverTLS extends BaseDNS { - async _lookup (hostname) { - const { answers } = await dnstls.query(hostname) + constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort, dnsServerName) { + super(dnsName, cacheSize, preSetIpList) + this.dnsServer = dnsServer + this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort + this.dnsServerName = dnsServerName + } - const answer = answers.find(answer => answer.type === 'A' && answer.class === 'IN') + async _doDnsQuery (hostname) { + const options = { + host: this.dnsServer, + port: this.dnsServerPort, + servername: this.dnsServerName || this.dnsServer, - log.info('DNS lookup:', hostname, answer) - if (answer) { - return answer.data + name: hostname, + klass: 'IN', + type: 'A', } + + return await dnstls.query(options) } } diff --git a/packages/mitmproxy/src/lib/dns/udp.js b/packages/mitmproxy/src/lib/dns/udp.js new file mode 100644 index 0000000..d751ac5 --- /dev/null +++ b/packages/mitmproxy/src/lib/dns/udp.js @@ -0,0 +1,44 @@ +const dgram = require('node:dgram') +const dnsPacket = require('dns-packet') +const randi = require('random-int') +const BaseDNS = require('./base') + +const udpClient = dgram.createSocket('udp4') + +const defaultPort = 53 // UDP类型的DNS服务默认端口号 + +module.exports = class DNSOverUDP extends BaseDNS { + constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort) { + super(dnsName, cacheSize, preSetIpList) + this.dnsServer = dnsServer + this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort + } + + _doDnsQuery (hostname) { + return new Promise((resolve, reject) => { + // 构造 DNS 查询报文 + const packet = dnsPacket.encode({ + flags: dnsPacket.RECURSION_DESIRED, + type: 'query', + id: randi(0x0, 0xFFFF), + questions: [{ + type: 'A', + name: hostname, + }], + }) + + // 发送 UDP 查询 + udpClient.send(packet, 0, packet.length, this.dnsServerPort, this.dnsServer, (err) => { + if (err) { + reject(err) + } + }) + + // 接收 UDP 响应 + udpClient.on('message', (msg) => { + const response = dnsPacket.decode(msg) + resolve(response) + }) + }) + } +} diff --git a/packages/mitmproxy/test/dnsTest.mjs b/packages/mitmproxy/test/dnsTest.mjs index 0f23a95..b12103f 100644 --- a/packages/mitmproxy/test/dnsTest.mjs +++ b/packages/mitmproxy/test/dnsTest.mjs @@ -1,56 +1,99 @@ +import assert from 'node:assert' import dns from '../src/lib/dns/index.js' +const presetIp = '100.100.100.100' + const dnsProviders = dns.initDNS({ - aliyun: { - type: 'https', - server: 'https://dns.alidns.com/dns-query', - cacheSize: 1000, - }, - cloudflare: { - type: 'https', - server: 'https://1.1.1.1/dns-query', - cacheSize: 1000, - }, ipaddress: { type: 'ipaddress', server: 'ipaddress', cacheSize: 1000, }, - quad9: { + + // https + cloudflare: { type: 'https', + server: 'https://1.1.1.1/dns-query', + cacheSize: 1000, + }, + quad9: { server: 'https://9.9.9.9/dns-query', cacheSize: 1000, }, - rubyfish: { + aliyun: { type: 'https', + server: 'https://dns.alidns.com/dns-query', + cacheSize: 1000, + }, + safe360: { + server: 'https://doh.360.cn/dns-query', + cacheSize: 1000, + }, + rubyfish: { server: 'https://rubyfish.cn/dns-query', cacheSize: 1000, }, py233: { - type: 'https', server: ' https://i.233py.com/dns-query', cacheSize: 1000, }, - // sb: { - // type: 'https', - // server: 'https://doh.dns.sb/dns-query', - // cacheSize: 1000 - // }, - // adguard: { - // type: 'https', - // server: ' https://dns.adguard.com/dns-query', - // cacheSize: 1000 - // } + // tls + cloudflareTLS: { + type: 'tls', + server: '1.1.1.1', + servername: 'cloudflare-dns.com', + cacheSize: 1000, + }, + quad9TLS: { + server: 'tls://9.9.9.9', + servername: 'dns.quad9.net', + cacheSize: 1000, + }, + aliyunTLS: { + type: 'tls', + server: '223.5.5.5', + cacheSize: 1000, + }, + aliyunTLS2: { + server: 'tls://223.6.6.6', + cacheSize: 1000, + }, + safe360TLS: { + server: 'tls://dot.360.cn', + cacheSize: 1000, + }, + + // tcp + googleTCP: { + type: 'tcp', + server: '8.8.8.8', + cacheSize: 1000, + }, + aliyunTCP: { + server: 'tcp://223.5.5.5', + cacheSize: 1000, + }, + + // udp + googleUDP: { + type: 'udp', + server: '8.8.8.8', + cacheSize: 1000, + }, + aliyunUDP: { + server: 'udp://223.5.5.5', + cacheSize: 1000, + }, +}, { + origin: { + 'xxx.com': [ + presetIp + ] + } }) -// const test = '111