mm_ip 1.0.6 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/README_EN.md +22 -0
- package/cache/ip/black.json +8 -11
- package/cache/ip/violation.json +3 -29
- package/cache/ip/white.json +1 -1
- package/index.js +453 -29
- package/package.json +3 -2
- package/test.js +91 -3
package/README.md
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
- ✅ **IP请求频率监控** - 实时监控IP请求频率,防止高频访问
|
|
14
14
|
- ✅ **自动拉黑机制** - 支持高频访问自动拉黑和违规次数超限自动拉黑
|
|
15
15
|
- ✅ **违规记录系统** - 记录IP违规行为,支持违规次数统计和重置
|
|
16
|
+
- ✅ **IP地区检测** - 内置ip2region离线IP库,检测到国外IP自动加入黑名单
|
|
17
|
+
- ✅ **操作系统级拉黑** - 支持直接调用系统防火墙拉黑/释放IP(Windows netsh / Linux iptables)
|
|
16
18
|
- ✅ **持久化存储** - 支持数据分文件存储(白名单、黑名单、违规记录独立存储)
|
|
17
19
|
- ✅ **自动保存机制** - 支持定时自动保存和操作触发保存
|
|
18
20
|
- ✅ **重启数据恢复** - 支持重启后自动加载持久化数据
|
|
@@ -89,10 +91,16 @@ new Ip(config)
|
|
|
89
91
|
| `auto_black_enable` | boolean | true | 启用自动高频拉黑 |
|
|
90
92
|
| `auto_black_threshold` | number | 50 | 自动拉黑阈值 |
|
|
91
93
|
| `auto_black_duration` | number | 3600000 | 自动拉黑持续时间(毫秒) |
|
|
94
|
+
| `auto_black_grace_count` | number | 3 | 自动拉黑宽限次数,连续触限达到该次数后才拉黑 |
|
|
92
95
|
| `violation_enable` | boolean | true | 启用违规记录 |
|
|
93
96
|
| `violation_max_count` | number | 10 | 最大违规次数 |
|
|
94
97
|
| `violation_reset_time` | number | 86400000 | 违规记录重置时间(毫秒) |
|
|
95
98
|
| `violation_auto_black` | boolean | true | 违规次数超限自动拉黑 |
|
|
99
|
+
| `region_check_enable` | boolean | true | 启用国外IP检测 |
|
|
100
|
+
| `region_checker` | function | 自动加载ip2region | IP地区检测函数 (ip) => 地区信息 |
|
|
101
|
+
| `region_cache_time` | number | 86400000 | 地区检测结果缓存时间(毫秒) |
|
|
102
|
+
| `os_black_enable` | boolean | false | 启用系统级拉黑联动(需管理员权限,默认关闭避免误拦截) |
|
|
103
|
+
| `os_rule_prefix` | string | 'mm_ip_black_' | 系统防火墙规则名前缀 |
|
|
96
104
|
| `dir` | string | './cache/ip' | 持久化存储目录 |
|
|
97
105
|
|
|
98
106
|
### 主要方法
|
|
@@ -124,6 +132,20 @@ new Ip(config)
|
|
|
124
132
|
- `clearViolations()` - 清空所有违规记录
|
|
125
133
|
- `getAllViolations()` - 获取所有违规记录
|
|
126
134
|
|
|
135
|
+
#### IP地区检测
|
|
136
|
+
- `setRegionChecker(fn)` - 设置自定义地区检测函数
|
|
137
|
+
- `getRegion(ip)` - 获取IP地区信息(带缓存)
|
|
138
|
+
- `isForeign(ip)` - 判断IP是否为国外IP
|
|
139
|
+
- `checkRegion(ip)` - 检测IP地区,国外IP自动加入黑名单
|
|
140
|
+
- `clearRegionCache()` - 清空地区检测结果缓存
|
|
141
|
+
|
|
142
|
+
#### 操作系统级拉黑
|
|
143
|
+
- `osBlack(ip)` - 在操作系统层拉黑IP(入站拦截,需管理员权限)
|
|
144
|
+
- `osUnblack(ip)` - 在操作系统层释放IP
|
|
145
|
+
- `isOsBlack(ip)` - 检查IP是否已在操作系统层拉黑
|
|
146
|
+
|
|
147
|
+
> 启用 `os_black_enable` 后,`addBlack()` 会自动同步系统防火墙拉黑,`delBlack()`/`del()` 会自动释放。运行环境需具备管理员权限,否则系统命令执行失败仅记录日志,不影响应用层黑名单功能。
|
|
148
|
+
|
|
127
149
|
#### 持久化存储
|
|
128
150
|
- `save()` - 手动保存数据
|
|
129
151
|
- `load()` - 手动加载数据
|
package/README_EN.md
CHANGED
|
@@ -13,6 +13,8 @@ A powerful IP management module that supports whitelist, blacklist management, I
|
|
|
13
13
|
- ✅ **IP Request Frequency Monitoring** - Real-time IP request frequency monitoring to prevent high-frequency access
|
|
14
14
|
- ✅ **Auto-Blacklist Mechanism** - Automatic blacklisting for high-frequency access and violation limit exceeding
|
|
15
15
|
- ✅ **Violation Record System** - Record IP violation behaviors with violation count statistics and reset
|
|
16
|
+
- ✅ **IP Region Detection** - Built-in ip2region offline IP database, auto-blacklist foreign IPs
|
|
17
|
+
- ✅ **OS-Level Blacklisting** - Directly blacklist/release IPs via system firewall (Windows netsh / Linux iptables)
|
|
16
18
|
- ✅ **Persistent Storage** - Support for separate file storage (whitelist, blacklist, violation records stored independently)
|
|
17
19
|
- ✅ **Auto-Save Mechanism** - Support for timed auto-save and operation-triggered save
|
|
18
20
|
- ✅ **Data Recovery on Restart** - Automatic loading of persistent data after restart
|
|
@@ -89,10 +91,16 @@ new Ip(config)
|
|
|
89
91
|
| `auto_black_enable` | boolean | true | Enable auto-blacklist for high frequency |
|
|
90
92
|
| `auto_black_threshold` | number | 50 | Auto-blacklist threshold |
|
|
91
93
|
| `auto_black_duration` | number | 3600000 | Auto-blacklist duration (milliseconds) |
|
|
94
|
+
| `auto_black_grace_count` | number | 3 | Grace count before auto-blacklist |
|
|
92
95
|
| `violation_enable` | boolean | true | Enable violation records |
|
|
93
96
|
| `violation_max_count` | number | 10 | Maximum violation count |
|
|
94
97
|
| `violation_reset_time` | number | 86400000 | Violation record reset time (milliseconds) |
|
|
95
98
|
| `violation_auto_black` | boolean | true | Auto-blacklist when violation count exceeds limit |
|
|
99
|
+
| `region_check_enable` | boolean | true | Enable foreign IP detection |
|
|
100
|
+
| `region_checker` | function | auto load ip2region | IP region check function (ip) => region info |
|
|
101
|
+
| `region_cache_time` | number | 86400000 | Region check result cache time (ms) |
|
|
102
|
+
| `os_black_enable` | boolean | false | Enable OS-level blacklist sync (requires admin, off by default) |
|
|
103
|
+
| `os_rule_prefix` | string | 'mm_ip_black_' | System firewall rule name prefix |
|
|
96
104
|
| `dir` | string | './cache/ip' | Persistent storage directory |
|
|
97
105
|
|
|
98
106
|
### Main Methods
|
|
@@ -124,6 +132,20 @@ new Ip(config)
|
|
|
124
132
|
- `clearViolations()` - Clear all violation records
|
|
125
133
|
- `getAllViolations()` - Get all violation records
|
|
126
134
|
|
|
135
|
+
#### IP Region Detection
|
|
136
|
+
- `setRegionChecker(fn)` - Set custom region check function
|
|
137
|
+
- `getRegion(ip)` - Get IP region info (with cache)
|
|
138
|
+
- `isForeign(ip)` - Check if IP is foreign
|
|
139
|
+
- `checkRegion(ip)` - Check IP region, auto-blacklist foreign IPs
|
|
140
|
+
- `clearRegionCache()` - Clear region check result cache
|
|
141
|
+
|
|
142
|
+
#### OS-Level Blacklisting
|
|
143
|
+
- `osBlack(ip)` - Blacklist IP at OS level (inbound block, requires admin)
|
|
144
|
+
- `osUnblack(ip)` - Release IP at OS level
|
|
145
|
+
- `isOsBlack(ip)` - Check if IP is blacklisted at OS level
|
|
146
|
+
|
|
147
|
+
> When `os_black_enable` is enabled, `addBlack()` automatically syncs the system firewall, and `delBlack()`/`del()` automatically release it. The running environment needs administrator privileges; otherwise, failed system commands only log errors and do not affect the application-level blacklist.
|
|
148
|
+
|
|
127
149
|
#### Persistent Storage
|
|
128
150
|
- `save()` - Manually save data
|
|
129
151
|
- `load()` - Manually load data
|
package/cache/ip/black.json
CHANGED
|
@@ -1,29 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"black": [
|
|
3
|
-
"192.168.100.
|
|
4
|
-
"192.168.
|
|
3
|
+
"192.168.100.2",
|
|
4
|
+
"192.168.200.1",
|
|
5
|
+
"192.168.200.3"
|
|
5
6
|
],
|
|
6
7
|
"black_time": [
|
|
7
8
|
[
|
|
8
9
|
"172.16.0.2",
|
|
9
|
-
|
|
10
|
+
1787927071109
|
|
10
11
|
],
|
|
11
12
|
[
|
|
12
13
|
"192.168.100.2",
|
|
13
|
-
|
|
14
|
+
1787928865115
|
|
14
15
|
],
|
|
15
16
|
[
|
|
16
17
|
"192.168.200.2",
|
|
17
|
-
|
|
18
|
+
1787928361999
|
|
18
19
|
],
|
|
19
20
|
[
|
|
20
21
|
"192.168.200.3",
|
|
21
|
-
|
|
22
|
-
],
|
|
23
|
-
[
|
|
24
|
-
"192.168.100.1",
|
|
25
|
-
1786650999158
|
|
22
|
+
1787928866136
|
|
26
23
|
]
|
|
27
24
|
],
|
|
28
|
-
"timestamp":
|
|
25
|
+
"timestamp": 1787928873206
|
|
29
26
|
}
|
package/cache/ip/violation.json
CHANGED
|
@@ -1,31 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"violation_count": [
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
1
|
|
6
|
-
],
|
|
7
|
-
[
|
|
8
|
-
"172.16.0.2",
|
|
9
|
-
2
|
|
10
|
-
],
|
|
11
|
-
[
|
|
12
|
-
"192.168.100.1",
|
|
13
|
-
7
|
|
14
|
-
]
|
|
15
|
-
],
|
|
16
|
-
"violation_time": [
|
|
17
|
-
[
|
|
18
|
-
"10.0.0.1",
|
|
19
|
-
1786650994127
|
|
20
|
-
],
|
|
21
|
-
[
|
|
22
|
-
"172.16.0.2",
|
|
23
|
-
1786650994129
|
|
24
|
-
],
|
|
25
|
-
[
|
|
26
|
-
"192.168.100.1",
|
|
27
|
-
1786650994156
|
|
28
|
-
]
|
|
29
|
-
],
|
|
30
|
-
"timestamp": 1786650994160
|
|
2
|
+
"violation_count": [],
|
|
3
|
+
"violation_time": [],
|
|
4
|
+
"timestamp": 1787928873206
|
|
31
5
|
}
|
package/cache/ip/white.json
CHANGED
package/index.js
CHANGED
|
@@ -17,10 +17,16 @@ class Ip {
|
|
|
17
17
|
auto_black_enable: true, // 启用自动高频拉黑
|
|
18
18
|
auto_black_threshold: 50, // 自动拉黑阈值(每分钟请求数)
|
|
19
19
|
auto_black_duration: 3600000, // 自动拉黑持续时间(1小时)
|
|
20
|
+
auto_black_grace_count: 3, // 自动拉黑宽限次数,连续触限达到该次数后才拉黑
|
|
20
21
|
violation_enable: true, // 启用违规记录
|
|
21
22
|
violation_max_count: 10, // 最大违规次数
|
|
22
23
|
violation_reset_time: 86400000, // 违规记录重置时间(24小时)
|
|
23
24
|
violation_auto_black: true, // 违规次数超限自动拉黑
|
|
25
|
+
region_check_enable: true, // 启用国外IP检测
|
|
26
|
+
region_checker: null, // IP地区检测函数 (ip) => { country: 'CN' },支持同步或异步
|
|
27
|
+
region_cache_time: 86400000, // 地区检测结果缓存时间(24小时)
|
|
28
|
+
os_black_enable: false, // 启用系统级拉黑联动(需管理员权限,默认关闭避免误拦截)
|
|
29
|
+
os_rule_prefix: 'mm_ip_black_', // 系统防火墙规则名前缀
|
|
24
30
|
dir: './cache/ip' // 持久化存储目录
|
|
25
31
|
}
|
|
26
32
|
/**
|
|
@@ -34,12 +40,19 @@ class Ip {
|
|
|
34
40
|
this._black = new Set();
|
|
35
41
|
this._req_count = new Map();
|
|
36
42
|
this._req_time = new Map();
|
|
43
|
+
this._req_start = new Map(); // 请求窗口开始时间
|
|
37
44
|
this._black_time = new Map(); // 记录拉黑时间
|
|
38
45
|
this._violation_count = new Map(); // 违规次数记录
|
|
39
|
-
this._violation_time = new Map(); //
|
|
46
|
+
this._violation_time = new Map(); // 违规时间记录(首次违规时间)
|
|
47
|
+
this._violation_flagged = new Map(); // 同一请求窗口违规标记
|
|
48
|
+
this._region_cache = new Map(); // IP地区检测结果缓存
|
|
40
49
|
this._logger = $.log || console;
|
|
41
50
|
|
|
42
51
|
this.setConfig(config);
|
|
52
|
+
// 默认使用ip2region作为地区检测器
|
|
53
|
+
if (!this._config.region_checker && this._config.region_check_enable) {
|
|
54
|
+
this._initIp2Region();
|
|
55
|
+
}
|
|
43
56
|
// 自动加载持久化数据
|
|
44
57
|
if (this._config.dir) {
|
|
45
58
|
this.load();
|
|
@@ -118,6 +131,11 @@ Ip.prototype.addBlack = function(ip) {
|
|
|
118
131
|
this._black.add(ip);
|
|
119
132
|
this.log('info', '添加黑名单IP:', ip);
|
|
120
133
|
|
|
134
|
+
// 系统级联动拉黑
|
|
135
|
+
if (this._config.os_black_enable) {
|
|
136
|
+
this.osBlack(ip);
|
|
137
|
+
}
|
|
138
|
+
|
|
121
139
|
// 自动保存到持久化存储
|
|
122
140
|
if (this._config.dir) {
|
|
123
141
|
this.save();
|
|
@@ -160,6 +178,11 @@ Ip.prototype.delBlack = function(ip) {
|
|
|
160
178
|
if (result) {
|
|
161
179
|
this.log('info', '删除黑名单IP:', ip);
|
|
162
180
|
|
|
181
|
+
// 系统级联动释放
|
|
182
|
+
if (this._config.os_black_enable) {
|
|
183
|
+
this.osUnblack(ip);
|
|
184
|
+
}
|
|
185
|
+
|
|
163
186
|
// 自动保存到持久化存储
|
|
164
187
|
if (this._config.dir) {
|
|
165
188
|
this.save();
|
|
@@ -214,30 +237,50 @@ Ip.prototype.record = function(ip) {
|
|
|
214
237
|
}
|
|
215
238
|
}
|
|
216
239
|
|
|
240
|
+
// 内网IP豁免:不参与限流、违规与地区检测,避免误拉黑内网访问
|
|
241
|
+
if (this._isPrivate(ip)) {
|
|
242
|
+
this._updateReqCount(ip);
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
|
|
217
246
|
// 白名单直接通过
|
|
218
247
|
if (this.isWhite(ip)) {
|
|
219
248
|
this._updateReqCount(ip);
|
|
220
249
|
return true;
|
|
221
250
|
}
|
|
222
251
|
|
|
252
|
+
// 国外IP检测:检测到国外IP自动拉黑
|
|
253
|
+
if (this._config.region_check_enable && this._checkForeign(ip)) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
|
|
223
257
|
// 检查频率限制
|
|
224
258
|
if (!this._checkRateLimit(ip)) {
|
|
225
259
|
this.log('warn', 'IP请求频率超限:', ip);
|
|
260
|
+
const rate = this.getReqRate(ip);
|
|
226
261
|
|
|
227
|
-
//
|
|
262
|
+
// 同一请求窗口只记一次违规,避免连续请求导致违规次数暴涨
|
|
228
263
|
if (this._config.violation_enable) {
|
|
229
|
-
this.
|
|
264
|
+
const flag_time = this._violation_flagged.get(ip);
|
|
265
|
+
const win_start = this._req_start.get(ip);
|
|
266
|
+
if (flag_time !== win_start) {
|
|
267
|
+
this.recordViolation(ip, '请求频率超限');
|
|
268
|
+
this._violation_flagged.set(ip, win_start);
|
|
269
|
+
}
|
|
230
270
|
}
|
|
231
271
|
|
|
232
|
-
//
|
|
233
|
-
if (this._config.auto_black_enable
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
272
|
+
// 达到宽限次数且频率超阈值才自动拉黑
|
|
273
|
+
if (this._config.auto_black_enable
|
|
274
|
+
&& rate >= this._config.auto_black_threshold
|
|
275
|
+
&& this.getViolationCount(ip) >= this._config.auto_black_grace_count) {
|
|
276
|
+
this._autoBlack(ip);
|
|
277
|
+
return false;
|
|
239
278
|
}
|
|
240
279
|
|
|
280
|
+
// 重置计数窗口(软限流),下一次请求重新计数
|
|
281
|
+
this._req_start.set(ip, Date.now());
|
|
282
|
+
this._req_count.set(ip, 0);
|
|
283
|
+
|
|
241
284
|
return false;
|
|
242
285
|
}
|
|
243
286
|
|
|
@@ -266,17 +309,16 @@ Ip.prototype.getReqRate = function(ip) {
|
|
|
266
309
|
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
267
310
|
|
|
268
311
|
const now = Date.now();
|
|
269
|
-
const
|
|
312
|
+
const start = this._req_start.get(ip) || now;
|
|
270
313
|
const count = this._req_count.get(ip) || 0;
|
|
271
314
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const elapsed = Math.max(1, now - lastTime); // 避免除零
|
|
275
|
-
return Math.round((count / elapsed) * this._config.check_interval);
|
|
276
|
-
} else {
|
|
277
|
-
// 超过检查间隔,频率为0
|
|
315
|
+
// 当前窗口已过期,频率为0
|
|
316
|
+
if (now - start >= this._config.check_interval) {
|
|
278
317
|
return 0;
|
|
279
318
|
}
|
|
319
|
+
|
|
320
|
+
// 按窗口换算为每分钟请求数,不做时间外推
|
|
321
|
+
return Math.round((count / this._config.check_interval) * 60000);
|
|
280
322
|
};
|
|
281
323
|
|
|
282
324
|
/**
|
|
@@ -361,6 +403,7 @@ Ip.prototype.clearBlack = function() {
|
|
|
361
403
|
Ip.prototype.clearReq = function() {
|
|
362
404
|
this._req_count.clear();
|
|
363
405
|
this._req_time.clear();
|
|
406
|
+
this._req_start.clear();
|
|
364
407
|
this.log('info', '清空请求记录');
|
|
365
408
|
};
|
|
366
409
|
|
|
@@ -372,20 +415,17 @@ Ip.prototype.clearReq = function() {
|
|
|
372
415
|
*/
|
|
373
416
|
Ip.prototype._checkRateLimit = function(ip) {
|
|
374
417
|
const now = Date.now();
|
|
375
|
-
|
|
418
|
+
let start = this._req_start.get(ip);
|
|
376
419
|
|
|
377
|
-
//
|
|
378
|
-
if (now -
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
return false;
|
|
382
|
-
}
|
|
383
|
-
} else {
|
|
384
|
-
// 重置计数
|
|
420
|
+
// 无窗口或窗口已过期,开启新窗口并重置计数
|
|
421
|
+
if (start === undefined || now - start >= this._config.check_interval) {
|
|
422
|
+
start = now;
|
|
423
|
+
this._req_start.set(ip, start);
|
|
385
424
|
this._req_count.set(ip, 0);
|
|
386
425
|
}
|
|
387
426
|
|
|
388
|
-
|
|
427
|
+
const count = this._req_count.get(ip) || 0;
|
|
428
|
+
return count < this._config.max_req_per_min;
|
|
389
429
|
};
|
|
390
430
|
|
|
391
431
|
/**
|
|
@@ -394,6 +434,10 @@ Ip.prototype._checkRateLimit = function(ip) {
|
|
|
394
434
|
* @param {string} ip - IP地址
|
|
395
435
|
*/
|
|
396
436
|
Ip.prototype._updateReqCount = function(ip) {
|
|
437
|
+
if (this._req_start.get(ip) === undefined) {
|
|
438
|
+
this._req_start.set(ip, Date.now());
|
|
439
|
+
}
|
|
440
|
+
|
|
397
441
|
const count = this.getReqCount(ip) + 1;
|
|
398
442
|
this._req_count.set(ip, count);
|
|
399
443
|
this._req_time.set(ip, Date.now());
|
|
@@ -410,6 +454,28 @@ Ip.prototype._validIp = function(ip) {
|
|
|
410
454
|
return ipRegex.test(ip);
|
|
411
455
|
};
|
|
412
456
|
|
|
457
|
+
/**
|
|
458
|
+
* 判断IP是否为内网/回环地址(私有方法)
|
|
459
|
+
* @private
|
|
460
|
+
* @param {string} ip - IP地址
|
|
461
|
+
* @returns {boolean} 是否为内网IP
|
|
462
|
+
*/
|
|
463
|
+
Ip.prototype._isPrivate = function(ip) {
|
|
464
|
+
const parts = ip.split('.');
|
|
465
|
+
if (parts.length !== 4) return false;
|
|
466
|
+
|
|
467
|
+
const first = parseInt(parts[0], 10);
|
|
468
|
+
const second = parseInt(parts[1], 10);
|
|
469
|
+
|
|
470
|
+
// 回环 127.0.0.0/8、A类 10.0.0.0/8、B类 172.16.0.0/12、C类 192.168.0.0/16
|
|
471
|
+
if (first === 127) return true;
|
|
472
|
+
if (first === 10) return true;
|
|
473
|
+
if (first === 172 && second >= 16 && second <= 31) return true;
|
|
474
|
+
if (first === 192 && second === 168) return true;
|
|
475
|
+
|
|
476
|
+
return false;
|
|
477
|
+
};
|
|
478
|
+
|
|
413
479
|
/**
|
|
414
480
|
* 批量添加IP到白名单
|
|
415
481
|
* @param {Array} ip_list - IP地址数组
|
|
@@ -558,8 +624,11 @@ Ip.prototype.recordViolation = function(ip, reason) {
|
|
|
558
624
|
const now = Date.now();
|
|
559
625
|
const count = this.getViolationCount(ip) + 1;
|
|
560
626
|
|
|
627
|
+
// 记录首次违规时间,用于违规窗口重置
|
|
628
|
+
if (!this._violation_time.has(ip)) {
|
|
629
|
+
this._violation_time.set(ip, now);
|
|
630
|
+
}
|
|
561
631
|
this._violation_count.set(ip, count);
|
|
562
|
-
this._violation_time.set(ip, now);
|
|
563
632
|
|
|
564
633
|
// 检查是否达到自动拉黑条件
|
|
565
634
|
if (this._config.violation_auto_black && count >= this._config.violation_max_count) {
|
|
@@ -622,7 +691,7 @@ Ip.prototype.getViolationInfo = function(ip) {
|
|
|
622
691
|
|
|
623
692
|
return {
|
|
624
693
|
count: count,
|
|
625
|
-
time: this._violation_time.get(ip)
|
|
694
|
+
time: this._violation_time.get(ip) // 首次违规时间
|
|
626
695
|
};
|
|
627
696
|
};
|
|
628
697
|
|
|
@@ -842,6 +911,349 @@ Ip.prototype.stopAutoSave = function() {
|
|
|
842
911
|
}
|
|
843
912
|
};
|
|
844
913
|
|
|
914
|
+
/**
|
|
915
|
+
* 设置IP地区检测函数
|
|
916
|
+
* @param {Function} fn - 地区检测函数,入参为IP地址,返回地区信息对象或国家代码
|
|
917
|
+
* @throws {TypeError} 当fn非函数时
|
|
918
|
+
*/
|
|
919
|
+
Ip.prototype.setRegionChecker = function(fn) {
|
|
920
|
+
if (typeof fn !== 'function') throw new TypeError('fn must be function');
|
|
921
|
+
|
|
922
|
+
this._config.region_checker = fn;
|
|
923
|
+
this._config.region_check_enable = true;
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* 初始化ip2region地区检测器(私有方法)
|
|
928
|
+
* @private
|
|
929
|
+
*/
|
|
930
|
+
Ip.prototype._initIp2Region = function() {
|
|
931
|
+
try {
|
|
932
|
+
const IP2Region = require('ip2region').default;
|
|
933
|
+
const searcher = new IP2Region();
|
|
934
|
+
|
|
935
|
+
this._ip2region = searcher;
|
|
936
|
+
this._config.region_checker = function(ip) {
|
|
937
|
+
const result = searcher.search(ip);
|
|
938
|
+
// 内网IP或查询失败时country为空,不返回地区信息
|
|
939
|
+
if (!result || !result.country) return null;
|
|
940
|
+
return { country: result.country };
|
|
941
|
+
};
|
|
942
|
+
this.log('info', '已加载ip2region地区检测器');
|
|
943
|
+
} catch (err) {
|
|
944
|
+
this.log('warn', 'ip2region加载失败,地区检测不可用:', err.message);
|
|
945
|
+
this._config.region_check_enable = false;
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* 获取IP地区信息(带缓存)
|
|
951
|
+
* @param {string} ip - IP地址
|
|
952
|
+
* @returns {Object|null|Promise} 地区信息对象,无检测函数或检测失败返回null,异步检测返回Promise
|
|
953
|
+
* @throws {TypeError} 当IP非字符串时
|
|
954
|
+
*/
|
|
955
|
+
Ip.prototype.getRegion = function(ip) {
|
|
956
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
957
|
+
if (!this._config.region_checker) return null;
|
|
958
|
+
|
|
959
|
+
// 优先读取缓存
|
|
960
|
+
const cached = this._region_cache.get(ip);
|
|
961
|
+
if (cached && Date.now() - cached.time < this._config.region_cache_time) {
|
|
962
|
+
return cached.region;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
try {
|
|
966
|
+
const result = this._config.region_checker(ip);
|
|
967
|
+
|
|
968
|
+
// 异步检测函数:缓存结果后返回
|
|
969
|
+
if (result && typeof result.then === 'function') {
|
|
970
|
+
return result.then(region => {
|
|
971
|
+
if (region) {
|
|
972
|
+
this._region_cache.set(ip, { region, time: Date.now() });
|
|
973
|
+
}
|
|
974
|
+
return region;
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
if (result) {
|
|
979
|
+
this._region_cache.set(ip, { region: result, time: Date.now() });
|
|
980
|
+
}
|
|
981
|
+
return result || null;
|
|
982
|
+
} catch (err) {
|
|
983
|
+
this.log('error', 'IP地区检测失败:', err.message);
|
|
984
|
+
return null;
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* 判断IP是否为国外IP
|
|
990
|
+
* @param {string} ip - IP地址
|
|
991
|
+
* @returns {boolean} 是否为国外IP
|
|
992
|
+
* @throws {TypeError} 当IP非字符串时
|
|
993
|
+
*/
|
|
994
|
+
Ip.prototype.isForeign = function(ip) {
|
|
995
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
996
|
+
|
|
997
|
+
const region = this.getRegion(ip);
|
|
998
|
+
if (!region || typeof region.then === 'function') return false;
|
|
999
|
+
|
|
1000
|
+
return this._isForeign(region);
|
|
1001
|
+
};
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* 检测IP地区,国外IP自动加入黑名单(永久)
|
|
1005
|
+
* @param {string} ip - IP地址
|
|
1006
|
+
* @returns {Object|null|Promise} 检测结果对象,检测失败返回null,异步检测返回Promise
|
|
1007
|
+
* @throws {TypeError} 当IP非字符串时
|
|
1008
|
+
*/
|
|
1009
|
+
Ip.prototype.checkRegion = function(ip) {
|
|
1010
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
1011
|
+
|
|
1012
|
+
const region = this.getRegion(ip);
|
|
1013
|
+
if (region && typeof region.then === 'function') {
|
|
1014
|
+
return region.then(region_info => this._handleRegionCheck(ip, region_info));
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
return this._handleRegionCheck(ip, region);
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* 清空IP地区检测结果缓存
|
|
1022
|
+
*/
|
|
1023
|
+
Ip.prototype.clearRegionCache = function() {
|
|
1024
|
+
this._region_cache.clear();
|
|
1025
|
+
this.log('info', '清空IP地区缓存');
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* 判断地区信息是否为国外(私有方法)
|
|
1030
|
+
* @private
|
|
1031
|
+
* @param {Object|string} region - 地区信息对象或国家代码
|
|
1032
|
+
* @returns {boolean} 是否为国外
|
|
1033
|
+
*/
|
|
1034
|
+
Ip.prototype._isForeign = function(region) {
|
|
1035
|
+
if (!region) return false;
|
|
1036
|
+
|
|
1037
|
+
let country = '';
|
|
1038
|
+
if (typeof region === 'string') {
|
|
1039
|
+
country = region;
|
|
1040
|
+
} else if (typeof region === 'object') {
|
|
1041
|
+
// 支持直接指定国外标识
|
|
1042
|
+
if (typeof region.is_foreign === 'boolean') {
|
|
1043
|
+
return region.is_foreign;
|
|
1044
|
+
}
|
|
1045
|
+
country = region.country || region.country_code || '';
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
if (!country) return false;
|
|
1049
|
+
|
|
1050
|
+
const cn_list = ['cn', 'chn', '中国', '中国大陆', 'china'];
|
|
1051
|
+
return !cn_list.includes(country.toLowerCase());
|
|
1052
|
+
};
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* 检测国外IP并自动拉黑(私有方法)
|
|
1056
|
+
* @private
|
|
1057
|
+
* @param {string} ip - IP地址
|
|
1058
|
+
* @returns {boolean} 是否已拉黑国外IP
|
|
1059
|
+
*/
|
|
1060
|
+
Ip.prototype._checkForeign = function(ip) {
|
|
1061
|
+
if (!this._config.region_checker) return false;
|
|
1062
|
+
if (this._isPrivate(ip)) return false;
|
|
1063
|
+
|
|
1064
|
+
const region = this.getRegion(ip);
|
|
1065
|
+
// 异步检测函数无法同步拉黑,跳过自动处理
|
|
1066
|
+
if (!region || typeof region.then === 'function') return false;
|
|
1067
|
+
|
|
1068
|
+
if (this._isForeign(region)) {
|
|
1069
|
+
if (this.addBlack(ip)) {
|
|
1070
|
+
// 国外IP拉黑带过期时间,避免永久封禁
|
|
1071
|
+
this._black_time.set(ip, Date.now() + this._config.auto_black_duration);
|
|
1072
|
+
}
|
|
1073
|
+
this.log('warn', '检测到国外IP,自动拉黑:', ip);
|
|
1074
|
+
return true;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
return false;
|
|
1078
|
+
};
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* 处理地区检测结果并拉黑国外IP(私有方法)
|
|
1082
|
+
* @private
|
|
1083
|
+
* @param {string} ip - IP地址
|
|
1084
|
+
* @param {Object} region - 地区信息
|
|
1085
|
+
* @returns {Object|null} 检测结果对象
|
|
1086
|
+
*/
|
|
1087
|
+
Ip.prototype._handleRegionCheck = function(ip, region) {
|
|
1088
|
+
if (!region) return null;
|
|
1089
|
+
if (this._isPrivate(ip)) return null;
|
|
1090
|
+
|
|
1091
|
+
const is_foreign = this._isForeign(region);
|
|
1092
|
+
let blacklisted = false;
|
|
1093
|
+
|
|
1094
|
+
if (is_foreign) {
|
|
1095
|
+
blacklisted = this.addBlack(ip);
|
|
1096
|
+
if (blacklisted) {
|
|
1097
|
+
// 国外IP拉黑带过期时间,避免永久封禁
|
|
1098
|
+
this._black_time.set(ip, Date.now() + this._config.auto_black_duration);
|
|
1099
|
+
}
|
|
1100
|
+
this.log('warn', '检测到国外IP,加入黑名单:', ip);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
return {
|
|
1104
|
+
ip: ip,
|
|
1105
|
+
region: region,
|
|
1106
|
+
is_foreign: is_foreign,
|
|
1107
|
+
blacklisted: blacklisted
|
|
1108
|
+
};
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* 在操作系统层拉黑IP(入站拦截)
|
|
1113
|
+
* @param {string} ip - IP地址
|
|
1114
|
+
* @returns {boolean} 是否拉黑成功
|
|
1115
|
+
* @throws {TypeError} 当IP非字符串时
|
|
1116
|
+
*/
|
|
1117
|
+
Ip.prototype.osBlack = function(ip) {
|
|
1118
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
1119
|
+
if (!this._validIp(ip)) return false;
|
|
1120
|
+
|
|
1121
|
+
const cmd = this._getOsCmd(ip, 'black');
|
|
1122
|
+
if (!cmd) return false;
|
|
1123
|
+
|
|
1124
|
+
const result = this._execOsCmd(cmd);
|
|
1125
|
+
if (result.success) {
|
|
1126
|
+
this.log('info', '系统层拉黑IP:', ip);
|
|
1127
|
+
return true;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
this.log('error', '系统层拉黑IP失败:', ip, result.output);
|
|
1131
|
+
return false;
|
|
1132
|
+
};
|
|
1133
|
+
|
|
1134
|
+
/**
|
|
1135
|
+
* 在操作系统层释放IP(解除入站拦截)
|
|
1136
|
+
* @param {string} ip - IP地址
|
|
1137
|
+
* @returns {boolean} 是否释放成功
|
|
1138
|
+
* @throws {TypeError} 当IP非字符串时
|
|
1139
|
+
*/
|
|
1140
|
+
Ip.prototype.osUnblack = function(ip) {
|
|
1141
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
1142
|
+
if (!this._validIp(ip)) return false;
|
|
1143
|
+
|
|
1144
|
+
const cmd = this._getOsCmd(ip, 'unblack');
|
|
1145
|
+
if (!cmd) return false;
|
|
1146
|
+
|
|
1147
|
+
const result = this._execOsCmd(cmd);
|
|
1148
|
+
if (result.success) {
|
|
1149
|
+
this.log('info', '系统层释放IP:', ip);
|
|
1150
|
+
return true;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
this.log('error', '系统层释放IP失败:', ip, result.output);
|
|
1154
|
+
return false;
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* 检查IP是否已在操作系统层拉黑
|
|
1159
|
+
* @param {string} ip - IP地址
|
|
1160
|
+
* @returns {boolean} 是否已系统拉黑
|
|
1161
|
+
* @throws {TypeError} 当IP非字符串时
|
|
1162
|
+
*/
|
|
1163
|
+
Ip.prototype.isOsBlack = function(ip) {
|
|
1164
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
1165
|
+
if (!this._validIp(ip)) return false;
|
|
1166
|
+
|
|
1167
|
+
const cmd = this._getOsCmd(ip, 'check');
|
|
1168
|
+
if (!cmd) return false;
|
|
1169
|
+
|
|
1170
|
+
const result = this._execOsCmd(cmd);
|
|
1171
|
+
if (!result.success) return false;
|
|
1172
|
+
|
|
1173
|
+
return result.output.includes(this._config.os_rule_prefix + ip)
|
|
1174
|
+
|| result.output.includes(ip);
|
|
1175
|
+
};
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* 生成系统拉黑命令(私有方法)
|
|
1179
|
+
* @private
|
|
1180
|
+
* @param {string} ip - IP地址
|
|
1181
|
+
* @param {string} action - 操作类型(black, unblack, check)
|
|
1182
|
+
* @returns {string|null} 系统命令,不支持的平台返回null
|
|
1183
|
+
*/
|
|
1184
|
+
Ip.prototype._getOsCmd = function(ip, action) {
|
|
1185
|
+
if (process.platform === 'win32') {
|
|
1186
|
+
return this._getWinCmd(ip, action);
|
|
1187
|
+
}
|
|
1188
|
+
if (process.platform === 'linux') {
|
|
1189
|
+
return this._getLinuxCmd(ip, action);
|
|
1190
|
+
}
|
|
1191
|
+
return null;
|
|
1192
|
+
};
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* 生成Windows防火墙命令(私有方法)
|
|
1196
|
+
* @private
|
|
1197
|
+
* @param {string} ip - IP地址
|
|
1198
|
+
* @param {string} action - 操作类型(black, unblack, check)
|
|
1199
|
+
* @returns {string|null} 系统命令
|
|
1200
|
+
*/
|
|
1201
|
+
Ip.prototype._getWinCmd = function(ip, action) {
|
|
1202
|
+
const rule = this._config.os_rule_prefix + ip;
|
|
1203
|
+
|
|
1204
|
+
if (action === 'black') {
|
|
1205
|
+
return `netsh advfirewall firewall add rule name="${rule}" dir=in action=block remoteip=${ip}`;
|
|
1206
|
+
}
|
|
1207
|
+
if (action === 'unblack') {
|
|
1208
|
+
return `netsh advfirewall firewall delete rule name="${rule}"`;
|
|
1209
|
+
}
|
|
1210
|
+
if (action === 'check') {
|
|
1211
|
+
return `netsh advfirewall firewall show rule name="${rule}"`;
|
|
1212
|
+
}
|
|
1213
|
+
return null;
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
/**
|
|
1217
|
+
* 生成Linux iptables命令(私有方法)
|
|
1218
|
+
* @private
|
|
1219
|
+
* @param {string} ip - IP地址
|
|
1220
|
+
* @param {string} action - 操作类型(black, unblack, check)
|
|
1221
|
+
* @returns {string|null} 系统命令
|
|
1222
|
+
*/
|
|
1223
|
+
Ip.prototype._getLinuxCmd = function(ip, action) {
|
|
1224
|
+
if (action === 'black') {
|
|
1225
|
+
return `iptables -I INPUT -s ${ip} -j DROP`;
|
|
1226
|
+
}
|
|
1227
|
+
if (action === 'unblack') {
|
|
1228
|
+
return `iptables -D INPUT -s ${ip} -j DROP`;
|
|
1229
|
+
}
|
|
1230
|
+
if (action === 'check') {
|
|
1231
|
+
return `iptables -L INPUT -n`;
|
|
1232
|
+
}
|
|
1233
|
+
return null;
|
|
1234
|
+
};
|
|
1235
|
+
|
|
1236
|
+
/**
|
|
1237
|
+
* 执行系统命令(私有方法)
|
|
1238
|
+
* @private
|
|
1239
|
+
* @param {string} cmd - 系统命令
|
|
1240
|
+
* @returns {Object} 执行结果 { success, output }
|
|
1241
|
+
*/
|
|
1242
|
+
Ip.prototype._execOsCmd = function(cmd) {
|
|
1243
|
+
try {
|
|
1244
|
+
const { execSync } = require('child_process');
|
|
1245
|
+
const output = execSync(cmd, {
|
|
1246
|
+
encoding: 'utf8',
|
|
1247
|
+
timeout: 8000,
|
|
1248
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
1249
|
+
});
|
|
1250
|
+
return { success: true, output: output || '' };
|
|
1251
|
+
} catch (err) {
|
|
1252
|
+
const output = (err.stdout && err.stdout.toString()) || err.message || '';
|
|
1253
|
+
return { success: false, output: output };
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
|
|
845
1257
|
/**
|
|
846
1258
|
* 删除IP记录(从黑名单、白名单、临时记录中删除)
|
|
847
1259
|
* @param {string} ip - IP地址
|
|
@@ -864,6 +1276,11 @@ Ip.prototype.del = function(ip) {
|
|
|
864
1276
|
if (this._black.has(ip)) {
|
|
865
1277
|
this._black.delete(ip);
|
|
866
1278
|
result.black = true;
|
|
1279
|
+
|
|
1280
|
+
// 系统级联动释放
|
|
1281
|
+
if (this._config.os_black_enable) {
|
|
1282
|
+
this.osUnblack(ip);
|
|
1283
|
+
}
|
|
867
1284
|
}
|
|
868
1285
|
|
|
869
1286
|
// 从白名单删除
|
|
@@ -883,12 +1300,19 @@ Ip.prototype.del = function(ip) {
|
|
|
883
1300
|
result.violation_time = true;
|
|
884
1301
|
}
|
|
885
1302
|
|
|
1303
|
+
// 清理请求窗口与违规标记
|
|
1304
|
+
this._req_start.delete(ip);
|
|
1305
|
+
this._violation_flagged.delete(ip);
|
|
1306
|
+
|
|
886
1307
|
// 从黑名单过期时间记录删除
|
|
887
1308
|
if (this._black_time.has(ip)) {
|
|
888
1309
|
this._black_time.delete(ip);
|
|
889
1310
|
result.black_time = true;
|
|
890
1311
|
}
|
|
891
1312
|
|
|
1313
|
+
// 从地区缓存删除
|
|
1314
|
+
this._region_cache.delete(ip);
|
|
1315
|
+
|
|
892
1316
|
// 如果有任何删除操作,记录日志并自动保存
|
|
893
1317
|
if (Object.values(result).some(Boolean)) {
|
|
894
1318
|
// 自动保存到持久化存储
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mm_ip",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "用于管理IP名单,可设置白名单、黑名单和IP检查,查询IP请求次数、频率等。",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"author": "qww",
|
|
22
22
|
"license": "ISC",
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"
|
|
24
|
+
"ip2region": "^2.3.0",
|
|
25
|
+
"mm_expand": "^2.5.4"
|
|
25
26
|
}
|
|
26
27
|
}
|
package/test.js
CHANGED
|
@@ -16,7 +16,8 @@ const ip_manager = new Ip({
|
|
|
16
16
|
violation_enable: true, // 启用违规记录
|
|
17
17
|
violation_max_count: 3, // 降低阈值便于测试
|
|
18
18
|
violation_reset_time: 10000, // 10秒便于测试
|
|
19
|
-
violation_auto_black: true // 违规次数超限自动拉黑
|
|
19
|
+
violation_auto_black: true, // 违规次数超限自动拉黑
|
|
20
|
+
os_black_enable: false // 测试环境禁用系统级联动
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
|
|
@@ -35,6 +36,91 @@ console.log('添加 10.0.0.1:', ip_manager.addBlack('10.0.0.1'));
|
|
|
35
36
|
console.log('添加 10.0.0.2:', ip_manager.addBlack('10.0.0.2'));
|
|
36
37
|
console.log('黑名单列表:', ip_manager.getAllBlack());
|
|
37
38
|
|
|
39
|
+
// 测试2.5: 国外IP检测功能
|
|
40
|
+
console.log('\n2.5 测试国外IP检测功能:');
|
|
41
|
+
|
|
42
|
+
// 创建独立实例,避免影响其他测试
|
|
43
|
+
const region_ip = new Ip({ dir: null, os_black_enable: false });
|
|
44
|
+
|
|
45
|
+
// 模拟地区检测函数
|
|
46
|
+
region_ip.setRegionChecker(function(ip) {
|
|
47
|
+
const region_map = {
|
|
48
|
+
'8.8.8.8': { country: 'US' }, // 国外
|
|
49
|
+
'1.1.1.1': 'US', // 国外(字符串国家代码)
|
|
50
|
+
'114.114.114.114': { country: 'CN' } // 国内
|
|
51
|
+
};
|
|
52
|
+
return region_map[ip] || null;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// 获取地区信息
|
|
56
|
+
console.log('获取 8.8.8.8 地区:', region_ip.getRegion('8.8.8.8'));
|
|
57
|
+
console.log('获取 114.114.114.114 地区:', region_ip.getRegion('114.114.114.114'));
|
|
58
|
+
|
|
59
|
+
// 判断国外
|
|
60
|
+
console.log('8.8.8.8 是否国外:', region_ip.isForeign('8.8.8.8'));
|
|
61
|
+
console.log('114.114.114.114 是否国外:', region_ip.isForeign('114.114.114.114'));
|
|
62
|
+
|
|
63
|
+
// checkRegion 检测并拉黑国外IP
|
|
64
|
+
console.log('检测 8.8.8.8 地区:', region_ip.checkRegion('8.8.8.8'));
|
|
65
|
+
console.log('8.8.8.8 是否在黑名单:', region_ip.isBlack('8.8.8.8'));
|
|
66
|
+
console.log('检测 114.114.114.114 地区:', region_ip.checkRegion('114.114.114.114'));
|
|
67
|
+
console.log('114.114.114.114 是否在黑名单:', region_ip.isBlack('114.114.114.114'));
|
|
68
|
+
|
|
69
|
+
// record 自动检测国外IP并拉黑
|
|
70
|
+
console.log('记录 1.1.1.1 请求:', region_ip.record('1.1.1.1'));
|
|
71
|
+
console.log('1.1.1.1 是否在黑名单:', region_ip.isBlack('1.1.1.1'));
|
|
72
|
+
|
|
73
|
+
// 未知地区IP不会被拉黑
|
|
74
|
+
console.log('记录未知IP 172.16.9.9 请求:', region_ip.record('172.16.9.9'));
|
|
75
|
+
console.log('172.16.9.9 是否在黑名单:', region_ip.isBlack('172.16.9.9'));
|
|
76
|
+
|
|
77
|
+
// 清空地区缓存
|
|
78
|
+
region_ip.clearRegionCache();
|
|
79
|
+
console.log('地区缓存已清空');
|
|
80
|
+
|
|
81
|
+
// 测试2.6: ip2region 接入测试
|
|
82
|
+
console.log('\n2.6 测试ip2region接入:');
|
|
83
|
+
|
|
84
|
+
// 使用默认配置创建实例,自动加载ip2region检测器
|
|
85
|
+
const ipr = new Ip({ dir: null, os_black_enable: false });
|
|
86
|
+
console.log('ip2region 检测器已加载:', typeof ipr.getRegion('8.8.8.8') === 'object');
|
|
87
|
+
|
|
88
|
+
// 国外IP自动拉黑
|
|
89
|
+
console.log('8.8.8.8 地区:', ipr.getRegion('8.8.8.8'));
|
|
90
|
+
console.log('8.8.8.8 是否国外:', ipr.isForeign('8.8.8.8'));
|
|
91
|
+
console.log('记录 8.8.8.8 请求:', ipr.record('8.8.8.8'));
|
|
92
|
+
console.log('8.8.8.8 是否在黑名单:', ipr.isBlack('8.8.8.8'));
|
|
93
|
+
|
|
94
|
+
// 国内IP正常放行
|
|
95
|
+
console.log('记录 114.114.114.114 请求:', ipr.record('114.114.114.114'));
|
|
96
|
+
console.log('114.114.114.114 是否在黑名单:', ipr.isBlack('114.114.114.114'));
|
|
97
|
+
|
|
98
|
+
// 内网IP不会被拉黑
|
|
99
|
+
console.log('记录 192.168.1.1 请求:', ipr.record('192.168.1.1'));
|
|
100
|
+
console.log('192.168.1.1 是否在黑名单:', ipr.isBlack('192.168.1.1'));
|
|
101
|
+
|
|
102
|
+
// 测试2.7: 系统级拉黑功能
|
|
103
|
+
console.log('\n2.7 测试系统级拉黑功能:');
|
|
104
|
+
const os_ip = new Ip({ dir: null });
|
|
105
|
+
|
|
106
|
+
// 无效IP不执行系统命令
|
|
107
|
+
console.log('无效IP osBlack:', os_ip.osBlack('not_a_ip'));
|
|
108
|
+
console.log('无效IP isOsBlack:', os_ip.isOsBlack('not_a_ip'));
|
|
109
|
+
|
|
110
|
+
// 系统级拉黑(需管理员权限,无权限时返回false且不崩溃)
|
|
111
|
+
console.log('系统拉黑 1.2.3.4:', os_ip.osBlack('1.2.3.4'));
|
|
112
|
+
console.log('1.2.3.4 是否系统拉黑:', os_ip.isOsBlack('1.2.3.4'));
|
|
113
|
+
|
|
114
|
+
// 系统级释放
|
|
115
|
+
console.log('系统释放 1.2.3.4:', os_ip.osUnblack('1.2.3.4'));
|
|
116
|
+
console.log('释放后是否系统拉黑:', os_ip.isOsBlack('1.2.3.4'));
|
|
117
|
+
|
|
118
|
+
// addBlack 联动系统拉黑
|
|
119
|
+
os_ip.setConfig({ os_black_enable: true });
|
|
120
|
+
console.log('联动addBlack系统拉黑 1.2.3.5:', os_ip.addBlack('1.2.3.5'));
|
|
121
|
+
console.log('1.2.3.5 在应用层黑名单:', os_ip.isBlack('1.2.3.5'));
|
|
122
|
+
console.log('delBlack 联动系统释放:', os_ip.delBlack('1.2.3.5'));
|
|
123
|
+
|
|
38
124
|
// 测试3: 检查IP状态
|
|
39
125
|
console.log('\n3. 测试检查IP状态:');
|
|
40
126
|
console.log('192.168.1.1 在白名单:', ip_manager.isWhite('192.168.1.1'));
|
|
@@ -196,7 +282,8 @@ setTimeout(() => {
|
|
|
196
282
|
const persistent_ip = new Ip({
|
|
197
283
|
dir: './cache/ip',
|
|
198
284
|
max_white: 3,
|
|
199
|
-
max_black: 3
|
|
285
|
+
max_black: 3,
|
|
286
|
+
os_black_enable: false
|
|
200
287
|
});
|
|
201
288
|
|
|
202
289
|
// 添加测试数据
|
|
@@ -246,7 +333,8 @@ setTimeout(() => {
|
|
|
246
333
|
// 创建新实例并加载数据
|
|
247
334
|
console.log('创建新实例并加载数据:');
|
|
248
335
|
const loaded_ip = new Ip({
|
|
249
|
-
dir: './cache/ip'
|
|
336
|
+
dir: './cache/ip',
|
|
337
|
+
os_black_enable: false
|
|
250
338
|
});
|
|
251
339
|
|
|
252
340
|
console.log('加载后数据统计:');
|