commit a17a156f18e86a79572fee147f2237f6ef8202b2
parent 05a47b70919628cf439751ea5bad9fb955a9c065
Author: Matthias Jaros <jaros@mailbox.org>
Date: Thu, 2 Jul 2026 11:29:17 +0200
Added new Class ScanState in order to make Scanner
stateless during scan process. Not regarding config
which is given to the constructor
Diffstat:
3 files changed, 47 insertions(+), 31 deletions(-)
diff --git a/src/main/java/com/it_jaros/jscanner/ProgressBar.java b/src/main/java/com/it_jaros/jscanner/ProgressBar.java
@@ -15,11 +15,15 @@ import java.util.regex.Pattern;
public class ProgressBar {
- private final ScheduledExecutorService ui = Executors.newSingleThreadScheduledExecutor();
+ private List<Progress> hosts = new ArrayList<>();
private final ReentrantLock lock = new ReentrantLock();
+ private final ScheduledExecutorService ui = Executors.newSingleThreadScheduledExecutor();
+ private final boolean quiet;
private final int columnWidth = ProgressBar.getColumnWidth();
- private List<Progress> hosts;
+ public ProgressBar(boolean quiet) {
+ this.quiet = quiet;
+ }
private static int getColumnWidth() {
try {
@@ -42,10 +46,9 @@ public class ProgressBar {
return 120;
}
- public void start(boolean quiet) {
- this.hosts = new ArrayList<>();
- System.out.print("Scanning targets...\n\n");
+ public void start() {
if (quiet) { return; }
+ System.out.print("Scanning targets...\n\n");
ui.scheduleAtFixedRate(() -> {
lock.lock();
drawProgress(false);
@@ -106,7 +109,7 @@ public class ProgressBar {
return String.format("%s%s%s %s %s", label, " ".repeat(spacesBetween),statsLeft, brackets, statsRight);
}
- public void stop(boolean quiet) {
+ public void stop() {
ui.shutdown();
try {
ui.awaitTermination(10, TimeUnit.SECONDS);
diff --git a/src/main/java/com/it_jaros/jscanner/ScanState.java b/src/main/java/com/it_jaros/jscanner/ScanState.java
@@ -0,0 +1,5 @@
+package com.it_jaros.jscanner;
+
+import java.util.List;
+
+public record ScanState(Counter counter, ProgressBar progressBar, List<ScanResult> scanResult) {}
diff --git a/src/main/java/com/it_jaros/jscanner/Scanner.java b/src/main/java/com/it_jaros/jscanner/Scanner.java
@@ -21,11 +21,8 @@ public class Scanner implements AutoCloseable {
private static final int READ_BUFFER_SIZE = 1024;
- private ArrayList<ScanResult> scanResult;
private volatile boolean cancelled = false;
- private final Counter counter = new Counter();
- private final ProgressBar progressBar = new ProgressBar();
private final Semaphore socketLimit;
private final boolean disableOnlineCheck;
private final boolean quiet;
@@ -34,14 +31,22 @@ public class Scanner implements AutoCloseable {
private final int timeoutInMillis;
private final long delayInNanos;
- public Scanner(int socketLimit, int timeoutInMillis, int delayInMillis, int maxWorkersPerHost, int maxHostsLimit, boolean disableOnlineCheck, boolean quiet) {
- this.timeoutInMillis = timeoutInMillis;
+ public Scanner(
+ int socketLimit,
+ int timeoutInMillis,
+ int delayInMillis,
+ int maxWorkersPerHost,
+ int maxHostsLimit,
+ boolean disableOnlineCheck,
+ boolean quiet
+ ) {
this.delayInNanos = TimeUnit.MILLISECONDS.toNanos(Math.max(0, delayInMillis));
- this.socketLimit = new Semaphore(socketLimit);
- this.maxWorkersPerHost = maxWorkersPerHost;
- this.maxHostsLimit = maxHostsLimit;
this.disableOnlineCheck = disableOnlineCheck;
+ this.maxHostsLimit = maxHostsLimit;
+ this.maxWorkersPerHost = maxWorkersPerHost;
this.quiet = quiet;
+ this.socketLimit = new Semaphore(socketLimit);
+ this.timeoutInMillis = timeoutInMillis;
}
public Scanner(ScanOptions options) {
@@ -78,8 +83,8 @@ public class Scanner implements AutoCloseable {
}
public List<ScanResult> scanHosts(Stream<String> hosts, String ports) throws IOException {
- this.scanResult = new ArrayList<>();
- progressBar.start(quiet);
+ ScanState scanState = new ScanState(new Counter(), new ProgressBar(quiet), new ArrayList<>());
+ scanState.progressBar().start();
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
CompletionService<ScanResult> completionService = new ExecutorCompletionService<>(executor);
Queue<String> hostsInQueue = hosts.collect(Collectors.toCollection(ArrayDeque::new));
@@ -91,7 +96,7 @@ public class Scanner implements AutoCloseable {
while (!hostsInQueue.isEmpty() && activeHostWorkers <= maxHostsLimit && !cancelled) {
activeHostWorkers++;
final String host = hostsInQueue.poll();
- completionService.submit(() -> scanHost(host, ports));
+ completionService.submit(() -> scanHost(host, ports, scanState));
}
try {
@@ -101,7 +106,7 @@ public class Scanner implements AutoCloseable {
}
activeHostWorkers--;
hostsToProcess--;
- this.scanResult.add(finishedHost.get());
+ scanState.scanResult().add(finishedHost.get());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
@@ -114,16 +119,16 @@ public class Scanner implements AutoCloseable {
}
}
- progressBar.stop(quiet);
+ scanState.progressBar().stop();
System.out.println("--------------------");
System.out.println("Finished scan!");
System.out.println("Stats:");
- System.out.println("Peak concurrent connects: " + counter.max());
+ System.out.println("Peak concurrent connects: " + scanState.counter().max());
System.out.println("--------------------");
- return scanResult;
+ return scanState.scanResult();
}
- private ScanResult scanHost(String host, String ports) {
+ private ScanResult scanHost(String host, String ports, ScanState scanState) {
PortRange portRange = new PortRange(ports);
AtomicLong portSlotFactory = new AtomicLong(System.nanoTime());
BitSet openPorts = new BitSet(PortRange.MAX_PORT);
@@ -132,7 +137,7 @@ public class Scanner implements AutoCloseable {
// Give progressbar the current progress object which is then updated in the sub virtual threads
Progress progress = new Progress(host, portRange.getTotal(), new AtomicInteger(), new AtomicInteger(), new AtomicInteger());
- progressBar.submit(progress);
+ scanState.progressBar().submit(progress);
if (!disableOnlineCheck && !checkHostOnline(host)) {
// Visually show that this host is basically done
@@ -151,13 +156,13 @@ public class Scanner implements AutoCloseable {
final int currentPort = portRange.next();
completionService.submit(() -> {
waitForSlot(portSlotFactory);
- return checkPort(host, currentPort);
+ return checkPort(host, currentPort, scanState);
});
}
try {
- Future<PortResult> f = completionService.poll(10, TimeUnit.MILLISECONDS);
- if (f == null) {
+ Future<PortResult> portResultFuture = completionService.poll(10, TimeUnit.MILLISECONDS);
+ if (portResultFuture == null) {
continue;
}
activePerHostWorkers--;
@@ -166,7 +171,7 @@ public class Scanner implements AutoCloseable {
// bitset is not thread-safe, so it is set
// outside the other virtual threads that update progress
- PortResult portResult = f.get();
+ PortResult portResult = portResultFuture.get();
switch (portResult.getState()) {
case OPEN -> {
openPorts.set(portResult.getPort());
@@ -212,12 +217,15 @@ public class Scanner implements AutoCloseable {
}
}
- private PortResult checkPort(String host, int port) {
- socketLimit.acquireUninterruptibly();
- counter.inc();
+ private PortResult checkPort(String host, int port, ScanState scanState) {
PortResult result = new PortResult();
result.setPort(port);
result.setState(PortState.UNKNOWN);
+
+ // don't allow more sockets then specified
+ socketLimit.acquireUninterruptibly();
+ // count how many ports are concurrently checked
+ scanState.counter().inc();
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(host, port), timeoutInMillis);
result.setBanner(getBanner(socket));
@@ -230,7 +238,7 @@ public class Scanner implements AutoCloseable {
// NoRouteToHostException: this can be safely ignored because the port is closed if a host is unreachable
// Will happen a lot when scanning for open ports, so not needed
} finally {
- counter.dec();
+ scanState.counter().dec();
socketLimit.release();
}