summaryrefslogtreecommitdiff
path: root/AGENTS.md
blob: 434d657a0df166d4f881290fa2d7997bb782ef5d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# AGENTS.md — jns

## What this project is

**jns** is a high-performance network port scanner (Java 25, virtual threads),
scanning IPv4/IPv6 ranges for open TCP (+ optional UDP) ports with service-type
detection from banners. It is a CLI library + app: `App`/`Main` parse args,
`Scan` holds config, `Scanner` orchestrates, `CliPrinter` renders results,
`ScannerLibrary` exposes a programmatic API for other apps.

## Build / run / test

- Build: `mvn -q package -DskipTests` → `target/jns-1.0-SNAPSHOT.jar`
  (no unit tests exist; `mvn test` is a no-op).
- Run: `java -jar target/jns-1.0-SNAPSHOT.jar <ip-or-file> -p 1-1024 ...`
  (see `--help`; `-i -` reads hosts from stdin, `-u`/`-uu` for UDP, `-q` quiet,
  `-sf` show filtered ports, `-f` rate limit, `-b` banner recognition).
- Sanity checks used during development:
  - open port: start `python3 -m http.server 8765`, scan `127.0.0.1 -p 22,80,8765`
    → expect exactly `127.0.0.1` + `\t (1) open:\t8765`;
  - offline host: 1024 ports → empty output, port count 1 (online check only);
  - `scanTests/` holds recorded expected outputs (`scan-result-*`, `scan-error-*`);
    compare new output against the matching file before declaring a change done.

## Architecture notes (read before touching)

- Concurrency: one virtual thread per port probe today
  (`ProducerThread` → `executor.submit(new ScanPortTask(...))`), throttled by
  semaphores: `socketLimit` (default 2048), `maxHostsLimit`, `maxWorkersPerHost`.
  `CompletionConsumer` drains `PortScanResultAccumulator`; Ctrl-C cancels but
  partial results are still printed — preserve that contract.
- Shared per-scan state flows through the record `ScanExecutionContext`
  (semaphores, rate limiter, shared `PortRange`, ...). `ScanHostContext` is the
  host-level record. **Multi-scan constraint: the scanner must support several
  concurrent scans with different configs in one JVM — never store per-scan state
  in statics; only true compile-time constants may be static.**
- Carrier-thread pinning is forbidden: never sleep/lock around blocking calls
  inside virtual threads (see `PortScanRateLimiter` sleep-outside-synchronized
  pattern and `Scan*PortTask` non-blocking wait loops).
- `LinuxUdpSocketAPI` (Foreign Memory API, per-probe `Arena.ofConfined`):
  do NOT share its errno call-state segment across threads (data race).
- `CliPrinter.getColumnWidth()`: the `stty size` process spawn per call is
  intentional (terminal resize) — do not cache the width.

## Optimization state

- Phase 2 (allocation/CPU hot-path cleanups) is **done and measured**:
  see `TODO.md` (commits, benchmark evidence: −31 % CPU, −4 % wall, −8 % RSS on
  a 20 k hosts × 65535-port run). The scan is network-latency-bound; remaining
  headroom is in `TODO.md` Phase 2b / 3 / 4 (deferred, measure-first).
- Benchmark method that was used: `/usr/bin/time -v` around
  `java -jar ... -i - -p all` (host list on stdin), keep the
  `before-optimization.txt` / `after-optimization-N.txt` artifacts.

## Working rules for this repo

- Never change user-visible output format or scan behavior "while at it" —
  only when it is the explicit purpose of the change.
- Small, logical steps per file edit; verify with `mvn -q compile` before
  showing the diff.
- `/tmp` is not persistent between tool calls; re-read files before editing to
  confirm their current state.