whoisport 0.5.2 → 0.5.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 +132 -63
- package/bin/whoisport.js +24 -12
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -2,10 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://crates.io/crates/whoisport)
|
|
4
4
|
[](https://github.com/dpologdvinity/whoisport/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
5
6
|
|
|
6
|
-
Find out what is listening on a
|
|
7
|
+
**Find out what is listening on a port, who started it, and stop it safely.**
|
|
8
|
+
Works on Linux, macOS and Windows. One small binary, no runtime dependencies.
|
|
7
9
|
|
|
8
|
-

|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
`lsof -i :3000` gives you a PID and a wall of columns. `whoisport` gives you the command
|
|
13
|
+
line, the directory it was started from, who owns it and how long it has been running,
|
|
14
|
+
which is usually what you need to decide whether to kill it. And when you do kill it,
|
|
15
|
+
whoisport makes sure it only stops the process you were looking at.
|
|
16
|
+
|
|
17
|
+
## Quickstart
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
whoisport # what is listening on this machine?
|
|
21
|
+
whoisport 3000 # what is on port 3000, and who started it?
|
|
22
|
+
whoisport 3000 --kill # ask it to exit (add --force if it refuses)
|
|
23
|
+
```
|
|
9
24
|
|
|
10
25
|
```console
|
|
11
26
|
$ whoisport 3000
|
|
@@ -29,88 +44,142 @@ PORT PID USER CMD
|
|
|
29
44
|
ports marked ? are owned by another user; run with sudo to see it
|
|
30
45
|
```
|
|
31
46
|
|
|
32
|
-
`lsof -i :3000` gives you a PID and a wall of columns. `whoisport` gives you the
|
|
33
|
-
command line, the directory it was started from, who owns it and how long it has
|
|
34
|
-
been running, which is usually what you need to decide whether to kill it.
|
|
35
|
-
|
|
36
47
|
## Install
|
|
37
48
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
cargo install whoisport
|
|
41
|
-
pipx install whoisport
|
|
42
|
-
|
|
49
|
+
| Platform | Command |
|
|
50
|
+
|----------|---------|
|
|
51
|
+
| Any, with Rust | `cargo binstall whoisport` (prebuilt) or `cargo install whoisport` (builds from source) |
|
|
52
|
+
| Any, with Python | `pipx install whoisport` or `uvx whoisport` |
|
|
53
|
+
| macOS, Linux | `brew install dpologdvinity/tap/whoisport` |
|
|
54
|
+
| Windows | `scoop bucket add whoisport https://github.com/dpologdvinity/scoop-whoisport` then `scoop install whoisport` |
|
|
55
|
+
| Debian, Ubuntu | download the `.deb` from [Releases](https://github.com/dpologdvinity/whoisport/releases), then `sudo apt install ./whoisport_*.deb` |
|
|
56
|
+
| Fedora, RHEL | download the `.rpm` from [Releases](https://github.com/dpologdvinity/whoisport/releases), then `sudo dnf install ./whoisport-*.rpm` |
|
|
43
57
|
|
|
44
|
-
|
|
58
|
+
Or download a binary from the [Releases page](https://github.com/dpologdvinity/whoisport/releases):
|
|
59
|
+
Linux (static, x86_64 and aarch64), macOS (Apple silicon and Intel) and Windows (x86_64).
|
|
45
60
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
```
|
|
61
|
+
**Supported systems:** Linux (reads `/proc`, no other tools needed), macOS (uses the
|
|
62
|
+
built-in `lsof`), Windows 10 and 11.
|
|
49
63
|
|
|
50
|
-
|
|
51
|
-
from the [Releases page](https://github.com/dpologdvinity/whoisport/releases), then
|
|
64
|
+
## Usage
|
|
52
65
|
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
sudo dnf install ./whoisport-*.rpm # Fedora, RHEL
|
|
66
|
+
```
|
|
67
|
+
whoisport [PORTS]... [--kill [--force]] [--json] [--udp]
|
|
56
68
|
```
|
|
57
69
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
| You type | You get |
|
|
71
|
+
|----------|---------|
|
|
72
|
+
| `whoisport` | A table of every listening port |
|
|
73
|
+
| `whoisport 3000 8080` | Details of what is on each port (repeated ports are shown once) |
|
|
74
|
+
| `whoisport 3000 --kill` | The details, then the process is asked to exit and given 3 seconds |
|
|
75
|
+
| `whoisport 3000 --kill --force` | Same, but forcibly ended if it does not exit |
|
|
76
|
+
| `whoisport --json` | The same data as a JSON array, for scripts |
|
|
77
|
+
| `whoisport --udp` | UDP sockets instead of TCP |
|
|
78
|
+
|
|
79
|
+
`--kill` always needs explicit ports; it never means "kill everything". `--json` cannot
|
|
80
|
+
be combined with `--kill`.
|
|
81
|
+
|
|
82
|
+
### JSON output
|
|
83
|
+
|
|
84
|
+
`--json` prints an array with one object per listener. Every object has the same keys;
|
|
85
|
+
what is unknown (for example the owner of a process you may not inspect) is `null`:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
[
|
|
89
|
+
{
|
|
90
|
+
"port": 3000, "proto": "tcp", "pid": 373232, "ppid": 373230, "user": "kaitlyn",
|
|
91
|
+
"cmd": "node node_modules/.bin/vite --port 3000", "cwd": "/home/kaitlyn/site",
|
|
92
|
+
"uptime_secs": 252, "container": null, "image": null
|
|
93
|
+
}
|
|
94
|
+
]
|
|
63
95
|
```
|
|
64
96
|
|
|
65
|
-
|
|
66
|
-
[Releases page](https://github.com/dpologdvinity/whoisport/releases): Linux (static,
|
|
67
|
-
x86_64 and aarch64), macOS (Apple Silicon and Intel) and Windows (x86_64).
|
|
97
|
+
### Exit codes
|
|
68
98
|
|
|
69
|
-
|
|
70
|
-
|
|
99
|
+
| Code | Meaning |
|
|
100
|
+
|------|---------|
|
|
101
|
+
| `0` | Success: everything asked for was found (and killed, if asked) |
|
|
102
|
+
| `1` | A requested port has nothing listening, or nothing is listening at all |
|
|
103
|
+
| `2` | Invalid arguments |
|
|
104
|
+
| `3` | A kill failed, or a process holding the port was deliberately not killed (see below), so the port may still be in use |
|
|
71
105
|
|
|
72
|
-
|
|
106
|
+
### Reading `?`
|
|
73
107
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
108
|
+
On Linux, a process owned by another user cannot be inspected without `sudo`, so its row
|
|
109
|
+
shows `?`. On macOS `lsof` only lists your own processes without `sudo`, so other
|
|
110
|
+
users' listeners are missing. On Windows, details of system services need an elevated
|
|
111
|
+
terminal.
|
|
112
|
+
|
|
113
|
+
## Stopping processes safely
|
|
114
|
+
|
|
115
|
+
`--kill` is built so that it cannot take down more than you meant to:
|
|
116
|
+
|
|
117
|
+
- **The process you saw is the process that is signalled.** Each target is identified by
|
|
118
|
+
its pid *and* its start time. If it exits and the system hands its pid to something
|
|
119
|
+
else, that something else is left alone.
|
|
120
|
+
- **Service managers and parents are never signalled.** When several processes share a
|
|
121
|
+
socket (a supervisor and its workers, or `systemd` socket activation), stopping the
|
|
122
|
+
supervisor takes down far more than one port. `systemd`, `launchd` and `svchost.exe`,
|
|
123
|
+
and any process that is a parent of another holder, are skipped. whoisport then says
|
|
124
|
+
which one still holds the port and exits `3`.
|
|
125
|
+
- **It asks first.** Each target is asked to exit; it is forced only with `--force`.
|
|
126
|
+
|
|
127
|
+
| Platform | `--kill` asks by | `--force` |
|
|
128
|
+
|----------|------------------|-----------|
|
|
129
|
+
| Linux, macOS | `SIGTERM` | `SIGKILL` |
|
|
130
|
+
| Windows | Ctrl+C in the process's console, and `WM_CLOSE` to its windows | `TerminateProcess` |
|
|
131
|
+
|
|
132
|
+
On Windows, Ctrl+C reaches every program attached to a console, so whoisport only sends
|
|
133
|
+
it when the target's console holds nothing but the target, its child processes and a
|
|
134
|
+
shell. Otherwise it says so and leaves the choice of `--force` to you.
|
|
81
135
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
| `--force` | With `--kill`: forcibly terminate it if asking did not work. |
|
|
86
|
-
| `--json` | Print a JSON array with `port`, `proto`, `pid`, `ppid`, `user`, `cmd`, `cwd`, `uptime_secs`, `container`, `image`. |
|
|
87
|
-
| `--udp` | Look at UDP sockets instead of TCP. UDP has no listening state, so any bound socket without a remote peer counts. Windows doesn't report UDP peers, so there every bound UDP socket is listed, including short-lived client ones. |
|
|
136
|
+
Text from other processes (command lines, paths) is printed with control characters,
|
|
137
|
+
bidirectional-text overrides and zero-width characters escaped, so a hostile process
|
|
138
|
+
cannot rewrite your terminal or disguise what it is.
|
|
88
139
|
|
|
89
|
-
Docker
|
|
90
|
-
name and image (`docker web (nginx:alpine)`), even when the `docker-proxy`
|
|
91
|
-
process itself is owned by root. It asks `docker ps` only in that case.
|
|
140
|
+
## Docker
|
|
92
141
|
|
|
93
|
-
|
|
142
|
+
When a port is published by a container, whoisport shows the container name and image
|
|
143
|
+
(`docker web (nginx:alpine)`), even though the `docker-proxy` process behind it is owned
|
|
144
|
+
by root. Docker is consulted only when a listener looks like Docker's forwarder or its
|
|
145
|
+
owner cannot be read, only for a local daemon (not an `ssh://` or remote `DOCKER_HOST`),
|
|
146
|
+
and each Docker call is cut off after 2 seconds, so a stuck daemon never hangs
|
|
147
|
+
whoisport: the names are simply left out. `--kill` on such a port prints the matching
|
|
148
|
+
`docker stop <name>` command, because killing the forwarder only breaks the port mapping.
|
|
94
149
|
|
|
95
|
-
|
|
150
|
+
## UDP
|
|
96
151
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
| Windows | Ctrl+C in the process's console, and `WM_CLOSE` to its windows | `TerminateProcess` |
|
|
152
|
+
UDP has no listening state, so `--udp` lists bound sockets that have no remote peer.
|
|
153
|
+
Windows does not report UDP peers, so there every bound UDP socket is listed, including
|
|
154
|
+
short-lived client ones.
|
|
101
155
|
|
|
102
|
-
|
|
103
|
-
sends it when the target's console holds nothing but the target, its child
|
|
104
|
-
processes and a shell. Otherwise it says so and leaves the choice of `--force` to you.
|
|
156
|
+
## How it works
|
|
105
157
|
|
|
106
|
-
|
|
107
|
-
|
|
158
|
+
A short version (the long one is in [ARCHITECTURE.md](ARCHITECTURE.md)): whoisport asks
|
|
159
|
+
the operating system which sockets are listening and which process owns each one, looks
|
|
160
|
+
up details of those processes, and prints them. Each platform has its own small backend
|
|
161
|
+
for that (`/proc` on Linux, `lsof` on macOS, the IP Helper API on Windows); everything
|
|
162
|
+
else is shared.
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
cli ─► scan ─► output (list, details, JSON)
|
|
166
|
+
│
|
|
167
|
+
└───► kill (identity, safety rules, ask-then-force)
|
|
168
|
+
▲
|
|
169
|
+
proc backend per OS · docker names
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Development
|
|
173
|
+
|
|
174
|
+
```sh
|
|
175
|
+
cargo test # unit and end-to-end tests
|
|
176
|
+
cargo clippy --all-targets -- -D warnings
|
|
177
|
+
```
|
|
108
178
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
other users' listeners only appear when run with `sudo`.
|
|
179
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full workflow (formatting, coverage,
|
|
180
|
+
checking the other platforms, commit style) and [ARCHITECTURE.md](ARCHITECTURE.md) for
|
|
181
|
+
how the code is organised.
|
|
113
182
|
|
|
114
183
|
## License
|
|
115
184
|
|
|
116
|
-
MIT
|
|
185
|
+
[MIT](LICENSE)
|
package/bin/whoisport.js
CHANGED
|
@@ -1,33 +1,45 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* whoisport launcher.
|
|
5
|
+
*
|
|
6
|
+
* The `whoisport` npm package contains no binary of its own. Each platform's binary lives
|
|
7
|
+
* in its own package (`whoisport-linux-x64`, `whoisport-windows-x64`, ...), listed as an
|
|
8
|
+
* optional dependency with `os`/`cpu` set, so npm, pnpm, yarn and bun install only the one
|
|
9
|
+
* that matches this machine. This script finds that package, runs its binary with the same
|
|
10
|
+
* arguments, and passes the exit code (or terminating signal) straight through.
|
|
11
|
+
*/
|
|
5
12
|
const { spawnSync } = require("node:child_process");
|
|
6
13
|
|
|
14
|
+
/** Platform + architecture, as `process.platform process.arch`, to npm package name. */
|
|
7
15
|
const packages = {
|
|
8
16
|
"linux x64": "whoisport-linux-x64",
|
|
9
17
|
"linux arm64": "whoisport-linux-arm64",
|
|
10
18
|
"darwin x64": "whoisport-darwin-x64",
|
|
11
19
|
"darwin arm64": "whoisport-darwin-arm64",
|
|
12
|
-
"win32 x64": "whoisport-
|
|
20
|
+
"win32 x64": "whoisport-windows-x64", // Node calls Windows "win32"; the package is named "windows"
|
|
13
21
|
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
|
|
23
|
+
/** Print `message` to stderr and exit with failure. */
|
|
24
|
+
function fail(message) {
|
|
25
|
+
console.error(`whoisport: ${message}`);
|
|
17
26
|
process.exit(1);
|
|
18
27
|
}
|
|
28
|
+
|
|
29
|
+
const name = packages[`${process.platform} ${process.arch}`];
|
|
30
|
+
if (!name) fail(`no prebuilt binary for ${process.platform}-${process.arch}`);
|
|
31
|
+
|
|
19
32
|
const exe = process.platform === "win32" ? "whoisport.exe" : "whoisport";
|
|
20
33
|
let bin;
|
|
21
34
|
try {
|
|
35
|
+
// `require.resolve` finds the binary inside the platform package's install directory.
|
|
22
36
|
bin = require.resolve(`${name}/bin/${exe}`);
|
|
23
37
|
} catch {
|
|
24
|
-
|
|
25
|
-
process.exit(1);
|
|
38
|
+
fail(`${name} is not installed (were optional dependencies skipped?)`);
|
|
26
39
|
}
|
|
40
|
+
|
|
27
41
|
const result = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" });
|
|
28
|
-
if (result.error)
|
|
29
|
-
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
42
|
+
if (result.error) fail(result.error.message);
|
|
43
|
+
// Re-raise the child's signal so the shell sees the same cause of death.
|
|
32
44
|
if (result.signal) process.kill(process.pid, result.signal);
|
|
33
45
|
process.exit(result.status ?? 1);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whoisport",
|
|
3
3
|
"description": "Show what is listening on a TCP or UDP port, and optionally kill it",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.7",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"node": ">=16"
|
|
29
29
|
},
|
|
30
30
|
"optionalDependencies": {
|
|
31
|
-
"whoisport-linux-x64": "0.5.
|
|
32
|
-
"whoisport-linux-arm64": "0.5.
|
|
33
|
-
"whoisport-darwin-x64": "0.5.
|
|
34
|
-
"whoisport-darwin-arm64": "0.5.
|
|
35
|
-
"whoisport-
|
|
31
|
+
"whoisport-linux-x64": "0.5.7",
|
|
32
|
+
"whoisport-linux-arm64": "0.5.7",
|
|
33
|
+
"whoisport-darwin-x64": "0.5.7",
|
|
34
|
+
"whoisport-darwin-arm64": "0.5.7",
|
|
35
|
+
"whoisport-windows-x64": "0.5.7"
|
|
36
36
|
}
|
|
37
37
|
}
|