@fanboynz/network-scanner 2.0.44 → 2.0.46
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 +128 -2
- package/lib/cdp.js +1 -3
- package/lib/clear_sitedata.js +1 -2
- package/lib/interaction.js +10 -9
- package/lib/openvpn_vpn.js +46 -13
- package/lib/wireguard_vpn.js +20 -3
- package/nwss.js +4 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -152,8 +152,9 @@ Example:
|
|
|
152
152
|
| `even_blocked` | Boolean | `false` | Add matching rules even if requests are blocked |
|
|
153
153
|
| `bypass_cache` | Boolean | `false` | Skip all caching for this site's URLs |
|
|
154
154
|
| `window_cleanup` | Boolean or String | `false` | Close old/unused browser windows/tabs after entire URL group completes |
|
|
155
|
+
| `window_cleanup_threshold` | Integer | `8` | For `"realtime"` mode: max pages to keep open before cleanup |
|
|
155
156
|
|
|
156
|
-
**Window cleanup modes:** `false` (disabled), `true` (conservative - closes obvious leftovers), `"all"` (aggressive - closes all content pages). Both active modes preserve the main Puppeteer window and wait 16 seconds before cleanup to avoid interfering with active operations.
|
|
157
|
+
**Window cleanup modes:** `false` (disabled), `true` (conservative - closes obvious leftovers), `"realtime"` (continuously cleanup oldest pages when threshold exceeded), `"all"` (aggressive - closes all content pages after group). Both active modes preserve the main Puppeteer window and wait 16 seconds before cleanup to avoid interfering with active operations.
|
|
157
158
|
|
|
158
159
|
|
|
159
160
|
### Redirect Handling Options
|
|
@@ -193,6 +194,11 @@ When a page redirects to a new domain, first-party/third-party detection is base
|
|
|
193
194
|
"referrer_headers": {"mode": "custom", "custom": ["https://news.ycombinator.com/"]}
|
|
194
195
|
```
|
|
195
196
|
|
|
197
|
+
**Disable referrer for specific URLs:**
|
|
198
|
+
```json
|
|
199
|
+
"referrer_disable": ["https://example.com/no-ref", "sensitive-site.com"]
|
|
200
|
+
```
|
|
201
|
+
|
|
196
202
|
### Protection Bypassing
|
|
197
203
|
|
|
198
204
|
| Field | Values | Default | Description |
|
|
@@ -244,7 +250,7 @@ When a page redirects to a new domain, first-party/third-party detection is base
|
|
|
244
250
|
|:---------------------|:-------|:-------:|:------------|
|
|
245
251
|
| `goto_options` | Object | `{"waitUntil": "load"}` | Custom page.goto() options |
|
|
246
252
|
| `clear_sitedata` | Boolean | `false` | Clear all cookies, cache, storage before each load |
|
|
247
|
-
| `forcereload` | Boolean | `false` | Force
|
|
253
|
+
| `forcereload` | Boolean or Array | `false` | Force cache-clearing reload for all URLs (`true`) or specific domains (`["domain1.com"]`) |
|
|
248
254
|
| `isBrave` | Boolean | `false` | Spoof Brave browser detection |
|
|
249
255
|
| `evaluateOnNewDocument` | Boolean | `false` | Inject fetch/XHR interceptor in page |
|
|
250
256
|
| `cdp` | Boolean | `false` | Enable CDP logging for this site |
|
|
@@ -260,6 +266,54 @@ When a page redirects to a new domain, first-party/third-party detection is base
|
|
|
260
266
|
| `interact_clicks` | Boolean | `false` | Enable element clicking simulation |
|
|
261
267
|
| `interact_typing` | Boolean | `false` | Enable typing simulation |
|
|
262
268
|
| `interact_intensity` | String | `"medium"` | Interaction simulation intensity: "low", "medium", "high" |
|
|
269
|
+
| `dnsmasq` | Boolean | `false` | Force dnsmasq output for this site |
|
|
270
|
+
| `dnsmasq_old` | Boolean | `false` | Force dnsmasq old format output for this site |
|
|
271
|
+
| `unbound` | Boolean | `false` | Force unbound output for this site |
|
|
272
|
+
| `privoxy` | Boolean | `false` | Force Privoxy output for this site |
|
|
273
|
+
| `pihole` | Boolean | `false` | Force Pi-hole regex output for this site |
|
|
274
|
+
| `ignore_similar` | Boolean | - | Override global `ignore_similar` setting for this site |
|
|
275
|
+
| `ignore_similar_threshold` | Integer | - | Override global similarity threshold for this site |
|
|
276
|
+
| `ignore_similar_ignored_domains` | Boolean | - | Override global `ignore_similar_ignored_domains` for this site |
|
|
277
|
+
|
|
278
|
+
### VPN Options
|
|
279
|
+
|
|
280
|
+
Route traffic through a VPN for specific sites. Requires `sudo` privileges. The VPN connection is established before scanning and torn down after the site completes.
|
|
281
|
+
|
|
282
|
+
> **Note:** VPN modifies system-level routing. During concurrent scanning, all traffic routes through the active tunnel — not just the site that requested it. For isolated per-site VPN, run sites sequentially or use the same VPN config for all concurrent sites.
|
|
283
|
+
|
|
284
|
+
#### WireGuard
|
|
285
|
+
|
|
286
|
+
| Field | Values | Default | Description |
|
|
287
|
+
|:---------------------|:-------|:-------:|:------------|
|
|
288
|
+
| `vpn` | String or Object | - | WireGuard VPN configuration |
|
|
289
|
+
| `vpn.config` | String | - | Path to `.conf` file or interface name in `/etc/wireguard/` |
|
|
290
|
+
| `vpn.config_inline` | String | - | Inline WireGuard config content |
|
|
291
|
+
| `vpn.interface` | String | auto | Interface name (auto-derived from config filename) |
|
|
292
|
+
| `vpn.health_check` | Boolean | `true` | Ping through tunnel to verify connectivity |
|
|
293
|
+
| `vpn.test_host` | String | `"1.1.1.1"` | Host to ping for health check |
|
|
294
|
+
| `vpn.retry` | Boolean | `true` | Retry on connection failure |
|
|
295
|
+
| `vpn.max_retries` | Integer | `2` | Maximum retry attempts |
|
|
296
|
+
|
|
297
|
+
#### OpenVPN
|
|
298
|
+
|
|
299
|
+
| Field | Values | Default | Description |
|
|
300
|
+
|:---------------------|:-------|:-------:|:------------|
|
|
301
|
+
| `openvpn` | String or Object | - | OpenVPN configuration |
|
|
302
|
+
| `openvpn.config` | String | - | Path to `.ovpn` file |
|
|
303
|
+
| `openvpn.config_inline` | String | - | Inline OpenVPN config content |
|
|
304
|
+
| `openvpn.name` | String | auto | Connection name (auto-derived from config filename) |
|
|
305
|
+
| `openvpn.username` | String | - | VPN username (written to secure temp file) |
|
|
306
|
+
| `openvpn.password` | String | - | VPN password (written to secure temp file) |
|
|
307
|
+
| `openvpn.auth_file` | String | - | Path to existing auth credentials file |
|
|
308
|
+
| `openvpn.health_check` | Boolean | `true` | Ping through tunnel to verify connectivity |
|
|
309
|
+
| `openvpn.test_host` | String | `"1.1.1.1"` | Host to ping for health check |
|
|
310
|
+
| `openvpn.retry` | Boolean | `true` | Retry on connection failure |
|
|
311
|
+
| `openvpn.max_retries` | Integer | `2` | Maximum retry attempts |
|
|
312
|
+
| `openvpn.connect_timeout` | Milliseconds | `30000` | Timeout for connection establishment |
|
|
313
|
+
| `openvpn.extra_args` | Array | - | Additional OpenVPN command line arguments |
|
|
314
|
+
| `openvpn.verbosity` | String | `"3"` | OpenVPN log verbosity level |
|
|
315
|
+
|
|
316
|
+
> **Authentication:** If the `.ovpn` file already contains credentials (via `auth-user-pass /path/to/file` or an inline `<auth-user-pass>` block), no additional config is needed — just provide the config path. The `username`/`password` fields are only needed when the `.ovpn` file has a bare `auth-user-pass` directive that expects interactive input.
|
|
263
317
|
|
|
264
318
|
### Global Configuration Options
|
|
265
319
|
|
|
@@ -434,6 +488,41 @@ node nwss.js --max-concurrent 12 --cleanup-interval 300 -o rules.txt
|
|
|
434
488
|
}
|
|
435
489
|
```
|
|
436
490
|
|
|
491
|
+
#### Scanning through OpenVPN
|
|
492
|
+
```json
|
|
493
|
+
{
|
|
494
|
+
"url": "https://geo-restricted-site.com",
|
|
495
|
+
"openvpn": "/etc/openvpn/us-server.ovpn",
|
|
496
|
+
"filterRegex": "tracking|analytics",
|
|
497
|
+
"userAgent": "chrome",
|
|
498
|
+
"fingerprint_protection": "random"
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
#### OpenVPN with Credentials
|
|
503
|
+
```json
|
|
504
|
+
{
|
|
505
|
+
"url": "https://region-locked-site.com",
|
|
506
|
+
"openvpn": {
|
|
507
|
+
"config": "/etc/openvpn/eu-server.ovpn",
|
|
508
|
+
"username": "vpn_user",
|
|
509
|
+
"password": "vpn_pass",
|
|
510
|
+
"connect_timeout": 45000
|
|
511
|
+
},
|
|
512
|
+
"filterRegex": "ads|tracking"
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
#### Scanning through WireGuard
|
|
517
|
+
```json
|
|
518
|
+
{
|
|
519
|
+
"url": "https://another-site.com",
|
|
520
|
+
"vpn": "/etc/wireguard/wg-us.conf",
|
|
521
|
+
"filterRegex": "analytics",
|
|
522
|
+
"userAgent": "firefox"
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
437
526
|
---
|
|
438
527
|
|
|
439
528
|
## Memory Management
|
|
@@ -477,6 +566,39 @@ sudo apt install google-chrome-stable
|
|
|
477
566
|
sudo apt install bind9-dnsutils whois
|
|
478
567
|
```
|
|
479
568
|
|
|
569
|
+
#### OpenVPN (optional, for per-site VPN routing)
|
|
570
|
+
```
|
|
571
|
+
sudo apt install openvpn
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
Grant passwordless sudo for OpenVPN operations:
|
|
575
|
+
```
|
|
576
|
+
sudo visudo -f /etc/sudoers.d/openvpn-nwss
|
|
577
|
+
```
|
|
578
|
+
Add:
|
|
579
|
+
```
|
|
580
|
+
your_username ALL=(root) NOPASSWD: /usr/sbin/openvpn, /usr/bin/kill, /usr/bin/pgrep, /usr/bin/pkill
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
On WSL2, load the TUN module (required each reboot):
|
|
584
|
+
```
|
|
585
|
+
sudo modprobe tun
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
#### WireGuard (optional, for per-site VPN routing)
|
|
589
|
+
```
|
|
590
|
+
sudo apt install wireguard
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
Grant passwordless sudo for WireGuard operations:
|
|
594
|
+
```
|
|
595
|
+
sudo visudo -f /etc/sudoers.d/wg-nwss
|
|
596
|
+
```
|
|
597
|
+
Add:
|
|
598
|
+
```
|
|
599
|
+
your_username ALL=(root) NOPASSWD: /usr/bin/wg-quick, /usr/bin/wg
|
|
600
|
+
```
|
|
601
|
+
|
|
480
602
|
## Notes
|
|
481
603
|
|
|
482
604
|
- If both `firstParty: 0` and `thirdParty: 0` are set for a site, it will be skipped.
|
|
@@ -491,5 +613,9 @@ sudo apt install bind9-dnsutils whois
|
|
|
491
613
|
- For maximum stealth, combine `fingerprint_protection: "random"` with appropriate `referrer_headers` modes
|
|
492
614
|
- User agents are automatically updated to latest versions (Chrome 131, Firefox 133, Safari 18.2)
|
|
493
615
|
- Referrer headers work independently from fingerprint protection - use both for best results
|
|
616
|
+
- VPN connections (`vpn`/`openvpn`) are established before scanning and torn down after the site completes
|
|
617
|
+
- If an `.ovpn` file contains embedded credentials, no additional auth config is needed in the JSON
|
|
618
|
+
- VPN affects system-level routing — all concurrent scans will route through the active tunnel
|
|
619
|
+
- Both `vpn` (WireGuard) and `openvpn` can be set, but `vpn` takes precedence
|
|
494
620
|
|
|
495
621
|
---
|
package/lib/cdp.js
CHANGED
|
@@ -425,7 +425,5 @@ async function createEnhancedCDPSession(page, currentUrl, options = {}) {
|
|
|
425
425
|
module.exports = {
|
|
426
426
|
createCDPSession,
|
|
427
427
|
createPageWithTimeout,
|
|
428
|
-
setRequestInterceptionWithTimeout
|
|
429
|
-
validateCDPConfig,
|
|
430
|
-
createEnhancedCDPSession
|
|
428
|
+
setRequestInterceptionWithTimeout
|
|
431
429
|
};
|
package/lib/clear_sitedata.js
CHANGED
package/lib/interaction.js
CHANGED
|
@@ -373,7 +373,6 @@ async function humanLikeMouseMove(page, fromX, fromY, toX, toY, options = {}) {
|
|
|
373
373
|
|
|
374
374
|
// Add slight curve to movement (more human-like)
|
|
375
375
|
if (curve > 0 && i > 0 && i < actualSteps) {
|
|
376
|
-
const midpoint = actualSteps / 2;
|
|
377
376
|
const curveIntensity = Math.sin((i / actualSteps) * Math.PI) * curve * distance * MOUSE_MOVEMENT.CURVE_INTENSITY_RATIO;
|
|
378
377
|
const perpX = -(toY - fromY) / distance;
|
|
379
378
|
const perpY = (toX - fromX) / distance;
|
|
@@ -449,7 +448,7 @@ async function simulateScrolling(page, options = {}) {
|
|
|
449
448
|
// Smooth scrolling by breaking into smaller increments
|
|
450
449
|
for (let j = 0; j < smoothness; j++) {
|
|
451
450
|
await page.mouse.wheel({ deltaY: scrollDelta / smoothness });
|
|
452
|
-
await
|
|
451
|
+
await fastTimeout(SCROLLING.SMOOTH_INCREMENT_DELAY);
|
|
453
452
|
}
|
|
454
453
|
|
|
455
454
|
if (i < amount - 1) {
|
|
@@ -549,7 +548,7 @@ async function interactWithElements(page, options = {}) {
|
|
|
549
548
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
550
549
|
try {
|
|
551
550
|
// Find visible, clickable elements
|
|
552
|
-
const elements = await page.evaluate((selectors, avoidWords) => {
|
|
551
|
+
const elements = await page.evaluate((selectors, avoidWords, textPreviewLen) => {
|
|
553
552
|
const clickableElements = [];
|
|
554
553
|
|
|
555
554
|
selectors.forEach(selector => {
|
|
@@ -571,7 +570,7 @@ async function interactWithElements(page, options = {}) {
|
|
|
571
570
|
y: rect.top + rect.height / 2,
|
|
572
571
|
width: rect.width,
|
|
573
572
|
height: rect.height,
|
|
574
|
-
text: text.substring(0,
|
|
573
|
+
text: text.substring(0, textPreviewLen)
|
|
575
574
|
});
|
|
576
575
|
}
|
|
577
576
|
}
|
|
@@ -579,7 +578,7 @@ async function interactWithElements(page, options = {}) {
|
|
|
579
578
|
});
|
|
580
579
|
|
|
581
580
|
return clickableElements;
|
|
582
|
-
}, elementTypes, avoidDestructive ? ['delete', 'remove', 'submit', 'buy', 'purchase', 'order'] : []);
|
|
581
|
+
}, elementTypes, avoidDestructive ? ['delete', 'remove', 'submit', 'buy', 'purchase', 'order'] : [], ELEMENT_INTERACTION.TEXT_PREVIEW_LENGTH);
|
|
583
582
|
|
|
584
583
|
if (elements.length > 0) {
|
|
585
584
|
// Choose a random element to interact with
|
|
@@ -590,12 +589,12 @@ async function interactWithElements(page, options = {}) {
|
|
|
590
589
|
await humanLikeMouseMove(page, currentPos.x, currentPos.y, element.x, element.y);
|
|
591
590
|
|
|
592
591
|
// Brief pause before clicking
|
|
593
|
-
await fastTimeout(TIMING.CLICK_PAUSE_MIN + Math.random() * TIMING.CLICK_PAUSE_MAX);
|
|
592
|
+
await fastTimeout(TIMING.CLICK_PAUSE_MIN + Math.random() * (TIMING.CLICK_PAUSE_MAX - TIMING.CLICK_PAUSE_MIN));
|
|
594
593
|
|
|
595
594
|
await page.mouse.click(element.x, element.y);
|
|
596
595
|
|
|
597
596
|
// Brief pause after clicking
|
|
598
|
-
await fastTimeout(TIMING.POST_CLICK_MIN + Math.random() * TIMING.POST_CLICK_MAX);
|
|
597
|
+
await fastTimeout(TIMING.POST_CLICK_MIN + Math.random() * (TIMING.POST_CLICK_MAX - TIMING.POST_CLICK_MIN));
|
|
599
598
|
}
|
|
600
599
|
} catch (elementErr) {
|
|
601
600
|
// Continue to next attempt if this one fails
|
|
@@ -673,9 +672,9 @@ async function simulateTyping(page, text, options = {}) {
|
|
|
673
672
|
if (mistakes && Math.random() < mistakeRate) {
|
|
674
673
|
const wrongChar = String.fromCharCode(97 + Math.floor(Math.random() * 26));
|
|
675
674
|
await page.keyboard.type(wrongChar);
|
|
676
|
-
await fastTimeout(TIMING.MISTAKE_PAUSE_MIN + Math.random() * TIMING.MISTAKE_PAUSE_MAX);
|
|
675
|
+
await fastTimeout(TIMING.MISTAKE_PAUSE_MIN + Math.random() * (TIMING.MISTAKE_PAUSE_MAX - TIMING.MISTAKE_PAUSE_MIN));
|
|
677
676
|
await page.keyboard.press('Backspace');
|
|
678
|
-
await fastTimeout(TIMING.BACKSPACE_DELAY_MIN + Math.random() * TIMING.BACKSPACE_DELAY_MAX);
|
|
677
|
+
await fastTimeout(TIMING.BACKSPACE_DELAY_MIN + Math.random() * (TIMING.BACKSPACE_DELAY_MAX - TIMING.BACKSPACE_DELAY_MIN));
|
|
679
678
|
}
|
|
680
679
|
|
|
681
680
|
await page.keyboard.type(char);
|
|
@@ -818,6 +817,7 @@ async function performPageInteraction(page, currentUrl, options = {}, forceDebug
|
|
|
818
817
|
}
|
|
819
818
|
return;
|
|
820
819
|
}
|
|
820
|
+
await bodyExists.dispose();
|
|
821
821
|
} catch (bodyCheckErr) {
|
|
822
822
|
if (forceDebug) {
|
|
823
823
|
console.log(`[interaction] Page not ready for interaction on ${currentUrl} (waited ${Math.min(Math.max((options.siteTimeout || 20000) / 8, 2000), 5000)}ms): ${bodyCheckErr.message}`);
|
|
@@ -922,6 +922,7 @@ async function performPageInteraction(page, currentUrl, options = {}, forceDebug
|
|
|
922
922
|
const bodyElement = await page.$('body');
|
|
923
923
|
if (bodyElement) {
|
|
924
924
|
await page.hover('body');
|
|
925
|
+
await bodyElement.dispose();
|
|
925
926
|
}
|
|
926
927
|
} catch (hoverErr) {
|
|
927
928
|
// Silently handle hover failures - not critical
|
package/lib/openvpn_vpn.js
CHANGED
|
@@ -13,6 +13,22 @@ const fs = require('fs');
|
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const { formatLogMessage } = require('./colorize');
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Fetch external IP address through the active tunnel
|
|
18
|
+
* @param {string} tunDevice - TUN device name (optional)
|
|
19
|
+
* @returns {string|null} External IP or null
|
|
20
|
+
*/
|
|
21
|
+
function getExternalIP(tunDevice) {
|
|
22
|
+
const services = ['https://api.ipify.org', 'https://ifconfig.me/ip', 'https://icanhazip.com'];
|
|
23
|
+
for (const service of services) {
|
|
24
|
+
try {
|
|
25
|
+
const iface = tunDevice ? `--interface ${tunDevice}` : '';
|
|
26
|
+
return execSync(`curl -s -m 5 ${iface} ${service}`, { encoding: 'utf8', timeout: 8000 }).trim();
|
|
27
|
+
} catch {}
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
16
32
|
// Track active connections: name ? { process, configPath, pid, tunDevice, startedAt, sites }
|
|
17
33
|
const activeConnections = new Map();
|
|
18
34
|
|
|
@@ -107,7 +123,7 @@ function checkTunDevice() {
|
|
|
107
123
|
*/
|
|
108
124
|
function ensureTempDir() {
|
|
109
125
|
if (!fs.existsSync(TEMP_DIR)) {
|
|
110
|
-
fs.mkdirSync(TEMP_DIR, { recursive: true, mode:
|
|
126
|
+
fs.mkdirSync(TEMP_DIR, { recursive: true, mode: 0o755 });
|
|
111
127
|
}
|
|
112
128
|
}
|
|
113
129
|
|
|
@@ -340,12 +356,24 @@ async function startConnection(configPath, vpnConfig, forceDebug = false) {
|
|
|
340
356
|
return { success: true, connection: connectionName, tunDevice: existing.tunDevice, alreadyActive: true };
|
|
341
357
|
}
|
|
342
358
|
|
|
359
|
+
// Kill any stale processes from a previous run using this config
|
|
360
|
+
try {
|
|
361
|
+
execSync(`sudo pkill -TERM -f "openvpn.*${connectionName}" 2>/dev/null`, {
|
|
362
|
+
encoding: 'utf8', timeout: 3000
|
|
363
|
+
});
|
|
364
|
+
// Brief wait for cleanup
|
|
365
|
+
execSync('sleep 1', { timeout: 3000 });
|
|
366
|
+
} catch {}
|
|
367
|
+
|
|
343
368
|
ensureTempDir();
|
|
344
369
|
const logPath = path.join(TEMP_DIR, `${connectionName}.log`);
|
|
345
370
|
|
|
346
371
|
// Clean stale log
|
|
347
372
|
try { if (fs.existsSync(logPath)) fs.unlinkSync(logPath); } catch {}
|
|
348
373
|
|
|
374
|
+
// Pre-create log file writable by all so sudo openvpn can write and user can read
|
|
375
|
+
try { fs.writeFileSync(logPath, '', { mode: 0o666 }); } catch {}
|
|
376
|
+
|
|
349
377
|
const args = buildArgs(configPath, vpnConfig, connectionName, logPath);
|
|
350
378
|
|
|
351
379
|
if (forceDebug) {
|
|
@@ -365,7 +393,7 @@ async function startConnection(configPath, vpnConfig, forceDebug = false) {
|
|
|
365
393
|
fgArgs.push(args[i]);
|
|
366
394
|
}
|
|
367
395
|
|
|
368
|
-
const child = spawn('openvpn', fgArgs, {
|
|
396
|
+
const child = spawn('sudo', ['openvpn', ...fgArgs], {
|
|
369
397
|
stdio: ['ignore', 'ignore', 'ignore'],
|
|
370
398
|
detached: false
|
|
371
399
|
});
|
|
@@ -411,17 +439,21 @@ function stopConnection(connectionName, forceDebug = false) {
|
|
|
411
439
|
}
|
|
412
440
|
|
|
413
441
|
try {
|
|
414
|
-
//
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
442
|
+
// Find the actual openvpn PID (child of sudo) and kill it
|
|
443
|
+
try {
|
|
444
|
+
execSync(`sudo pkill -TERM -f "openvpn.*${connectionName}" 2>/dev/null`, {
|
|
445
|
+
encoding: 'utf8', timeout: 3000
|
|
446
|
+
});
|
|
447
|
+
} catch {}
|
|
448
|
+
|
|
449
|
+
const killed = waitForProcessExit(info.pid, 5000);
|
|
450
|
+
if (!killed) {
|
|
451
|
+
try {
|
|
452
|
+
execSync(`sudo pkill -9 -f "openvpn.*${connectionName}" 2>/dev/null`, {
|
|
453
|
+
encoding: 'utf8', timeout: 3000
|
|
454
|
+
});
|
|
455
|
+
} catch {}
|
|
423
456
|
}
|
|
424
|
-
}
|
|
425
457
|
} catch (killErr) {
|
|
426
458
|
// Process may already be dead
|
|
427
459
|
if (forceDebug) {
|
|
@@ -766,7 +798,8 @@ async function connectForSite(siteConfig, forceDebug = false) {
|
|
|
766
798
|
}
|
|
767
799
|
}
|
|
768
800
|
|
|
769
|
-
|
|
801
|
+
const externalIP = getExternalIP(startResult.tunDevice);
|
|
802
|
+
return { success: true, connection: connectionName, tunDevice: startResult.tunDevice, externalIP };
|
|
770
803
|
}
|
|
771
804
|
|
|
772
805
|
return { success: false, connection: connectionName, error: 'All attempts failed' };
|
package/lib/wireguard_vpn.js
CHANGED
|
@@ -7,6 +7,22 @@ const fs = require('fs');
|
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const { formatLogMessage } = require('./colorize');
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Fetch external IP address through the active tunnel
|
|
12
|
+
* @param {string} interfaceName - WireGuard interface name (optional)
|
|
13
|
+
* @returns {string|null} External IP or null
|
|
14
|
+
*/
|
|
15
|
+
function getExternalIP(interfaceName) {
|
|
16
|
+
const services = ['https://api.ipify.org', 'https://ifconfig.me/ip', 'https://icanhazip.com'];
|
|
17
|
+
for (const service of services) {
|
|
18
|
+
try {
|
|
19
|
+
const iface = interfaceName ? `--interface ${interfaceName}` : '';
|
|
20
|
+
return execSync(`curl -s -m 5 ${iface} ${service}`, { encoding: 'utf8', timeout: 8000 }).trim();
|
|
21
|
+
} catch {}
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
10
26
|
// Track active interfaces for cleanup
|
|
11
27
|
const activeInterfaces = new Map();
|
|
12
28
|
|
|
@@ -94,7 +110,7 @@ function interfaceUp(configPath, interfaceName, forceDebug = false) {
|
|
|
94
110
|
|
|
95
111
|
try {
|
|
96
112
|
// wg-quick accepts a config path or interface name in /etc/wireguard/
|
|
97
|
-
execSync(`wg-quick up "${configPath}"`, {
|
|
113
|
+
execSync(`sudo wg-quick up "${configPath}"`, {
|
|
98
114
|
encoding: 'utf8',
|
|
99
115
|
timeout: 15000
|
|
100
116
|
});
|
|
@@ -109,7 +125,8 @@ function interfaceUp(configPath, interfaceName, forceDebug = false) {
|
|
|
109
125
|
console.log(formatLogMessage('debug', `[vpn] Interface ${interfaceName} is up`));
|
|
110
126
|
}
|
|
111
127
|
|
|
112
|
-
|
|
128
|
+
const externalIP = getExternalIP(interfaceName);
|
|
129
|
+
return { success: true, interface: interfaceName, externalIP };
|
|
113
130
|
} catch (error) {
|
|
114
131
|
return {
|
|
115
132
|
success: false,
|
|
@@ -132,7 +149,7 @@ function interfaceDown(interfaceName, forceDebug = false) {
|
|
|
132
149
|
}
|
|
133
150
|
|
|
134
151
|
try {
|
|
135
|
-
execSync(`wg-quick down "${info.configPath}"`, {
|
|
152
|
+
execSync(`sudo wg-quick down "${info.configPath}"`, {
|
|
136
153
|
encoding: 'utf8',
|
|
137
154
|
timeout: 10000
|
|
138
155
|
});
|
package/nwss.js
CHANGED
|
@@ -1673,7 +1673,8 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1673
1673
|
return { url: currentUrl, rules: [], success: false, vpnFailed: true };
|
|
1674
1674
|
}
|
|
1675
1675
|
if (!silentMode) {
|
|
1676
|
-
|
|
1676
|
+
const ipInfo = vpnResult.externalIP ? ` (${vpnResult.externalIP})` : '';
|
|
1677
|
+
console.log(formatLogMessage('info', `[vpn] WireGuard connected via ${vpnResult.interface}${ipInfo} for ${currentUrl}`));
|
|
1677
1678
|
}
|
|
1678
1679
|
} else if (siteConfig.openvpn) {
|
|
1679
1680
|
const ovpnResult = await ovpnConnect(siteConfig, forceDebug);
|
|
@@ -1682,7 +1683,8 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1682
1683
|
return { url: currentUrl, rules: [], success: false, vpnFailed: true };
|
|
1683
1684
|
}
|
|
1684
1685
|
if (!silentMode) {
|
|
1685
|
-
|
|
1686
|
+
const ipInfo = ovpnResult.externalIP ? ` (${ovpnResult.externalIP})` : '';
|
|
1687
|
+
console.log(formatLogMessage('info', `[vpn] OpenVPN connected via ${ovpnResult.connection}${ipInfo} for ${currentUrl}`));
|
|
1686
1688
|
}
|
|
1687
1689
|
}
|
|
1688
1690
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fanboynz/network-scanner",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.46",
|
|
4
4
|
"description": "A Puppeteer-based network scanner for analyzing web traffic, generating adblock filter rules, and identifying third-party requests. Features include fingerprint spoofing, Cloudflare bypass, content analysis with curl/grep, and multiple output formats.",
|
|
5
5
|
"main": "nwss.js",
|
|
6
6
|
"scripts": {
|