cfpilot-scanner 0.4.0 → 0.4.1

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.
Files changed (2) hide show
  1. package/dist/cli.js +129 -72
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -406,69 +406,6 @@ function readIpFile(text) {
406
406
  return out;
407
407
  }
408
408
 
409
- // src/candidates.ts
410
- var DEFAULT_CANDIDATE_SAMPLE = 2e4;
411
- var DEFAULT_CANDIDATE_SEED = 24301;
412
- var DEFAULT_BRIDGE_SAMPLE = 6556;
413
- function normalizeCidrInput(cidr) {
414
- if (cidr === void 0) return [];
415
- const list = Array.isArray(cidr) ? cidr : [cidr];
416
- const out = [];
417
- for (const item of list) {
418
- if (typeof item !== "string") continue;
419
- for (const part of item.split(/[\n,]+/)) {
420
- const value = part.trim();
421
- if (value) out.push(value);
422
- }
423
- }
424
- return out;
425
- }
426
- function isIpv6Cidr(cidr) {
427
- return cidr.includes(":");
428
- }
429
- function filterByFamily(cidrs, family) {
430
- if (family === "both") return cidrs;
431
- const wantIpv6 = family === "v6";
432
- return cidrs.filter((cidr) => isIpv6Cidr(cidr) === wantIpv6);
433
- }
434
- async function loadCloudflareCidrs(family) {
435
- let ipv4 = BUILTIN_IPV4_CIDRS;
436
- let ipv6 = BUILTIN_IPV6_CIDRS;
437
- try {
438
- const ranges = await fetchCloudflareCidrs();
439
- if (ranges.ipv4.length > 0) ipv4 = ranges.ipv4;
440
- if (ranges.ipv6.length > 0) ipv6 = ranges.ipv6;
441
- } catch {
442
- }
443
- if (family === "v4") return [...ipv4];
444
- if (family === "v6") return [...ipv6];
445
- return [...ipv4, ...ipv6];
446
- }
447
- async function resolveCandidates(opts = {}) {
448
- const sample = opts.sample ?? DEFAULT_CANDIDATE_SAMPLE;
449
- const seed = opts.seed ?? DEFAULT_CANDIDATE_SEED;
450
- const family = opts.family ?? "both";
451
- const customCidrs = normalizeCidrInput(opts.cidr);
452
- if (customCidrs.length > 0) {
453
- return expandCidrs(filterByFamily(customCidrs, family), { sample, seed });
454
- }
455
- const cidrs = await loadCloudflareCidrs(family);
456
- return expandCidrs(cidrs, { sample, seed });
457
- }
458
- function normalizeCandidateList(input) {
459
- if (!Array.isArray(input) || input.length === 0) return [];
460
- const lines = input.filter((item) => typeof item === "string");
461
- if (lines.length === 0) return [];
462
- const seen = /* @__PURE__ */ new Set();
463
- const out = [];
464
- for (const ip of readIpFile(lines.join("\n"))) {
465
- if (seen.has(ip)) continue;
466
- seen.add(ip);
467
- out.push(ip);
468
- }
469
- return out;
470
- }
471
-
472
409
  // src/net-env.ts
473
410
  import * as net from "node:net";
474
411
  import { execFile } from "node:child_process";
@@ -532,6 +469,44 @@ function isFakeIp(ip) {
532
469
  const [a, b] = parts;
533
470
  return a === 198 && (b === 18 || b === 19);
534
471
  }
472
+ function isPublicAddress(ip) {
473
+ const value = ip.trim();
474
+ if (!value) return false;
475
+ if (!value.includes(":")) {
476
+ const parts = value.split(".");
477
+ if (parts.length !== 4) return false;
478
+ const nums = [];
479
+ for (const p of parts) {
480
+ if (!/^\d{1,3}$/.test(p)) return false;
481
+ const n = Number(p);
482
+ if (n > 255) return false;
483
+ nums.push(n);
484
+ }
485
+ const [a, b] = nums;
486
+ if (a === 0) return false;
487
+ if (a === 10) return false;
488
+ if (a === 127) return false;
489
+ if (a >= 224) return false;
490
+ if (a === 169 && b === 254) return false;
491
+ if (a === 172 && b >= 16 && b <= 31) return false;
492
+ if (a === 192 && b === 168) return false;
493
+ if (a === 100 && b >= 64 && b <= 127) return false;
494
+ if (a === 198 && (b === 18 || b === 19)) return false;
495
+ return true;
496
+ }
497
+ const lower = value.toLowerCase();
498
+ if (lower.startsWith("::ffff:") && lower.includes(".")) {
499
+ return isPublicAddress(lower.slice(7));
500
+ }
501
+ const groups = lower.split(":");
502
+ if (groups.length < 3) return false;
503
+ const first = groups[0];
504
+ if (!/^[0-9a-f]{1,4}$/.test(first)) return false;
505
+ const firstByte = Number.parseInt(first.slice(0, 2).padEnd(2, "0"), 16);
506
+ if (firstByte < 32 || firstByte > 63) return false;
507
+ if (lower.startsWith("2001:db8:")) return false;
508
+ return true;
509
+ }
535
510
  async function detectProxyInterference(host) {
536
511
  const findings = [];
537
512
  const listening = [];
@@ -574,6 +549,74 @@ function isMsysPollutedPath(value) {
574
549
  return looksWindows || hasMsysHint;
575
550
  }
576
551
 
552
+ // src/candidates.ts
553
+ var DEFAULT_CANDIDATE_SAMPLE = 2e4;
554
+ var DEFAULT_CANDIDATE_SEED = 24301;
555
+ var DEFAULT_BRIDGE_SAMPLE = 6556;
556
+ function normalizeCidrInput(cidr) {
557
+ if (cidr === void 0) return [];
558
+ const list = Array.isArray(cidr) ? cidr : [cidr];
559
+ const out = [];
560
+ for (const item of list) {
561
+ if (typeof item !== "string") continue;
562
+ for (const part of item.split(/[\n,]+/)) {
563
+ const value = part.trim();
564
+ if (value) out.push(value);
565
+ }
566
+ }
567
+ return out;
568
+ }
569
+ function isIpv6Cidr(cidr) {
570
+ return cidr.includes(":");
571
+ }
572
+ function filterByFamily(cidrs, family) {
573
+ if (family === "both") return cidrs;
574
+ const wantIpv6 = family === "v6";
575
+ return cidrs.filter((cidr) => isIpv6Cidr(cidr) === wantIpv6);
576
+ }
577
+ async function loadCloudflareCidrs(family) {
578
+ let ipv4 = BUILTIN_IPV4_CIDRS;
579
+ let ipv6 = BUILTIN_IPV6_CIDRS;
580
+ try {
581
+ const ranges = await fetchCloudflareCidrs();
582
+ if (ranges.ipv4.length > 0) ipv4 = ranges.ipv4;
583
+ if (ranges.ipv6.length > 0) ipv6 = ranges.ipv6;
584
+ } catch {
585
+ }
586
+ if (family === "v4") return [...ipv4];
587
+ if (family === "v6") return [...ipv6];
588
+ return [...ipv4, ...ipv6];
589
+ }
590
+ async function resolveCandidates(opts = {}) {
591
+ const sample = opts.sample ?? DEFAULT_CANDIDATE_SAMPLE;
592
+ const seed = opts.seed ?? DEFAULT_CANDIDATE_SEED;
593
+ const family = opts.family ?? "both";
594
+ const customCidrs = normalizeCidrInput(opts.cidr);
595
+ if (customCidrs.length > 0) {
596
+ return expandCidrs(filterByFamily(customCidrs, family), { sample, seed });
597
+ }
598
+ const cidrs = await loadCloudflareCidrs(family);
599
+ return expandCidrs(cidrs, { sample, seed });
600
+ }
601
+ function normalizeCandidateList(input) {
602
+ if (!Array.isArray(input) || input.length === 0) return [];
603
+ const lines = input.filter((item) => typeof item === "string");
604
+ if (lines.length === 0) return [];
605
+ const seen = /* @__PURE__ */ new Set();
606
+ const out = [];
607
+ for (const ip of readIpFile(lines.join("\n"))) {
608
+ if (seen.has(ip)) continue;
609
+ seen.add(ip);
610
+ const isIpv4Literal = /^\d{1,3}(\.\d{1,3}){3}$/.test(ip);
611
+ const isIpv6Literal = ip.includes(":");
612
+ if (isIpv4Literal || isIpv6Literal) {
613
+ if (!isPublicAddress(ip)) continue;
614
+ }
615
+ out.push(ip);
616
+ }
617
+ return out;
618
+ }
619
+
577
620
  // src/speed-url.ts
578
621
  import * as tls from "node:tls";
579
622
  var SPEED_URL_PRESETS = [
@@ -1307,10 +1350,20 @@ async function pool(items, concurrency, worker) {
1307
1350
  await Promise.all(runners);
1308
1351
  return out;
1309
1352
  }
1353
+ async function withRetry(attempts, fn, isOk = (r) => r.ok === true) {
1354
+ const total = Math.max(1, Math.floor(attempts));
1355
+ let last;
1356
+ for (let i = 0; i < total; i++) {
1357
+ last = await fn();
1358
+ if (isOk(last)) return last;
1359
+ }
1360
+ return last;
1361
+ }
1310
1362
  async function runScan(options) {
1311
1363
  const started = Date.now();
1312
1364
  const concurrency = options.concurrency ?? 300;
1313
1365
  const tcpTimeoutMs = options.tcpTimeoutMs ?? 1200;
1366
+ const retries = options.retries ?? 2;
1314
1367
  const tlsConcurrency = options.tlsConcurrency ?? 64;
1315
1368
  const tlsTimeoutMs = options.tlsTimeoutMs ?? 4e3;
1316
1369
  const maxLatencyMs = options.maxLatencyMs ?? 0;
@@ -1328,7 +1381,7 @@ async function runScan(options) {
1328
1381
  report({ type: "stage", stage: 1, name: "TCP \u63E1\u624B\u521D\u7B5B", total: candidates.length });
1329
1382
  let done = 0;
1330
1383
  const tcpResults = await pool(candidates, concurrency, async (ip) => {
1331
- const result = await tcpPing(ip, options.port, tcpTimeoutMs);
1384
+ const result = await withRetry(retries, () => tcpPing(ip, options.port, tcpTimeoutMs));
1332
1385
  done++;
1333
1386
  if (done % 500 === 0 || done === candidates.length) {
1334
1387
  report({
@@ -1346,7 +1399,11 @@ async function runScan(options) {
1346
1399
  report({ type: "stage", stage: 1, name: "TLS \u63E1\u624B\u7B5B\u9009", total: filtered.length });
1347
1400
  let tlsDone = 0;
1348
1401
  const tlsResults = await pool(filtered, tlsConcurrency, async (item) => {
1349
- const probe = await probeTlsHandshake(item.ip, options.port, options.host, tlsTimeoutMs);
1402
+ const probe = await withRetry(
1403
+ retries,
1404
+ () => probeTlsHandshake(item.ip, options.port, options.host, tlsTimeoutMs),
1405
+ (r) => r.tlsMs > 0
1406
+ );
1350
1407
  tlsDone++;
1351
1408
  if (tlsDone % 200 === 0 || tlsDone === filtered.length) {
1352
1409
  report({
@@ -1374,12 +1431,9 @@ async function runScan(options) {
1374
1431
  const probeResults = await pool(stage1, probeConcurrency, async (item) => {
1375
1432
  const probeFn = strictProbe ? probeWsPing : probeWs101;
1376
1433
  const probePath = strictProbe ? strictProbePath : options.wsPath ?? "/__edge_probe";
1377
- const result = await probeFn(
1378
- item.ip,
1379
- options.port,
1380
- options.host,
1381
- probeTimeoutMs,
1382
- probePath
1434
+ const result = await withRetry(
1435
+ retries,
1436
+ () => probeFn(item.ip, options.port, options.host, probeTimeoutMs, probePath)
1383
1437
  );
1384
1438
  probeDone++;
1385
1439
  if (result.pongVerified) verifiedCount++;
@@ -1804,7 +1858,7 @@ async function handleRequest(req, res) {
1804
1858
  }
1805
1859
 
1806
1860
  // src/version.ts
1807
- var VERSION = "0.4.0";
1861
+ var VERSION = "0.4.1";
1808
1862
  var NAME = "cfpilot-scanner";
1809
1863
 
1810
1864
  // src/cli.ts
@@ -1838,6 +1892,8 @@ scan \u9009\u9879:
1838
1892
  --speed-conns <n> \u6D4B\u901F\u5E76\u53D1\u8FDE\u63A5\u6570\uFF0C\u9ED8\u8BA4 3
1839
1893
  \u5355\u6761 TCP \u8FDE\u63A5\u53D7\u62E5\u585E\u7A97\u53E3\u9650\u5236\uFF0C\u591A\u8FDE\u63A5\u66F4\u63A5\u8FD1\u771F\u5B9E\u5E26\u5BBD
1840
1894
  --speed-duration <ms> \u6D4B\u901F\u65F6\u957F\uFF0C\u9ED8\u8BA4 6000
1895
+ --retries <n> \u6BCF\u4E2A\u63A2\u6D4B\u7684\u5C1D\u8BD5\u6B21\u6570\uFF08\u542B\u9996\u6B21\uFF09\uFF0C\u9ED8\u8BA4 2
1896
+ \u771F\u673A\u5076\u53D1\u5931\u8D25\u7387 3~5%\uFF0C\u91CD\u8BD5\u53D6\u7B2C\u4E00\u6B21\u6210\u529F\u7684\u7ED3\u679C
1841
1897
  --no-verify \u9636\u6BB5\u4E8C\u53EA\u505A 101 \u63E1\u624B\uFF0C\u8DF3\u8FC7\u54CD\u5E94\u4F53\u6821\u9A8C
1842
1898
  \uFF08\u9ED8\u8BA4\u6821\u9A8C\uFF1A\u53D1 PING \u5E76\u9A8C\u8BC1\u6570\u636E\u9762\u56DE {"pong":true}\uFF09
1843
1899
  --no-speed \u8DF3\u8FC7\u6D4B\u901F\uFF0C\u53EA\u6309\u5EF6\u8FDF\u6392\u5E8F
@@ -2121,6 +2177,7 @@ ${NAME} v${VERSION}
2121
2177
  skipSpeed,
2122
2178
  verifyPayload,
2123
2179
  concurrency: flagNumber(args, "concurrency", 300),
2180
+ retries: flagNumber(args, "retries", 2),
2124
2181
  tcpTimeoutMs: flagNumber(args, "timeout", 1200),
2125
2182
  tlsConcurrency: flagNumber(args, "tls-concurrency", 64),
2126
2183
  tlsTimeoutMs: flagNumber(args, "tls-timeout", 4e3),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cfpilot-scanner",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "CFPilot 优选器:用你自己的域名做 WebSocket 101 真连接校验,只保留真正可用的 Cloudflare IP;兼容任意 VLESS-WS 项目",
5
5
  "license": "MIT",
6
6
  "author": "CFPilot Contributors",