diff options
| -rw-r--r-- | src/main/java/com/it_jaros/jscanner/Scanner.java | 56 |
1 files changed, 34 insertions, 22 deletions
diff --git a/src/main/java/com/it_jaros/jscanner/Scanner.java b/src/main/java/com/it_jaros/jscanner/Scanner.java index 39fd584..6d3fd96 100644 --- a/src/main/java/com/it_jaros/jscanner/Scanner.java +++ b/src/main/java/com/it_jaros/jscanner/Scanner.java @@ -9,7 +9,6 @@ import java.util.*; 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.function.Consumer; import java.util.function.Function; @@ -198,7 +197,10 @@ public class Scanner implements AutoCloseable { cancel(); } + private final class ScanHostTask implements Callable<ScanResult> { + private final PortScanRateLimiter rateLimiter = new PortScanRateLimiter(); + private final Scan scan; private final String host; // input parameter @@ -223,11 +225,6 @@ public class Scanner implements AutoCloseable { } } - /** - * Scans the ports of a given host - * - * @return - */ private ScanResult scanHostPorts() { if (!disableOnlineCheck && !checkHostOnline(host)) { // Unreachable host @@ -241,12 +238,11 @@ public class Scanner implements AutoCloseable { } final PortRange portRange = new PortRange(scan.getPorts()); - final AtomicLong portSlotFactory = new AtomicLong(System.nanoTime()); // producer thread ProducerState<PortResult> state = startProducer( portRange.iterator(), maxWorkersPerHost, - port -> new ScanPortTask(host, port, scan, portSlotFactory), + port -> new ScanPortTask(host, port, scan, rateLimiter), scan.getThreadCounter() ); @@ -300,24 +296,49 @@ public class Scanner implements AutoCloseable { } } + /** + * Per-host rate limiter. Each ScanHostTask creates its own instance and + * shares it with all its ScanPortTasks via constructor. + * + * Java allows one inner class to access another's private members, so this works. + */ + private final class PortScanRateLimiter { + private final Object lock = new Object(); + private volatile long nextAllowedTime; + + void apply() throws InterruptedException { + if (delayInNanos <= 0) { + return; + } + synchronized (lock) { + long now = System.nanoTime(); + if (nextAllowedTime > now) { + Thread.sleep(Duration.ofNanos(nextAllowedTime - now)); + now = System.nanoTime(); // re-read after waking + } + nextAllowedTime = now + delayInNanos; + } + } + } + private final class ScanPortTask implements Callable<PortResult> { private final Scan scan; private final String host; private final int port; - private final AtomicLong portSlotFactory; + private final PortScanRateLimiter portScanRateLimiter; // per-host shared limiter - private ScanPortTask(String host, int port, Scan scan, AtomicLong portSlotFactory) { - this.scan = scan; + private ScanPortTask(String host, int port, Scan scan, PortScanRateLimiter portScanRateLimiter) { this.host = host; this.port = port; - this.portSlotFactory = portSlotFactory; + this.scan = scan; + this.portScanRateLimiter = portScanRateLimiter; } @Override public PortResult call() throws Exception { try { scan.getThreadCounter().inc(); - waitForSlot(portSlotFactory); + portScanRateLimiter.apply(); // per-host serial pacing if (cancelled) { return null; } @@ -327,15 +348,6 @@ public class Scanner implements AutoCloseable { } } - private void waitForSlot(AtomicLong scanSlotFactory) throws InterruptedException { - if (delayInNanos > 0) { - long slot = scanSlotFactory.getAndAdd(delayInNanos); - long wait = slot - System.nanoTime(); - if (wait > 0) - Thread.sleep(Duration.ofNanos(wait)); - } - } - private PortResult checkPort() throws InterruptedException { PortResult result = new PortResult(); result.setPort(port); |
