diff options
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.java | 148 |
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; + } +} |
