[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
248
electron/bridges/systemManager/portOps.test.cjs
Normal file
248
electron/bridges/systemManager/portOps.test.cjs
Normal file
@@ -0,0 +1,248 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
const test = require("node:test");
|
||||
const {
|
||||
parseSsOutput,
|
||||
parseNetstatOutput,
|
||||
parseLsofOutput,
|
||||
parseListeningPorts,
|
||||
} = require("./portOps.cjs");
|
||||
|
||||
test("parseSsOutput reads tcp/udp listeners and process fields", () => {
|
||||
const sample = `
|
||||
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
|
||||
tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1234,fd=3))
|
||||
tcp LISTEN 0 511 *:80 *:* users:(("nginx",pid=99,fd=6))
|
||||
udp UNCONN 0 0 127.0.0.1:53 0.0.0.0:* users:(("systemd-resolve",pid=500,fd=12))
|
||||
tcp6 LISTEN 0 128 [::]:443 [::]:* users:(("nginx",pid=99,fd=7))
|
||||
`;
|
||||
const ports = parseSsOutput(sample);
|
||||
assert.equal(ports.length, 4);
|
||||
assert.deepEqual(
|
||||
ports.find((p) => p.port === 22),
|
||||
{
|
||||
id: "tcp|*|22|1234",
|
||||
protocol: "tcp",
|
||||
address: "*",
|
||||
port: 22,
|
||||
pid: 1234,
|
||||
processName: "sshd",
|
||||
},
|
||||
);
|
||||
assert.equal(ports.find((p) => p.port === 80)?.processName, "nginx");
|
||||
assert.equal(ports.find((p) => p.port === 53)?.protocol, "udp");
|
||||
assert.equal(ports.find((p) => p.port === 443)?.protocol, "tcp6");
|
||||
});
|
||||
|
||||
test("parseNetstatOutput maps pid/program and keeps BusyBox UDP without PID", () => {
|
||||
const sample = `
|
||||
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
|
||||
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1234/sshd
|
||||
tcp6 0 0 :::80 :::* LISTEN 99/nginx
|
||||
udp 0 0 127.0.0.1:53 0.0.0.0:*
|
||||
udp 0 0 0.0.0.0:5353 0.0.0.0:* 456/dnsmasq
|
||||
`;
|
||||
const ports = parseNetstatOutput(sample);
|
||||
assert.equal(ports.length, 4);
|
||||
assert.equal(ports.find((p) => p.port === 22)?.processName, "sshd");
|
||||
assert.equal(ports.find((p) => p.port === 80)?.address, "*");
|
||||
assert.equal(ports.find((p) => p.port === 53)?.protocol, "udp");
|
||||
assert.equal(ports.find((p) => p.port === 53)?.pid, null);
|
||||
assert.equal(ports.find((p) => p.port === 5353)?.pid, 456);
|
||||
assert.equal(ports.find((p) => p.port === 5353)?.processName, "dnsmasq");
|
||||
});
|
||||
|
||||
test("parseNetstatOutput keeps spaced UDP program names without State", () => {
|
||||
const sample = `
|
||||
udp 0 0 0.0.0.0:5353 0.0.0.0:* 882/avahi-daemon: r
|
||||
udp 0 0 0.0.0.0:53 0.0.0.0:* 100/named -u bind
|
||||
`;
|
||||
const ports = parseNetstatOutput(sample);
|
||||
assert.equal(ports.find((p) => p.port === 5353)?.pid, 882);
|
||||
assert.equal(ports.find((p) => p.port === 5353)?.processName, "avahi-daemon: r");
|
||||
assert.equal(ports.find((p) => p.port === 53)?.pid, 100);
|
||||
assert.equal(ports.find((p) => p.port === 53)?.processName, "named -u bind");
|
||||
});
|
||||
|
||||
test("parseNetstatOutput understands macOS dotted addresses", () => {
|
||||
const sample = `
|
||||
Active Internet connections
|
||||
Proto Recv-Q Send-Q Local Address Foreign Address (state)
|
||||
tcp4 0 0 *.22 *.* LISTEN
|
||||
tcp4 0 0 127.0.0.1.631 *.* LISTEN
|
||||
`;
|
||||
const ports = parseNetstatOutput(sample);
|
||||
assert.equal(ports.length, 2);
|
||||
assert.equal(ports.find((p) => p.port === 22)?.address, "*");
|
||||
assert.equal(ports.find((p) => p.port === 631)?.address, "127.0.0.1");
|
||||
});
|
||||
|
||||
test("parseListeningPorts merges ss/netstat/lsof and prefers process-aware rows", () => {
|
||||
const stdout = `
|
||||
__NC_PORTS_BEGIN__
|
||||
__NC_NETSTAT__
|
||||
tcp4 0 0 *.22 *.* LISTEN
|
||||
__NC_LSOF__
|
||||
sshd 1234 root 3u IPv4 0x1 0t0 TCP *:22 (LISTEN)
|
||||
__NC_PORTS_END__
|
||||
`;
|
||||
const ports = parseListeningPorts(stdout);
|
||||
assert.equal(ports.length, 1);
|
||||
assert.equal(ports[0].port, 22);
|
||||
assert.equal(ports[0].pid, 1234);
|
||||
assert.equal(ports[0].processName, "sshd");
|
||||
});
|
||||
|
||||
test("parseLsofOutput reads TCP LISTEN rows", () => {
|
||||
const sample = `
|
||||
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
|
||||
nginx 99 root 6u IPv4 0xabc 0t0 TCP *:80 (LISTEN)
|
||||
`;
|
||||
const ports = parseLsofOutput(sample);
|
||||
assert.equal(ports.length, 1);
|
||||
assert.equal(ports[0].port, 80);
|
||||
assert.equal(ports[0].processName, "nginx");
|
||||
});
|
||||
|
||||
test("parseNetstatOutput rejects established TCP and connected UDP", () => {
|
||||
const sample = `
|
||||
tcp 0 0 10.0.0.1:22 10.0.0.2:40000 ESTABLISHED 1234/sshd
|
||||
tcp 0 0 10.0.0.1:22 10.0.0.2:40000 1234/sshd
|
||||
udp 0 0 10.0.0.5:68 10.0.0.1:67
|
||||
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 9/sshd
|
||||
`;
|
||||
const ports = parseNetstatOutput(sample);
|
||||
assert.equal(ports.length, 1);
|
||||
assert.equal(ports[0].port, 22);
|
||||
assert.equal(ports[0].processName, "sshd");
|
||||
});
|
||||
|
||||
test("parseLsofOutput ignores connected UDP and non-listen TCP", () => {
|
||||
const sample = `
|
||||
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
|
||||
chrome 1000 user 10u IPv4 0x1 0t0 UDP 10.0.0.1:53122->8.8.8.8:53
|
||||
nginx 99 root 6u IPv4 0xabc 0t0 TCP 10.0.0.1:80->10.0.0.2:12345 (ESTABLISHED)
|
||||
sshd 1234 root 3u IPv4 0x2 0t0 TCP *:22 (LISTEN)
|
||||
`;
|
||||
const ports = parseLsofOutput(sample);
|
||||
assert.equal(ports.length, 1);
|
||||
assert.equal(ports[0].port, 22);
|
||||
});
|
||||
|
||||
test("parseLsofOutput keeps bracketed IPv6 listeners as tcp6", () => {
|
||||
const sample = `
|
||||
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
|
||||
nginx 99 root 7u IPv6 0xabc 0t0 TCP [::1]:80 (LISTEN)
|
||||
`;
|
||||
const ports = parseLsofOutput(sample);
|
||||
assert.equal(ports.length, 1);
|
||||
assert.equal(ports[0].protocol, "tcp6");
|
||||
assert.equal(ports[0].address, "::1");
|
||||
assert.equal(ports[0].port, 80);
|
||||
});
|
||||
|
||||
test("parseNetstatOutput keeps macOS IPv6 dotted ports", () => {
|
||||
const sample = `
|
||||
tcp6 0 0 ::1.631 *.* LISTEN
|
||||
tcp6 0 0 fe80::1%lo0.22 *.* LISTEN
|
||||
`;
|
||||
const ports = parseNetstatOutput(sample);
|
||||
assert.equal(ports.length, 2);
|
||||
assert.equal(ports.find((p) => p.port === 631)?.address, "::1");
|
||||
assert.equal(ports.find((p) => p.port === 22)?.address, "fe80::1");
|
||||
});
|
||||
|
||||
test("parseSsOutput rejects UDP with non-wildcard peers", () => {
|
||||
const sample = `
|
||||
udp UNCONN 0 0 10.0.0.5:68 10.0.0.1:67 users:(("dhclient",pid=7,fd=1))
|
||||
udp UNCONN 0 0 0.0.0.0:5353 0.0.0.0:* users:(("avahi",pid=8,fd=1))
|
||||
`;
|
||||
const ports = parseSsOutput(sample);
|
||||
assert.equal(ports.length, 1);
|
||||
assert.equal(ports[0].port, 5353);
|
||||
});
|
||||
|
||||
test("parseListeningPorts merges ss tcp6 with lsof IPv6 without duplicating", () => {
|
||||
const stdout = `
|
||||
__NC_PORTS_BEGIN__
|
||||
__NC_SS__
|
||||
tcp6 LISTEN 0 128 [::]:80 [::]:* users:(("nginx",pid=99,fd=7))
|
||||
__NC_LSOF__
|
||||
nginx 99 root 7u IPv6 0xabc 0t0 TCP *:80 (LISTEN)
|
||||
__NC_PORTS_END__
|
||||
`;
|
||||
const ports = parseListeningPorts(stdout);
|
||||
assert.equal(ports.length, 1);
|
||||
assert.equal(ports[0].protocol, "tcp6");
|
||||
assert.equal(ports[0].pid, 99);
|
||||
});
|
||||
|
||||
test("parseListeningPorts keeps SO_REUSEPORT listeners as separate pid rows", () => {
|
||||
const sample = `
|
||||
tcp LISTEN 0 128 0.0.0.0:8080 0.0.0.0:* users:(("app",pid=11,fd=3))
|
||||
tcp LISTEN 0 128 0.0.0.0:8080 0.0.0.0:* users:(("app",pid=12,fd=3))
|
||||
`;
|
||||
const ports = parseSsOutput(sample);
|
||||
assert.equal(ports.length, 2);
|
||||
assert.deepEqual(ports.map((p) => p.pid).sort((a, b) => a - b), [11, 12]);
|
||||
});
|
||||
|
||||
test("parseListeningPorts reads Windows UDP endpoints from Protocol field", () => {
|
||||
const stdout = `
|
||||
__NC_PORTS_BEGIN__
|
||||
__NC_WIN__
|
||||
[{"LocalAddress":"0.0.0.0","LocalPort":53,"OwningProcess":500,"Protocol":"udp"},{"LocalAddress":"::","LocalPort":22,"OwningProcess":1234,"Protocol":"tcp"}]
|
||||
__NC_PORTS_END__
|
||||
`;
|
||||
const ports = parseListeningPorts(stdout);
|
||||
assert.equal(ports.length, 2);
|
||||
assert.equal(ports.find((p) => p.port === 53)?.protocol, "udp");
|
||||
assert.equal(ports.find((p) => p.port === 22)?.protocol, "tcp6");
|
||||
});
|
||||
|
||||
test("parseLsofOutput keeps Idle UDP listeners", () => {
|
||||
const sample = `
|
||||
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
|
||||
mdnsd 200 root 12u IPv4 0x1 0t0 UDP *:5353
|
||||
`;
|
||||
const ports = parseLsofOutput(sample);
|
||||
assert.equal(ports.length, 1);
|
||||
assert.equal(ports[0].protocol, "udp");
|
||||
assert.equal(ports[0].port, 5353);
|
||||
});
|
||||
|
||||
test("parseNetstatOutput accepts macOS tcp46/udp46 dual-stack rows", () => {
|
||||
const sample = `
|
||||
tcp46 0 0 *.80 *.* LISTEN
|
||||
udp46 0 0 *.5353 *.*
|
||||
`;
|
||||
const ports = parseNetstatOutput(sample);
|
||||
assert.equal(ports.length, 2);
|
||||
assert.equal(ports.find((p) => p.port === 80)?.protocol, "tcp6");
|
||||
assert.equal(ports.find((p) => p.port === 5353)?.protocol, "udp6");
|
||||
});
|
||||
|
||||
test("parseListeningPorts merges macOS tcp46 netstat with lsof IPv6", () => {
|
||||
const stdout = `
|
||||
__NC_PORTS_BEGIN__
|
||||
__NC_NETSTAT__
|
||||
tcp46 0 0 *.80 *.* LISTEN
|
||||
__NC_LSOF__
|
||||
nginx 99 root 7u IPv6 0xabc 0t0 TCP *:80 (LISTEN)
|
||||
__NC_PORTS_END__
|
||||
`;
|
||||
const ports = parseListeningPorts(stdout);
|
||||
assert.equal(ports.length, 1);
|
||||
assert.equal(ports[0].protocol, "tcp6");
|
||||
assert.equal(ports[0].pid, 99);
|
||||
});
|
||||
|
||||
test("parseSsOutput expands multi-pid users tuples", () => {
|
||||
const sample = `
|
||||
tcp LISTEN 0 128 0.0.0.0:8080 0.0.0.0:* users:(("app",pid=11,fd=3),("app",pid=12,fd=4))
|
||||
`;
|
||||
const ports = parseSsOutput(sample);
|
||||
assert.equal(ports.length, 2);
|
||||
assert.deepEqual(ports.map((p) => p.pid).sort((a, b) => a - b), [11, 12]);
|
||||
});
|
||||
Reference in New Issue
Block a user