commit 5ff81526ddfa739fb374a07a6bd20d0f1a5e6594
parent e82fd4ef3f2da8d74bc22d925455e1e05aa6b1d7
Author: Matthias Jaros <jaros@mailbox.org>
Date: Thu, 2 Jul 2026 18:09:16 +0200
Reformatted code so that Scan is initialized in Scan class and Scanner
takes care of the actual Scan and not initializing Scan objects anymore
Diffstat:
5 files changed, 104 insertions(+), 76 deletions(-)
diff --git a/src/main/java/com/it_jaros/jscanner/App.java b/src/main/java/com/it_jaros/jscanner/App.java
@@ -20,7 +20,7 @@ public class App {
ScanOptions options = parseOptions(args);
if (options == null) return;
try (Scanner scanner = new Scanner(options)) {
- Scan scan = scanner.createScan(options);
+ Scan scan = Scan.create(options);
addShutdownHook(scan, scanner, options);
scanner.runScan(scan);
printScanResults(scan, options);
diff --git a/src/main/java/com/it_jaros/jscanner/ProgressBar.java b/src/main/java/com/it_jaros/jscanner/ProgressBar.java
@@ -47,7 +47,9 @@ public class ProgressBar {
}
public void start() {
- if (quiet) { return; }
+ if (quiet) {
+ return;
+ }
System.out.print("Scanning targets...\n\n");
ui.scheduleAtFixedRate(() -> {
lock.lock();
@@ -58,7 +60,7 @@ public class ProgressBar {
private void drawProgress(boolean last) {
// find and print finished hosts one last time
- for (Iterator<Progress> it = hosts.iterator(); it.hasNext();) {
+ for (Iterator<Progress> it = hosts.iterator(); it.hasNext(); ) {
Progress p = it.next();
if (p.done().get() == p.total()) {
it.remove();
@@ -78,7 +80,7 @@ public class ProgressBar {
}
}
- public void submit(Progress progress) {
+ public void add(Progress progress) {
lock.lock();
hosts.add(progress);
lock.unlock();
@@ -106,7 +108,7 @@ public class ProgressBar {
String brackets = "[" + "#".repeat(filled) + "-".repeat(bracketWidth - filled) + "]";
int spacesBetween = terminalWidth - bracketWidth - (label + statsLeft + statsRight).length() - 5;
- return String.format("%s%s%s %s %s", label, " ".repeat(spacesBetween),statsLeft, brackets, statsRight);
+ return String.format("%s%s%s %s %s", label, " ".repeat(spacesBetween), statsLeft, brackets, statsRight);
}
public void stop() {
diff --git a/src/main/java/com/it_jaros/jscanner/Scan.java b/src/main/java/com/it_jaros/jscanner/Scan.java
@@ -1,14 +1,88 @@
package com.it_jaros.jscanner;
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
import java.util.List;
-public record Scan(List<String> hosts, String ports, ScanState state) {
+public class Scan {
+ private final List<String> hosts;
+ private final ScanState state;
+ private final String ports;
+ private final Counter counter;
+ private final ProgressBar progressBar;
+
+ private Scan(List<String> hosts, String ports, ScanState state, boolean quiet) {
+ this.hosts = hosts;
+ this.ports = ports;
+ this.state = state;
+ this.counter = new Counter();
+ this.progressBar = new ProgressBar(quiet);
+ }
public List<ScanResult> getResults() {
- return this.state.getSnapshotOfScanResults();
+ return this.state.getCopyOfResults();
}
public int getPeakConcurrentConnects() {
- return this.state.getCounter().max();
+ return counter.max();
+ }
+
+ public static Scan create(ScanOptions options) {
+ if (options.hostsFile() != null) {
+ return create(options.hostsFile(), options.ports(), options.quiet());
+ }
+ return create(options.hostsArgv(), options.ports(), options.quiet());
+ }
+
+ public static Scan create(String sourceFile, String ports, boolean quiet) {
+ try (BufferedReader reader = Files.newBufferedReader(Path.of(sourceFile))) {
+ return create(reader.lines().toList(), ports, quiet);
+ } catch (FileNotFoundException | NoSuchFileException e) {
+ System.out.printf("Could not open file %s%n", sourceFile);
+ } catch (IOException e) {
+ System.out.printf("Error while trying to read hosts: %s -> %s %n", e.getClass().getSimpleName(), e.getMessage());
+ }
+
+ return null;
+ }
+
+ public static Scan create(List<String> hostsArgv, String ports, boolean quiet) {
+ return new Scan(hostsArgv, ports, new ScanState(), quiet);
+ }
+
+ public List<String> getHosts() {
+ return hosts;
+ }
+
+ public String getPorts() {
+ return ports;
+ }
+
+ public void start() {
+ progressBar.start();
+ }
+
+ public void stop() {
+ progressBar.stop();
+ }
+
+ public void addHostProgress(Progress progress) {
+ progressBar.add(progress);
+ }
+
+ public void incSocketCounter() {
+ counter.inc();
+ }
+
+ public void decSocketCounter() {
+ counter.dec();
+ }
+
+ public void addScanResult(ScanResult scanResult) {
+ state.addScanResult(scanResult);
}
}
diff --git a/src/main/java/com/it_jaros/jscanner/ScanState.java b/src/main/java/com/it_jaros/jscanner/ScanState.java
@@ -4,32 +4,20 @@ import java.util.ArrayList;
import java.util.List;
public class ScanState {
- private final Counter counter;
- private final ProgressBar progressBar;
private final List<ScanResult> scanResult;
- public ScanState(Counter counter, ProgressBar progressBar) {
- this.counter = counter;
- this.progressBar = progressBar;
+ public ScanState() {
// scanResult can not be given from outside because this class controls full access to it
this.scanResult = new ArrayList<>();
}
- public Counter getCounter() {
- return counter;
- }
-
- public ProgressBar getProgressBar() {
- return progressBar;
- }
-
public void addScanResult(ScanResult newResult) {
synchronized (this.scanResult) {
this.scanResult.add(newResult);
}
}
- public List<ScanResult> getSnapshotOfScanResults() {
+ public List<ScanResult> getCopyOfResults() {
synchronized (scanResult) {
return List.copyOf(scanResult);
}
diff --git a/src/main/java/com/it_jaros/jscanner/Scanner.java b/src/main/java/com/it_jaros/jscanner/Scanner.java
@@ -1,20 +1,14 @@
package com.it_jaros.jscanner;
-import java.io.BufferedReader;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.*;
import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.LockSupport;
-import java.util.stream.Stream;
public class Scanner implements AutoCloseable {
@@ -25,7 +19,6 @@ public class Scanner implements AutoCloseable {
private final Semaphore socketLimit;
private final boolean bannerRecognition;
private final boolean disableOnlineCheck;
- private final boolean quiet;
private final int maxHostsLimit;
private final int maxWorkersPerHost;
private final int timeoutInMillis;
@@ -38,7 +31,6 @@ public class Scanner implements AutoCloseable {
int maxWorkersPerHost,
int maxHostsLimit,
boolean disableOnlineCheck,
- boolean quiet,
boolean bannerRecognition
) {
this.delayInNanos = TimeUnit.MILLISECONDS.toNanos(Math.max(0, delayInMillis));
@@ -46,52 +38,24 @@ public class Scanner implements AutoCloseable {
this.disableOnlineCheck = disableOnlineCheck;
this.maxHostsLimit = maxHostsLimit;
this.maxWorkersPerHost = maxWorkersPerHost;
- this.quiet = quiet;
this.socketLimit = new Semaphore(socketLimit);
this.timeoutInMillis = timeoutInMillis;
}
public Scanner(ScanOptions options) {
- this(options.openSocketLimit(), options.timeoutInMillis(), options.delayInMillis(), options.maxWorkersPerHost(), options.maxHostsLimit(), options.disableHostCheck(), options.quiet(), options.bannerRecognition());
+ this(options.openSocketLimit(), options.timeoutInMillis(), options.delayInMillis(), options.maxWorkersPerHost(), options.maxHostsLimit(), options.disableHostCheck(), options.bannerRecognition());
}
- public Scan createScan(ScanOptions options) {
- if (options.hostsFile() != null) {
- return createScan(options.hostsFile(), options.ports());
- }
- return createScan(options.hostsArgv(), options.ports());
- }
-
- public Scan createScan(String sourceFile, String ports) {
- try (BufferedReader reader = Files.newBufferedReader(Path.of(sourceFile))) {
- return createScan(reader.lines(), ports);
- } catch (FileNotFoundException | NoSuchFileException e) {
- System.out.printf("Could not open file %s%n", sourceFile);
- } catch (IOException e) {
- System.out.printf("Error while trying to read hosts: %s -> %s %n", e.getClass().getSimpleName(), e.getMessage());
- }
-
- return null;
- }
-
- public Scan createScan(Stream<String> hosts, String ports) {
- return new Scan(hosts.toList(), ports, new ScanState(new Counter(), new ProgressBar(quiet)));
- }
-
- public Scan createScan(List<String> hostsArgv, String ports) {
- return new Scan(hostsArgv, ports, new ScanState(new Counter(), new ProgressBar(quiet)));
- }
public List<ScanResult> runScan(Scan scan) {
if (scan == null) {
throw new IllegalArgumentException("Scan argument cannot be null");
}
- ScanState scanState = scan.state();
- scanState.getProgressBar().start();
+ scan.start();
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
CompletionService<ScanResult> completionService = new ExecutorCompletionService<>(executor);
- Queue<String> hostsInQueue = new ArrayDeque<>(scan.hosts());
+ Queue<String> hostsInQueue = new ArrayDeque<>(scan.getHosts());
int hostsToProcess = hostsInQueue.size();
int activeHostWorkers = 0;
@@ -100,7 +64,7 @@ public class Scanner implements AutoCloseable {
while (!hostsInQueue.isEmpty() && activeHostWorkers < maxHostsLimit && !cancelled) {
activeHostWorkers++;
final String host = hostsInQueue.poll();
- completionService.submit(() -> scanHostPorts(host, scan.ports(), scanState));
+ completionService.submit(() -> scanHostPorts(host, scan.getPorts(), scan));
}
try {
@@ -110,7 +74,7 @@ public class Scanner implements AutoCloseable {
}
activeHostWorkers--;
hostsToProcess--;
- scanState.addScanResult(finishedHost.get());
+ scan.addScanResult(finishedHost.get());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
@@ -123,30 +87,30 @@ public class Scanner implements AutoCloseable {
}
}
- scanState.getProgressBar().stop();
- return scanState.getSnapshotOfScanResults();
+ scan.stop();
+ return scan.getResults();
}
- private ScanResult scanHostPorts(String host, String ports, ScanState scanState) {
+ private ScanResult scanHostPorts(String host, String ports, Scan scan) {
PortRange portRange = new PortRange(ports);
- AtomicLong portSlotFactory = new AtomicLong(System.nanoTime());
BitSet openPorts = new BitSet(PortRange.MAX_PORT);
BitSet filteredPorts = new BitSet(PortRange.MAX_PORT);
- HashMap<Integer, ServiceType> bannerRecognition = new HashMap<>();
+ HashMap<Integer, ServiceType> serviceTypes = new HashMap<>();
// 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());
- scanState.getProgressBar().submit(progress);
+ scan.addHostProgress(progress);
if (!disableOnlineCheck && !checkHostOnline(host)) {
// Visually show that this host is basically done
progress.done().set(progress.total());
- return new ScanResult(host, openPorts, filteredPorts, bannerRecognition);
+ return new ScanResult(host, openPorts, filteredPorts, serviceTypes);
}
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
CompletionService<PortResult> completionService = new ExecutorCompletionService<>(executor);
List<Throwable> errors = new ArrayList<>();
+ AtomicLong portSlotFactory = new AtomicLong(System.nanoTime());
int activePerHostWorkers = 0;
int portsToProcess = portRange.getTotal();
while (portsToProcess > 0 && !cancelled) {
@@ -155,7 +119,7 @@ public class Scanner implements AutoCloseable {
final int currentPort = portRange.next();
completionService.submit(() -> {
waitForSlot(portSlotFactory);
- return checkPort(host, currentPort, scanState);
+ return checkPort(host, currentPort, scan);
});
}
@@ -174,7 +138,7 @@ public class Scanner implements AutoCloseable {
switch (portResult.getState()) {
case OPEN -> {
openPorts.set(portResult.getPort());
- bannerRecognition.put(portResult.getPort(), ServiceDetector.detect(portResult.getBanner()));
+ serviceTypes.put(portResult.getPort(), ServiceDetector.detect(portResult.getBanner()));
}
case FILTERED -> filteredPorts.set(portResult.getPort());
}
@@ -194,7 +158,7 @@ public class Scanner implements AutoCloseable {
}
}
- return new ScanResult(host, openPorts, filteredPorts, bannerRecognition);
+ return new ScanResult(host, openPorts, filteredPorts, serviceTypes);
}
private boolean checkHostOnline(String host) {
@@ -216,7 +180,7 @@ public class Scanner implements AutoCloseable {
}
}
- private PortResult checkPort(String host, int port, ScanState scanState) {
+ private PortResult checkPort(String host, int port, Scan scan) {
PortResult result = new PortResult();
result.setPort(port);
result.setState(PortState.UNKNOWN);
@@ -224,7 +188,7 @@ public class Scanner implements AutoCloseable {
// don't allow more sockets then specified
socketLimit.acquireUninterruptibly();
// count how many ports are concurrently checked
- scanState.getCounter().inc();
+ scan.incSocketCounter();
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(host, port), timeoutInMillis);
@@ -240,7 +204,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 {
- scanState.getCounter().dec();
+ scan.decSocketCounter();
socketLimit.release();
}