jscanner

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

commit 1080e5e415824804fab289e04a67a00081ed372d
parent d4780f5d5efbb95f7035e5bea6cb957ac131bd6d
Author: Matthias Jaros <jaros@mailbox.org>
Date:   Wed,  1 Jul 2026 18:12:19 +0200

Added CompletionService

Use while loop to fill efficiently all available possible virtual threads before starting to check for results

Diffstat:
Msrc/main/java/com/it_jaros/jscanner/Scanner.java | 104+++++++++++++++++++++++++++++++++++++++++++-------------------------------------
1 file changed, 56 insertions(+), 48 deletions(-)

diff --git a/src/main/java/com/it_jaros/jscanner/Scanner.java b/src/main/java/com/it_jaros/jscanner/Scanner.java @@ -9,14 +9,12 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.HashMap; -import java.util.List; +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.Collectors; import java.util.stream.Stream; public class Scanner { @@ -59,7 +57,7 @@ public class Scanner { } public List<ScanResult> scanHosts(List<String> hostsArgv, String ports) { - try (Stream<String> stream = hostsArgv.stream()){ + try (Stream<String> stream = hostsArgv.stream()) { return scanHosts(stream, ports); } catch (IOException e) { System.out.printf("Error while trying to read hosts: %s -> %s %n", e.getClass().getSimpleName(), e.getMessage()); @@ -72,28 +70,33 @@ public class Scanner { List<ScanResult> results = new ArrayList<>(); progressBar.start(); try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { - List<Future<ScanResult>> futures = new ArrayList<>(); - Semaphore maxHosts = new Semaphore(maxHostsLimit); - hosts.forEach((final String host) -> { - maxHosts.acquireUninterruptibly(); - futures.add(executor.submit(() -> { - try { - return scanHost(host, ports); - } finally { - maxHosts.release(); - } - })); - }); + CompletionService<ScanResult> completionService = new ExecutorCompletionService<>(executor); + Queue<String> hostsInQueue = hosts.collect(Collectors.toCollection(ArrayDeque::new)); + + int hostsToProcess = hostsInQueue.size(); + int activeHostWorkers = 0; + while (hostsToProcess > 0) { + // process queue + while (!hostsInQueue.isEmpty() && activeHostWorkers <= maxHostsLimit) { + activeHostWorkers++; + final String host = hostsInQueue.poll(); + completionService.submit(() -> scanHost(host, ports)); + } - futures.forEach((Future<ScanResult> f) -> { try { - results.add(f.get()); + Future<ScanResult> finishedHost = completionService.poll(10, TimeUnit.MILLISECONDS); + if (finishedHost == null) { + continue; + } + activeHostWorkers--; + hostsToProcess--; + results.add(finishedHost.get()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (ExecutionException e) { System.out.printf("%s -> %s%n", e.getClass().getSimpleName(), e.getMessage()); } - }); + } } progressBar.stop(); @@ -108,49 +111,55 @@ public class Scanner { private ScanResult scanHost(String host, String ports) { PortRange portRange = new PortRange(ports); AtomicLong portSlotFactory = new AtomicLong(System.nanoTime()); - Semaphore maxWorkers = new Semaphore(maxWorkersPerHost); BitSet openPorts = new BitSet(PortRange.MAX_PORT); BitSet filteredPorts = new BitSet(PortRange.MAX_PORT); - HashMap<Integer, ServiceType> bannerRecognition = new HashMap<Integer, ServiceType>(); + HashMap<Integer, ServiceType> bannerRecognition = 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()); progressBar.submit(progress); - if(!disableOnlineCheck && !checkHostOnline(host)) { + if (!disableOnlineCheck && !checkHostOnline(host)) { // Visually show that this host is basically done progress.done().set(progress.total()); return new ScanResult(host, openPorts, filteredPorts, bannerRecognition); } try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { - List<Future<PortResult>> futures = new ArrayList<>(); - while (portRange.hasNext()) { - maxWorkers.acquireUninterruptibly(); - final int currentPort = portRange.next(); - futures.add(executor.submit(() -> { - try { + CompletionService<PortResult> completionService = new ExecutorCompletionService<>(executor); + List<Throwable> errors = new ArrayList<>(); + int activePerHostWorkers = 0; + int portsToProcess = portRange.getTotal(); + while (portsToProcess > 0) { + while (portRange.hasNext() && activePerHostWorkers <= maxWorkersPerHost) { + activePerHostWorkers++; + final int currentPort = portRange.next(); + completionService.submit(() -> { waitForSlot(portSlotFactory); return checkPort(host, currentPort); - } finally { - maxWorkers.release(); - progress.done().incrementAndGet(); - } - })); - } + }); + } - List<Throwable> errors = new ArrayList<>(); - for (Future<PortResult> f : futures) { try { + Future<PortResult> f = completionService.poll(10, TimeUnit.MILLISECONDS); + if (f == null) { + continue; + } + activePerHostWorkers--; + portsToProcess--; + progress.done().incrementAndGet(); + // bitset is not thread-safe, so it is set // outside the other virtual threads that update progress PortResult portResult = f.get(); - if (portResult.getState().equals(PortState.OPEN)) { - openPorts.set(portResult.getPort()); - bannerRecognition.put(portResult.getPort(), ServiceDetector.detect(portResult.getBanner())); - } else if (portResult.getState().equals(PortState.FILTERED)) { - filteredPorts.set(portResult.getPort()); + switch (portResult.getState()) { + case OPEN -> { + openPorts.set(portResult.getPort()); + bannerRecognition.put(portResult.getPort(), ServiceDetector.detect(portResult.getBanner())); + } + case FILTERED -> filteredPorts.set(portResult.getPort()); } - } catch (InterruptedException e) { + } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); } catch (ExecutionException e) { errors.add(e); @@ -194,15 +203,14 @@ public class Scanner { socket.connect(new InetSocketAddress(host, port), timeoutInMillis); result.setBanner(getBanner(socket)); result.setState(PortState.OPEN); - } catch (NoRouteToHostException ignored) { - // this can be safely ignored because the port is closed if a host is unreachable } catch (SocketTimeoutException ignored) { result.setState(PortState.FILTERED); } catch (ConnectException ignored) { result.setState(PortState.CLOSED); } catch (IOException ignored) { + // 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 { + } finally { counter.dec(); socketLimit.release(); } @@ -213,13 +221,13 @@ public class Scanner { private String getBanner(Socket socket) { int READ_BUFFER_SIZE = 1024; byte[] buffer = new byte[READ_BUFFER_SIZE]; - try(InputStream input = socket.getInputStream()) { + try (InputStream input = socket.getInputStream()) { socket.setSoTimeout(timeoutInMillis); int bytesRead = input.read(buffer); if (bytesRead <= 0) { return null; } - return new String(buffer,0, bytesRead, StandardCharsets.UTF_8).trim(); + return new String(buffer, 0, bytesRead, StandardCharsets.UTF_8).trim(); } catch (IOException e) { // we ignore this failure }