package com.it_jaros.network_scanner; import java.io.IOException; import java.net.*; import java.util.ArrayList; import java.util.BitSet; import java.util.List; import java.util.concurrent.*; 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; private final int maxHostsLimit; private final boolean disableHostCheck; public Scanner(int socketLimit, int timeoutInMillis, int delayInMillis, int maxWorkersPerHost, int maxHostsLimit, boolean disableHostCheck) { this.timeoutInMillis = timeoutInMillis; this.delayInNanos = TimeUnit.MILLISECONDS.toNanos(Math.max(0, delayInMillis)); this.socketLimit = new Semaphore(socketLimit); this.maxWorkersPerHost = maxWorkersPerHost; this.maxHostsLimit = maxHostsLimit; this.disableHostCheck = disableHostCheck; } public List scan(ScanOptions options) { return scanHosts(options.targetHosts(), options.ports()); } public List scanHosts(List targetHosts, String ports) { List results = new ArrayList<>(); progressBar.start(); try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { List> futures = new ArrayList<>(); Semaphore maxHosts = new Semaphore(maxHostsLimit); for (String host : targetHosts) { maxHosts.acquireUninterruptibly(); futures.add(executor.submit(() -> { try { return scanHost(host, ports); } finally { maxHosts.release(); } })); } 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("--------------------"); System.out.println("Finished scan!"); System.out.println("Stats:"); System.out.println("Peak concurrent connects: " + counter.max()); System.out.println("--------------------"); return results; } private ScanResult scanHost(String host, 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); BitSet filteredPorts = new BitSet(PortRange.MAX_PORT); Progress progress = new Progress(host, portRange.getTotal(), new AtomicInteger(), new AtomicInteger(), new AtomicInteger()); progressBar.submit(progress); if(!disableHostCheck && !checkHostOnline(host)) { // Visually show that this host is basically done progress.done().set(progress.total()); return new ScanResult(host, openPorts, filteredPorts); } 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); PortState state = getPortState(host, currentPort); if (state.equals(PortState.OPEN)) { progress.open().incrementAndGet(); } else if (state.equals(PortState.FILTERED)) { progress.filtered().incrementAndGet(); } return new PortResult(currentPort, state); } finally { maxWorkers.release(); progress.done().incrementAndGet(); } })); } List errors = new ArrayList<>(); for (Future f : futures) { try { // bitset is not thread-safe, so it is set // outside the other virtual threads that update progress PortResult portResult = f.get(); if (portResult.state().equals(PortState.OPEN)) { openPorts.set(portResult.port()); } else if (portResult.state().equals(PortState.FILTERED)) { filteredPorts.set(portResult.port()); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (ExecutionException e) { errors.add(e); } } if (!errors.isEmpty()) { System.out.printf("Errors happened during scan of host %s%nErrors:%s -> %s", host, errors.size(), errors); } } return new ScanResult(host, openPorts, filteredPorts); } private boolean checkHostOnline(String host) { try { return InetAddress.getByName(host).isReachable(timeoutInMillis); } catch (IOException e) { // something went wrong } return false; } 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 PortState getPortState(String host, int port) { socketLimit.acquireUninterruptibly(); counter.inc(); try (Socket socket = new Socket()) { socket.connect(new InetSocketAddress(host, port), timeoutInMillis); return PortState.OPEN; } catch (NoRouteToHostException ignored) { // this can be safely ignored because the port is closed if a host is unreachable } catch (SocketTimeoutException ignored) { return PortState.FILTERED; } catch (ConnectException ignored) { return PortState.CLOSED; } catch (IOException ignored) { // Will happen a lot when scanning for open ports, so not needed } finally { counter.dec(); socketLimit.release(); } return PortState.CLOSED; } }