jscanner

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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:
Msrc/main/java/com/it_jaros/jscanner/App.java | 2+-
Msrc/main/java/com/it_jaros/jscanner/ProgressBar.java | 10++++++----
Msrc/main/java/com/it_jaros/jscanner/Scan.java | 80++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/main/java/com/it_jaros/jscanner/ScanState.java | 16++--------------
Msrc/main/java/com/it_jaros/jscanner/Scanner.java | 72++++++++++++++++++------------------------------------------------------
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(); }