From 04decbed48817fb5a50a852b9c1456ce7c89e97a Mon Sep 17 00:00:00 2001 From: 8odream Date: Fri, 16 May 2025 12:21:36 +0800 Subject: [PATCH 1/5] =?UTF-8?q?doc:=20=E6=B7=BB=E5=8A=A0=E6=8B=A6=E6=88=AA?= =?UTF-8?q?=E5=99=A8=E9=85=8D=E7=BD=AE=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/wiki/加速服务使用说明.md | 65 +++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/doc/wiki/加速服务使用说明.md b/doc/wiki/加速服务使用说明.md index c6cf4ba..2cd1d1e 100644 --- a/doc/wiki/加速服务使用说明.md +++ b/doc/wiki/加速服务使用说明.md @@ -35,8 +35,71 @@ | 响应篡改拦截器 | responseReplace | 203 | | 脚本拦截器 | script | 211 | -# 5. 域名白名单: + 3. 配置示例: + ```json + "*.example.com": { + ".*":{ //后续url匹配规则 + // 请求篡改配置 + "requestReplace": { + "headers": { + "User-Agent": "Mozilla/5.0", // 替换User-Agent + "Referer": "[remove]" // 删除Referer头 + }, + "doDownload": true // 启用下载请求处理.要转换为下载请求,需要 responseReplace 拦截器的配合使用 + }, + + // 代理配置 + "proxy": "proxy.example.com", // 代理目标地址 + "backup": [ // 备用代理服务器列表 + "backup1.example.com", + "backup2.example.com" + ], + //"proxy": "https://$1.proxy.com", + //"replace": "https://(.*?)\\.example\\.com" + //使用${path}数组捕获和替换 + + "sni": "example.com", // SNI服务器名称指示 + "unVerifySsl": true, // 跳过SSL证书验证 + + // 基本拦截配置 + "abort": false, // 是否拦截请求(返回403) + "success": true, // 是否快速返回成功(返回200) + + "cacheDays": 365, // 缓存天数 + //cache系列包含如下关键词:cacheSecods,cacheMinutes,cacheHours,cacheDays,cacheWeeks,cacheMonths,cacheYears + + // OPTIONS请求配置 + "options": { + "headers": { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET", //不设置默认返回GET,POST,PUT,DELETE,HEAD,OPTIONS,PATCH + "Access-Control-Allow-Headers": "Content-Type, X-Requested-With, X-Custom-Header", //不设置默认返回* + "Access-Control-Max-Age": "86400" //不设置默认一个月 + } + } + + // 响应篡改配置 + "responseReplace": { + "headers": { + "Cache-Control": "no-cache", // 禁用缓存 + "X-Powered-By": "[remove]", // 删除服务器信息头 + "Access-Control-Allow-Origin": "*" // 添加CORS头 + }, + "doDownload": true // 强制响应为文件下载 + }, + "cacheExcludeStatusCodeList":[200], // 缓存排除状态码列表 + "cacheMinStatusCode": 200, // 缓存最小状态码,默认200 + "cacheMaxStatusCode": 303, // 缓存最大状态码,默认303 + "cacheControlType": "public", // 缓存控制类型,默认public,可选private + "cacheImmutable": true, + + "tampermonkeyScript": "UrlOrPathToTampermonkeyScript.js", // 注入Tampermonkey + "script" : "UrlOrPathToYourScript.js" //支持数组,填入多个地址 + } + } + ``` +# 5. 域名白名单: # 6. DNS服务管理: ## 6.1. 配置 `DNS-over-HTTPS` 的DNS服务: From 8a722282fb8d01b123804371fdc3f6b66e5f4d2c Mon Sep 17 00:00:00 2001 From: 8odream Date: Thu, 22 May 2025 16:10:36 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E5=B0=9D=E8=AF=95ipv6=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/wiki/加速服务使用说明.md | 5 +- packages/gui/src/view/pages/server.vue | 54 +++++++++++++- .../src/lib/proxy/mitmproxy/dnsLookup.js | 73 +++++++++++++------ .../mitmproxy/src/lib/speed/SpeedTester.js | 31 +++++++- packages/mitmproxy/test/ipv6nettest.js | 55 ++++++++++++++ 5 files changed, 188 insertions(+), 30 deletions(-) create mode 100644 packages/mitmproxy/test/ipv6nettest.js diff --git a/doc/wiki/加速服务使用说明.md b/doc/wiki/加速服务使用说明.md index 2cd1d1e..4cbfb5c 100644 --- a/doc/wiki/加速服务使用说明.md +++ b/doc/wiki/加速服务使用说明.md @@ -43,7 +43,7 @@ "requestReplace": { "headers": { "User-Agent": "Mozilla/5.0", // 替换User-Agent - "Referer": "[remove]" // 删除Referer头 + "Referer": "[remove]" // 删除Refzerer头 }, "doDownload": true // 启用下载请求处理.要转换为下载请求,需要 responseReplace 拦截器的配合使用 }, @@ -55,9 +55,6 @@ "backup2.example.com" ], - //"proxy": "https://$1.proxy.com", - //"replace": "https://(.*?)\\.example\\.com" - //使用${path}数组捕获和替换 "sni": "example.com", // SNI服务器名称指示 "unVerifySsl": true, // 跳过SSL证书验证 diff --git a/packages/gui/src/view/pages/server.vue b/packages/gui/src/view/pages/server.vue index 7ceb64f..a3b02a9 100644 --- a/packages/gui/src/view/pages/server.vue +++ b/packages/gui/src/view/pages/server.vue @@ -163,7 +163,43 @@ export default { const listener = async (event, message) => { console.log('get speed event', event, message) if (message.key === 'getList') { - this.speedTestList = message.value + // 详细记录接收到的原始数据 + console.log('speedTestList raw data:', JSON.stringify(message.value, null, 2)) + + // 数据验证和标准化 + const validatedData = {} + for (const hostname in message.value) { + const item = message.value[hostname] + if (!item.backupList) { + console.warn(`Missing backupList for ${hostname}`) + continue + } + + validatedData[hostname] = { + alive: item.alive || [], + backupList: item.backupList.map(ipObj => { + // 标准化IP地址格式 + const standardized = { + host: ipObj.host, + port: ipObj.port || 443, + dns: ipObj.dns || 'unknown', + time: ipObj.time || null + } + + // 特殊处理IPv6地址 + if (ipObj.host.includes(':')) { + console.log('Found IPv6 address:', { + original: ipObj.host, + standardized: standardized.host + }) + } + return standardized + }) + } + } + + this.speedTestList = validatedData + console.log('Validated speed test data:', JSON.stringify(validatedData, null, 2)) } } this.$api.ipc.on('speed', listener) @@ -453,8 +489,10 @@ export default { {{ element.host }} {{ element.time }}{{ element.time ? 'ms' : '' }} {{ element.dns }} + IPv6 @@ -509,4 +547,18 @@ export default { width: 45px; } } +.ipv6-tag { + position: relative; + padding-right: 40px; +} +.ipv6-badge { + position: absolute; + right: 5px; + top: 2px; + font-size: 10px; + background: #1890ff; + color: white; + padding: 0 4px; + border-radius: 3px; +} diff --git a/packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js b/packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js index 6668ac0..adc2dcd 100644 --- a/packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js +++ b/packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js @@ -11,52 +11,77 @@ module.exports = { if (tester) { const aliveIpObj = tester.pickFastAliveIpObj() if (aliveIpObj) { + const family = aliveIpObj.host.includes(':') ? 6 : 4 log.info(`----- ${action}: ${hostname}, use alive ip from dns '${aliveIpObj.dns}': ${aliveIpObj.host}${target} -----`) if (res) { res.setHeader('DS-DNS-Lookup', `IpTester: ${aliveIpObj.host} ${aliveIpObj.dns === '预设IP' ? 'PreSet' : aliveIpObj.dns}`) } - callback(null, aliveIpObj.host, 4) + callback(null, aliveIpObj.host, family) return } else { log.info(`----- ${action}: ${hostname}, no alive ip${target}, tester: { "ready": ${tester.ready}, "backupList": ${JSON.stringify(tester.backupList)} }`) } } - dns.lookup(hostname).then((ip) => { + + // 优先尝试IPv6查询 + dns.lookup(hostname, { family: 6 }).then((ip) => { + if (ip && ip !== hostname) { + if (isDnsIntercept) { + isDnsIntercept.dns = dns + isDnsIntercept.hostname = hostname + isDnsIntercept.ip = ip + } + log.info(`----- ${action}: ${hostname}, use ipv6 from dns '${dns.dnsName}': ${ip}${target} -----`) + if (res) { + res.setHeader('DS-DNS-Lookup', `DNS: ${ip} ${dns.dnsName === '预设IP' ? 'PreSet' : dns.dnsName}`) + } + callback(null, ip, 6) + return + } + + // 回退到IPv4查询 + return dns.lookup(hostname) + }).then((ip) => { + if (!ip || ip === hostname) { + // 使用默认dns + log.info(`----- ${action}: ${hostname}, use hostname by default DNS: ${hostname}${target}, options:`, options, ', dns:', dns) + return defaultDns.lookup(hostname, options, callback) + } + if (isDnsIntercept) { isDnsIntercept.dns = dns isDnsIntercept.hostname = hostname isDnsIntercept.ip = ip } - if (ip !== hostname) { - // 判断是否为测速失败的IP,如果是,则不使用当前IP - let isTestFailedIp = false - if (tester && tester.ready && tester.backupList && tester.backupList.length > 0) { - for (let i = 0; i < tester.backupList.length; i++) { - const item = tester.backupList[i] - if (item.host === ip) { - if (item.time == null) { - isTestFailedIp = true - } - break + // 判断是否为测速失败的IP + let isTestFailedIp = false + if (tester && tester.ready && tester.backupList && tester.backupList.length > 0) { + for (let i = 0; i < tester.backupList.length; i++) { + const item = tester.backupList[i] + if (item.host === ip) { + if (item.time == null) { + isTestFailedIp = true } + break } } - if (isTestFailedIp === false) { - log.info(`----- ${action}: ${hostname}, use ip from dns '${dns.dnsName}': ${ip}${target} -----`) - if (res) { - res.setHeader('DS-DNS-Lookup', `DNS: ${ip} ${dns.dnsName === '预设IP' ? 'PreSet' : dns.dnsName}`) - } - callback(null, ip, 4) - return - } else { - // 使用默认dns - log.info(`----- ${action}: ${hostname}, use hostname by default DNS: ${hostname}, skip test failed ip from dns '${dns.dnsName}: ${ip}'${target}, options:`, options) + } + + if (!isTestFailedIp) { + const family = ip.includes(':') ? 6 : 4 + log.info(`----- ${action}: ${hostname}, use ip from dns '${dns.dnsName}': ${ip}${target} -----`) + if (res) { + res.setHeader('DS-DNS-Lookup', `DNS: ${ip} ${dns.dnsName === '预设IP' ? 'PreSet' : dns.dnsName}`) } + callback(null, ip, family) } else { // 使用默认dns - log.info(`----- ${action}: ${hostname}, use hostname by default DNS: ${hostname}${target}, options:`, options, ', dns:', dns) + log.info(`----- ${action}: ${hostname}, use hostname by default DNS: ${hostname}, skip test failed ip from dns '${dns.dnsName}: ${ip}'${target}, options:`, options) + defaultDns.lookup(hostname, options, callback) } + }).catch((e) => { + log.error(`DNS lookup error for ${hostname}:`, e) defaultDns.lookup(hostname, options, callback) }) } diff --git a/packages/mitmproxy/src/lib/speed/SpeedTester.js b/packages/mitmproxy/src/lib/speed/SpeedTester.js index 59f4995..26858e8 100644 --- a/packages/mitmproxy/src/lib/speed/SpeedTester.js +++ b/packages/mitmproxy/src/lib/speed/SpeedTester.js @@ -79,7 +79,31 @@ class SpeedTester { } async getFromOneDns (dns) { - return await dns._lookupInternal(this.hostname) + // 优先尝试IPv6查询 + try { + const ipv6Result = await dns._lookupInternal(this.hostname, { family: 6 }) + if (ipv6Result && ipv6Result.length > 0) { + // 标准化IPv6地址格式 + const standardized = ipv6Result.map(ip => { + // 确保IPv6地址格式统一 + if (ip.includes(':')) { + return ip.toLowerCase().replace(/\[|\]/g, '') + } + return ip + }) + log.debug(`[dns] Got IPv6 addresses for ${this.hostname}:`, standardized) + return standardized + } + } catch (e) { + log.debug(`[dns] IPv6 lookup failed for ${this.hostname}: ${e.message}`) + } + + // 回退到IPv4查询 + const ipv4Result = await dns._lookupInternal(this.hostname) + if (ipv4Result) { + log.debug(`[dns] Got IPv4 addresses for ${this.hostname}:`, ipv4Result) + } + return ipv4Result } async test () { @@ -88,6 +112,11 @@ class SpeedTester { this.backupList = _.unionBy(newBackupList, 'host') this.testCount++ + // 详细记录IPv6地址 + const ipv6List = this.backupList.filter(item => item.host.includes(':')) + if (ipv6List.length > 0) { + log.info('[speed] IPv6 addresses found for', this.hostname, ':', ipv6List) + } log.info('[speed]', this.hostname, '➜ ip-list:', this.backupList) await this.testBackups() if (config.notify) { diff --git a/packages/mitmproxy/test/ipv6nettest.js b/packages/mitmproxy/test/ipv6nettest.js new file mode 100644 index 0000000..b31eb10 --- /dev/null +++ b/packages/mitmproxy/test/ipv6nettest.js @@ -0,0 +1,55 @@ +const net = require('net'); +const { setTimeout } = require('timers/promises'); + +// 测试的IPv6地址和端口 +const TEST_HOST = '6.ipw.cn'; +const TEST_PORT = 80; +const TIMEOUT = 5000; // 5秒超时 + +async function testIPv6Connection() { + const socket = new net.Socket(); + + // 设置超时 + socket.setTimeout(TIMEOUT); + + try { + // 尝试连接 + await new Promise((resolve, reject) => { + socket.on('connect', () => { + const { address, port } = socket.address(); + console.log(`成功连接到 ${TEST_HOST} 的IPv6地址 [${address}]:${port}`); + socket.end(); + resolve(); + }); + + socket.on('timeout', () => { + socket.destroy(); + reject(new Error('连接超时')); + }); + + socket.on('error', (err) => { + reject(err); + }); + + socket.connect(TEST_PORT, TEST_HOST); + }); + + return true; + } catch (err) { + console.error('IPv6连接测试失败:', err.message); + return false; + } finally { + socket.destroy(); + } +} + +// 执行测试 +testIPv6Connection() + .then(success => { + console.log(`IPv6连接测试结果: ${success ? '成功' : '失败'}`); + process.exit(success ? 0 : 1); + }) + .catch(err => { + console.error('测试过程中发生错误:', err); + process.exit(1); + }); From a19a73813cbf86d4e3b28235aff8886986314df6 Mon Sep 17 00:00:00 2001 From: 8odream Date: Fri, 13 Jun 2025 17:48:49 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E6=94=AF=E6=8C=81ipv6=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/gui/src/view/pages/server.vue | 102 +++++++++++++++--- packages/mitmproxy/src/lib/dns/base.js | 24 +++-- packages/mitmproxy/src/lib/dns/https.js | 14 ++- packages/mitmproxy/src/lib/dns/tcp.js | 8 +- packages/mitmproxy/src/lib/dns/tls.js | 10 +- packages/mitmproxy/src/lib/dns/udp.js | 4 +- .../src/lib/proxy/mitmproxy/dnsLookup.js | 7 -- .../mitmproxy/src/lib/speed/SpeedTester.js | 29 +++-- 8 files changed, 140 insertions(+), 58 deletions(-) diff --git a/packages/gui/src/view/pages/server.vue b/packages/gui/src/view/pages/server.vue index a3b02a9..d44855a 100644 --- a/packages/gui/src/view/pages/server.vue +++ b/packages/gui/src/view/pages/server.vue @@ -161,11 +161,7 @@ export default { }, registerSpeedTestEvent () { const listener = async (event, message) => { - console.log('get speed event', event, message) if (message.key === 'getList') { - // 详细记录接收到的原始数据 - console.log('speedTestList raw data:', JSON.stringify(message.value, null, 2)) - // 数据验证和标准化 const validatedData = {} for (const hostname in message.value) { @@ -185,21 +181,12 @@ export default { dns: ipObj.dns || 'unknown', time: ipObj.time || null } - - // 特殊处理IPv6地址 - if (ipObj.host.includes(':')) { - console.log('Found IPv6 address:', { - original: ipObj.host, - standardized: standardized.host - }) - } return standardized }) } } this.speedTestList = validatedData - console.log('Validated speed test data:', JSON.stringify(validatedData, null, 2)) } } this.$api.ipc.on('speed', listener) @@ -549,16 +536,101 @@ export default { } .ipv6-tag { position: relative; - padding-right: 40px; + padding-right: 45px !important; + margin-right: 5px !important; + display: inline-flex !important; + align-items: center !important; + min-width: 200px !important; } .ipv6-badge { position: absolute; right: 5px; - top: 2px; + top: 50%; + transform: translateY(-50%); font-size: 10px; background: #1890ff; color: white; padding: 0 4px; border-radius: 3px; + line-height: 16px; + height: 16px; +} +.ip-box { + display: flex; + flex-wrap: wrap; + gap: 8px; + padding: 8px; + background-color: #fafafa; + border-radius: 4px; + margin-top: 8px; + max-width: 100%; + overflow: hidden; +} +.ip-item { + display: flex; + align-items: center; + padding: 4px 8px; + background-color: #fff; + border: 1px solid #e8e8e8; + border-radius: 4px; + font-size: 12px; + color: #666; + word-break: break-all; + max-width: calc(100% - 16px); + flex: 1 1 auto; + min-width: 0; +} +.ip-item .ip-text { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.ip-item .ip-speed { + margin-left: 8px; + white-space: nowrap; +} +.ip-item .ip-speed.success { + color: #52c41a; +} +.ip-item .ip-speed.warning { + color: #faad14; +} +.ip-item .ip-speed.error { + color: #ff4d4f; +} +.domain-box { + margin-bottom: 16px; + padding: 12px; + background-color: #fff; + border: 1px solid #e8e8e8; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + overflow: hidden; +} +.domain-box .domain-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; +} +.domain-box .domain-title { + font-size: 14px; + font-weight: 500; + color: #333; + margin: 0; + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.domain-box .domain-actions { + display: flex; + align-items: center; + gap: 8px; + margin-left: 8px; + flex-shrink: 0; } diff --git a/packages/mitmproxy/src/lib/dns/base.js b/packages/mitmproxy/src/lib/dns/base.js index fb44844..6a745c6 100644 --- a/packages/mitmproxy/src/lib/dns/base.js +++ b/packages/mitmproxy/src/lib/dns/base.js @@ -52,7 +52,7 @@ module.exports = class BaseDNS { } } - async lookup (hostname) { + async lookup (hostname, options = {}) { try { let ipCache = this.cache.get(hostname) if (ipCache) { @@ -66,9 +66,9 @@ module.exports = class BaseDNS { } const t = new Date() - let ipList = await this._lookupInternal(hostname) + let ipList = await this._lookupInternal(hostname, options) if (ipList == null) { - // 没有获取到ipv4地址 + // 没有获取到ip ipList = [] } ipList.push(hostname) // 把原域名加入到统计里去 @@ -83,7 +83,7 @@ module.exports = class BaseDNS { } } - async _lookupInternal (hostname) { + async _lookupInternal (hostname, options = {}) { // 获取当前域名的预设IP列表 let hostnamePreSetIpList = matchUtil.matchHostname(this.preSetIpList, hostname, `matched preSetIpList(${this.dnsName})`) if (hostnamePreSetIpList && (hostnamePreSetIpList.length > 0 || hostnamePreSetIpList.length === undefined)) { @@ -100,24 +100,28 @@ module.exports = class BaseDNS { } } - return await this._lookup(hostname) + return await this._lookup(hostname, options) } - async _lookup (hostname) { + async _lookup (hostname, options = {}) { const start = Date.now() try { - const response = await this._doDnsQuery(hostname) + const response = await this._doDnsQuery(hostname, options) const cost = Date.now() - start if (response == null || response.answers == null || response.answers.length == null || response.answers.length === 0) { // 说明没有获取到ip log.warn(`[DNS-over-${this.dnsType} '${this.dnsName}'] 没有该域名的IP地址: ${hostname}, cost: ${cost} ms, response:`, response) return [] } - const ret = response.answers.filter(item => item.type === 'A').map(item => item.data) + + // 根据查询类型过滤结果 + const type = options.family === 6 ? 'AAAA' : 'A' + const ret = response.answers.filter(item => item.type === type).map(item => item.data) + if (ret.length === 0) { - log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 没有该域名的IPv4地址: ${hostname}, cost: ${cost} ms`) + log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 没有该域名的IPv${options.family === 6 ? '6' : '4'}地址: ${hostname}, cost: ${cost} ms`) } else { - log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的IPv4地址: ${hostname} - ${JSON.stringify(ret)}, cost: ${cost} ms`) + log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的IPv${options.family === 6 ? '6' : '4'}地址: ${hostname} - ${JSON.stringify(ret)}, cost: ${cost} ms`) } return ret } catch (e) { diff --git a/packages/mitmproxy/src/lib/dns/https.js b/packages/mitmproxy/src/lib/dns/https.js index 3ab889c..42f773d 100644 --- a/packages/mitmproxy/src/lib/dns/https.js +++ b/packages/mitmproxy/src/lib/dns/https.js @@ -8,9 +8,19 @@ module.exports = class DNSOverHTTPS extends BaseDNS { constructor (dnsName, cacheSize, preSetIpList, dnsServer) { super(dnsName, 'HTTPS', cacheSize, preSetIpList) this.dnsServer = dnsServer + this.isIPv6 = dnsServer.includes(':') && dnsServer.includes('[') && dnsServer.includes(']') } - async _doDnsQuery (hostname) { - return await dohQueryAsync({ url: this.dnsServer }, [{ type: 'A', name: hostname }]) + async _doDnsQuery (hostname, options = {}) { + return await dohQueryAsync( + { + url: this.dnsServer, + family: this.isIPv6 ? 6 : 4 + }, + [{ + type: options.family === 6 ? 'AAAA' : 'A', + name: hostname + }] + ) } } diff --git a/packages/mitmproxy/src/lib/dns/tcp.js b/packages/mitmproxy/src/lib/dns/tcp.js index 6141609..8ab8209 100644 --- a/packages/mitmproxy/src/lib/dns/tcp.js +++ b/packages/mitmproxy/src/lib/dns/tcp.js @@ -4,16 +4,17 @@ const dnsPacket = require('dns-packet') const randi = require('random-int') const BaseDNS = require('./base') -const defaultPort = 53 // UDP类型的DNS服务默认端口号 +const defaultPort = 53 // TCP类型的DNS服务默认端口号 module.exports = class DNSOverTCP extends BaseDNS { constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort) { super(dnsName, 'TCP', cacheSize, preSetIpList) this.dnsServer = dnsServer this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort + this.isIPv6 = dnsServer.includes(':') && dnsServer.includes('[') && dnsServer.includes(']') } - _doDnsQuery (hostname) { + _doDnsQuery (hostname, options = {}) { return new Promise((resolve, reject) => { // 构造 DNS 查询报文 const packet = dnsPacket.encode({ @@ -21,7 +22,7 @@ module.exports = class DNSOverTCP extends BaseDNS { type: 'query', id: randi(0x0, 0xFFFF), questions: [{ - type: 'A', + type: options.family === 6 ? 'AAAA' : 'A', name: hostname, }], }) @@ -30,6 +31,7 @@ module.exports = class DNSOverTCP extends BaseDNS { const tcpClient = net.createConnection({ host: this.dnsServer, port: this.dnsServerPort, + family: this.isIPv6 ? 6 : 4 }, () => { // TCP DNS 报文前需添加 2 字节长度头 const lengthBuffer = Buffer.alloc(2) diff --git a/packages/mitmproxy/src/lib/dns/tls.js b/packages/mitmproxy/src/lib/dns/tls.js index b4b5d99..80b01ee 100644 --- a/packages/mitmproxy/src/lib/dns/tls.js +++ b/packages/mitmproxy/src/lib/dns/tls.js @@ -9,19 +9,21 @@ module.exports = class DNSOverTLS extends BaseDNS { this.dnsServer = dnsServer this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort this.dnsServerName = dnsServerName + this.isIPv6 = dnsServer.includes(':') && dnsServer.includes('[') && dnsServer.includes(']') } - async _doDnsQuery (hostname) { - const options = { + async _doDnsQuery (hostname, options = {}) { + const queryOptions = { host: this.dnsServer, port: this.dnsServerPort, servername: this.dnsServerName || this.dnsServer, + family: this.isIPv6 ? 6 : 4, name: hostname, klass: 'IN', - type: 'A', + type: options.family === 6 ? 'AAAA' : 'A', } - return await dnstls.query(options) + return await dnstls.query(queryOptions) } } diff --git a/packages/mitmproxy/src/lib/dns/udp.js b/packages/mitmproxy/src/lib/dns/udp.js index 86d9b30..57552bc 100644 --- a/packages/mitmproxy/src/lib/dns/udp.js +++ b/packages/mitmproxy/src/lib/dns/udp.js @@ -15,7 +15,7 @@ module.exports = class DNSOverUDP extends BaseDNS { this.socketType = this.isIPv6 ? 'udp6' : 'udp4' } - _doDnsQuery (hostname) { + _doDnsQuery (hostname, options = {}) { return new Promise((resolve, reject) => { // 构造 DNS 查询报文 const packet = dnsPacket.encode({ @@ -23,7 +23,7 @@ module.exports = class DNSOverUDP extends BaseDNS { type: 'query', id: randi(0x0, 0xFFFF), questions: [{ - type: 'A', + type: options.family === 6 ? 'AAAA' : 'A', name: hostname, }], }) diff --git a/packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js b/packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js index adc2dcd..0dea8c4 100644 --- a/packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js +++ b/packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js @@ -12,14 +12,11 @@ module.exports = { const aliveIpObj = tester.pickFastAliveIpObj() if (aliveIpObj) { const family = aliveIpObj.host.includes(':') ? 6 : 4 - log.info(`----- ${action}: ${hostname}, use alive ip from dns '${aliveIpObj.dns}': ${aliveIpObj.host}${target} -----`) if (res) { res.setHeader('DS-DNS-Lookup', `IpTester: ${aliveIpObj.host} ${aliveIpObj.dns === '预设IP' ? 'PreSet' : aliveIpObj.dns}`) } callback(null, aliveIpObj.host, family) return - } else { - log.info(`----- ${action}: ${hostname}, no alive ip${target}, tester: { "ready": ${tester.ready}, "backupList": ${JSON.stringify(tester.backupList)} }`) } } @@ -31,7 +28,6 @@ module.exports = { isDnsIntercept.hostname = hostname isDnsIntercept.ip = ip } - log.info(`----- ${action}: ${hostname}, use ipv6 from dns '${dns.dnsName}': ${ip}${target} -----`) if (res) { res.setHeader('DS-DNS-Lookup', `DNS: ${ip} ${dns.dnsName === '预设IP' ? 'PreSet' : dns.dnsName}`) } @@ -44,7 +40,6 @@ module.exports = { }).then((ip) => { if (!ip || ip === hostname) { // 使用默认dns - log.info(`----- ${action}: ${hostname}, use hostname by default DNS: ${hostname}${target}, options:`, options, ', dns:', dns) return defaultDns.lookup(hostname, options, callback) } @@ -70,14 +65,12 @@ module.exports = { if (!isTestFailedIp) { const family = ip.includes(':') ? 6 : 4 - log.info(`----- ${action}: ${hostname}, use ip from dns '${dns.dnsName}': ${ip}${target} -----`) if (res) { res.setHeader('DS-DNS-Lookup', `DNS: ${ip} ${dns.dnsName === '预设IP' ? 'PreSet' : dns.dnsName}`) } callback(null, ip, family) } else { // 使用默认dns - log.info(`----- ${action}: ${hostname}, use hostname by default DNS: ${hostname}, skip test failed ip from dns '${dns.dnsName}: ${ip}'${target}, options:`, options) defaultDns.lookup(hostname, options, callback) } }).catch((e) => { diff --git a/packages/mitmproxy/src/lib/speed/SpeedTester.js b/packages/mitmproxy/src/lib/speed/SpeedTester.js index 26858e8..c781e99 100644 --- a/packages/mitmproxy/src/lib/speed/SpeedTester.js +++ b/packages/mitmproxy/src/lib/speed/SpeedTester.js @@ -79,6 +79,8 @@ class SpeedTester { } async getFromOneDns (dns) { + const results = [] + // 优先尝试IPv6查询 try { const ipv6Result = await dns._lookupInternal(this.hostname, { family: 6 }) @@ -91,19 +93,23 @@ class SpeedTester { } return ip }) - log.debug(`[dns] Got IPv6 addresses for ${this.hostname}:`, standardized) - return standardized + results.push(...standardized) } } catch (e) { - log.debug(`[dns] IPv6 lookup failed for ${this.hostname}: ${e.message}`) + // IPv6查询失败,继续尝试IPv4 } - // 回退到IPv4查询 - const ipv4Result = await dns._lookupInternal(this.hostname) - if (ipv4Result) { - log.debug(`[dns] Got IPv4 addresses for ${this.hostname}:`, ipv4Result) + // 尝试IPv4查询 + try { + const ipv4Result = await dns._lookupInternal(this.hostname) + if (ipv4Result) { + results.push(...ipv4Result) + } + } catch (e) { + // IPv4查询失败 } - return ipv4Result + + return results } async test () { @@ -111,13 +117,6 @@ class SpeedTester { const newBackupList = [...newList, ...this.backupList] this.backupList = _.unionBy(newBackupList, 'host') this.testCount++ - - // 详细记录IPv6地址 - const ipv6List = this.backupList.filter(item => item.host.includes(':')) - if (ipv6List.length > 0) { - log.info('[speed] IPv6 addresses found for', this.hostname, ':', ipv6List) - } - log.info('[speed]', this.hostname, '➜ ip-list:', this.backupList) await this.testBackups() if (config.notify) { config.notify({ key: 'test' }) From 6d18a3ba09a6c3b9f20dde72876b4ffe61c06d52 Mon Sep 17 00:00:00 2001 From: 8odream Date: Sat, 14 Jun 2025 01:08:01 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E6=94=AF=E6=8C=81IP=E9=A2=84=E8=AE=BE?= =?UTF-8?q?=E4=BD=BF=E7=94=A8CNAME=EF=BC=8C=E5=90=8C=E6=97=B6=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E4=BA=86=E6=9D=A5=E6=BA=90=E6=98=BE=E7=A4=BA=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/mitmproxy/src/lib/dns/base.js | 27 ++++++++++++++++--- .../mitmproxy/src/lib/speed/SpeedTester.js | 6 +++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/packages/mitmproxy/src/lib/dns/base.js b/packages/mitmproxy/src/lib/dns/base.js index 6a745c6..3ee308c 100644 --- a/packages/mitmproxy/src/lib/dns/base.js +++ b/packages/mitmproxy/src/lib/dns/base.js @@ -1,4 +1,5 @@ const LRUCache = require('lru-cache') +const net = require('node:net') const log = require('../../utils/util.log.server') const matchUtil = require('../../utils/util.match') const { DynamicChoice } = require('../choice/index') @@ -94,9 +95,29 @@ module.exports = class BaseDNS { } if (hostnamePreSetIpList.length > 0) { - hostnamePreSetIpList.isPreSet = true - log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的预设IP列表: ${hostname} - ${JSON.stringify(hostnamePreSetIpList)}`) - return hostnamePreSetIpList + const result = [] + for (const item of hostnamePreSetIpList) { + if (net.isIP(item)) { + // 如果是IP地址,直接使用 + result.push(item) + } else { + // 如果是域名,进行DNS解析 + try { + const resolved = await this._lookup(item, options) + if (resolved && resolved.length > 0) { + result.push(...resolved) + } + } catch (e) { + log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] 解析预设域名失败: ${item}`, e) + } + } + } + + if (result.length > 0) { + result.isPreSet = true + log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的预设IP列表: ${hostname} - ${JSON.stringify(result)}`) + return result + } } } diff --git a/packages/mitmproxy/src/lib/speed/SpeedTester.js b/packages/mitmproxy/src/lib/speed/SpeedTester.js index c781e99..0ac40ad 100644 --- a/packages/mitmproxy/src/lib/speed/SpeedTester.js +++ b/packages/mitmproxy/src/lib/speed/SpeedTester.js @@ -80,11 +80,13 @@ class SpeedTester { async getFromOneDns (dns) { const results = [] + let isPreSet = false // 优先尝试IPv6查询 try { const ipv6Result = await dns._lookupInternal(this.hostname, { family: 6 }) if (ipv6Result && ipv6Result.length > 0) { + isPreSet = ipv6Result.isPreSet === true // 标准化IPv6地址格式 const standardized = ipv6Result.map(ip => { // 确保IPv6地址格式统一 @@ -103,12 +105,16 @@ class SpeedTester { try { const ipv4Result = await dns._lookupInternal(this.hostname) if (ipv4Result) { + isPreSet = isPreSet || ipv4Result.isPreSet === true results.push(...ipv4Result) } } catch (e) { // IPv4查询失败 } + if (isPreSet) { + results.isPreSet = true + } return results } From 076795dc6e370a5bdeac2f889cd303508b8d0ac5 Mon Sep 17 00:00:00 2001 From: 8odream Date: Sat, 14 Jun 2025 11:23:31 +0800 Subject: [PATCH 5/5] Merge branch 'master' into master --- doc/wiki/加速服务使用说明.md | 270 ++++++++++++++++++----------------- 1 file changed, 137 insertions(+), 133 deletions(-) diff --git a/doc/wiki/加速服务使用说明.md b/doc/wiki/加速服务使用说明.md index 4cbfb5c..2ea608a 100644 --- a/doc/wiki/加速服务使用说明.md +++ b/doc/wiki/加速服务使用说明.md @@ -1,39 +1,50 @@ # 1. 加速服务: - 1. 什么是 `加速服务`? - `加速服务` 即 `代理服务`,它通过中间人攻击的方式,将网络请求拦截下来,并经过DNS加速、篡改、重定向、代理等一系列的功能,达到加速访问、或访问原本无法访问的站点等目的。 - 2. 启动加速服务:点击首页的 `代理服务` 右侧的开关按钮,即可启动加速服务。 +1. 什么是 `加速服务`? + + - `加速服务` 即 `代理服务`,它通过中间人攻击的方式,将网络请求拦截下来,并经过DNS加速、修改、重定向、代理等一系列的功能,达到加速访问、或访问原本无法访问的站点等目的。
+2. 如何启动加速服务:
+ - 点击首页的【代理服务】右侧的开关按钮,即可启动/关闭加速服务。
+ - 点击首页的【系统代理】右侧的开关按钮,即可将dev-sidecar设置/不设置为系统默认代理。(系统只能有一个默认代理,在将dev-sidecar与其他网络辅助软件共用时请谨慎开启本开关)
+ - 点击首页的【NPM加速】和【Git.exe代理】右侧的开关按钮,即可启动/关闭dev-sidecar为对应软件提供的加速服务。如果你的电脑上并未安装NPM或Git,则这两个按钮将不可用,这是正常情况。 + # 2. 根证书使用说明: - 1. 什么是根证书? - 2. [为什么需要安装根证书这么高风险性的步骤](https://github.com/docmirror/dev-sidecar/blob/master/doc/caroot.md) - 3. 如何安装根证书: + +1. 什么是根证书:TODO +2. [为什么需要安装根证书这么高风险性的步骤](https://github.com/docmirror/dev-sidecar/blob/master/doc/caroot.md) +3. 如何安装根证书:参见dev-sidecar【首页】的【安装根证书】按钮(注意Firefox浏览器还需要一次手动导入根证书) # 3. 模式: - 1. 安全模式: - 2. 默认模式: - 3. 增强模式(彩蛋): + +1. 安全模式:TODO +2. 默认模式:TODO +3. 增强模式(彩蛋):TODO # 4. 拦截功能使用和配置说明: - 1. 请求拦截器: - | 请求拦截器名称 | 拦截器配置名 | 请求拦截优先级 | 快速响应码 | - | ----------------- | -------------- | ------------- | --------- | - | OPTIONS请求拦截器 | options | 101 | 200 | - | 快速成功拦截器 | success | 102 | 200 | - | 快速失败拦截器 | abort | 103 | 403 | - | 缓存请求拦截器 | cacheXxx | 104 | 304 | - | 重定向拦截器 | redirect | 105 | 302 | - | 请求篡改拦截器 | requestReplace | 111 | | - | 代理拦截器 | proxy | 121 | | - | SNI拦截器 | sni | 122 | | - 2. 响应拦截器: - | 响应拦截器名称 | 拦截器配置名 | 响应拦截优先级 | - | ---------------- | --------------- | ------------- | - | OPTIONS响应拦截器 | options | 201 | - | 缓存响应拦截器 | cacheXxx | 202 | - | 响应篡改拦截器 | responseReplace | 203 | - | 脚本拦截器 | script | 211 | +## 4.1. 拦截器类型: + +### 1)请求拦截器: +| 请求拦截器名称 | 拦截器配置名 | 请求拦截优先级 | 作用 | +| ----------------- | -------------- | ------------- | --------- | +| OPTIONS请求拦截器 | options | 101 | 直接响应200,不发送该OPTIONS请求 | +| 快速成功拦截器 | success | 102 | 直接响应200,不发送该请求 | +| 快速失败拦截器 | abort | 103 | 直接响应403,不发送该请求 | +| 缓存请求拦截器 | cacheXxx | 104 | 如果缓存还生效,直接响应304,不发送该请求
如果缓存已过期或无缓存,则发送请求
注:只对GET请求生效! | +| 重定向拦截器 | redirect | 105 | 重定向到指定地址,直接响应302,不发送该请求 | +| 请求篡改拦截器 | requestReplace | 111 | 篡改请求头,达到想要的目的 | +| 代理拦截器 | proxy | 121 | 将请求转发到指定地址 | +| SNI拦截器 | sni | 122 | 设置 `servername`,用于避开GFW | + +### 2)响应拦截器: + +| 响应拦截器名称 | 拦截器配置名 | 响应拦截优先级 | 作用 | +| ---------------- | --------------- | ------------- | --------- | +| OPTIONS响应拦截器 | options | 201 | 设置跨域所需的响应头,避免被浏览器的跨域策略阻拦 | +| 缓存响应拦截器 | cacheXxx | 202 | 设置缓存所需的响应头,使浏览器缓存当前请求
注:只对GET请求生效! | +| 响应篡改拦截器 | responseReplace | 203 | 篡改响应头,避免被浏览器的安全策略阻拦 | +| 脚本拦截器 | script | 211 | 注入JavaScript脚本到页面中,如:Github油猴脚本 | 3. 配置示例: ```json @@ -97,137 +108,130 @@ } ``` # 5. 域名白名单: + +选择哪些域名不会被dev-sidecar处理。 + +**注意:** 该设置与【系统代理-自定义排除域名】的区别在于: + +1. 前者只是被dev-sidecar自身忽略,后者则是写入系统设置、不会被(任何的)系统代理处理,在手动修改系统代理设置时务必小心后者可能残留的作用! +2. 在条目较多时,前者的性能不如后者,可能产生明显延迟。
+ +在config.json的 `proxy.excludeIpList:object` 中设置,**该字段**格式如下:
+> 注意:这里点号用来作为JSON object嵌套关系的缩写,冒号指明该条目的类型(主要用来区分object和list),并没有哪一个Object的key为 `proxy.excludeIpList`。为避免歧义,配置中object和list的key总不应包含点号。下同) + +```json +{ + "proxy": { + "excludeIpList": { + "example1.com": true, + "example2.com": false, + "example3.com": null, + "example4.com": { + "desc1": "域名对应字段设置为false时会被处理,null会移除现有设置(多用于远程配置)", + "desc2": "其他情况下就和设置true一样,不会被处理。因而你可以像这样插入注释", + "desc3": "同样的技巧可以用在其他本应设置一个bool值的地方", + "desc4": "原则上来说config.json不支持//形式的注释,但下文为了方便阅读,还是这么写了" + } + } + } +} +``` + # 6. DNS服务管理: -## 6.1. 配置 `DNS-over-HTTPS` 的DNS服务: +用来配置在dev-sidecar中需要的指定DNS,出于保密和可靠起见建议使用DoH和DoT。
+在 `server.dns.provider:object` 中设置,**其中的每个条目** 格式如下: + +## 6.1. 配置 `DNS-over-HTTPS`(简称DoH): +> 注:并非被所有DNS支持,但是保证只要能使用就一定匿名且可靠的DNS服务。 + ```json -{ - "cloudflare": { - "type": "https", - "server": "https://1.1.1.1/dns-query", - "cacheSize": 1000 - } -} -``` -或 -```json -{ - "cloudflare": { - "server": "https://1.1.1.1/dns-query", // 地址上带有 `https://`,type可以不配置 - "cacheSize": 1000 - } +"cloudflare": { + "type": "https", // 如果server上以"https://"开头指明了协议,就不需要写type了 + "server": "https://1.1.1.1/dns-query", + "cacheSize": 1000 } ``` -## 6.2. 配置 `DNS-over-TLS` 的DNS服务: +## 6.2. 配置 `DNS-over-TLS`(简称DoT): +> 并非被所有DNS支持,但是保证只要能使用就一定匿名且可靠的DNS服务。 ```json -{ - "cloudflareTLS": { - "type": "tls", - "server": "1.1.1.1", - "port": 853, // 不配置时,默认端口为:853 - "servername": "cloudflare-dns.com", // SNI - "cacheSize": 1000 - } -} -``` -或 - -```json -{ - "cloudflareTLS": { - "server": "tls://1.1.1.1", - "port": 853, // 不配置时,默认端口为:853 - "servername": "cloudflare-dns.com", // SNI - //"sni": "cloudflare-dns.com", // SNI缩写配置 - "cacheSize": 1000 - } -} -``` -或 - -```json -{ - "cloudflareTLS": { - "server": "tls://1.1.1.1:853", - "servername": "cloudflare-dns.com", // SNI - //"sni": "cloudflare-dns.com", // SNI缩写配置 - "cacheSize": 1000 - } +"cloudflareTLS": { + "type": "tls", // 如果server上以"tls://"开头指明了协议,就不需要写type了 + "server": "1.1.1.1", + "port": 853, // 不配置时,默认端口为:853 + "servername": "cloudflare-dns.com", // 需要伪造成的SNI + //"sni": "cloudflare-dns.com", // SNI缩写配置 + "cacheSize": 1000 } ``` ## 6.3. 配置 `TCP` 的DNS服务: +> 并非被所有DNS支持,该方法既不保密也不可靠 ```json -{ - "googleTCP": { - "type": "tcp", - "server": "8.8.8.8", - "port": 53, // 不配置时,默认端口为:53 - "cacheSize": 1000, - } -} -``` -或 - -```json -{ - "googleTCP": { - "server": "tcp://8.8.8.8", - "port": 53, // 不配置时,默认端口为:53 - "cacheSize": 1000, - } -} -``` -或 - -```json -{ - "googleTCP": { - "server": "tcp://8.8.8.8:53", - "cacheSize": 1000, - } +"googleTCP": { + "type": "tcp", // 如果server上以"tcp://"开头指明了协议,就不需要写type了 + "server": "8.8.8.8", + "port": 53, // 不配置时,默认端口为:53 + "cacheSize": 1000 } ``` ## 6.4. 配置 `UDP` 的DNS服务: +> 所有DNS服务器均支持UDP方式,但该方法既不保密也不可靠 ```json -{ - "google": { - "type": "udp", - "server": "8.8.8.8", - "port": 53, // 不配置时,默认端口为:53 - "cacheSize": 1000, - } -} -``` -或 - -```json -{ - "google": { - "server": "udp://8.8.8.8", - "port": 53, // 不配置时,默认端口为:53 - "cacheSize": 1000, - } -} -``` -或 - -```json -{ - "google": { - "server": "udp://8.8.8.8:53", - "cacheSize": 1000, - } +"google": { + "type": "udp", // 如果server上以"udp://"开头指明了协议,就不需要写type了 + "server": "8.8.8.8", + "port": 53, // 不配置时,默认端口为:53 + "cacheSize": 1000 } ``` # 7. DNS设置: +选择哪些域名需要使用指定的DNS(需要先在【DNS服务管理】中设置)获取IP。
+在config.json中的 `server.dns.mapping:key-value` 中设置,**其中的每个条目**格式如下: + +```json +"*.example.com": "your-dns-name" +``` + # 8. IP预设置: +为一些DNS无法获取的域名手动设置ip,起到类似于hosts的作用(仅在dev-sidecar开启时生效)。
+在config.json中的 `server.preSetIpList:object` 中设置,**其中的每个条目**格式如下: + +```json +{ + "example.com": { + "1.1.1.1": true, // 如果有多个IP,可以继续添加 + "1.0.0.1": false, // 指定为false时,不使用该IP + "2.2.2.2": { + "desc": "这样可以合法的在配置中插入注释。上面使用的//注释方式在文件中是不允许的" + } + } +} +``` + # 9. IP测速: + +用来对从指定的DNS与IP预设置中获取到的IP测试TCP延迟,也可以用来测试DoH和DoT服务器的可用性,后者操作如下:先在【DNS服务管理】中配置好需要测试的DNS设置,然后在【IP测速】里添加一个没有设置【IP预设置】的辅助域名,并选择使用需检测的DNS进行解析。
+对于DoH/DoT而言,由于答案不能被篡改和窃听,所以辅助域名要么获得真实IP(说明可用)要么没有收到答案(说明不可用)。该方法不适用于常规TCP/UDP的DNS,因为它们没有加密,即使收到答案也可能被篡改而不可用)。
+在config.json中的 `server.dns.speedTest:object`中设置,**该条目** 格式如下: + +```json +"speedTest": { + "hostnameList": [ + "example1.com", + "example2.com" + ], + "dnsProviders": [ + "your-DNS-name-used-in-test1", + "your-DNS-name-used-in-test2" + ] +} +```