Skip to content

Commit 84a1548

Browse files
committed
EYE BACK
1 parent 94d2159 commit 84a1548

21 files changed

Lines changed: 1558 additions & 16 deletions

File tree

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
shamefully-hoist=true
2+
phantomjs_cdnurl=https://npmmirror.com/mirrors/phantomjs
23
# Native modules (e.g. @parcel/watcher) require C++17.
34
# In CI, CXXFLAGS=-std=c++17 (Linux/macOS) and CL=/std:c++17 (Windows) are
45
# set before `pnpm install` so node-gyp compiles with the correct standard.

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,10 +375,24 @@ npm config delete https-proxy
375375

376376
### 8.1、准备环境
377377

378-
#### 1)安装 `nodejs`
378+
#### 1)安装 `nodejs` 及其他环境
379379

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

382+
Windows上需要msvc,推荐使用VS 2022(node-gyp对VS 2026支持可能存在问题),安装时选择C++桌面开发工作负载即可。
383+
384+
另外还需要带distutils的python,推荐安装自带setuptools的python 3.11版本。如果本地有uv,则可以简单的运行以下命令
385+
386+
```shell
387+
uv init .
388+
uv sync
389+
.venv/Scripts/activate.ps1 # for windows pwsh
390+
.venv/Scripts/activate.bat # for windows cmd
391+
source .venv/bin/activate # for linux/mac
392+
```
393+
394+
这会根据.python-version文件自动安装python 3.11版本。如不想使用python 3.11,也可删除.python-version文件,pyproject.toml已经指定了所需依赖。
395+
382396
#### 2)安装 `pnpm`
383397

384398
运行如下命令即可安装所需依赖:

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@docmirror/dev-sidecar-cli",
3-
"version": "2.0.2",
3+
"version": "2.1.0",
44
"private": false,
55
"description": "给开发者的加速代理工具",
66
"author": "docmirror.cn",

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@docmirror/dev-sidecar",
3-
"version": "2.0.2",
3+
"version": "2.1.0",
44
"private": false,
55
"description": "给开发者的加速代理工具",
66
"author": "docmirror.cn",

packages/core/src/config/index.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,15 @@ const defaultConfig = {
228228
},
229229
'fonts.googleapis.com': {
230230
'.*': {
231-
proxy: 'fonts.loli.net',
231+
proxy: 'fonts.googleapis.cn',
232+
backup: ['fonts.loli.net'],
233+
test: 'https://fonts.googleapis.com/css?family=Oswald',
234+
},
235+
},
236+
'fonts.gstatic.com': {
237+
'.*': {
238+
proxy: 'fonts-gstatic.proxy.ustclug.org',
239+
backup: ['gstatic.loli.net'],
232240
test: 'https://fonts.googleapis.com/css?family=Oswald',
233241
},
234242
},
@@ -241,12 +249,6 @@ const defaultConfig = {
241249
'themes.googleusercontent.com': {
242250
'.*': { proxy: 'google-themes.proxy.ustclug.org' },
243251
},
244-
// 'fonts.gstatic.com': {
245-
// '.*': {
246-
// proxy: 'gstatic.loli.net',
247-
// backup: ['fonts-gstatic.proxy.ustclug.org']
248-
// }
249-
// },
250252
'clients*.google.com': { '.*': { abort: false, desc: '设置abort:true可以快速失败,节省时间' } },
251253
'www.googleapis.com': { '.*': { abort: false, desc: '设置abort:true可以快速失败,节省时间' } },
252254
'lh*.googleusercontent.com': { '.*': { abort: false, desc: '设置abort:true可以快速失败,节省时间' } },
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# 网络审查检测器
2+
3+
FreeEye 是一个用 JavaScript 编写的网络审查检测器,自动化检测网络环境并推荐可能的规避方法。
4+
5+
为中国大陆用户设计,但也可用于其他地区。
6+
7+
希望使得用户能够使用本工具回答以下问题:
8+
9+
1. 我的网络是被审查了,还是只是出现了异常故障?
10+
2. 使用了哪些审查手段?
11+
3. 有哪些规避方法可以绕过这些审查?
12+
13+
## 使用方法
14+
15+
前提条件是你需要在设备上安装 `node.js`
16+
17+
启动向导的方法:
18+
19+
```bash
20+
git clone https://github.com/cute-omega/free-eye.git
21+
cd free-eye
22+
npm install
23+
npm start
24+
```
25+
26+
(如果你不准备进行开发,可以跳过 `npm install`并直接运行 `npm start`;中国大陆用户可能需要设置npm镜像)
27+
28+
不同测试的代码位于 `checkpoints/` 目录中。每个测试都有唯一的 `tag` 标识。单个测试的参数在 `config.json` 文件中设置,使用测试的 tag 作为键:
29+
30+
```json
31+
{
32+
"tag": {
33+
// 测试特定的参数
34+
}
35+
// ...
36+
}
37+
```
38+
39+
## 测试
40+
41+
### Route(路由)
42+
43+
通过尝试创建一个套接字并连接到一个非本地地址,检测设备是否具有互联网连通性。
44+
45+
### DNS
46+
47+
使用系统的 DNS 解析器尝试解析允许和被封锁的主机名。测试被封锁主机名是否存在 DNS 缓存投毒。
48+
49+
### TCP
50+
51+
尝试与已知允许和已知被封锁的 IP 地址建立 TCP 连接。
52+
53+
### TLS
54+
55+
尝试与已知允许但可能遭受审查的 IP 地址(例如对中国用户来说的“干净”的外国 IP)完成 TLS 握手。测试内容包括:
56+
57+
- 不带任何 SNI 的握手
58+
- 带已知允许的 SNI 的握手
59+
- 带已知被封锁的 SNI 的握手
60+
61+
还测试将 TLS 记录分片作为一种规避方法,通过尝试对被封锁的 SNI 进行握手但分片 ClientHello 来实现。
62+
63+
## 编写你自己的测试
64+
65+
如果你想编写自定义测试,只需实现 `template.js` 中描述的接口,将测试模块保存到 `checkpoints/` 目录,并在 `config.json` 中添加该测试的参数。
66+
67+
## 相关项目
68+
69+
本项目受到来自 [wallpunch/wizard](https://github.com/wallpunch/wizard) 的启发;
70+
用作 [docmirror/dev-sidecar](https://github.com/docmirror/dev-sidecar) 中的网络检测插件。
71+
72+
---
73+
74+
不论在哪里,人们的目光都应该是自由的。
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { randomBytes } from 'node:crypto'
2+
import { promises as dns } from 'node:dns'
3+
import { TestGroup } from '../template.js'
4+
import { FAMILY_VALUES, getCensorsString, getResultIcon } from '../utils.js'
5+
6+
class DnsTester extends TestGroup {
7+
/**
8+
* A test group to assess the system's DNS resolver
9+
*/
10+
constructor (globalConfig, globalResults) {
11+
super(globalConfig, globalResults, 'DNS')
12+
}
13+
14+
static getTestTag () {
15+
return 'DNS'
16+
}
17+
18+
static getPrereqs () {
19+
return ['Route']
20+
}
21+
22+
getDefaultResults () {
23+
return {
24+
IPv4: false,
25+
IPv6: false,
26+
}
27+
}
28+
29+
checkIfShouldSkip (globalResults) {
30+
/**
31+
* Skip this test if all routing tests failed
32+
*/
33+
let skip = true
34+
for (const family in FAMILY_VALUES) {
35+
if (Object.values(globalResults.Route[family]).includes(true)) {
36+
this.results[family] = {}
37+
skip = false
38+
}
39+
}
40+
if (skip) {
41+
return 'no routable networks'
42+
}
43+
return null
44+
}
45+
46+
async startTest () {
47+
this.testPrefix = `${randomBytes(30).toString('hex')}.`
48+
console.log(`Using POISON test prefix: ${this.testPrefix}`)
49+
50+
for (const family in FAMILY_VALUES) {
51+
if (this.results[family] === false) {
52+
continue // not routable
53+
}
54+
for (const host of this.config.allow) {
55+
this.startResolveTest(host, family, false)
56+
}
57+
for (const host of this.config.block) {
58+
this.startResolveTest(host, family, true)
59+
}
60+
}
61+
}
62+
63+
async startResolveTest (host, family, testPoison) {
64+
const testPrefs = ['']
65+
if (testPoison) {
66+
testPrefs.push(this.testPrefix)
67+
}
68+
for (const prefix of testPrefs) {
69+
this.startTestThread(
70+
DnsTester.resolveThread,
71+
[family, prefix + host],
72+
`${family}, ${host}${prefix ? ', POISON' : ''}`,
73+
this.config.timeout,
74+
)
75+
}
76+
}
77+
78+
logResults () {
79+
let resStr = ''
80+
for (const [family, results] of Object.entries(this.results)) {
81+
if (results === false) {
82+
continue
83+
}
84+
this.results[family] = true
85+
const allowList = this.config.allow
86+
const allowOkCnt = allowList.reduce((sum, host) => sum + (results[host] || 0), 0)
87+
const allowTotal = allowList.length
88+
let resIcon
89+
if (allowOkCnt === allowTotal) { // DNS can resolve
90+
resIcon = getResultIcon(true)
91+
} else if (allowOkCnt === 0) { // DNS can't resolve
92+
resIcon = getResultIcon(false)
93+
this.results[family] = false
94+
} else { // test inconclusive
95+
resIcon = getResultIcon(null, `resolved ${allowOkCnt}/${allowTotal}`)
96+
}
97+
resStr += `${family}: DNS ${resIcon}\n`
98+
99+
const censors = []
100+
const blockList = this.config.block
101+
const blockOkCnt = blockList.reduce((sum, host) => sum + (results[host] || 0), 0)
102+
const blockTotal = blockList.length
103+
if (blockOkCnt < blockTotal) {
104+
censors.push(`DNS blocking: ${blockTotal - blockOkCnt}/${blockTotal} blocked`)
105+
}
106+
107+
const blockPoisonCnt = blockList.reduce((sum, host) => sum + (results[this.testPrefix + host] || 0), 0)
108+
if (blockPoisonCnt > 0) {
109+
censors.push(`DNS poisoning: ${blockPoisonCnt}/${blockTotal} poisoned`)
110+
}
111+
resStr += getCensorsString(censors)
112+
}
113+
return resStr
114+
}
115+
116+
static async resolveThread (timeout, logger, results, family, host) {
117+
if (results[family] === false) {
118+
return // Not routable
119+
}
120+
results[family][host] = 0 // default to failed
121+
try {
122+
let records
123+
if (FAMILY_VALUES[family] === 4) {
124+
records = await dns.resolve4(host)
125+
} else {
126+
records = await dns.resolve6(host)
127+
}
128+
if (!timeout.isSet) {
129+
logger(`Got ${records.length} records`)
130+
results[family][host] = 1
131+
} else {
132+
logger(`Timeout occurred for ${host}`)
133+
}
134+
} catch (error) {
135+
if (!timeout.isSet) {
136+
logger(`Failed with error: ${error.message}`)
137+
results[family][host] = 0 // explicitly set to failed
138+
} else {
139+
logger(`Timeout occurred for ${host}`)
140+
}
141+
}
142+
}
143+
}
144+
145+
function getClientTests () {
146+
return [DnsTester]
147+
}
148+
149+
export default {
150+
DnsTester,
151+
getClientTests,
152+
}

0 commit comments

Comments
 (0)