jscanner

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

commit 44f793206e7c1f0ccce8c63d2ae443129c0a4cba
parent 46d71fb4a8085fc6c094d74a99585db95dfa7722
Author: Matthias Jaros <jaros@mailbox.org>
Date:   Sun,  5 Jul 2026 07:01:51 +0200

Same optimization for scanHostPorts as for runScan. Moved producer into
same method using function and generics

Diffstat:
Msrc/main/java/com/it_jaros/jscanner/PortRange.java | 7+++++--
Msrc/main/java/com/it_jaros/jscanner/Scanner.java | 117+++++++++++++++++++++++++++++++++++++-------------------------------------------
2 files changed, 59 insertions(+), 65 deletions(-)

diff --git a/src/main/java/com/it_jaros/jscanner/PortRange.java b/src/main/java/com/it_jaros/jscanner/PortRange.java @@ -1,9 +1,10 @@ package com.it_jaros.jscanner; import java.util.BitSet; +import java.util.Iterator; import java.util.NoSuchElementException; -public class PortRange { +public class PortRange implements Iterator<Integer> { public static final int MIN_PORT = 1; public static final int MAX_PORT = 65535; @@ -79,7 +80,8 @@ public class PortRange { } } - public int next() { + @Override + public Integer next() { int p = availablePorts.nextSetBit(currentPortCursor); if (p > 0) { availablePorts.clear(p); @@ -91,6 +93,7 @@ public class PortRange { throw new NoSuchElementException("Reached end of port range"); } + @Override public boolean hasNext() { return availablePorts.nextSetBit(currentPortCursor) > 0; } diff --git a/src/main/java/com/it_jaros/jscanner/Scanner.java b/src/main/java/com/it_jaros/jscanner/Scanner.java @@ -10,10 +10,8 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; import java.util.function.Function; -import java.util.stream.Stream; public class Scanner implements AutoCloseable { @@ -67,7 +65,7 @@ public class Scanner implements AutoCloseable { scan.start(); // start producer thread - final ProducerState state = startProducer( + final ProducerState<ScanResult> state = startProducer( scan.getHosts().iterator(), maxHostsLimit, host -> () -> scanHostPorts(host, scan) @@ -109,35 +107,6 @@ public class Scanner implements AutoCloseable { return scan.getResults(); } - private ProducerState startProducer(Iterator<String> queue, int maxWorkers, Function<String, Callable<ScanResult>> taskFactory) { - final AtomicInteger inPipeline = new AtomicInteger(0); - final AtomicBoolean running = new AtomicBoolean(true); - final Semaphore activeWorkers = new Semaphore(maxWorkers); - CompletionService<ScanResult> completionService = new ExecutorCompletionService<>(executor); - executor.submit(() -> { - try { - while (queue.hasNext() && !cancelled) { - activeWorkers.acquire(); - // just in case while waiting something changed - if (cancelled) { - break; - } - - // here we are filling the completion service - // host <-> virtual thread - final String item = queue.next(); - inPipeline.incrementAndGet(); - completionService.submit(taskFactory.apply(item)); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } finally { - running.set(false); - } - }); - return new ProducerState(running, inPipeline, activeWorkers, completionService); - } - private ScanResult scanHostPorts(final String host, final Scan scan) { PortRange portRange = new PortRange(scan.getPorts()); BitSet openPorts = new BitSet(PortRange.MAX_PORT); @@ -154,41 +123,22 @@ public class Scanner implements AutoCloseable { return new ScanResult(host, openPorts, filteredPorts, serviceTypes); } - CompletionService<PortResult> completionService = new ExecutorCompletionService<>(executor); List<Throwable> errors = new ArrayList<>(); final AtomicLong portSlotFactory = new AtomicLong(System.nanoTime()); - final Semaphore activePerHostWorkers = new Semaphore(maxWorkersPerHost); - final AtomicInteger inPipeline = new AtomicInteger(0); - final AtomicBoolean producerWorking = new AtomicBoolean(true); - // producer thread - executor.submit(() -> { - try { - while (portRange.hasNext() && !cancelled) { - activePerHostWorkers.acquire(); - // Here we are filling the completion service - // port <-> virtual thread - final int currentPort = portRange.next(); - inPipeline.incrementAndGet(); - try { - completionService.submit(() -> { - waitForSlot(portSlotFactory); - return checkPort(host, currentPort, scan); - }); - } catch (RuntimeException e) { - inPipeline.decrementAndGet(); - throw e; - } + ProducerState<PortResult> state = startProducer( + portRange, + maxWorkersPerHost, + port -> () -> { + waitForSlot(portSlotFactory); + return checkPort(host, port, scan); } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } finally { - producerWorking.set(false); - } - }); + ); + CompletionService<PortResult> completionService = state.completionService(); + // consumer is the main thread - while ((producerWorking.get() && !cancelled) || inPipeline.get() > 0) { + while ((state.running().get() && !cancelled) || state.inPipeline().get() > 0) { try { Future<PortResult> portResultFuture = completionService.poll(10, TimeUnit.MILLISECONDS); if (portResultFuture == null) { @@ -214,8 +164,8 @@ public class Scanner implements AutoCloseable { } } } finally { - activePerHostWorkers.release(); - inPipeline.decrementAndGet(); + state.activeWorkers().release(); + state.inPipeline().decrementAndGet(); progress.done().incrementAndGet(); } } catch (InterruptedException ignored) { @@ -232,6 +182,47 @@ public class Scanner implements AutoCloseable { return new ScanResult(host, openPorts, filteredPorts, serviceTypes); } + /** + * This method helps to cleanup the code a bit and remove redundancy + * The producer for providing hosts and the one for providing ports + * are similar and the small differences can be handled using a function + * + * @param queue + * @param maxWorkers + * @param taskFactory + * @param <INPUT> + * @param <OUTPUT> + * @return + */ + private <INPUT, OUTPUT> ProducerState startProducer(Iterator<INPUT> queue, int maxWorkers, Function<INPUT, Callable<OUTPUT>> taskFactory) { + final AtomicInteger inPipeline = new AtomicInteger(0); + final AtomicBoolean running = new AtomicBoolean(true); + final Semaphore activeWorkers = new Semaphore(maxWorkers); + CompletionService<OUTPUT> completionService = new ExecutorCompletionService<>(executor); + executor.submit(() -> { + try { + while (queue.hasNext() && !cancelled) { + activeWorkers.acquire(); + // just in case while waiting something changed + if (cancelled) { + break; + } + + // here we are filling the completion service + // host <-> virtual thread + final INPUT item = queue.next(); + inPipeline.incrementAndGet(); + completionService.submit(taskFactory.apply(item)); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + running.set(false); + } + }); + return new ProducerState<>(running, inPipeline, activeWorkers, completionService); + } + private boolean checkHostOnline(String host) { try { return InetAddress.getByName(host).isReachable(timeoutInMillis);