summaryrefslogtreecommitdiff
path: root/src/main/java/com/it_jaros/network_scanner/Scanner.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/it_jaros/network_scanner/Scanner.java')
-rw-r--r--src/main/java/com/it_jaros/network_scanner/Scanner.java148
1 files changed, 148 insertions, 0 deletions
diff --git a/src/main/java/com/it_jaros/network_scanner/Scanner.java b/src/main/java/com/it_jaros/network_scanner/Scanner.java
new file mode 100644
index 0000000..81cbe1b
--- /dev/null
+++ b/src/main/java/com/it_jaros/network_scanner/Scanner.java
@@ -0,0 +1,148 @@
+package com.it_jaros.network_scanner;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.LockSupport;
+
+public class Scanner {
+
+ private final Counter counter = new Counter();
+ private final ProgressBar progressBar = new ProgressBar();
+ private final Semaphore socketLimit;
+ private final int timeoutInMillis;
+ private final long delayInNanos;
+ private final int maxWorkersPerHost;
+
+ public Scanner(int socketLimit, int timeoutInMillis, int delayInMillis, int maxWorkersPerHost) {
+ this.timeoutInMillis = timeoutInMillis;
+ this.delayInNanos = TimeUnit.MILLISECONDS.toNanos(Math.max(0, delayInMillis));
+ this.socketLimit = new Semaphore(socketLimit);
+ this.maxWorkersPerHost = maxWorkersPerHost;
+ }
+
+ public List<ScanResult> scan(ScanOptions target) {
+ return scanTargets(target.targets(), target.ports());
+ }
+
+ public List<ScanResult> scanTargets(List<String> targets, String ports) {
+ List<ScanResult> results = new ArrayList<>();
+ progressBar.start();
+ try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
+ List<Future<ScanResult>> futures = new ArrayList<>();
+ for (String target : targets) {
+ futures.add(executor.submit(() -> scanTarget(target, ports)));
+ }
+
+ futures.forEach((Future<ScanResult> f) -> {
+ try {
+ results.add(f.get());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ } catch (ExecutionException e) {
+ System.out.printf("%s -> %s%n", e.getClass().getSimpleName(), e.getMessage());
+ }
+ });
+ }
+
+ progressBar.stop();
+ System.out.println("Peak concurrent connects: " + counter.max());
+ return results;
+ }
+
+ private ScanResult scanTarget(String target, 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);
+
+ Progress progress = new Progress(target, portRange.getTotal(), new AtomicInteger(), new AtomicInteger());
+ progressBar.submit(progress);
+
+ try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
+ List<Future<Integer>> futures = new ArrayList<>();
+ while (portRange.hasNext()) {
+ maxWorkers.acquireUninterruptibly();
+ final int currentPort = portRange.next();
+ futures.add(executor.submit(() -> {
+ try {
+ waitForSlot(portSlotFactory);
+ int port = scanPort(target, currentPort);
+ if (port != -1) {
+ progress.open().incrementAndGet();
+ }
+ return port;
+ } finally {
+ maxWorkers.release();
+ progress.done().incrementAndGet();
+ }
+ }));
+ }
+
+ List<Throwable> errors = new ArrayList<>();
+ for (Future<Integer> f : futures) {
+ try {
+ int port = f.get();
+ if (port != -1) {
+ // openPorts is not threadsafe, so we do not update it outside the worker threads
+ openPorts.set(port);
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ } catch (ExecutionException e) {
+ errors.add(e);
+ }
+ }
+
+ if (!errors.isEmpty()) {
+ System.out.printf("Errors happened during scan of target %s%nErrors:%s -> %s", target, errors.size(), errors);
+ }
+ }
+
+ return new ScanResult(target, openPorts);
+ }
+
+ private int scanPort(String target, final int currentPort) {
+ return isTargetPortOpen(target, currentPort) ? currentPort : -1;
+ }
+
+ private void waitForSlot(AtomicLong scanSlotFactory) {
+ if (delayInNanos > 0) {
+ long slot = scanSlotFactory.getAndAdd(delayInNanos);
+ long wait = slot - System.nanoTime();
+ if (wait > 0)
+ LockSupport.parkNanos(wait);
+ }
+ }
+
+ private boolean isTargetPortOpen(String target, int port) {
+ socketLimit.acquireUninterruptibly();
+ counter.inc();
+ try (Socket socket = new Socket()) {
+ socket.connect(new InetSocketAddress(target, port), timeoutInMillis);
+ return true;
+ } catch (SocketException | SocketTimeoutException ignored) {
+ // Will happen a lot when scanning for open ports, so not needed
+ } catch (IOException e) {
+ System.out.printf("%s -> %s", e.getClass().getSimpleName(), e.getMessage());
+ } finally {
+ counter.dec();
+ socketLimit.release();
+ }
+
+ return false;
+ }
+}