Merge remote-tracking branch 'upstream/master'

pull/376/head
王良 2024-11-07 17:11:07 +08:00
commit 715e747a99
24 changed files with 440 additions and 95 deletions

View File

@ -14,8 +14,8 @@
>
## 打个广告
>
> https://ai.handsfree.work
> 我的ChatGPT开发者必备无需fanQ快速稳定价格良心100问仅需1元按需扣费余额永久有效大家可以试试
> https://github.com/certd/certd
> 我的开源证书管理工具项目全自动申请和部署证书有需求的可以去试试帮忙点个star
@ -399,9 +399,9 @@ npm run electron:build
1、 加群请备注dev-sidecar或简称DS
- QQ 1群390691483人数500 / 500
- QQ 2群[667666069](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=n4nksr4sji93vZtD5e8YEHRT6qbh6VyQ&authKey=XKBZnzmoiJrAFyOT4V%2BCrgX5c13ds59b84g%2FVRhXAIQd%2FlAiilsuwDRGWJct%2B570&noverify=0&group_code=667666069)人数439 / 500
- QQ 2群[667666069](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=n4nksr4sji93vZtD5e8YEHRT6qbh6VyQ&authKey=XKBZnzmoiJrAFyOT4V%2BCrgX5c13ds59b84g%2FVRhXAIQd%2FlAiilsuwDRGWJct%2B570&noverify=0&group_code=667666069)人数447 / 500
- QQ 3群419807815人数500 / 500
- QQ 4群438148299人数200 / 200
- QQ 4群[438148299](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=i_NCBB5f_Bkm2JsEV1tLs2TkQ79UlCID&authKey=nMsVJbJ6P%2FGNO7Q6vsVUadXRKnULUURwR8zvUZJnP3IgzhHYPhYdcBCHvoOh8vYr&noverify=0&group_code=438148299)人数203 / 1000
- QQ 5群[767622917](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=nAWi_Rxj7mM4Unp5LMiatmUWhGimtbcB&authKey=aswmlWGjbt3GIWXtvjB2GJqqAKuv7hWjk6UBs3MTb%2Biyvr%2Fsbb1kA9CjF6sK7Hgg&noverify=0&group_code=767622917)人数016 / 200new

View File

@ -14,5 +14,5 @@
"ignore": []
}
},
"version": "1.8.8"
"version": "1.8.9"
}

View File

@ -1,6 +1,6 @@
{
"name": "@docmirror/dev-sidecar",
"version": "1.8.8",
"version": "1.8.9",
"description": "给开发者的加速代理工具",
"main": "src/index.js",
"keywords": [
@ -17,7 +17,7 @@
"test": "mocha"
},
"dependencies": {
"@docmirror/mitmproxy": "^1.8.8",
"@docmirror/mitmproxy": "^1.8.9",
"agentkeepalive": "^2.1.1",
"babel-preset-es2020": "^1.0.2",
"charset": "^1.0.0",

View File

@ -65,7 +65,10 @@ module.exports = {
timeout: 20000,
keepAliveTimeout: 30000
}
}
},
// 慢速IP延迟时间测速超过该值时则视为延迟高显示为橙色
lowSpeedDelay: 150
},
compatible: {
// **** 自定义兼容配置 **** //

View File

@ -1,6 +1,6 @@
[SwitchyOmega Conditions]
; Require: SwitchyOmega >= 2.3.2
; Update Date: 2024/10/14
; Update Date: 2024/11/07
; Author: Pluwen
; Usage: https://github.com/FelisCatus/SwitchyOmega/wiki/RuleListUsage
@ -236,6 +236,8 @@
*.ccb.com
*.ccgslb.com
*.ccgslb.net
*.cckefu.net
*.cckefu3.com
*.cctv.com
*.cctvpic.com
*.cdn-apple.com
@ -333,6 +335,7 @@
*.dmzj.com
*.dns.com
*.dnspao.com
*.doc88.com
*.docer.com
*.docin.com
*.docschina.org
@ -395,6 +398,8 @@
*.fiio.com
*.fir.im
*.firefox.com
*.fj12379.com
*.fjdzyz.com
*.fjgdwl.com
*.fjhxbank.com
*.fliggy.com
@ -471,6 +476,7 @@
*.homestyler.com
*.hommk.com
*.hongxiu.com
*.honor.com
*.hostbuf.com
*.hostker.com
*.hotmail.com
@ -838,6 +844,7 @@
*.pterclub.com
*.pythonclub.org
*.qbox.me
*.qcc.com
*.qcloud.com
*.qcloudcdn.com
*.qcwgg.com
@ -883,6 +890,7 @@
*.redacted.ch
*.renren.com
*.renrenche.com
*.renrendoc.com
*.researchgate.net
*.rework.tools
*.rkecloud.com
@ -966,7 +974,6 @@
*.staticfile.org
*.steamcn.com
*.steamcontent.com
*.steamdb.info
*.subhd.tv
*.sui.com
*.suning.com

View File

@ -1,6 +1,6 @@
{
"name": "@docmirror/dev-sidecar-gui",
"version": "1.8.8",
"version": "1.8.9",
"private": false,
"license": "MPL-2.0",
"main": "index.js",
@ -21,8 +21,10 @@
"name": "Greper"
},
"dependencies": {
"@docmirror/dev-sidecar": "^1.8.8",
"@docmirror/mitmproxy": "^1.8.8",
"@docmirror/dev-sidecar": "^1.8.9",
"@docmirror/mitmproxy": "^1.8.9",
"@mihomo-party/sysproxy": "^2.0.4",
"@natmri/platform-napi": "0.0.7",
"adm-zip": "^0.5.5",
"ant-design-vue": "^1.6.5",
"compressing": "^1.5.1",
@ -52,9 +54,9 @@
"@vue/eslint-config-standard": "^5.1.2",
"babel-eslint": "^10.1.0",
"electron": "^17.4.11",
"electron-builder": "^23.0.3",
"electron-devtools-installer": "^3.1.0",
"electron-icon-builder": "^2.0.1",
"electron-builder": "^23.0.3",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",

View File

@ -1,12 +1,15 @@
'use strict'
/* global __static */
import path from 'path'
import { app, protocol, BrowserWindow, Menu, Tray, ipcMain, dialog, powerMonitor, nativeImage, nativeTheme, globalShortcut } from 'electron'
import { app, protocol, BrowserWindow, Menu, Tray, ipcMain, dialog, nativeImage, nativeTheme, globalShortcut } from 'electron'
import { powerMonitor } from './background/powerMonitor'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import backend from './bridge/backend'
import DevSidecar from '@docmirror/dev-sidecar'
import log from './utils/util.log'
import minimist from 'minimist'
const isWindows = process.platform === 'win32'
// eslint-disable-next-line no-unused-vars
const isMac = process.platform === 'darwin'
// import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
@ -187,6 +190,11 @@ function createWindow (startHideWindow) {
Menu.setApplicationMenu(null)
win.setMenu(null)
// !!IMPORTANT
if (isWindows) {
powerMonitor.setupMainWindow(win)
}
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
@ -443,8 +451,14 @@ if (!isFirstInstance) {
}
powerMonitor.on('shutdown', async (e) => {
e.preventDefault()
if (e) {
e.preventDefault()
}
log.info('系统关机,恢复代理设置')
if (isWindows) {
const Sysproxy = require('@mihomo-party/sysproxy')
Sysproxy.triggerManualProxy(false, '', 0, '')
}
await quit()
})
})

View File

@ -0,0 +1,135 @@
import { powerMonitor as _powerMonitor } from 'electron'
import { setMainWindowHandle, insertWndProcHook, removeWndProcHook, releaseShutdownBlock, acquireShutdownBlock } from '@natmri/platform-napi'
class PowerMonitor {
constructor () {
this.setup = false
this._listeners = []
this._shutdownCallback = null
}
/**
* @param {BrowserWindow} window
*/
setupMainWindow (window) {
if (!this.setup) {
setMainWindowHandle(window.getNativeWindowHandle().readBigInt64LE())
this.setup = true
}
}
addListener (event, listener) {
return this.on(event, listener)
}
removeListener (event, listener) {
return this.off(event, listener)
}
removeAllListeners (event) {
if (event === 'shutdown' && process.platform === 'win32') {
this._listeners = []
if (this._shutdownCallback) {
removeWndProcHook()
releaseShutdownBlock()
this._shutdownCallback = null
}
} else {
return _powerMonitor.removeAllListeners(event)
}
}
on (event, listener) {
if (event === 'shutdown' && process.platform === 'win32') {
if (!this._shutdownCallback) {
this._shutdownCallback = async () => {
await Promise.all(this._listeners.map((fn) => fn()))
releaseShutdownBlock()
}
insertWndProcHook(this._shutdownCallback)
acquireShutdownBlock('正在停止 DevSidecar 代理')
}
this._listeners.push(listener)
} else {
return _powerMonitor.on(event, listener)
}
}
off (event, listener) {
if (event === 'shutdown' && process.platform === 'win32') {
this._listeners = this._listeners.filter((fn) => fn !== listener)
} else {
return _powerMonitor.off(event, listener)
}
}
once (event, listener) {
if (event === 'shutdown' && process.platform === 'win32') {
return this.on(event, listener)
} else {
return _powerMonitor.once(event, listener)
}
}
emit (event, ...args) {
return _powerMonitor.emit(event, ...args)
}
eventNames () {
return _powerMonitor.eventNames()
}
getMaxListeners () {
return _powerMonitor.getMaxListeners()
}
listeners (event) {
return _powerMonitor.listeners(event)
}
rawListeners (event) {
return _powerMonitor.rawListeners(event)
}
listenerCount (event, listener) {
return _powerMonitor.listenerCount(event, listener)
}
/**
* @returns {boolean}
*/
get onBatteryPower () {
return _powerMonitor.onBatteryPower
}
/**
* @param {number} idleThreshold
* @returns {'active'|'idle'|'locked'|'unknown'}
*/
getSystemIdleState (idleThreshold) {
return _powerMonitor.getSystemIdleState(idleThreshold)
}
/**
* @returns {number}
*/
getSystemIdleTime () {
return _powerMonitor.getSystemIdleTime()
}
/**
* @returns {'unknown'|'nominal'|'fair'|'serious'|'critical'}
*/
getCurrentThermalState () {
return _powerMonitor.getCurrentThermalState()
}
/**
* @returns {boolean}
*/
isOnBatteryPower () {
return _powerMonitor.isOnBatteryPower()
}
}
export const powerMonitor = new PowerMonitor()

View File

@ -24,7 +24,7 @@
<a-checkbox v-model="config.plugin.overwall.pac.enabled">
启用PAC
</a-checkbox>
<div class="form-help">PAC内收录了常见的被封杀的域名当里面某些域名你不想被拦截时可以关闭PAC</div>
<div class="form-help">PAC内收录了常见的被封杀的域名<br/>当里面某些域名你不想被拦截时你可以配置这些域名为<code>禁用</code>可以关闭PAC</div>
</a-form-item>
<a-form-item label="自动更新PAC" :label-col="labelCol" :wrapper-col="wrapperCol">
<a-checkbox v-model="config.plugin.overwall.pac.autoUpdate">
@ -46,16 +46,23 @@
<div>
<a-row :gutter="10" style="">
<a-col :span="22">
<span>PAC没有拦截到的域名可以在此处定义</span>
<span>PAC没有拦截到的域名可以在此处定义配置为<code>禁用</code>将不使用梯子</span>
</a-col>
<a-col :span="2">
<a-button type="primary" icon="plus" @click="addTarget()"/>
</a-col>
</a-row>
<a-row :gutter="10" v-for="(item,index) of targets" :key="index">
<a-col :span="22">
<a-col :span="18">
<a-input v-model="item.key"></a-input>
</a-col>
<a-col :span="4">
<a-select v-model="item.value" style="width:100%">
<a-select-option v-for="(item) of overwallOptions" :key="item.value" :value="item.value">
{{ item.label }}
</a-select-option>
</a-select>
</a-col>
<a-col :span="2">
<a-button type="danger" icon="minus" @click="deleteTarget(item,index)"/>
</a-col>
@ -116,7 +123,17 @@ export default {
return {
key: 'plugin.overwall',
targets: undefined,
servers: undefined
servers: undefined,
overwallOptions: [
{
value: true,
label: '启用'
},
{
value: false,
label: '禁用'
}
]
}
},
created () {

View File

@ -166,8 +166,11 @@
</a-checkbox>
</a-form-item>
<a-form-item label="自动测试间隔" :label-col="labelCol" :wrapper-col="wrapperCol">
<a-input-number id="inputNumber" v-model="getSpeedTestConfig().interval" :step="1000" :min="1"/> ms
<a-input-number v-model="getSpeedTestConfig().interval" :step="1000" :min="1"/> ms
</a-form-item>
<!--<a-form-item label="慢速IP阈值" :label-col="labelCol" :wrapper-col="wrapperCol">
<a-input-number v-model="config.server.setting.lowSpeedDelay" :step="10" :min="100"/> ms
</a-form-item>-->
<div>使用以下DNS获取IP进行测速</div>
<a-row style="margin-top:10px">
<a-col span="24">
@ -211,7 +214,7 @@
<a-icon v-else type="info-circle"/>
</a>
<a-tag style="margin:2px;" v-for="(element,index) of item.backupList" :title="element.dns"
:color="element.time?'green':'red'" :key='index'>
:color="element.time?(element.time>config.server.setting.lowSpeedDelay?'orange':'green'):'red'" :key='index'>
{{ element.host }} {{ element.time }}{{ element.time ? 'ms' : '' }} {{ element.dns }}
</a-tag>
</a-card>
@ -260,7 +263,7 @@ export default {
if (!this.config || !this.config.server || !this.config.server.dns || !this.config.server.dns.providers) {
return options
}
_.forEach(this.config.server.dns.providers, (dnsConf, key) => {
_.forEach(this.config.server.dns.providers, (dnsConfig, key) => {
options.push({
value: key,
label: key

View File

@ -122,6 +122,12 @@ $dark-input: #777; //输入框:背景色
border-color: #505f5f;
color: #90cb9f;
}
/* 标签:警告 */
.ant-tag-orange{
background: #5a5750;
border-color: #5a5750;
color: #cfa572;
}
/* 按钮 */
.ant-btn:not(.ant-btn-danger, .ant-btn-primary){

View File

@ -32,6 +32,27 @@ module.exports = {
},
pluginOptions: {
electronBuilder: {
externals: [
'@mihomo-party/sysproxy',
'@mihomo-party/sysproxy-win32-ia32-msvc',
'@mihomo-party/sysproxy-win32-x64-msvc',
'@mihomo-party/sysproxy-win32-arm64-msvc',
'@mihomo-party/sysproxy-linux-x64-gnu',
'@mihomo-party/sysproxy-linux-arm64-gnu',
'@mihomo-party/sysproxy-darwin-x64',
'@mihomo-party/sysproxy-darwin-arm64',
'@natmri/platform-napi',
"@natmri/platform-napi-win32-x64-msvc",
"@natmri/platform-napi-darwin-x64",
"@natmri/platform-napi-linux-x64-gnu",
"@natmri/platform-napi-darwin-arm64",
"@natmri/platform-napi-linux-arm64-gnu",
"@natmri/platform-napi-linux-arm64-musl",
"@natmri/platform-napi-win32-arm64-msvc",
"@natmri/platform-napi-linux-arm-gnueabihf",
"@natmri/platform-napi-linux-x64-musl",
"@natmri/platform-napi-win32-ia32-msvc"
],
nodeIntegration: true,
// Provide an array of files that, when changed, will recompile the main process and restart Electron
// Your main process file will be added by default

View File

@ -1,6 +1,6 @@
{
"name": "@docmirror/mitmproxy",
"version": "1.8.8",
"version": "1.8.9",
"description": "",
"main": "src/index.js",
"keywords": [

View File

@ -1,12 +1,14 @@
const DNSOverTLS = require('./tls.js')
const DNSOverHTTPS = require('./https.js')
const DNSOverIpAddress = require('./ipaddress.js')
const DNSOverPreSetIpList = require('./preset.js')
const matchUtil = require('../../utils/util.match')
const log = require('../../utils/util.log')
module.exports = {
initDNS (dnsProviders, preSetIpList) {
const dnsMap = {}
// 创建普通的DNS
for (const provider in dnsProviders) {
const conf = dnsProviders[provider]
@ -20,30 +22,33 @@ module.exports = {
// 设置DNS名称到name属性中
dnsMap[provider].name = provider
dnsMap[provider].type = conf.type
}
// 创建预设IP的DNS
dnsMap.PreSet = new DNSOverPreSetIpList(preSetIpList)
return dnsMap
},
hasDnsLookup (dnsConfig, hostname) {
let providerName = matchUtil.matchHostname(dnsConfig.mapping, hostname, 'get dns providerName')
let providerName = null
// usa已重命名为cloudflare以下为向下兼容处理
if (providerName === 'usa') {
providerName = 'cloudflare'
// 先匹配 预设IP配置
const hostnamePreSetIpList = matchUtil.matchHostname(dnsConfig.preSetIpList, hostname, 'matched preSetIpList')
if (hostnamePreSetIpList) {
return dnsConfig.dnsMap.PreSet
}
// 如果为空尝试从预设IP中匹配如果配置过预设IP则随便
if (providerName == null) {
const hostnamePreSetIpList = matchUtil.matchHostname(dnsConfig.preSetIpList, hostname, 'matched preSetIpList')
if (hostnamePreSetIpList) {
for (const name in dnsConfig.providers) {
log.debug(`当前域名未配置过DNS但配置了预设IP现返回DNS '${name}' 作为预设IP的使用工具hostname: ${hostname}, preSetIpList:`, hostnamePreSetIpList)
return dnsConfig.providers[name]
}
}
// 再匹配 DNS映射配置
providerName = matchUtil.matchHostname(dnsConfig.mapping, hostname, 'get dns providerName')
// 由于DNS中的usa已重命名为cloudflare所以做以下处理为了向下兼容
if (providerName === 'usa' && dnsConfig.dnsMap.usa == null && dnsConfig.dnsMap.cloudflare != null) {
return dnsConfig.dnsMap.cloudflare
}
if (providerName) {
return dnsConfig.providers[providerName]
return dnsConfig.dnsMap[providerName]
}
}
}

View File

@ -0,0 +1,39 @@
const BaseDNS = require('./base')
const matchUtil = require('../../utils/util.match')
function mapToList (ipMap) {
const ipList = []
for (const key in ipMap) {
if (!ipMap[key]) continue
ipList.push(ipMap[key])
}
return ipList
}
module.exports = class DNSOverPreSetIpList extends BaseDNS {
constructor (preSetIpList) {
super()
this.preSetIpList = preSetIpList
this.name = 'PreSet'
this.type = 'PreSet'
}
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) {
return hostnamePreSetIpList
}
}
// 未预设当前域名的IP列表
return []
}
}

View File

@ -6,8 +6,25 @@ module.exports = {
responseIntercept (context, interceptOpt, req, res, proxyReq, proxyRes, ssl, next) {
const { rOptions, log } = context
// 只有GET请求且响应码为2xx时才进行缓存
if (rOptions.method !== 'GET' || proxyRes.statusCode < 200 || proxyRes.statusCode >= 300) {
// 只有GET请求
if (rOptions.method !== 'GET') {
return
}
// 判断当前响应码是否不使用缓存
if (interceptOpt.cacheExcludeStatusCodeList && interceptOpt.cacheExcludeStatusCodeList[proxyRes.statusCode + '']) {
return
}
// 响应码为 200~303 时才进行缓存(可通过以下两个参数调整范围)
let minStatusCode = interceptOpt.cacheMinStatusCode || 200
let maxStatusCode = interceptOpt.cacheMaxStatusCode || 303
if (minStatusCode > maxStatusCode) {
const temp = minStatusCode
minStatusCode = maxStatusCode
maxStatusCode = temp
}
if (proxyRes.statusCode < minStatusCode || proxyRes.statusCode > maxStatusCode) {
// res.setHeader('DS-Cache-Response-Interceptor', `skip: 'method' or 'status' not match`)
return
}

View File

@ -20,6 +20,7 @@ function replaceResponseHeaders (newHeaders, res, proxyRes) {
const preHeaders = {}
// 替换响应头
const needDeleteKeys = []
for (let i = 0; i < proxyRes.rawHeaders.length; i += 2) {
const headerKey = proxyRes.rawHeaders[i].toLowerCase()
@ -27,15 +28,19 @@ function replaceResponseHeaders (newHeaders, res, proxyRes) {
if (newHeaderValue) {
if (newHeaderValue !== proxyRes.rawHeaders[i + 1]) {
preHeaders[headerKey] = proxyRes.rawHeaders[i + 1] // 先保存原先响应头
if (newHeaderValue === REMOVE) { // 由于拦截配置中不允许配置null会被删所以配置一个[remove],当作删除响应头的意思
if (newHeaderValue === REMOVE) { // 由于拦截配置中不允许配置null会被删所以配置一个 "[remove]",当作删除响应头的意思
proxyRes.rawHeaders[i + 1] = ''
} else {
proxyRes.rawHeaders[i + 1] = newHeaderValue
}
}
delete newHeaders[headerKey]
needDeleteKeys.push(headerKey)
}
}
// 处理删除响应头
for (const headerKey of needDeleteKeys) {
delete newHeaders[headerKey]
}
// 新增响应头
for (const headerKey in newHeaders) {
const headerValue = newHeaders[headerKey]

View File

@ -10,17 +10,23 @@ const { Buffer } = require('buffer')
let pacClient = null
function matched (hostname, overWallTargetMap) {
// 匹配配置文件
const ret1 = matchUtil.matchHostname(overWallTargetMap, hostname, 'matched overwall')
if (ret1) {
return 'overwall config'
return 'in config'
} else if (ret1 === false || ret1 === 'false') {
log.debug(`域名 ${hostname} 的overwall配置为 false跳过增强功能即使它在 pac.txt 里`)
return null
}
// 匹配 pac.txt
if (pacClient == null) {
return null
}
const ret = pacClient.FindProxyForURL('https://' + hostname, hostname)
if (ret && ret.indexOf('PROXY ') === 0) {
log.info(`matchHostname: matched overwall: '${hostname}' -> '${ret}' in pac.txt`)
return 'overwall pac'
return 'in pac.txt'
} else {
log.debug(`matchHostname: matched overwall: Not-Matched '${hostname}' -> '${ret}' in pac.txt`)
return null
@ -148,14 +154,7 @@ function createOverwallMiddleware (overWallConfig) {
return {
sslConnectInterceptor: (req, cltSocket, head) => {
const hostname = req.url.split(':')[0]
const ret = matched(hostname, overWallTargetMap)
if (ret == null) {
return null // 返回 null由下一个拦截器校验
}
if (ret === false) {
return false // 不拦截,预留这个判断,避免以后修改 matched 方法的代码出BUG
}
return true // 拦截
return matched(hostname, overWallTargetMap)
},
requestIntercept (context, req, res, ssl, next) {
const { rOptions, log, RequestCounter } = context
@ -164,7 +163,7 @@ function createOverwallMiddleware (overWallConfig) {
}
const hostname = rOptions.hostname
const matchedResult = matched(hostname, overWallTargetMap)
if (matchedResult == null || matchedResult === false) {
if (matchedResult == null || matchedResult === false || matchedResult === 'false') {
return
}
const cacheKey = '__over_wall_proxy__'

View File

@ -9,11 +9,11 @@ const jsonApi = require('../../../json')
function isSslConnect (sslConnectInterceptors, req, cltSocket, head) {
for (const intercept of sslConnectInterceptors) {
const ret = intercept(req, cltSocket, head)
log.debug(`拦截判断结果:${ret}, url: ${req.url}, intercept:`, intercept)
if (ret === false || ret === true) {
return ret
log.debug('当前拦截器返回结果:', ret, `, url: ${req.url}, intercept:`, intercept)
if (ret == null) {
continue
}
// continue
return !(ret === false || ret === 'false')
}
return false
}
@ -58,19 +58,64 @@ function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDire
const isDnsIntercept = {}
const hostport = `${hostname}:${port}`
try {
// 客户端的连接事件监听
cltSocket.on('timeout', (e) => {
log.error(`cltSocket timeout: ${hostport}, errorMsg: ${e.message}`)
})
cltSocket.on('error', (e) => {
log.error(`cltSocket error: ${hostport}, errorMsg: ${e.message}`)
})
// 开发过程中如有需要可以将此参数临时改为true打印所有事件的日志
const printDebugLog = false && process.env.NODE_ENV === 'development'
if (printDebugLog) {
cltSocket.on('close', (hadError) => {
log.debug('【cltSocket close】', hadError)
})
cltSocket.on('connect', () => {
log.debug('【cltSocket connect】')
})
cltSocket.on('connectionAttempt', (ip, port, family) => {
log.debug(`【cltSocket connectionAttempt】${ip}:${port}, family:`, family)
})
cltSocket.on('connectionAttemptFailed', (ip, port, family) => {
log.debug(`【cltSocket connectionAttemptFailed】${ip}:${port}, family:`, family)
})
cltSocket.on('connectionAttemptTimeout', (ip, port, family) => {
log.debug(`【cltSocket connectionAttemptTimeout】${ip}:${port}, family:`, family)
})
cltSocket.on('data', (data) => {
log.debug('【cltSocket data】')
})
cltSocket.on('drain', () => {
log.debug('【cltSocket drain】')
})
cltSocket.on('end', () => {
log.debug('【cltSocket end】')
})
// cltSocket.on('lookup', (err, address, family, host) => {
// })
cltSocket.on('ready', () => {
log.debug('【cltSocket ready】')
})
}
// ---------------------------------------------------------------------------------------------------
const options = {
port,
host: hostname,
connectTimeout: 10000
}
if (dnsConfig && dnsConfig.providers) {
if (dnsConfig && dnsConfig.dnsMap) {
const dns = DnsUtil.hasDnsLookup(dnsConfig, hostname)
if (dns) {
options.lookup = dnsLookup.createLookupFunc(null, dns, 'connect', hostport, isDnsIntercept)
}
}
// 代理连接事件监听
const proxySocket = net.connect(options, () => {
if (!isDirect) log.info('Proxy connect start:', hostport)
else log.debug('Direct connect start:', hostport)
cltSocket.write('HTTP/1.1 200 Connection Established\r\n' +
'Proxy-agent: dev-sidecar\r\n' +
@ -80,15 +125,9 @@ function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDire
cltSocket.pipe(proxySocket)
})
cltSocket.on('timeout', (e) => {
log.error(`cltSocket timeout: ${hostport}, errorMsg: ${e.message}`)
})
cltSocket.on('error', (e) => {
log.error(`cltSocket error: ${hostport}, errorMsg: ${e.message}`)
})
proxySocket.on('timeout', () => {
const cost = new Date() - start
const errorMsg = `代理连接超时: ${hostport}, cost: ${cost} ms`
const errorMsg = `${isDirect ? '直连' : '代理连接'}超时: ${hostport}, cost: ${cost} ms`
log.error(errorMsg)
cltSocket.destroy()
@ -102,8 +141,8 @@ function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDire
proxySocket.on('error', (e) => {
// 连接失败可能被GFW拦截或者服务端拥挤
const cost = new Date() - start
const errorMsg = `代理连接失败: ${hostport}, cost: ${cost} ms, errorMsg: ${e.message}`
log.error(errorMsg)
const errorMsg = `${isDirect ? '直连' : '代理连接'}失败: ${hostport}, cost: ${cost} ms, errorMsg: ${e.message}`
log.error(`${errorMsg}\r\n`, e)
cltSocket.destroy()
@ -113,8 +152,41 @@ function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDire
log.error(`记录ip失败次数用于优选ip hostname: ${hostname}, ip: ${ip}, reason: ${errorMsg}, dns: ${dns.name}`)
}
})
if (printDebugLog) {
proxySocket.on('close', (hadError) => {
log.debug('【proxySocket close】', hadError)
})
proxySocket.on('connect', () => {
log.debug('【proxySocket connect】')
})
proxySocket.on('connectionAttempt', (ip, port, family) => {
log.debug(`【proxySocket connectionAttempt】${ip}:${port}, family:`, family)
})
proxySocket.on('connectionAttemptFailed', (ip, port, family) => {
log.debug(`【proxySocket connectionAttemptFailed】${ip}:${port}, family:`, family)
})
proxySocket.on('connectionAttemptTimeout', (ip, port, family) => {
log.debug(`【proxySocket connectionAttemptTimeout】${ip}:${port}, family:`, family)
})
proxySocket.on('data', (data) => {
log.debug('【proxySocket data】')
})
proxySocket.on('drain', () => {
log.debug('【proxySocket drain】')
})
proxySocket.on('end', () => {
log.debug('【proxySocket end】')
})
// proxySocket.on('lookup', (err, address, family, host) => {
// })
proxySocket.on('ready', () => {
log.debug('【proxySocket ready】')
})
}
return proxySocket
} catch (e) {
log.error(`Proxy connect error: ${hostport}, exception:`, e)
log.error(`${isDirect ? '直连' : '代理连接'}错误: ${hostport}, error:`, e)
}
}

View File

@ -110,17 +110,23 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
log.info('发起代理请求:', url, (rOptions.servername ? ', sni: ' + rOptions.servername : ''), ', headers:', jsonApi.stringify2(rOptions.headers))
const isDnsIntercept = {}
if (dnsConfig && dnsConfig.providers) {
if (dnsConfig && dnsConfig.dnsMap) {
let dns = DnsUtil.hasDnsLookup(dnsConfig, rOptions.hostname)
if (!dns && rOptions.servername) {
dns = dnsConfig.providers.quad9
dns = dnsConfig.dnsMap.quad9
if (dns) {
log.info(`域名 ${rOptions.hostname} 在dns中未配置但使用了 sni: ${rOptions.servername}, 必须使用dns现默认使用 'quad9' DNS.`)
}
}
if (dns) {
rOptions.lookup = dnsLookup.createLookupFunc(res, dns, 'request url', url, isDnsIntercept)
log.debug(`域名 ${rOptions.hostname} DNS: ${dns.name}`)
res.setHeader('DS-DNS', dns.name)
} else {
log.info(`域名 ${rOptions.hostname} 在DNS中未配置`)
}
} else {
log.info(`域名 ${rOptions.hostname} DNS配置不存在`)
}
// rOptions.sigalgs = 'RSA-PSS+SHA256:RSA-PSS+SHA512:ECDSA+SHA256'
@ -147,9 +153,9 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
proxyReq = (rOptions.protocol === 'https:' ? https : http).request(rOptions, (proxyRes) => {
const cost = new Date() - start
if (rOptions.protocol === 'https:') {
log.info(`代理请求返回: ${url}, cost: ${cost} ms`)
log.info(`代理请求返回: ${proxyRes.statusCode}${url}, cost: ${cost} ms`)
} else {
log.info(`请求返回: ${url}, cost: ${cost} ms`)
log.info(`请求返回: ${proxyRes.statusCode}${url}, cost: ${cost} ms`)
}
// console.log('request:', proxyReq, proxyReq.socket)

View File

@ -8,7 +8,7 @@ module.exports = {
return (hostname, options, callback) => {
const tester = speedTest.getSpeedTester(hostname)
if (tester && tester.ready) {
if (tester) {
const aliveIpObj = tester.pickFastAliveIpObj()
if (aliveIpObj) {
log.info(`----- ${action}: ${hostname}, use alive ip from dns '${aliveIpObj.dns}': ${aliveIpObj.host}${target} -----`)
@ -16,7 +16,7 @@ module.exports = {
callback(null, aliveIpObj.host, 4)
return
} else {
log.info(`----- ${action}: ${hostname}, no alive ip${target}, tester:`, tester)
log.info(`----- ${action}: ${hostname}, no alive ip${target}, tester: { "ready": ${tester.ready}, "backupList": ${JSON.stringify(tester.backupList)} }`)
}
}
dns.lookup(hostname).then(ip => {

View File

@ -41,7 +41,7 @@ module.exports = {
port = ~~port
const speedTestConfig = dnsConfig.speedTest
const dnsMap = dnsConfig.providers
const dnsMap = dnsConfig.dnsMap
if (speedTestConfig) {
const dnsProviders = speedTestConfig.dnsProviders
const map = {}

View File

@ -95,7 +95,7 @@ module.exports = (serverConfig) => {
port: serverConfig.port,
dnsConfig: {
preSetIpList,
providers: dnsUtil.initDNS(serverConfig.dns.providers, preSetIpList),
dnsMap: dnsUtil.initDNS(serverConfig.dns.providers, preSetIpList),
mapping: matchUtil.domainMapRegexply(dnsMapping),
speedTest: serverConfig.dns.speedTest
},
@ -119,10 +119,10 @@ module.exports = (serverConfig) => {
const matched = matchUtil.matchHostname(intercepts, hostname, 'matched intercepts')
if ((!!matched) === true) {
log.debug(`拦截器拦截:${req.url}, matched:`, matched)
return true // 拦截
return matched // 拦截
}
return null // 未匹配到任何拦截配置,由下一个拦截器判断
return null // 不在白名单中,也未配置在拦截功能中,跳过当前拦截器,由下一个拦截器判断
},
createIntercepts: (context) => {
const rOptions = context.rOptions

View File

@ -26,11 +26,11 @@ function domainRegexply (target) {
}
function domainMapRegexply (hostMap) {
if (hostMap == null) {
return { origin: {} }
}
const regexpMap = {}
const origin = {} // 用于快速匹配见matchHostname、matchHostnameAll方法
if (hostMap == null) {
return regexpMap
}
lodash.each(hostMap, (value, domain) => {
if (domain.indexOf('*') >= 0 || domain[0] === '^') {
const regDomain = domain[0] !== '^' ? domainRegexply(domain) : domain
@ -61,17 +61,17 @@ function matchHostname (hostMap, hostname, action) {
// 域名快速匹配:直接匹配 或者 两种前缀通配符匹配
let value = hostMap.origin[hostname]
if (value) {
if (value != null) {
log.info(`matchHostname: ${action}: '${hostname}' -> { "${hostname}": ${JSON.stringify(value)} }`)
return value // 快速匹配成功
}
value = hostMap.origin['*' + hostname]
if (value) {
if (value != null) {
log.info(`matchHostname: ${action}: '${hostname}' -> { "*${hostname}": ${JSON.stringify(value)} }`)
return value // 快速匹配成功
}
value = hostMap.origin['*.' + hostname]
if (value) {
if (value != null) {
log.info(`matchHostname: ${action}: '${hostname}' -> { "*.${hostname}": ${JSON.stringify(value)} }`)
return value // 快速匹配成功
}
@ -127,8 +127,8 @@ function matchHostnameAll (hostMap, hostname, action) {
let value
// 通配符匹配 或 正则表达式匹配优先级1最低
for (const target in hostMap) {
if (target === 'origin') {
for (const regexp in hostMap) {
if (regexp === 'origin') {
continue
}
@ -136,16 +136,10 @@ function matchHostnameAll (hostMap, hostname, action) {
// continue // 不是通配符匹配串,也不是正则表达式,跳过
// }
// 如果是通配符匹配串,转换为正则表达式
let regexp = target
// if (target[0] !== '^') {
// regexp = domainRegexply(regexp)
// }
// 正则表达式匹配
if (hostname.match(regexp)) {
value = hostMap[target]
log.debug(`matchHostname-one: ${action}: '${hostname}' -> { "${target}": ${JSON.stringify(value)} }`)
value = hostMap[regexp]
log.debug(`matchHostname-one: ${action}: '${hostname}' -> { "${regexp}": ${JSON.stringify(value)} }`)
values = merge(values, value)
}
}