summaryrefslogtreecommitdiff
path: root/src/main/java/com/it_jaros/jscanner/Scanner.java
diff options
context:
space:
mode:
authorGravatar Matthias Jaros <jarlucmat@mailbox.org>2026-04-13 17:31:55 +0200
committerGravatar Matthias Jaros <jarlucmat@mailbox.org>2026-07-09 09:59:28 +0200
commit8b881560dd8884e8e54e1e64654f79749d9ff456 (patch)
treefaffd2348b9430bcda9591120a0aa392923e422c /src/main/java/com/it_jaros/jscanner/Scanner.java
parentb1bdcf02479f67a65f6176ed8a87b47925903e81 (diff)
Renamed package from network_scanner to jscanner
Diffstat (limited to 'src/main/java/com/it_jaros/jscanner/Scanner.java')
-rw-r--r--src/main/java/com/it_jaros/jscanner/Scanner.java209
1 files changed, 209 insertions, 0 deletions
diff --git a/src/main/java/com/it_jaros/jscanner/Scanner.java b/src/main/java/com/it_jaros/jscanner/Scanner.java
new file mode 100644
index 0000000..2fa1fd0
--- /dev/null
+++ b/src/main/java/com/it_jaros/jscanner/Scanner.java
@@ -0,0 +1,209 @@
+package com.it_jaros.jscanner;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.*;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+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;
+import java.util.stream.Stream;
+
+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<ScanResult> scan(ScanOptions options) {
+ if (options.hostsFile() != null) {
+ return scanHosts(options.hostsFile(), options.ports());
+ }
+ return scanHosts(options.hostsArgv(), options.ports());
+ }
+
+ public List<ScanResult> scanHosts(String sourceFile, String ports) {
+ try (BufferedReader reader = Files.newBufferedReader(Path.of(sourceFile))) {
+ return scanHosts(reader.lines(), ports);
+ } catch (FileNotFoundException | NoSuchFileException e) {
+ System.out.printf("Could not open file %s%n", sourceFile);
+ } catch (IOException e) {
+ System.out.printf("Error while trying to read hosts: %s -> %s %n", e.getClass().getSimpleName(), e.getMessage());
+ }
+
+ return List.of();
+ }
+
+ public List<ScanResult> scanHosts(List<String> hostsArgv, String ports) {
+ try (Stream<String> stream = hostsArgv.stream()){
+ return scanHosts(stream, ports);
+ } catch (IOException e) {
+ System.out.printf("Error while trying to read hosts: %s -> %s %n", e.getClass().getSimpleName(), e.getMessage());
+ }
+
+ return List.of();
+ }
+
+ public List<ScanResult> scanHosts(Stream<String> hosts, String ports) throws IOException {
+ List<ScanResult> results = new ArrayList<>();
+ progressBar.start();
+ try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
+ List<Future<ScanResult>> futures = new ArrayList<>();
+ Semaphore maxHosts = new Semaphore(maxHostsLimit);
+ hosts.forEach((final String host) -> {
+ maxHosts.acquireUninterruptibly();
+ futures.add(executor.submit(() -> {
+ try {
+ return scanHost(host, ports);
+ } finally {
+ maxHosts.release();
+ }
+ }));
+ });
+
+ 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("--------------------");
+ 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<Future<PortResult>> 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<Throwable> errors = new ArrayList<>();
+ for (Future<PortResult> 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;
+ }
+}