Skip to content

Commit

Permalink
feature: 新增兼容程序,在遇到部分特定异常时,通过自动调整参数达到规避异常的目的 (#375)
Browse files Browse the repository at this point in the history
  • Loading branch information
wangliang181230 authored Oct 16, 2024
1 parent 5901a2e commit 2218e80
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 55 deletions.
19 changes: 19 additions & 0 deletions packages/core/src/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,25 @@ module.exports = {
}
}
},
compatible: {
// **** 自定义兼容配置 **** //
// connect阶段所需的兼容性配置
connect: {
// 参考配置(无path)
// 'xxx.xxx.xxx.xxx:443': {
// ssl: false
// }
},
// request阶段所需的兼容性配置
request: {
// 参考配置(配置方式同 `拦截配置`)
// 'xxx.xxx.xxx.xxx:443': {
// '.*': {
// rejectUnauthorized: false
// }
// }
}
},
intercept: {
enabled: true
},
Expand Down
19 changes: 14 additions & 5 deletions packages/gui/src/view/pages/server.vue
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,16 @@
</a-col>
</a-row>
</a-tab-pane>
<a-tab-pane tab="IP预设置" key="5">
<a-tab-pane tab="兼容程序" key="5">
<div style="height:100%;display:flex;flex-direction:column">
<div>
说明:<code>兼容程序</code>会自动根据错误信息进行兼容性调整,并将兼容设置保存在 <code>~/.dev-sidecar/automaticCompatibleConfig.json</code> 文件中。但并不是所有的兼容设置都是正确的,所以需要通过以下配置来覆盖错误的兼容设置。
</div>
<vue-json-editor style="flex-grow:1;min-height:300px;margin-top:10px;" ref="editor" v-model="config.server.compatible" mode="code"
:show-btns="false" :expandedOnStart="true"></vue-json-editor>
</div>
</a-tab-pane>
<a-tab-pane tab="IP预设置" key="6">
<div style="height:100%;display:flex;flex-direction:column">
<div>
提示:<code>IP预设置</code>功能,优先级高于 <code>DNS设置</code>
Expand All @@ -116,11 +125,11 @@
:show-btns="false" :expandedOnStart="true"></vue-json-editor>
</div>
</a-tab-pane>
<a-tab-pane tab="DNS服务管理" key="6">
<a-tab-pane tab="DNS服务管理" key="7">
<vue-json-editor style="height:100%" ref="editor" v-model="config.server.dns.providers" mode="code"
:show-btns="false" :expandedOnStart="true"></vue-json-editor>
</a-tab-pane>
<a-tab-pane tab="DNS设置" key="7">
<a-tab-pane tab="DNS设置" key="8">
<div>
<a-row style="margin-top:10px">
<a-col span="19">
Expand Down Expand Up @@ -148,7 +157,7 @@
</a-row>
</div>
</a-tab-pane>
<a-tab-pane tab="IP测速" key="8">
<a-tab-pane tab="IP测速" key="9">
<div class="ip-tester" style="padding-right: 10px">
<a-alert type="info" message="对从DNS获取到的IP进行测速,使用速度最快的IP进行访问(注意:对使用了增强功能的域名没啥用)"></a-alert>
<a-form-item label="开启DNS测速" :label-col="labelCol" :wrapper-col="wrapperCol">
Expand Down Expand Up @@ -384,7 +393,7 @@ export default {
}, 5000)
},
async handleTabChange (key) {
if (key !== '2' && key !== '3' && key !== '5' && key !== '6') {
if (key !== '2' && key !== '3' && key !== '5' && key !== '6' && key !== '7') {
return
}
Expand Down
5 changes: 3 additions & 2 deletions packages/mitmproxy/src/lib/proxy/common/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ util.parseHostnameAndPort = (host, defaultPort) => {
return arr
}

util.getOptionsFromRequest = (req, ssl, externalProxy = null, serverSetting) => {
util.getOptionsFromRequest = (req, ssl, externalProxy = null, serverSetting, compatibleConfig = null) => {
// eslint-disable-next-line node/no-deprecated-api
const urlObject = url.parse(req.url)
const defaultPort = ssl ? 443 : 80
Expand Down Expand Up @@ -148,7 +148,8 @@ util.getOptionsFromRequest = (req, ssl, externalProxy = null, serverSetting) =>
port,
path: urlObject.path,
headers: req.headers,
agent
agent,
compatibleConfig
}

// eslint-disable-next-line node/no-deprecated-api
Expand Down
143 changes: 143 additions & 0 deletions packages/mitmproxy/src/lib/proxy/compatible/compatible.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/**
* 兼容程序自适应生成配置
* 此脚本会针对各种兼容性问题,为对应域名生成相应的兼容性配置,并将自适应配置写入到 `~/.dev-sidecar/automaticCompatibleConfig.json` 文件中。
* 当然,也有可能会生成错误的配置,导致无法兼容,这时候可以通过 `config.server.compatible` 配置项,来覆盖这里生成的配置,达到主动适配的效果。
*
* @author WangLiang
*/
const fs = require('fs')
const path = require('path')
const jsonApi = require('../../../json')
const log = require('../../../utils/util.log')
const matchUtil = require('../../../utils/util.match')

const defaultConfig = {
// connect阶段所需的兼容性配置
connect: {
// 参考配置
// 'xxx.xxx.xxx.xxx:443': {
// ssl: false
// }
},
// request阶段所需的兼容性配置
request: {
// 参考配置
// 'xxx.xxx.xxx.xxx:443': {
// rejectUnauthorized: false
// }
}
}

const config = _loadFromFile(defaultConfig)

function _getConnectConfig (hostname, port) {
const connectConfig = config.connect[`${hostname}:${port}`]
log.info(`getConnectConfig: ${hostname}:${port}, ${jsonApi.stringify2(connectConfig)}`)
return connectConfig
}
function _getRequestConfig (hostname, port) {
const requestConfig = config.request[`${hostname}:${port}`]
log.info(`getRequestConfig: ${hostname}:${port}, ${jsonApi.stringify2(requestConfig)}`)
return requestConfig
}

// region 本地配置文件所需函数

function _getConfigPath () {
const userHome = process.env.USERPROFILE || process.env.HOME || '/'
const dir = path.resolve(userHome, './.dev-sidecar')
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir)
}
return path.join(dir, '/automaticCompatibleConfig.json')
}

function _loadFromFile (defaultConfig) {
const configPath = _getConfigPath()
let config
if (!fs.existsSync(configPath)) {
config = defaultConfig
log.info('automaticCompatibleConfig.json 文件不存在,使用默认配置:', configPath)
} else {
const file = fs.readFileSync(configPath)
log.info('读取 automaticCompatibleConfig.json 成功:', configPath)
const fileStr = file.toString()
config = fileStr && fileStr.length > 2 ? jsonApi.parse(fileStr) : {}
}

return config
}

function _saveConfigToFile () {
const filePath = _getConfigPath()
try {
fs.writeFileSync(filePath, jsonApi.stringify(config))
log.info('保存 automaticCompatibleConfig.json 成功:', filePath)
} catch (e) {
log.error('保存 automaticCompatibleConfig.json 失败:', filePath, e)
}
}

// endregion

module.exports = {
/**
* 获取 connect 阶段所需的兼容性配置
*
* @param hostname 域名
* @param port 端口
* @param manualCompatibleConfig 手动兼容性配置
* @returns connect阶段所需的兼容性配置
*/
getConnectCompatibleConfig (hostname, port, manualCompatibleConfig = null) {
let connectCompatibleConfig = manualCompatibleConfig == null ? null : matchUtil.matchHostname(manualCompatibleConfig.connect, `${hostname}:${port}`, 'getConnectCompatibleConfig')
if (connectCompatibleConfig == null) {
connectCompatibleConfig = _getConnectConfig(hostname, port)
}
return connectCompatibleConfig
},

setConnectSsl (hostname, port, ssl, autoSave = true) {
const connectCompatibleConfig = this.getConnectCompatibleConfig(hostname, port)
if (connectCompatibleConfig) {
connectCompatibleConfig.ssl = ssl
} else {
config.connect[`${hostname}:${port}`] = { ssl }
}

// 配置保存到文件
if (autoSave) _saveConfigToFile()

log.info(`【兼容程序】${hostname}:${port}: 设置 connect.ssl = ${ssl}`)
},

// --------------------------------------------------------------------------------------------------------------------------

/**
* 获取 request 阶段所需的兼容性配置
*
* @param rOptions
* @param manualCompatibleConfig
*/
getRequestCompatibleConfig (rOptions, manualCompatibleConfig = null) {
let requestCompatibleConfig = manualCompatibleConfig == null ? null : matchUtil.matchHostname(manualCompatibleConfig.request, `${rOptions.hostname}:${rOptions.port}`, 'getRequestCompatibleConfig')
if (requestCompatibleConfig == null) {
requestCompatibleConfig = _getRequestConfig(rOptions.hostname, rOptions.port)
}
return requestCompatibleConfig
},

setRequestRejectUnauthorized (rOptions, rejectUnauthorized, autoSave = true) {
const requestCompatibleConfig = this.getRequestCompatibleConfig(rOptions.hostname, rOptions.port)
if (requestCompatibleConfig) {
requestCompatibleConfig.rejectUnauthorized = rejectUnauthorized
} else {
config.request[`${rOptions.hostname}:${rOptions.port}`] = { rejectUnauthorized }
}

// 配置保存到文件
if (autoSave) _saveConfigToFile()

log.info(`【兼容程序】${rOptions.hostname}:${rOptions.port}: 设置 request.rejectUnauthorized = ${rejectUnauthorized}`)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function isSslConnect (sslConnectInterceptors, req, cltSocket, head) {
}

// create connectHandler function
module.exports = function createConnectHandler (sslConnectInterceptor, middlewares, fakeServerCenter, dnsConfig) {
module.exports = function createConnectHandler (sslConnectInterceptor, middlewares, fakeServerCenter, dnsConfig, compatibleConfig) {
// return
const sslConnectInterceptors = []
sslConnectInterceptors.push(sslConnectInterceptor)
Expand All @@ -27,14 +27,14 @@ module.exports = function createConnectHandler (sslConnectInterceptor, middlewar
}
}

return function connectHandler (req, cltSocket, head) {
return function connectHandler (req, cltSocket, head, ssl) {
// eslint-disable-next-line node/no-deprecated-api
let { hostname, port } = url.parse(`https://${req.url}`)
let { hostname, port } = url.parse(`${ssl ? 'https' : 'http'}://${req.url}`)
port = parseInt(port)

if (isSslConnect(sslConnectInterceptors, req, cltSocket, head)) {
// 需要拦截,代替目标服务器,让客户端连接DS在本地启动的代理服务
fakeServerCenter.getServerPromise(hostname, port).then((serverObj) => {
fakeServerCenter.getServerPromise(hostname, port, ssl, compatibleConfig).then((serverObj) => {
log.info(`----- fakeServer connect: ${localIP}:${serverObj.port}${req.url} -----`)
connect(req, cltSocket, head, localIP, serverObj.port)
}, (e) => {
Expand Down
23 changes: 21 additions & 2 deletions packages/mitmproxy/src/lib/proxy/mitmproxy/createRequestHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ const log = require('../../../utils/util.log')
const RequestCounter = require('../../choice/RequestCounter')
const InsertScriptMiddleware = require('../middleware/InsertScriptMiddleware')
const dnsLookup = require('./dnsLookup')
const compatible = require('../compatible/compatible')
const MAX_SLOW_TIME = 8000 // 超过此时间 则认为太慢了

// create requestHandler function
module.exports = function createRequestHandler (createIntercepts, middlewares, externalProxy, dnsConfig, setting) {
module.exports = function createRequestHandler (createIntercepts, middlewares, externalProxy, dnsConfig, setting, compatibleConfig) {
// return
return function requestHandler (req, res, ssl) {
let proxyReq

const rOptions = commonUtil.getOptionsFromRequest(req, ssl, externalProxy, setting)
const rOptions = commonUtil.getOptionsFromRequest(req, ssl, externalProxy, setting, compatibleConfig)
let url = `${rOptions.method}${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${rOptions.path}`

if (rOptions.headers.connection === 'close') {
Expand Down Expand Up @@ -130,6 +131,19 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
// log.debug('agent:', rOptions.agent)
// log.debug('agent.options:', rOptions.agent.options)
res.setHeader('DS-Proxy-Request', rOptions.hostname)

// 兼容程序:2
if (rOptions.agent) {
const compatibleConfig = compatible.getRequestCompatibleConfig(rOptions, rOptions.compatibleConfig)
if (compatibleConfig && compatibleConfig.rejectUnauthorized != null && rOptions.agent.options.rejectUnauthorized !== compatibleConfig.rejectUnauthorized) {
if (compatibleConfig.rejectUnauthorized === false && rOptions.agent.unVerifySslAgent) {
log.info(`【兼容程序】${rOptions.hostname}:${rOptions.port}: 设置 'rOptions.agent.options.rejectUnauthorized = ${compatibleConfig.rejectUnauthorized}'`)
rOptions.agent = rOptions.agent.unVerifySslAgent
res.setHeader('DS-Compatible', 'unVerifySsl')
}
}
}

proxyReq = (rOptions.protocol === 'https:' ? https : http).request(rOptions, (proxyRes) => {
const cost = new Date() - start
if (rOptions.protocol === 'https:') {
Expand Down Expand Up @@ -163,6 +177,11 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
log.error(`代理请求错误: ${url}, cost: ${cost} ms, error:`, e, ', rOptions:', jsonApi.stringify2(rOptions))
countSlow(isDnsIntercept, '代理请求错误: ' + e.message)
reject(e)

// 兼容程序:2
if (e.code === 'DEPTH_ZERO_SELF_SIGNED_CERT') {
compatible.setRequestRejectUnauthorized(rOptions, false)
}
})
proxyReq.on('aborted', () => {
const cost = new Date() - start
Expand Down
Loading

0 comments on commit 2218e80

Please sign in to comment.