summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/com/it_jaros/jscanner/Scanner.java56
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);