@decentnetwork/lan 0.1.137 → 0.1.139
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/bin/tun-helper-darwin-amd64 +0 -0
- package/bin/tun-helper-darwin-arm64 +0 -0
- package/bin/tun-helper-linux-amd64 +0 -0
- package/bin/tun-helper-linux-arm64 +0 -0
- package/dist/carrier/peer-manager.d.ts +10 -0
- package/dist/carrier/peer-manager.js +25 -1
- package/dist/carrier/types.d.ts +1 -0
- package/dist/carrier/types.js +6 -0
- package/dist/cli/commands.d.ts +22 -0
- package/dist/cli/commands.js +99 -0
- package/dist/cli/index.js +16 -1
- package/dist/daemon/ipc.d.ts +7 -1
- package/dist/daemon/ipc.js +4 -0
- package/dist/daemon/server.js +38 -0
- package/dist/proxy/connect-proxy.d.ts +17 -0
- package/dist/proxy/connect-proxy.js +29 -1
- package/dist/proxy/data/chnroutes-cn.d.ts +1 -0
- package/dist/proxy/data/chnroutes-cn.js +691 -0
- package/dist/proxy/geo-cn.d.ts +25 -0
- package/dist/proxy/geo-cn.js +144 -0
- package/dist/proxy/hls-prefetch.d.ts +67 -0
- package/dist/proxy/hls-prefetch.js +195 -0
- package/dist/proxy/mitm-ca.d.ts +27 -0
- package/dist/proxy/mitm-ca.js +120 -0
- package/dist/proxy/mitm-gateway.d.ts +22 -0
- package/dist/proxy/mitm-gateway.js +76 -0
- package/dist/proxy/multi-exit-router.d.ts +13 -0
- package/dist/proxy/multi-exit-router.js +693 -78
- package/dist/proxy/route-learner.d.ts +33 -0
- package/dist/proxy/route-learner.js +129 -0
- package/dist/router/paced-forwarder.d.ts +45 -0
- package/dist/router/paced-forwarder.js +188 -0
- package/dist/router/packet-router.d.ts +10 -0
- package/dist/router/packet-router.js +74 -1
- package/dist/types.d.ts +7 -0
- package/dist/ui/desktop/app.js +1 -1
- package/dist/ui/desktop/index.html +1 -1
- package/dist/ui/server.js +1 -1
- package/package.json +4 -2
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -18,6 +18,9 @@ export interface PeerManagerOptions {
|
|
|
18
18
|
nickname?: string;
|
|
19
19
|
/** Status-message / short bio advertised to friends (Carrier USERINFO descr). */
|
|
20
20
|
statusMessage?: string;
|
|
21
|
+
/** Directory for persisting partially-received files so a dropped/restarted
|
|
22
|
+
* transfer resumes instead of restarting (断点续传). */
|
|
23
|
+
fileResumeDir?: string;
|
|
21
24
|
}
|
|
22
25
|
export declare class PeerManager extends EventEmitter {
|
|
23
26
|
private peer;
|
|
@@ -185,6 +188,13 @@ export declare class PeerManager extends EventEmitter {
|
|
|
185
188
|
* (162, lossless). Replaces the old `DORA:`-prefixed text on packet 64.
|
|
186
189
|
*/
|
|
187
190
|
sendDora(userid: string, text: string): Promise<void>;
|
|
191
|
+
/**
|
|
192
|
+
* Report to `pubkey` the delivered rate (bytes/sec) we're measuring on their
|
|
193
|
+
* inbound IP flow, so they can pace their sends to us toward it. Lossless +
|
|
194
|
+
* tiny (4 bytes). Best-effort: a failed report just means the sender keeps
|
|
195
|
+
* its previous pace estimate.
|
|
196
|
+
*/
|
|
197
|
+
sendRateReport(pubkey: string, bytesPerSec: number): Promise<void>;
|
|
188
198
|
/**
|
|
189
199
|
* Route a decoded decentlan frame to its packet-session, creating or
|
|
190
200
|
* replacing the session on a HANDSHAKE_REQ (handles the Carrier re-pair
|
|
@@ -6,7 +6,7 @@ import { Peer } from "@decentnetwork/peer";
|
|
|
6
6
|
import { EventEmitter } from "events";
|
|
7
7
|
import { PacketSession } from "./packet-session.js";
|
|
8
8
|
import { FrameCodec } from "./frame.js";
|
|
9
|
-
import { FRAME_OPCODE_HANDSHAKE_ACK, PACKET_ID_DL_SESSION, PACKET_ID_DL_DORA, PACKET_ID_DL_IP, } from "./types.js";
|
|
9
|
+
import { FRAME_OPCODE_HANDSHAKE_ACK, PACKET_ID_DL_SESSION, PACKET_ID_DL_DORA, PACKET_ID_DL_IP, PACKET_ID_DL_RATE, } from "./types.js";
|
|
10
10
|
import { Logger } from "../utils/logger.js";
|
|
11
11
|
export class PeerManager extends EventEmitter {
|
|
12
12
|
peer = null;
|
|
@@ -32,6 +32,7 @@ export class PeerManager extends EventEmitter {
|
|
|
32
32
|
// rides a single transport instead of fanning out over UDP + relay +
|
|
33
33
|
// TCP relay (which delivered 3-4 duplicates of every IP packet).
|
|
34
34
|
bulkDataPacketId: PACKET_ID_DL_IP,
|
|
35
|
+
fileResumeDir: opts.fileResumeDir,
|
|
35
36
|
});
|
|
36
37
|
this.setupEventHandlers();
|
|
37
38
|
this.logger.info("Peer instance created (not yet started)");
|
|
@@ -421,6 +422,22 @@ export class PeerManager extends EventEmitter {
|
|
|
421
422
|
}
|
|
422
423
|
await this.peer.sendCustomPacket(userid, PACKET_ID_DL_DORA, Buffer.from(text, "utf-8"));
|
|
423
424
|
}
|
|
425
|
+
/**
|
|
426
|
+
* Report to `pubkey` the delivered rate (bytes/sec) we're measuring on their
|
|
427
|
+
* inbound IP flow, so they can pace their sends to us toward it. Lossless +
|
|
428
|
+
* tiny (4 bytes). Best-effort: a failed report just means the sender keeps
|
|
429
|
+
* its previous pace estimate.
|
|
430
|
+
*/
|
|
431
|
+
async sendRateReport(pubkey, bytesPerSec) {
|
|
432
|
+
if (!this.peer)
|
|
433
|
+
return;
|
|
434
|
+
const buf = Buffer.alloc(4);
|
|
435
|
+
buf.writeUInt32BE(Math.max(0, Math.min(0xffffffff, Math.round(bytesPerSec))), 0);
|
|
436
|
+
try {
|
|
437
|
+
await this.peer.sendCustomPacket(pubkey, PACKET_ID_DL_RATE, buf);
|
|
438
|
+
}
|
|
439
|
+
catch { /* best-effort */ }
|
|
440
|
+
}
|
|
424
441
|
/**
|
|
425
442
|
* Route a decoded decentlan frame to its packet-session, creating or
|
|
426
443
|
* replacing the session on a HANDSHAKE_REQ (handles the Carrier re-pair
|
|
@@ -511,6 +528,13 @@ export class PeerManager extends EventEmitter {
|
|
|
511
528
|
this.emit("dora-message", pubkey, Buffer.from(data).toString("utf-8"));
|
|
512
529
|
return;
|
|
513
530
|
}
|
|
531
|
+
if (id === PACKET_ID_DL_RATE) {
|
|
532
|
+
if (data.length >= 4) {
|
|
533
|
+
const bytesPerSec = Buffer.from(data.buffer, data.byteOffset, 4).readUInt32BE(0);
|
|
534
|
+
this.emit("rate-report", pubkey, bytesPerSec);
|
|
535
|
+
}
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
514
538
|
if (id === PACKET_ID_DL_IP || id === PACKET_ID_DL_SESSION) {
|
|
515
539
|
const frame = FrameCodec.decode(data);
|
|
516
540
|
if (!frame) {
|
package/dist/carrier/types.d.ts
CHANGED
package/dist/carrier/types.js
CHANGED
|
@@ -25,3 +25,9 @@ export const PACKET_ID_DL_DORA = 162; // lossless: dora control (must arrive)
|
|
|
25
25
|
// The TCP-over-TCP cost on a direct path is the accepted trade — relay delivery
|
|
26
26
|
// is non-negotiable. (The handshake at 161 already exempts IP from chat-64.)
|
|
27
27
|
export const PACKET_ID_DL_IP = 163; // lossless: IP data frames (must traverse relays)
|
|
28
|
+
// lossless: egress pacing feedback. The RECEIVER of an IP flow measures how many
|
|
29
|
+
// bytes/sec it actually received from a peer and reports that number back; the
|
|
30
|
+
// SENDER paces its outbound to that peer toward the reported (delivered) rate,
|
|
31
|
+
// so it stops overrunning the path and inducing congestion drops. Payload: a
|
|
32
|
+
// single uint32be = delivered bytes/sec. Rare + tiny, so lossless is free.
|
|
33
|
+
export const PACKET_ID_DL_RATE = 164;
|
package/dist/cli/commands.d.ts
CHANGED
|
@@ -218,6 +218,27 @@ export declare function cmdProxyRevokeHost(args: {
|
|
|
218
218
|
export declare function cmdProxyListHosts(args: {
|
|
219
219
|
configDir?: string;
|
|
220
220
|
}): Promise<void>;
|
|
221
|
+
/**
|
|
222
|
+
* `agentnet proxy who` — show who is using THIS node's exit right now:
|
|
223
|
+
* per-client active tunnels, total tunnels opened, bytes moved, and refusals.
|
|
224
|
+
* Reads live stats from the running daemon (diag → proxy). Lets an exit
|
|
225
|
+
* operator spot a heavy user before deciding to `agentnet revoke` them.
|
|
226
|
+
*/
|
|
227
|
+
export declare function cmdProxyWho(args: {
|
|
228
|
+
configDir?: string;
|
|
229
|
+
}): Promise<void>;
|
|
230
|
+
/**
|
|
231
|
+
* `agentnet proxy block|allow <peer>` — live per-peer exit access control.
|
|
232
|
+
* Under the default "allow" policy, every friend can tunnel through this exit;
|
|
233
|
+
* `block` adds a deny rule on the proxy port (applied to the running daemon
|
|
234
|
+
* instantly, no restart), `allow` removes it. Accepts a userid or a friendly
|
|
235
|
+
* name (resolved via the live roster).
|
|
236
|
+
*/
|
|
237
|
+
export declare function cmdProxyAccess(args: {
|
|
238
|
+
peer: string;
|
|
239
|
+
allow: boolean;
|
|
240
|
+
configDir?: string;
|
|
241
|
+
}): Promise<void>;
|
|
221
242
|
/**
|
|
222
243
|
* Print the env-var line for a peer to use this node's proxy.
|
|
223
244
|
*/
|
|
@@ -309,6 +330,7 @@ export declare function cmdProxyRouter(args: {
|
|
|
309
330
|
all?: boolean;
|
|
310
331
|
routes?: string;
|
|
311
332
|
egress?: string;
|
|
333
|
+
hlsAccel?: boolean;
|
|
312
334
|
configDir?: string;
|
|
313
335
|
}): Promise<void>;
|
|
314
336
|
/**
|
package/dist/cli/commands.js
CHANGED
|
@@ -1040,6 +1040,102 @@ export async function cmdProxyListHosts(args) {
|
|
|
1040
1040
|
for (const h of allowHosts)
|
|
1041
1041
|
console.log(` ${h}`);
|
|
1042
1042
|
}
|
|
1043
|
+
/**
|
|
1044
|
+
* `agentnet proxy who` — show who is using THIS node's exit right now:
|
|
1045
|
+
* per-client active tunnels, total tunnels opened, bytes moved, and refusals.
|
|
1046
|
+
* Reads live stats from the running daemon (diag → proxy). Lets an exit
|
|
1047
|
+
* operator spot a heavy user before deciding to `agentnet revoke` them.
|
|
1048
|
+
*/
|
|
1049
|
+
export async function cmdProxyWho(args) {
|
|
1050
|
+
const dir = args.configDir || ConfigLoader.defaultConfigDir();
|
|
1051
|
+
const config = await ConfigLoader.load(resolve(dir, "config.yaml"));
|
|
1052
|
+
const pid = daemonPid(config);
|
|
1053
|
+
if (pid === null) {
|
|
1054
|
+
console.error("No running daemon found for this config — start it with 'agentnet up'.");
|
|
1055
|
+
process.exit(1);
|
|
1056
|
+
}
|
|
1057
|
+
const res = await ipcCall(config, { op: "diag" });
|
|
1058
|
+
if (!res.ok)
|
|
1059
|
+
throw new Error(`Daemon diag failed: ${res.error}`);
|
|
1060
|
+
const proxy = res.data?.proxy;
|
|
1061
|
+
if (!proxy) {
|
|
1062
|
+
console.log("The CONNECT proxy isn't running on this node (nobody can use it as an exit).");
|
|
1063
|
+
console.log("Enable it with: agentnet proxy enable");
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
const fmtBytes = (n) => {
|
|
1067
|
+
if (n >= 1024 ** 3)
|
|
1068
|
+
return `${(n / 1024 ** 3).toFixed(2)} GB`;
|
|
1069
|
+
if (n >= 1024 ** 2)
|
|
1070
|
+
return `${(n / 1024 ** 2).toFixed(1)} MB`;
|
|
1071
|
+
if (n >= 1024)
|
|
1072
|
+
return `${(n / 1024).toFixed(0)} KB`;
|
|
1073
|
+
return `${n} B`;
|
|
1074
|
+
};
|
|
1075
|
+
const ago = (ms) => {
|
|
1076
|
+
if (!ms)
|
|
1077
|
+
return "—";
|
|
1078
|
+
const s = Math.max(0, Math.floor((Date.now() - ms) / 1000));
|
|
1079
|
+
if (s < 60)
|
|
1080
|
+
return `${s}s ago`;
|
|
1081
|
+
if (s < 3600)
|
|
1082
|
+
return `${Math.floor(s / 60)}m ago`;
|
|
1083
|
+
return `${Math.floor(s / 3600)}h ago`;
|
|
1084
|
+
};
|
|
1085
|
+
console.log(`Exit proxy: ${proxy.active} active tunnel(s), ${proxy.totalOpened} opened total, ` +
|
|
1086
|
+
`${fmtBytes(proxy.bytesTransferred)} moved, ${proxy.totalRefused} refused.`);
|
|
1087
|
+
if (!proxy.peers.length) {
|
|
1088
|
+
console.log("No clients have used this exit yet.");
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
console.log("");
|
|
1092
|
+
console.log(" CLIENT ACTIVE OPENED BYTES REFUSED LAST");
|
|
1093
|
+
for (const p of proxy.peers) {
|
|
1094
|
+
const who = (p.srcName ? `${p.srcName} (${p.src})` : p.src).padEnd(22).slice(0, 22);
|
|
1095
|
+
console.log(` ${who} ${String(p.active).padStart(6)} ${String(p.totalOpened).padStart(6)} ` +
|
|
1096
|
+
`${fmtBytes(p.bytesTransferred).padEnd(11)} ${String(p.totalRefused).padStart(7)} ${ago(p.lastSeenMs)}`);
|
|
1097
|
+
}
|
|
1098
|
+
console.log("");
|
|
1099
|
+
console.log("Cut off a heavy user: agentnet proxy block <userid-or-name>");
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* `agentnet proxy block|allow <peer>` — live per-peer exit access control.
|
|
1103
|
+
* Under the default "allow" policy, every friend can tunnel through this exit;
|
|
1104
|
+
* `block` adds a deny rule on the proxy port (applied to the running daemon
|
|
1105
|
+
* instantly, no restart), `allow` removes it. Accepts a userid or a friendly
|
|
1106
|
+
* name (resolved via the live roster).
|
|
1107
|
+
*/
|
|
1108
|
+
export async function cmdProxyAccess(args) {
|
|
1109
|
+
const dir = args.configDir || ConfigLoader.defaultConfigDir();
|
|
1110
|
+
const config = await ConfigLoader.load(resolve(dir, "config.yaml"));
|
|
1111
|
+
const pid = daemonPid(config);
|
|
1112
|
+
if (pid === null) {
|
|
1113
|
+
console.error("No running daemon found for this config — start it with 'agentnet up'.");
|
|
1114
|
+
process.exit(1);
|
|
1115
|
+
}
|
|
1116
|
+
// Resolve a friendly name to its userid so the deny rule keys on the stable id.
|
|
1117
|
+
let userid = args.peer;
|
|
1118
|
+
try {
|
|
1119
|
+
const { peers } = await fetchLiveIpam(config);
|
|
1120
|
+
const stripDomain = (s) => s.replace(/\.[A-Za-z][A-Za-z0-9-]*$/, "");
|
|
1121
|
+
const hit = peers.find((p) => p.carrierId === args.peer || p.name === args.peer || p.name === stripDomain(args.peer));
|
|
1122
|
+
if (hit?.carrierId)
|
|
1123
|
+
userid = hit.carrierId;
|
|
1124
|
+
}
|
|
1125
|
+
catch {
|
|
1126
|
+
// fall back to the raw arg — the daemon matches on userid OR name
|
|
1127
|
+
}
|
|
1128
|
+
const res = await ipcCall(config, { op: "proxy-access", userid, enabled: args.allow });
|
|
1129
|
+
if (!res.ok)
|
|
1130
|
+
throw new Error(`Daemon refused: ${res.error}`);
|
|
1131
|
+
if (args.allow) {
|
|
1132
|
+
console.log(`✓ ${args.peer} may use this exit again (block removed, live).`);
|
|
1133
|
+
}
|
|
1134
|
+
else {
|
|
1135
|
+
console.log(`✓ ${args.peer} is now BLOCKED from this exit (live — existing tunnels drop on next reconnect).`);
|
|
1136
|
+
console.log(" Re-allow with: agentnet proxy allow " + args.peer);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1043
1139
|
/**
|
|
1044
1140
|
* Print the env-var line for a peer to use this node's proxy.
|
|
1045
1141
|
*/
|
|
@@ -1384,6 +1480,9 @@ export async function cmdProxyRouter(args) {
|
|
|
1384
1480
|
listenPort: args.port ?? 8889,
|
|
1385
1481
|
mode,
|
|
1386
1482
|
egressBindIp,
|
|
1483
|
+
learnedRoutesPath: resolve(dir, "learned-routes.json"),
|
|
1484
|
+
hlsAccel: args.hlsAccel ?? false,
|
|
1485
|
+
configDir: dir,
|
|
1387
1486
|
});
|
|
1388
1487
|
// Keep the process alive; the router runs until Ctrl-C / signal.
|
|
1389
1488
|
await new Promise(() => { });
|
package/dist/cli/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { hideBin } from "yargs/helpers";
|
|
|
10
10
|
// Belt-and-braces — also raise it here in case the CLI is run directly
|
|
11
11
|
// (e.g. `node dist/cli/index.js` rather than via dist/index.js).
|
|
12
12
|
EventEmitter.defaultMaxListeners = 100;
|
|
13
|
-
import { cmdInit, cmdIdentityShow, cmdPeersList, cmdIpamAssign, cmdGrant, cmdRevoke, cmdResolve, cmdStatus, cmdUp, cmdAuditLog, cmdFriendRequest, cmdFriendAccept, cmdFriendsList, cmdFriendsPending, cmdFriendsAccept, cmdFriendsAutoAccept, cmdFriendsReject, cmdProxyEnable, cmdProxyDisable, cmdProxyStatus, cmdProxyAllowHost, cmdProxyRevokeHost, cmdProxyListHosts, cmdProxyUse, cmdProxyRouter, cmdDoraEnable, cmdDoraDisable, cmdDoraStatus, cmdDoraAutofriend, cmdDiag, cmdDoctor, cmdDnsInstall, cmdDnsHosts, cmdServiceInstall, cmdRestart, cmdServiceStatus, cmdServiceRestart, cmdUi, cmdConsole, cmdFileSend, cmdChatSend, cmdChatHistory, cmdFriendRemove, cmdFriendAlias, } from "./commands.js";
|
|
13
|
+
import { cmdInit, cmdIdentityShow, cmdPeersList, cmdIpamAssign, cmdGrant, cmdRevoke, cmdResolve, cmdStatus, cmdUp, cmdAuditLog, cmdFriendRequest, cmdFriendAccept, cmdFriendsList, cmdFriendsPending, cmdFriendsAccept, cmdFriendsAutoAccept, cmdFriendsReject, cmdProxyEnable, cmdProxyDisable, cmdProxyStatus, cmdProxyWho, cmdProxyAccess, cmdProxyAllowHost, cmdProxyRevokeHost, cmdProxyListHosts, cmdProxyUse, cmdProxyRouter, cmdDoraEnable, cmdDoraDisable, cmdDoraStatus, cmdDoraAutofriend, cmdDiag, cmdDoctor, cmdDnsInstall, cmdDnsHosts, cmdServiceInstall, cmdRestart, cmdServiceStatus, cmdServiceRestart, cmdUi, cmdConsole, cmdFileSend, cmdChatSend, cmdChatHistory, cmdFriendRemove, cmdFriendAlias, } from "./commands.js";
|
|
14
14
|
async function main() {
|
|
15
15
|
await yargs(hideBin(process.argv))
|
|
16
16
|
.scriptName("agentnet")
|
|
@@ -317,6 +317,19 @@ async function main() {
|
|
|
317
317
|
})
|
|
318
318
|
.command("list-hosts", "List the CONNECT host allowlist", (yy) => yy.option("config-dir", { type: "string" }), async (argv) => {
|
|
319
319
|
await cmdProxyListHosts({ configDir: argv["config-dir"] });
|
|
320
|
+
})
|
|
321
|
+
.command("who", "Show who is using THIS node's exit (per-peer tunnels + bytes)", (yy) => yy.option("config-dir", { type: "string" }), async (argv) => {
|
|
322
|
+
await cmdProxyWho({ configDir: argv["config-dir"] });
|
|
323
|
+
})
|
|
324
|
+
.command("block <peer>", "Block a peer from using THIS node's exit (live, no restart)", (yy) => yy
|
|
325
|
+
.positional("peer", { type: "string", demandOption: true })
|
|
326
|
+
.option("config-dir", { type: "string" }), async (argv) => {
|
|
327
|
+
await cmdProxyAccess({ peer: argv.peer, allow: false, configDir: argv["config-dir"] });
|
|
328
|
+
})
|
|
329
|
+
.command("allow <peer>", "Re-allow a previously-blocked peer to use THIS node's exit (live)", (yy) => yy
|
|
330
|
+
.positional("peer", { type: "string", demandOption: true })
|
|
331
|
+
.option("config-dir", { type: "string" }), async (argv) => {
|
|
332
|
+
await cmdProxyAccess({ peer: argv.peer, allow: true, configDir: argv["config-dir"] });
|
|
320
333
|
})
|
|
321
334
|
.command("use", "Print env vars to route HTTPS via a peer's proxy node", (yy) => yy
|
|
322
335
|
.option("peer", { type: "string", demandOption: true, describe: "Peer name or carrier ID" })
|
|
@@ -335,6 +348,7 @@ async function main() {
|
|
|
335
348
|
.option("mode", { choices: ["loadbalance", "failover"], default: "loadbalance" })
|
|
336
349
|
.option("all", { type: "boolean", default: false, describe: "Route ALL hosts through exits (default: China-only split tunnel)" })
|
|
337
350
|
.option("egress", { type: "string", describe: "Bind the DIRECT internet dial to the physical NIC: 'physical' (auto) or an explicit source IPv4 — keeps egress off Tailscale/overlay" })
|
|
351
|
+
.option("hls-accel", { type: "boolean", default: false, describe: "Accelerate China video streams: selectively MITM video-CDN HTTPS to parallel-prefetch HLS segments across all exits (prints a CA cert to trust once)" })
|
|
338
352
|
.option("config-dir", { type: "string" }), async (argv) => {
|
|
339
353
|
await cmdProxyRouter({
|
|
340
354
|
exit: argv.exit,
|
|
@@ -344,6 +358,7 @@ async function main() {
|
|
|
344
358
|
mode: argv.mode,
|
|
345
359
|
all: argv.all,
|
|
346
360
|
egress: argv.egress,
|
|
361
|
+
hlsAccel: argv["hls-accel"],
|
|
347
362
|
configDir: argv["config-dir"],
|
|
348
363
|
});
|
|
349
364
|
})
|
package/dist/daemon/ipc.d.ts
CHANGED
|
@@ -84,6 +84,12 @@ export interface IpcHandlers {
|
|
|
84
84
|
* restart (which drops every Carrier session). Returns the applied
|
|
85
85
|
* allowlist for the CLI to echo. */
|
|
86
86
|
proxyReload: () => Promise<Record<string, unknown>>;
|
|
87
|
+
/** Block or allow a specific peer's access to THIS node's exit proxy port,
|
|
88
|
+
* LIVE (mutates the running ACL + persists policy.yaml). `allow=true`
|
|
89
|
+
* removes the block; `allow=false` adds a deny rule so the peer can no
|
|
90
|
+
* longer tunnel through this exit. Lets an operator cut off a heavy user
|
|
91
|
+
* without restarting the daemon. */
|
|
92
|
+
proxyAccess: (userid: string, allow: boolean) => Promise<Record<string, unknown>>;
|
|
87
93
|
/** Re-exec the running daemon with its original argv. The new
|
|
88
94
|
* process inherits the current process's privileges (so a
|
|
89
95
|
* root-owned daemon re-execs as root with no sudo prompt),
|
|
@@ -94,7 +100,7 @@ export interface IpcHandlers {
|
|
|
94
100
|
selfRestart: () => Promise<Record<string, unknown>>;
|
|
95
101
|
}
|
|
96
102
|
export interface IpcRequest {
|
|
97
|
-
op: "friend-request" | "ping" | "diag" | "friends-pending" | "friends-accept" | "friends-reject" | "chat-send" | "chat-history" | "friends-list" | "friend-remove" | "friend-set-alias" | "friends-autoaccept" | "set-profile" | "file-send" | "chat-mark-read" | "subscribe" | "sign" | "proxy-reload" | "self-restart";
|
|
103
|
+
op: "friend-request" | "ping" | "diag" | "friends-pending" | "friends-accept" | "friends-reject" | "chat-send" | "chat-history" | "friends-list" | "friend-remove" | "friend-set-alias" | "friends-autoaccept" | "set-profile" | "file-send" | "chat-mark-read" | "subscribe" | "sign" | "proxy-reload" | "proxy-access" | "self-restart";
|
|
98
104
|
address?: string;
|
|
99
105
|
hello?: string;
|
|
100
106
|
userid?: string;
|
package/dist/daemon/ipc.js
CHANGED
|
@@ -233,6 +233,10 @@ export class IpcServer {
|
|
|
233
233
|
}
|
|
234
234
|
case "proxy-reload":
|
|
235
235
|
return await this.handlers.proxyReload();
|
|
236
|
+
case "proxy-access":
|
|
237
|
+
if (!req.userid)
|
|
238
|
+
throw new Error("proxy-access requires a userid");
|
|
239
|
+
return await this.handlers.proxyAccess(req.userid, req.enabled !== false);
|
|
236
240
|
case "self-restart":
|
|
237
241
|
return await this.handlers.selfRestart();
|
|
238
242
|
default:
|
package/dist/daemon/server.js
CHANGED
|
@@ -283,6 +283,9 @@ export class DaemonServer {
|
|
|
283
283
|
// instead of the generic "@decentnetwork/peer".
|
|
284
284
|
nickname: this.config.node.name,
|
|
285
285
|
statusMessage: this.config.node.statusMessage,
|
|
286
|
+
// Persist partially-received files so a dropped/restarted transfer
|
|
287
|
+
// resumes instead of restarting from 0 (断点续传).
|
|
288
|
+
fileResumeDir: resolve(this.configDir, "downloads", ".partial"),
|
|
286
289
|
});
|
|
287
290
|
await this.peerManager.start();
|
|
288
291
|
this.logger.info(`Identity: ${this.peerManager.getAddress()}`);
|
|
@@ -507,6 +510,33 @@ export class DaemonServer {
|
|
|
507
510
|
}
|
|
508
511
|
return { applied: true, allowHosts };
|
|
509
512
|
},
|
|
513
|
+
proxyAccess: async (userid, allow) => {
|
|
514
|
+
// Live block/allow a peer's access to THIS node's exit proxy port.
|
|
515
|
+
// Mutates the running ACL (evaluated per-packet, so it's instant) and
|
|
516
|
+
// persists policy.yaml. Under the default "allow" policy, a friend can
|
|
517
|
+
// use the exit unless we add an explicit DENY — so "block" adds a deny
|
|
518
|
+
// rule on the proxy port and "allow" removes the peer's rules.
|
|
519
|
+
if (!this.policy)
|
|
520
|
+
throw new Error("ACL policy not initialised");
|
|
521
|
+
const port = this.config.proxy?.port ?? 8888;
|
|
522
|
+
// Accept a userid or a friendly name; store the rule under whatever
|
|
523
|
+
// identifier the ACL matches on (both are checked at evaluate time).
|
|
524
|
+
const peerKey = userid;
|
|
525
|
+
if (allow) {
|
|
526
|
+
this.policy.removePeer(peerKey);
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
this.policy.addRule({
|
|
530
|
+
peer: peerKey,
|
|
531
|
+
direction: "both",
|
|
532
|
+
deny: [{ proto: "tcp", port, purpose: "exit-block" }],
|
|
533
|
+
audit: true,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
await this.policy.save();
|
|
537
|
+
this.logger.info(`Exit access ${allow ? "ALLOWED" : "BLOCKED"} for ${peerKey.slice(0, 12)} (tcp:${port})`);
|
|
538
|
+
return { applied: true, userid: peerKey, allowed: allow, port };
|
|
539
|
+
},
|
|
510
540
|
selfRestart: async () => {
|
|
511
541
|
// Detach a child process that waits briefly and then re-execs
|
|
512
542
|
// the same argv we were started with. Inherits privileges
|
|
@@ -586,6 +616,10 @@ export class DaemonServer {
|
|
|
586
616
|
session: this.peerManager?.getSessionStatus(f.pubkey) ?? null,
|
|
587
617
|
})),
|
|
588
618
|
ipam: ipamPeers,
|
|
619
|
+
// Exit-operator view: live per-client usage of THIS node's CONNECT
|
|
620
|
+
// proxy (who's tunnelling through me + how much). Null when the
|
|
621
|
+
// proxy isn't running.
|
|
622
|
+
proxy: this.connectProxy?.getStats() ?? null,
|
|
589
623
|
};
|
|
590
624
|
},
|
|
591
625
|
});
|
|
@@ -877,6 +911,10 @@ export class DaemonServer {
|
|
|
877
911
|
peerManager: this.peerManager,
|
|
878
912
|
ipam: this.ipam,
|
|
879
913
|
acl: this.aclEngine,
|
|
914
|
+
// Pace + fair-queue egress IP data (default on). Disable with
|
|
915
|
+
// `forwarding: { paced: false }` in config.yaml to restore the old
|
|
916
|
+
// fire-immediately behavior.
|
|
917
|
+
pacedForwarding: this.config.forwarding?.paced !== false,
|
|
880
918
|
});
|
|
881
919
|
await this.packetRouter.start();
|
|
882
920
|
// 7b. In-process DNS server. Answers A/PTR for *.{dnsDomain}
|
|
@@ -51,6 +51,17 @@ export interface ConnectProxyOptions {
|
|
|
51
51
|
bytesTransferred: number;
|
|
52
52
|
}) => void;
|
|
53
53
|
}
|
|
54
|
+
/** Per-client (per source virtual-IP) usage of the exit, so the operator can
|
|
55
|
+
* see WHO is using their exit and HOW MUCH — spot heavy/abusive users. */
|
|
56
|
+
export interface ProxyPeerUsage {
|
|
57
|
+
src: string;
|
|
58
|
+
srcName?: string;
|
|
59
|
+
active: number;
|
|
60
|
+
totalOpened: number;
|
|
61
|
+
totalRefused: number;
|
|
62
|
+
bytesTransferred: number;
|
|
63
|
+
lastSeenMs: number;
|
|
64
|
+
}
|
|
54
65
|
export interface ConnectProxyStats {
|
|
55
66
|
bindIp: string;
|
|
56
67
|
port: number;
|
|
@@ -59,13 +70,19 @@ export interface ConnectProxyStats {
|
|
|
59
70
|
totalOpened: number;
|
|
60
71
|
totalRefused: number;
|
|
61
72
|
bytesTransferred: number;
|
|
73
|
+
/** Per-client breakdown, heaviest (by bytes) first. */
|
|
74
|
+
peers: ProxyPeerUsage[];
|
|
62
75
|
}
|
|
63
76
|
export declare class ConnectProxy {
|
|
64
77
|
private opts;
|
|
65
78
|
private server;
|
|
66
79
|
private logger;
|
|
67
80
|
private stats;
|
|
81
|
+
/** Live per-client usage, keyed by source virtual IP. */
|
|
82
|
+
private perPeer;
|
|
68
83
|
constructor(opts: ConnectProxyOptions);
|
|
84
|
+
/** Get-or-create the per-client usage row for a source IP. */
|
|
85
|
+
private peerRow;
|
|
69
86
|
start(): Promise<void>;
|
|
70
87
|
stop(): Promise<void>;
|
|
71
88
|
getStats(): ConnectProxyStats;
|
|
@@ -27,6 +27,8 @@ export class ConnectProxy {
|
|
|
27
27
|
server = null;
|
|
28
28
|
logger;
|
|
29
29
|
stats;
|
|
30
|
+
/** Live per-client usage, keyed by source virtual IP. */
|
|
31
|
+
perPeer = new Map();
|
|
30
32
|
constructor(opts) {
|
|
31
33
|
this.opts = opts;
|
|
32
34
|
this.logger = new Logger({ prefix: "ConnectProxy" });
|
|
@@ -38,8 +40,20 @@ export class ConnectProxy {
|
|
|
38
40
|
totalOpened: 0,
|
|
39
41
|
totalRefused: 0,
|
|
40
42
|
bytesTransferred: 0,
|
|
43
|
+
peers: [],
|
|
41
44
|
};
|
|
42
45
|
}
|
|
46
|
+
/** Get-or-create the per-client usage row for a source IP. */
|
|
47
|
+
peerRow(src, srcName) {
|
|
48
|
+
let row = this.perPeer.get(src);
|
|
49
|
+
if (!row) {
|
|
50
|
+
row = { src, srcName, active: 0, totalOpened: 0, totalRefused: 0, bytesTransferred: 0, lastSeenMs: 0 };
|
|
51
|
+
this.perPeer.set(src, row);
|
|
52
|
+
}
|
|
53
|
+
if (srcName && !row.srcName)
|
|
54
|
+
row.srcName = srcName;
|
|
55
|
+
return row;
|
|
56
|
+
}
|
|
43
57
|
async start() {
|
|
44
58
|
if (this.server) {
|
|
45
59
|
throw new Error("ConnectProxy already started");
|
|
@@ -84,7 +98,10 @@ export class ConnectProxy {
|
|
|
84
98
|
this.logger.info("Stopped");
|
|
85
99
|
}
|
|
86
100
|
getStats() {
|
|
87
|
-
|
|
101
|
+
const peers = [...this.perPeer.values()]
|
|
102
|
+
.sort((a, b) => b.bytesTransferred - a.bytesTransferred || b.totalOpened - a.totalOpened)
|
|
103
|
+
.map((p) => ({ ...p }));
|
|
104
|
+
return { ...this.stats, peers };
|
|
88
105
|
}
|
|
89
106
|
/**
|
|
90
107
|
* Update the host allowlist (and optionally the port allowlist) of a
|
|
@@ -139,6 +156,10 @@ export class ConnectProxy {
|
|
|
139
156
|
clientSocket.destroy();
|
|
140
157
|
this.stats.active = Math.max(0, this.stats.active - 1);
|
|
141
158
|
this.stats.bytesTransferred += bytes;
|
|
159
|
+
const row = this.peerRow(src, srcName);
|
|
160
|
+
row.active = Math.max(0, row.active - 1);
|
|
161
|
+
row.bytesTransferred += bytes;
|
|
162
|
+
row.lastSeenMs = Date.now();
|
|
142
163
|
this.opts.onTunnelClose?.({
|
|
143
164
|
src,
|
|
144
165
|
srcName,
|
|
@@ -161,6 +182,10 @@ export class ConnectProxy {
|
|
|
161
182
|
}
|
|
162
183
|
this.stats.active += 1;
|
|
163
184
|
this.stats.totalOpened += 1;
|
|
185
|
+
const row = this.peerRow(src, srcName);
|
|
186
|
+
row.active += 1;
|
|
187
|
+
row.totalOpened += 1;
|
|
188
|
+
row.lastSeenMs = Date.now();
|
|
164
189
|
this.opts.onTunnelOpen?.({ src, srcName, target: `${host}:${port}` });
|
|
165
190
|
this.logger.info(`tunnel open ${src}${srcName ? ` (${srcName})` : ""} -> ${host}:${port}`);
|
|
166
191
|
});
|
|
@@ -208,6 +233,9 @@ export class ConnectProxy {
|
|
|
208
233
|
}
|
|
209
234
|
refuse(clientSocket, src, srcName, target, code, reason) {
|
|
210
235
|
this.stats.totalRefused += 1;
|
|
236
|
+
const row = this.peerRow(src, srcName);
|
|
237
|
+
row.totalRefused += 1;
|
|
238
|
+
row.lastSeenMs = Date.now();
|
|
211
239
|
this.logger.info(`tunnel refused ${src}${srcName ? ` (${srcName})` : ""} -> ${target}: ${reason}`);
|
|
212
240
|
try {
|
|
213
241
|
const statusText = code === 400 ? "Bad Request" : "Forbidden";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CN_IPV4_RANGES: readonly number[];
|