From 7946b8aae3427b180d28441f8a02ae6665ac215c Mon Sep 17 00:00:00 2001 From: Matthias Jaros Date: Mon, 16 Mar 2026 16:41:25 +0100 Subject: Renamed package --- .../java/com/it_jaros/network_scanner/Scanner.java | 148 +++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 src/main/java/com/it_jaros/network_scanner/Scanner.java (limited to 'src/main/java/com/it_jaros/network_scanner/Scanner.java') 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 scan(ScanOptions target) { + return scanTargets(target.targets(), target.ports()); + } + + public List scanTargets(List targets, String ports) { + List results = new ArrayList<>(); + progressBar.start(); + try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { + List> futures = new ArrayList<>(); + for (String target : targets) { + futures.add(executor.submit(() -> scanTarget(target, ports))); + } + + futures.forEach((Future 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> 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 errors = new ArrayList<>(); + for (Future 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; + } +} -- cgit v1.3.1