Skip to content

Commit 683c5c1

Browse files
committed
Embed free-eye: convert submodule to regular directory
1 parent 580b543 commit 683c5c1

File tree

13 files changed

+1608
-1
lines changed

13 files changed

+1608
-1
lines changed
Submodule free-eye deleted from 386942e
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
**/__pycache__
2+
**/*.swp
3+
*.py
4+
clear_git_history.bat
5+
node_modules
6+
7+
.DS_Store
8+
9+
index.js
10+
config.js

packages/core/src/modules/plugin/free-eye/LICENSE

Lines changed: 373 additions & 0 deletions
Large diffs are not rendered by default.
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: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/**
2+
* Using the system's DNS resolver, try resolving both
3+
* allowed and blocked hostnames.
4+
*
5+
* Also test blocked hostnames for DNS cache poisoning by
6+
* trying to resolve a long, random (nonexisting) subdomain
7+
*/
8+
9+
const { randomBytes } = require('node:crypto')
10+
const { promises: dns } = require('node:dns')
11+
12+
const { TestGroup } = require('../template')
13+
const { FAMILY_VALUES, getCensorsString, getResultIcon } = require('../utils')
14+
15+
class DnsTester extends TestGroup {
16+
/**
17+
* A test group to assess the system's DNS resolver
18+
*/
19+
constructor(globalConfig, globalResults) {
20+
super(globalConfig, globalResults, 'DNS')
21+
}
22+
23+
static getTestTag() {
24+
return 'DNS'
25+
}
26+
27+
static getPrereqs() {
28+
return ['Route']
29+
}
30+
31+
getDefaultResults() {
32+
return {
33+
IPv4: false,
34+
IPv6: false,
35+
}
36+
}
37+
38+
checkIfShouldSkip(globalResults) {
39+
/**
40+
* Skip this test if all routing tests failed
41+
*/
42+
let skip = true
43+
for (const family in FAMILY_VALUES) {
44+
if (Object.values(globalResults.Route[family]).includes(true)) {
45+
this.results[family] = {}
46+
skip = false
47+
}
48+
}
49+
if (skip) {
50+
return 'no routable networks'
51+
}
52+
return null
53+
}
54+
55+
async startTest() {
56+
this.testPrefix = `${randomBytes(30).toString('hex')}.`
57+
console.log(`Using POISON test prefix: ${this.testPrefix}`)
58+
59+
for (const family in FAMILY_VALUES) {
60+
if (this.results[family] === false) {
61+
continue // not routable
62+
}
63+
for (const host of this.config.allow) {
64+
this.startResolveTest(host, family, false)
65+
}
66+
for (const host of this.config.block) {
67+
this.startResolveTest(host, family, true)
68+
}
69+
}
70+
}
71+
72+
async startResolveTest(host, family, testPoison) {
73+
const testPrefs = ['']
74+
if (testPoison) {
75+
testPrefs.push(this.testPrefix)
76+
}
77+
for (const prefix of testPrefs) {
78+
this.startTestThread(
79+
DnsTester.resolveThread,
80+
[family, prefix + host],
81+
`${family}, ${host}${prefix ? ', POISON' : ''}`,
82+
this.config.timeout,
83+
)
84+
}
85+
}
86+
87+
logResults() {
88+
let resStr = ''
89+
for (const [family, results] of Object.entries(this.results)) {
90+
if (results === false) {
91+
continue
92+
}
93+
this.results[family] = true
94+
const allowList = this.config.allow
95+
const allowOkCnt = allowList.reduce((sum, host) => sum + (results[host] || 0), 0)
96+
const allowTotal = allowList.length
97+
let resIcon
98+
if (allowOkCnt === allowTotal) { // DNS can resolve
99+
resIcon = getResultIcon(true)
100+
} else if (allowOkCnt === 0) { // DNS can't resolve
101+
resIcon = getResultIcon(false)
102+
this.results[family] = false
103+
} else { // test inconclusive
104+
resIcon = getResultIcon(null, `resolved ${allowOkCnt}/${allowTotal}`)
105+
}
106+
resStr += `${family}: DNS ${resIcon}\n`
107+
108+
const censors = []
109+
const blockList = this.config.block
110+
const blockOkCnt = blockList.reduce((sum, host) => sum + (results[host] || 0), 0)
111+
const blockTotal = blockList.length
112+
if (blockOkCnt < blockTotal) {
113+
censors.push(`DNS blocking: ${blockTotal - blockOkCnt}/${blockTotal} blocked`)
114+
}
115+
116+
const blockPoisonCnt = blockList.reduce((sum, host) => sum + (results[this.testPrefix + host] || 0), 0)
117+
if (blockPoisonCnt > 0) {
118+
censors.push(`DNS poisoning: ${blockPoisonCnt}/${blockTotal} poisoned`)
119+
}
120+
resStr += getCensorsString(censors)
121+
}
122+
return resStr
123+
}
124+
125+
static async resolveThread(timeout, logger, results, family, host) {
126+
if (results[family] === false) {
127+
return // Not routable
128+
}
129+
results[family][host] = 0 // default to failed
130+
try {
131+
let records
132+
if (FAMILY_VALUES[family] === 4) {
133+
records = await dns.resolve4(host)
134+
} else {
135+
records = await dns.resolve6(host)
136+
}
137+
if (!timeout.isSet) {
138+
logger(`Got ${records.length} records`)
139+
results[family][host] = 1
140+
} else {
141+
logger(`Timeout occurred for ${host}`)
142+
}
143+
} catch (error) {
144+
if (!timeout.isSet) {
145+
logger(`Failed with error: ${error.message}`)
146+
results[family][host] = 0 // explicitly set to failed
147+
} else {
148+
logger(`Timeout occurred for ${host}`)
149+
}
150+
}
151+
}
152+
}
153+
154+
function getClientTests() {
155+
return [DnsTester]
156+
}
157+
158+
module.exports = {
159+
DnsTester,
160+
getClientTests,
161+
}

0 commit comments

Comments
 (0)