Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
shamefully-hoist=true
phantomjs_cdnurl=https://npmmirror.com/mirrors/phantomjs
# Native modules (e.g. @parcel/watcher) require C++17.
# In CI, CXXFLAGS=-std=c++17 (Linux/macOS) and CL=/std:c++17 (Windows) are
# set before `pnpm install` so node-gyp compiles with the correct standard.
Expand Down
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@
> 1. ~~[hub.fastgit.org](https://hub.fastgit.org/) (2024/11/18:这个好像失效了?)~~
> 2. ~~[github.com.cnpmjs.org](https://github.com.cnpmjs.org/) 这个很容易超限(2024/11/18:这个好像失效了?)~~
> 3. [bgithub.xyz](https://bgithub.xyz/)(edge浏览器可能报毒)
> 4. [kkgithub.com](https://kkgithub.com/)(目前正在维护中)

## 五、api

Expand Down Expand Up @@ -375,10 +376,24 @@ npm config delete https-proxy

### 8.1、准备环境

#### 1)安装 `nodejs`
#### 1)安装 `nodejs` 及其他环境

推荐安装 nodejs `22.x.x` 的版本,其他版本未做测试

Windows上需要msvc,推荐使用VS 2022(node-gyp对VS 2026支持可能存在问题),安装时选择C++桌面开发工作负载即可。

另外还需要带distutils的python,推荐安装自带setuptools的python 3.11版本。如果本地有uv,则可以简单的运行以下命令

```shell
uv init .
uv sync
.venv/Scripts/activate.ps1 # for windows pwsh
.venv/Scripts/activate.bat # for windows cmd
source .venv/bin/activate # for linux/mac
```

这会根据.python-version文件自动安装python 3.11版本。如不想使用python 3.11,也可删除.python-version文件,pyproject.toml已经指定了所需依赖。

#### 2)安装 `pnpm`

运行如下命令即可安装所需依赖:
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docmirror/dev-sidecar-cli",
"version": "2.0.2",
"version": "2.1.0",
"private": false,
"description": "给开发者的加速代理工具",
"author": "docmirror.cn",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docmirror/dev-sidecar",
"version": "2.0.2",
"version": "2.1.0",
"private": false,
"description": "给开发者的加速代理工具",
"author": "docmirror.cn",
Expand Down
16 changes: 9 additions & 7 deletions packages/core/src/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,15 @@ const defaultConfig = {
},
'fonts.googleapis.com': {
'.*': {
proxy: 'fonts.loli.net',
proxy: 'fonts.googleapis.cn',
backup: ['fonts.loli.net'],
test: 'https://fonts.googleapis.com/css?family=Oswald',
},
},
'fonts.gstatic.com': {
'.*': {
proxy: 'fonts-gstatic.proxy.ustclug.org',
backup: ['gstatic.loli.net'],
test: 'https://fonts.googleapis.com/css?family=Oswald',
},
},
Expand All @@ -241,12 +249,6 @@ const defaultConfig = {
'themes.googleusercontent.com': {
'.*': { proxy: 'google-themes.proxy.ustclug.org' },
},
// 'fonts.gstatic.com': {
// '.*': {
// proxy: 'gstatic.loli.net',
// backup: ['fonts-gstatic.proxy.ustclug.org']
// }
// },
'clients*.google.com': { '.*': { abort: false, desc: '设置abort:true可以快速失败,节省时间' } },
'www.googleapis.com': { '.*': { abort: false, desc: '设置abort:true可以快速失败,节省时间' } },
'lh*.googleusercontent.com': { '.*': { abort: false, desc: '设置abort:true可以快速失败,节省时间' } },
Expand Down
74 changes: 74 additions & 0 deletions packages/core/src/modules/plugin/free-eye/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# 网络审查检测器

FreeEye 是一个用 JavaScript 编写的网络审查检测器,自动化检测网络环境并推荐可能的规避方法。

为中国大陆用户设计,但也可用于其他地区。

希望使得用户能够使用本工具回答以下问题:

1. 我的网络是被审查了,还是只是出现了异常故障?
2. 使用了哪些审查手段?
3. 有哪些规避方法可以绕过这些审查?

## 使用方法

前提条件是你需要在设备上安装 `node.js`。

启动向导的方法:

```bash
git clone https://github.com/cute-omega/free-eye.git
cd free-eye
npm install
npm start
```

(如果你不准备进行开发,可以跳过 `npm install`并直接运行 `npm start`;中国大陆用户可能需要设置npm镜像)

不同测试的代码位于 `checkpoints/` 目录中。每个测试都有唯一的 `tag` 标识。单个测试的参数在 `config.json` 文件中设置,使用测试的 tag 作为键:

```json
{
"tag": {
// 测试特定的参数
}
// ...
}
```

## 测试

### Route(路由)

通过尝试创建一个套接字并连接到一个非本地地址,检测设备是否具有互联网连通性。

### DNS

使用系统的 DNS 解析器尝试解析允许和被封锁的主机名。测试被封锁主机名是否存在 DNS 缓存投毒。

### TCP

尝试与已知允许和已知被封锁的 IP 地址建立 TCP 连接。

### TLS

尝试与已知允许但可能遭受审查的 IP 地址(例如对中国用户来说的“干净”的外国 IP)完成 TLS 握手。测试内容包括:

- 不带任何 SNI 的握手
- 带已知允许的 SNI 的握手
- 带已知被封锁的 SNI 的握手

还测试将 TLS 记录分片作为一种规避方法,通过尝试对被封锁的 SNI 进行握手但分片 ClientHello 来实现。

## 编写你自己的测试

如果你想编写自定义测试,只需实现 `template.js` 中描述的接口,将测试模块保存到 `checkpoints/` 目录,并在 `config.json` 中添加该测试的参数。

## 相关项目

本项目受到来自 [wallpunch/wizard](https://github.com/wallpunch/wizard) 的启发;
用作 [docmirror/dev-sidecar](https://github.com/docmirror/dev-sidecar) 中的网络检测插件。

---

不论在哪里,人们的目光都应该是自由的。
152 changes: 152 additions & 0 deletions packages/core/src/modules/plugin/free-eye/checkpoints/dns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { randomBytes } from 'node:crypto'
import { promises as dns } from 'node:dns'
import { TestGroup } from '../template.js'
import { FAMILY_VALUES, getCensorsString, getResultIcon } from '../utils.js'

class DnsTester extends TestGroup {
/**
* A test group to assess the system's DNS resolver
*/
constructor (globalConfig, globalResults) {
super(globalConfig, globalResults, 'DNS')
}

static getTestTag () {
return 'DNS'
}

static getPrereqs () {
return ['Route']
}

getDefaultResults () {
return {
IPv4: false,
IPv6: false,
}
}

checkIfShouldSkip (globalResults) {
/**
* Skip this test if all routing tests failed
*/
let skip = true
for (const family in FAMILY_VALUES) {
if (Object.values(globalResults.Route[family]).includes(true)) {
this.results[family] = {}
skip = false
}
}
if (skip) {
return 'no routable networks'
}
return null
}

async startTest () {
this.testPrefix = `${randomBytes(30).toString('hex')}.`
console.log(`Using POISON test prefix: ${this.testPrefix}`)

for (const family in FAMILY_VALUES) {
if (this.results[family] === false) {
continue // not routable
}
for (const host of this.config.allow) {
this.startResolveTest(host, family, false)
}
for (const host of this.config.block) {
this.startResolveTest(host, family, true)
}
}
}

async startResolveTest (host, family, testPoison) {
const testPrefs = ['']
if (testPoison) {
testPrefs.push(this.testPrefix)
}
for (const prefix of testPrefs) {
this.startTestThread(
DnsTester.resolveThread,
[family, prefix + host],
`${family}, ${host}${prefix ? ', POISON' : ''}`,
this.config.timeout,
)
}
}

logResults () {
let resStr = ''
for (const [family, results] of Object.entries(this.results)) {
if (results === false) {
continue
}
this.results[family] = true
const allowList = this.config.allow
const allowOkCnt = allowList.reduce((sum, host) => sum + (results[host] || 0), 0)
const allowTotal = allowList.length
let resIcon
if (allowOkCnt === allowTotal) { // DNS can resolve
resIcon = getResultIcon(true)
} else if (allowOkCnt === 0) { // DNS can't resolve
resIcon = getResultIcon(false)
this.results[family] = false
} else { // test inconclusive
resIcon = getResultIcon(null, `resolved ${allowOkCnt}/${allowTotal}`)
}
resStr += `${family}: DNS ${resIcon}\n`

const censors = []
const blockList = this.config.block
const blockOkCnt = blockList.reduce((sum, host) => sum + (results[host] || 0), 0)
const blockTotal = blockList.length
if (blockOkCnt < blockTotal) {
censors.push(`DNS blocking: ${blockTotal - blockOkCnt}/${blockTotal} blocked`)
}

const blockPoisonCnt = blockList.reduce((sum, host) => sum + (results[this.testPrefix + host] || 0), 0)
if (blockPoisonCnt > 0) {
censors.push(`DNS poisoning: ${blockPoisonCnt}/${blockTotal} poisoned`)
}
resStr += getCensorsString(censors)
}
return resStr
}

static async resolveThread (timeout, logger, results, family, host) {
if (results[family] === false) {
return // Not routable
}
results[family][host] = 0 // default to failed
try {
let records
if (FAMILY_VALUES[family] === 4) {
records = await dns.resolve4(host)
} else {
records = await dns.resolve6(host)
}
if (!timeout.isSet) {
logger(`Got ${records.length} records`)
results[family][host] = 1
} else {
logger(`Timeout occurred for ${host}`)
}
} catch (error) {
if (!timeout.isSet) {
logger(`Failed with error: ${error.message}`)
results[family][host] = 0 // explicitly set to failed
} else {
logger(`Timeout occurred for ${host}`)
}
}
}
}

function getClientTests () {
return [DnsTester]
}

export default {
DnsTester,
getClientTests,
}
Loading
Loading