summaryrefslogtreecommitdiff
path: root/src/main/java/com/it_jaros/jscanner
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/it_jaros/jscanner')
-rw-r--r--src/main/java/com/it_jaros/jscanner/App.java100
-rw-r--r--src/main/java/com/it_jaros/jscanner/CliDefaults.java18
-rw-r--r--src/main/java/com/it_jaros/jscanner/CliParser.java143
-rw-r--r--src/main/java/com/it_jaros/jscanner/CliPrinter.java258
-rw-r--r--src/main/java/com/it_jaros/jscanner/HelpRequested.java7
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/Counter.java31
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/ExceptionInfo.java13
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/Scan.java173
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/ScanException.java11
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/ScanOptions.java18
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/Scanner.java108
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/domain/PortList.java27
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/domain/PortRange.java80
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/domain/PortResult.java48
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/domain/PortState.java8
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/domain/ScanFailure.java9
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/domain/ScanResult.java26
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/engine/CancelledToken.java15
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/engine/PollState.java13
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/engine/PortRangeIterator.java38
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/engine/PortResultAccumulator.java49
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/engine/PortScanRateLimiter.java37
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/engine/ProducerState.java13
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/engine/ProducerThread.java83
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/engine/ScanExecutionContext.java16
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/engine/ScanHostTask.java123
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/engine/ScanPortTask.java135
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/service/ServiceDetector.java47
-rw-r--r--src/main/java/com/it_jaros/jscanner/scan/service/ServiceType.java5
29 files changed, 0 insertions, 1652 deletions
diff --git a/src/main/java/com/it_jaros/jscanner/App.java b/src/main/java/com/it_jaros/jscanner/App.java
deleted file mode 100644
index fd38ae0..0000000
--- a/src/main/java/com/it_jaros/jscanner/App.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package com.it_jaros.jscanner;
-
-import com.it_jaros.jscanner.scan.Scan;
-import com.it_jaros.jscanner.scan.ScanOptions;
-import com.it_jaros.jscanner.scan.Scanner;
-
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-
-public class App {
-
- private static Thread shutdownHook;
- private static final CountDownLatch shutdownLatch = new CountDownLatch(1);
- private static int exitCode = 0;
-
- public static void main(String[] args) {
- try {
- run(args);
- } catch (HelpRequested e) {
- // Do nothing
- } catch (Exception e) {
- System.err.println(e.getMessage());
- exitCode = 1;
- } finally {
- shutdownLatch.countDown();
- removeShutdownHook();
- }
-
- System.exit(exitCode);
- }
-
- private static void run(String[] args) throws Exception {
- ScanOptions options = parseOptions(args);
- try (Scanner scanner = new Scanner(options)) {
- runScan(scanner, options);
- }
- }
-
- private static void runScan(Scanner scanner, ScanOptions options) throws IOException {
- addShutdownHook(scanner);
- Scan scan = Scan.create(options);
- CliPrinter progressBar = new CliPrinter(scan, options.quiet());
- progressBar.start();
- scanner.runScan(scan, result -> progressBar.printResult(result, options.showFilteredPorts()));
- progressBar.stop();
- }
-
- private static ScanOptions parseOptions(String[] args) {
- CliParser cliParser = new CliParser();
- try {
- return cliParser.parseArgs(args);
- } catch (IllegalArgumentException e) {
- cliParser.printUsageHelp();
- throw e;
- }
- }
-
- /**
- * Notice user that the signal was received
- * Start shutdown of scanner
- *
- * @param scanner
- */
- private static void addShutdownHook(Scanner scanner) {
- shutdownHook = new Thread(() -> {
- System.err.printf("%nCtrl-C received. Shutting down...%n");
- shutdown(scanner);
- });
- Runtime.getRuntime().addShutdownHook(shutdownHook);
- }
-
- /**
- * Try to gracefully shutdown scanner
- * and wait
- *
- * @param scanner
- */
- private static void shutdown(Scanner scanner) {
- try {
- scanner.cancel();
- shutdownLatch.await();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- } catch (Exception e) {
- // Time ran out
- }
- }
-
- private static void removeShutdownHook() {
- if (shutdownHook == null) {
- return;
- }
-
- try {
- Runtime.getRuntime().removeShutdownHook(shutdownHook);
- } catch (IllegalStateException ignored) {
- // JVM is already shutting down
- }
- }
-}
diff --git a/src/main/java/com/it_jaros/jscanner/CliDefaults.java b/src/main/java/com/it_jaros/jscanner/CliDefaults.java
deleted file mode 100644
index 655a9ed..0000000
--- a/src/main/java/com/it_jaros/jscanner/CliDefaults.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.it_jaros.jscanner;
-
-public class CliDefaults {
- private CliDefaults() {
- /* This utility class should not be instantiated */
- }
-
- public static final String PORTS = "1-1024";
- public static final boolean BANNER_RECOGNITION = false;
- public static final boolean DISABLE_HOST_CHECK = false;
- public static final boolean QUIET = false;
- public static final boolean SHOW_FILTERED_PORTS = false;
- public static final int DELAY_IN_MILLIS = 0;
- public static final int MAX_HOSTS_LIMIT = 100;
- public static final int MAX_WORKERS_PER_HOST = Integer.MAX_VALUE;
- public static final int SOCKET_LIMIT = 2048;
- public static final int TIMEOUT_IN_MILLIS = 3000;
-}
diff --git a/src/main/java/com/it_jaros/jscanner/CliParser.java b/src/main/java/com/it_jaros/jscanner/CliParser.java
deleted file mode 100644
index 7e27064..0000000
--- a/src/main/java/com/it_jaros/jscanner/CliParser.java
+++ /dev/null
@@ -1,143 +0,0 @@
-package com.it_jaros.jscanner;
-
-import com.it_jaros.jscanner.scan.ScanOptions;
-
-import java.util.HashSet;
-import java.util.Set;
-
-public class CliParser {
-
- public ScanOptions parseArgs(String[] args) {
- if (args.length < 1 || args[0].trim().isEmpty()) {
- throw new IllegalArgumentException("No argument given");
- }
-
- Set<String> hostsArgv = new HashSet<>();
- String hostsFile = null;
- String ports = CliDefaults.PORTS;
- boolean bannerRecognition = CliDefaults.BANNER_RECOGNITION;
- boolean disableOnlineCheck = CliDefaults.DISABLE_HOST_CHECK;
- boolean quiet = CliDefaults.QUIET;
- boolean showFilteredPorts = CliDefaults.SHOW_FILTERED_PORTS;
- int delayInMillis = CliDefaults.DELAY_IN_MILLIS;
- int hostsLimit = CliDefaults.MAX_HOSTS_LIMIT;
- int workersPerHostLimit = CliDefaults.MAX_WORKERS_PER_HOST;
- int socketLimit = CliDefaults.SOCKET_LIMIT;
- int timeoutInMillis = CliDefaults.TIMEOUT_IN_MILLIS;
-
- for (int i = 0; i < args.length; i++) {
- final String currentArg = args[i];
- if (!currentArg.startsWith("-")) {
- hostsArgv.add(currentArg);
- continue;
- }
-
- switch (currentArg) {
- case "--bannerRecognition", "-b":
- bannerRecognition = true;
- break;
- case "--delay", "-d":
- delayInMillis = parseIntOption(args, ++i, currentArg);
- break;
- case "--disableOnlineCheck", "-do":
- disableOnlineCheck = true;
- break;
- case "--help", "-h":
- printUsageHelp();
- throw new HelpRequested();
- case "--input", "-i":
- hostsFile = getArgument(args, ++i, currentArg);
- break;
- case "--hostsLimit", "-hl":
- hostsLimit = parseIntOption(args, ++i, currentArg);
- break;
- case "--workersPerHostLimit", "-whl":
- workersPerHostLimit = parseIntOption(args, ++i, currentArg);
- break;
- case "--ports", "-p":
- ports = getArgument(args, ++i, currentArg);
- break;
- case "--quiet", "-q":
- quiet = true;
- break;
- case "--showFilteredPorts", "-sf":
- showFilteredPorts = true;
- break;
- case "--socketLimit", "-sl":
- socketLimit = parseIntOption(args, ++i, currentArg);
- break;
- case "--timeout", "-t":
- timeoutInMillis = parseIntOption(args, ++i, currentArg);
- break;
- default:
- throw new IllegalArgumentException("No such param " + currentArg);
- }
- }
-
- if (hostsArgv.isEmpty() && hostsFile == null) {
- throw new IllegalArgumentException("No input provided. Check with --help for available options.");
- }
-
- return new ScanOptions(
- hostsArgv.stream().toList(),
- hostsFile,
- ports,
- bannerRecognition,
- disableOnlineCheck,
- quiet,
- showFilteredPorts,
- delayInMillis,
- hostsLimit,
- workersPerHostLimit,
- socketLimit,
- timeoutInMillis
- );
- }
-
- private String getArgument(String[] args, int index, String option) {
- if (index >= args.length) {
- throw new IllegalArgumentException(String.format("Missing argument for option %s", option));
- }
- return args[index];
- }
-
- private int parseIntOption(String[] args, int index, String option) {
- String value = getArgument(args, index, option);
- try {
- return Integer.parseInt(value);
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException(String.format("Invalid argument %s for option %s", value, option));
- }
- }
-
- public void printUsageHelp() {
- StringBuilder usage = new StringBuilder();
- usage.append("""
- Usage:
- java -jar jscanner.jar [OPTIONS] [<host>,...]
-
- Options:
- """);
- usage.append("""
- --bannerRecognition, -b:\t\tEnable banner recognition to find out which service is running behind port (Default: %b)
- --delay, -d:\t\t\tWaiting period in millis for connection attempts for a host (Default: %d)
- --disableOnlineCheck, -do:\t\tDon't check if Host is reachable (Default: %b)
- --input, -i:\t\t\tRead file to get host targets to scan. If set to "-" than Stdin is read (Default: null)
- --hostsLimit, -hl:\t\t\tMax number of hosts that are scanned in parallel (Default: %d)
- --workersPerHostLimit, -whl:\tMax workers per host. (Default: %d)
- --ports, -p:\t\t\tDefine ports to be scanned (Default: %s)
- --quiet, -q:\t\t\tDo not output progress bar (Default: %b)
- --showFilteredPorts, -sf:\t\tShow also filtered ports (Default: %b)
- --socketLimit, -sl:\t\t\tSocket limit in total. You can not scan more ports than allowed here (Default: %d)
- --timeout, -t:\t\t\tTimeout in millis per connection attempt, 0 means infinite (Default: %d)
- """.formatted(CliDefaults.BANNER_RECOGNITION, CliDefaults.DELAY_IN_MILLIS, CliDefaults.DISABLE_HOST_CHECK, CliDefaults.MAX_HOSTS_LIMIT, CliDefaults.MAX_WORKERS_PER_HOST, CliDefaults.PORTS, CliDefaults.QUIET, CliDefaults.SHOW_FILTERED_PORTS, CliDefaults.SOCKET_LIMIT, CliDefaults.TIMEOUT_IN_MILLIS)
- );
- usage.append("""
-
- Examples:
- java -jar jscanner.jar localhost
- java -jar jscanner.jar fd00::1
- """);
- System.err.println(usage);
- }
-}
diff --git a/src/main/java/com/it_jaros/jscanner/CliPrinter.java b/src/main/java/com/it_jaros/jscanner/CliPrinter.java
deleted file mode 100644
index 709c3dd..0000000
--- a/src/main/java/com/it_jaros/jscanner/CliPrinter.java
+++ /dev/null
@@ -1,258 +0,0 @@
-package com.it_jaros.jscanner;
-
-import com.it_jaros.jscanner.scan.Scan;
-import com.it_jaros.jscanner.scan.domain.ScanFailure;
-import com.it_jaros.jscanner.scan.domain.ScanResult;
-import com.it_jaros.jscanner.scan.service.ServiceType;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-public class CliPrinter {
-
- private final ScheduledExecutorService ui = Executors.newSingleThreadScheduledExecutor();
- private final Scan scan;
- private final boolean quiet;
- private final Object outputLock = new Object();
-
- public CliPrinter(Scan scan, boolean quiet) {
- this.scan = scan;
- this.quiet = quiet;
- }
-
- private void clearStatusLine() {
- System.err.print("\r\033[2K");
- System.err.flush();
- }
-
- public void printResult(ScanResult result, boolean showFilteredPorts) {
- synchronized (outputLock) {
- if (result.openPorts().isEmpty()
- && (!showFilteredPorts || result.filteredPorts().isEmpty())
- && result.errors().isEmpty()) {
- return;
- }
-
- clearStatusLine();
- printErrors(result);
- printPortsResults(result, showFilteredPorts);
- printStatusLine();
- }
- }
-
- private void printErrors(ScanResult result) {
- if (result.errors().isEmpty()) return;
-
- StringBuilder sb = new StringBuilder();
- sb.append(String.format(
- "%s | errors: %d%n",
- result.host(),
- result.errors().size()
- ));
- String errors = result.errors().stream().map((ScanFailure error) -> String.format(
- "\t%s: %s, %s",
- error.port(),
- error.exception().type(),
- error.exception().message()
- )).collect(Collectors.joining("\n"));
- sb.append(errors);
- sb.append("\n");
-
- System.err.println(sb);
- System.err.flush();
- }
-
- private void printPortsResults(ScanResult result, boolean showFilteredPorts) {
- if (result.openPorts().isEmpty() && (!showFilteredPorts || result.filteredPorts().isEmpty())) {
- return;
- }
-
- StringBuilder sb = new StringBuilder();
- sb.append(result.host());
- sb.append("\n");
- if (!result.openPorts().isEmpty()) {
- sb.append(
- String.format("\t (%d) open:\t%s%n", result.openPorts().size(), map(result.openPorts().getPorts(), result.bannerRecognition()))
- );
- }
- if (showFilteredPorts && !result.filteredPorts().isEmpty()) {
- sb.append(
- String.format("\t (%d) filtered:\t%s%n", result.filteredPorts().size(), map(result.filteredPorts().getPorts()))
- );
- }
- sb.append("\n");
- System.out.println(sb);
- System.out.flush();
- }
-
- private static String map(List<Integer> ports) {
- return ports.stream().map(String::valueOf).collect(Collectors.joining(","));
- }
-
- private static String map(List<Integer> ports, Map<Integer, ServiceType> bannerRecognition) {
- return ports.stream().map(port -> {
- ServiceType serviceType = bannerRecognition.getOrDefault(port, ServiceType.UNKNOWN);
-
- if (serviceType == ServiceType.UNKNOWN) {
- return String.valueOf(port);
- }
-
- return port + "/" + serviceType.name().toLowerCase();
- }).collect(Collectors.joining(","));
- }
-
- private static int getColumnWidth() {
- try {
- Process process = new ProcessBuilder("sh", "-c", "stty size < /dev/tty").start();
-
- try (BufferedReader reader = new BufferedReader(
- new InputStreamReader(process.getInputStream()))) {
- String line = reader.readLine();
- if (line != null && !line.isBlank()) {
- Matcher matcher = Pattern.compile("^\\d+\\s+(\\d+)").matcher(line.trim());
- if (matcher.matches()) {
- return Integer.parseInt(matcher.group(1));
- }
- }
- }
- } catch (Exception ignored) {
- // does not matter why it did not work
- }
-
- return 120;
- }
-
- public void start() {
- if (quiet) {
- return;
- }
-
- // although we don't print anything, we still need to
- // gc all the done hosts
- System.err.print("Scanning targets...\n\n");
- ui.scheduleAtFixedRate(() -> {
- synchronized (outputLock) {
- printStatusLine();
- }
- }, 1, 1000, TimeUnit.MILLISECONDS);
- }
-
- public void stop() {
- ui.shutdownNow();
- try {
- ui.awaitTermination(1, TimeUnit.SECONDS);
- } catch (InterruptedException ignored) {
- Thread.currentThread().interrupt();
- }
- clearStatusLine();
- printStats();
- }
-
- private void printStats() {
- Scan.Statistics stats = scan.getStatistics();
- System.err.println("\n--------------------");
- System.err.println("Stats:");
- System.err.println("Total hosts: " + stats.hostTotal());
- System.err.println("Total ports: " + stats.portTotal());
- System.err.println("Duration: " + formatDuration(stats.durationInMillis()));
- System.err.println("Peak concurrent hosts: " + stats.hostMaxConcurrent());
- System.err.println("Peak concurrent sockets: " + stats.socketMaxConcurrent());
- System.err.println("Peak concurrent workers: " + stats.threadMaxConcurrent());
- System.err.println("--------------------\n");
- }
-
- private static String formatDuration(long millis) {
- if (millis <= 0) return "";
- long seconds = millis / 1000 % 60;
- long minutes = millis / 60000 % 60;
- long hours = millis / 3600000 % 24;
- long days = millis / 86400000;
-
- if (days > 0) return String.format("%dd %dh:%dm:%ds", days, hours, minutes, seconds);
- if (hours > 0) return String.format("%dh:%dm:%ds", hours, minutes, seconds);
- if (minutes > 0) return String.format("%dm:%ds", minutes, seconds);
- return String.format("%ds", seconds);
- }
-
-
- private void printStatusLine() {
- Scan.Statistics stats = scan.getStatistics();
- int total = stats.hostTotal();
- int running = stats.hostCurrent();
- int done = total - running;
- long percent = total == 0 ? 100 : done * 100L / total;
-
- String hostStat = String.format(
- "hosts: %d running, %d done",
- running,
- done
- );
- String portStat = String.format(
- " | ports: %d total",
- stats.portTotal()
- );
- String durationStat = String.format(
- " | %s",
- formatDuration(stats.durationInMillis())
- );
- String socketStat = String.format(
- " | sockets: %d active, %d peak",
- stats.socketCurrent(),
- stats.socketMaxConcurrent()
- );
- String workerStat = String.format(
- " | workers: %d active, %d peak",
- stats.threadCurrent(),
- stats.threadMaxConcurrent()
- );
-
- int availableWidth = CliPrinter.getColumnWidth();
- String statusLine = appendIfEnoughSpace("", availableWidth, hostStat, portStat, durationStat, socketStat, workerStat);
-
- String barFormat = " [%s] %3d%%";
- int remainingWidth = availableWidth - statusLine.length() - barFormat.length();
- if (remainingWidth > 0) {
- String progressBar = String.format(
- barFormat,
- progressBar(percent, remainingWidth),
- percent
- );
- statusLine = appendIfEnoughSpace(statusLine, availableWidth, progressBar);
- }
-
- remainingWidth = availableWidth - statusLine.length();
- if (remainingWidth > 0) {
- statusLine += " ".repeat(remainingWidth);
- }
- System.err.print("\r" + statusLine);
- System.err.flush();
- }
-
- private String appendIfEnoughSpace(String current, int availableSize, String... additional) {
- StringBuilder builder = new StringBuilder(current);
- for (String add : additional) {
- if ((builder.length() + add.length()) <= availableSize) {
- builder.append(add);
- }
- }
-
- return builder.toString();
- }
-
- private static String progressBar(long percent, int width) {
- if (width <= 0) {
- return "";
- }
-
- int filled = (int) (percent * width / 100);
- return "#".repeat(filled) + "-".repeat(width - filled);
- }
-}
diff --git a/src/main/java/com/it_jaros/jscanner/HelpRequested.java b/src/main/java/com/it_jaros/jscanner/HelpRequested.java
deleted file mode 100644
index ec97a2a..0000000
--- a/src/main/java/com/it_jaros/jscanner/HelpRequested.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.it_jaros.jscanner;
-
-public class HelpRequested extends RuntimeException {
- public HelpRequested() {
- super();
- }
-}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/Counter.java b/src/main/java/com/it_jaros/jscanner/scan/Counter.java
deleted file mode 100644
index 0e1d5c9..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/Counter.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.it_jaros.jscanner.scan;
-
-import java.util.concurrent.atomic.AtomicInteger;
-
-public final class Counter {
- private final AtomicInteger current = new AtomicInteger(0);
- private final AtomicInteger max = new AtomicInteger(0);
- private final AtomicInteger total = new AtomicInteger(0);
-
- public void inc() {
- total.incrementAndGet();
- int now = current.incrementAndGet();
- max.accumulateAndGet(now, Math::max);
- }
-
- public void dec() {
- current.decrementAndGet();
- }
-
- public int current() {
- return current.get();
- }
-
- public int max() {
- return max.get();
- }
-
- public int total() {
- return total.get();
- }
-}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/ExceptionInfo.java b/src/main/java/com/it_jaros/jscanner/scan/ExceptionInfo.java
deleted file mode 100644
index f62caf1..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/ExceptionInfo.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.it_jaros.jscanner.scan;
-
-public record ExceptionInfo(
- String type,
- String message
-) {
- public static ExceptionInfo from(Throwable t) {
- return new ExceptionInfo(
- t.getClass().getName(),
- t.getMessage()
- );
- }
-} \ No newline at end of file
diff --git a/src/main/java/com/it_jaros/jscanner/scan/Scan.java b/src/main/java/com/it_jaros/jscanner/scan/Scan.java
deleted file mode 100644
index 0820abc..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/Scan.java
+++ /dev/null
@@ -1,173 +0,0 @@
-package com.it_jaros.jscanner.scan;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.List;
-import java.util.stream.Stream;
-
-/**
- * Object holder for stateful volatile data during scan
- */
-public class Scan {
- private final Stream<String> hosts;
- private final String ports;
- private final Runnable onDone;
- private final Counter hostCounter;
- private final Counter portCounter;
- private final Counter socketCounter;
- private final Counter threadCounter;
- private long start = 0;
- private long stop = 0;
-
- private Scan(Stream<String> hosts, String ports) {
- this(hosts, ports, () -> {});
- }
-
- private Scan(Stream<String> hosts, String ports, Runnable onDone) {
- this.hosts = hosts;
- this.ports = ports;
- this.onDone = onDone;
- this.hostCounter = new Counter();
- this.portCounter = new Counter();
- this.socketCounter = new Counter();
- this.threadCounter = new Counter();
- }
-
- public static Scan create(ScanOptions options) throws IOException {
- if (options.hostsFile() != null) {
- return create(options.hostsFile(), options.ports());
- }
- return create(options.hostsArgv(), options.ports());
- }
-
- /**
- * Create Scan based on either a source file or stdin
- *
- * @param sourceFile
- * @param ports
- * @return
- * @throws IOException
- */
- public static Scan create(String sourceFile, String ports) {
- Stream<String> hosts;
- if (sourceFile == null || sourceFile.isEmpty()) {
- throw new IllegalArgumentException("Source file is null or empty");
- }
-
- if ("-".equals(sourceFile)) {
- BufferedReader stdinReader = new BufferedReader(new InputStreamReader(System.in));
- hosts = stdinReader.lines();
- return new Scan(hosts, ports);
- }
-
- try {
- BufferedReader reader = Files.newBufferedReader(Path.of(sourceFile));
- return new Scan(reader.lines(), ports, () -> {
- try {
- reader.close();
- } catch (IOException ignore) {
- // we ignore it because at this moment the program is shutting down anyway
- }
- });
- } catch (IOException e) {
- throw new ScanException("Error while trying to open source File", e);
- }
- }
-
- public static Scan create(List<String> hostsArgv, String ports) {
- return new Scan(hostsArgv.stream(), ports);
- }
-
- public Stream<String> getHosts() {
- return hosts;
- }
-
- public String getPorts() {
- return ports;
- }
-
- public void stop() {
- stop = System.currentTimeMillis();
- onDone.run();
- }
-
- public void start() {
- start = System.currentTimeMillis();
- }
-
- private long getDurationMillis() {
- if (stop == 0) {
- if (start == 0) {
- return 0;
- }
- return System.currentTimeMillis() - start;
- }
- return stop - start;
- }
-
- public void portStart() {
- this.portCounter.inc();
- this.threadCounter.inc();
- this.socketCounter.inc();
- }
-
- public void portFinish() {
- this.portCounter.dec();
- this.threadCounter.dec();
- this.socketCounter.dec();
- }
-
- public void hostStart() {
- this.hostCounter.inc();
- this.threadCounter.inc();
- }
-
- public void hostFinish() {
- this.threadCounter.dec();
- this.hostCounter.dec();
- }
-
- public void producerStart() {
- this.threadCounter.inc();
- }
-
- public void producerStop() {
- this.threadCounter.dec();
- }
-
- /**
- * This is not an atomic 100% correct snapshot but
- * more a relative snapshot optimized more for performance
- * than exactness
- */
- public Statistics getStatistics() {
- return new Statistics(
- this.getDurationMillis(),
- this.hostCounter.total(),
- this.hostCounter.current(),
- this.hostCounter.max(),
- this.portCounter.total(),
- this.portCounter.current(),
- this.threadCounter.current(),
- this.threadCounter.max(),
- this.socketCounter.current(),
- this.socketCounter.max()
- );
- }
-
- public record Statistics(
- long durationInMillis,
- int hostTotal,
- int hostCurrent,
- int hostMaxConcurrent,
- int portTotal,
- int portCurrent,
- int threadCurrent,
- int threadMaxConcurrent,
- int socketCurrent,
- int socketMaxConcurrent
- ) {}
-}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/ScanException.java b/src/main/java/com/it_jaros/jscanner/scan/ScanException.java
deleted file mode 100644
index d667346..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/ScanException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.it_jaros.jscanner.scan;
-
-public class ScanException extends java.lang.RuntimeException {
- public ScanException(String message) {
- super(message);
- }
-
- public ScanException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/ScanOptions.java b/src/main/java/com/it_jaros/jscanner/scan/ScanOptions.java
deleted file mode 100644
index ce0d562..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/ScanOptions.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.it_jaros.jscanner.scan;
-
-import java.util.List;
-
-public record ScanOptions(
- List<String> hostsArgv,
- String hostsFile,
- String ports,
- boolean bannerRecognition,
- boolean disableOnlineCheck,
- boolean quiet,
- boolean showFilteredPorts,
- int delayInMillis,
- int maxHostsLimit,
- int maxWorkersPerHost,
- int socketLimit,
- int timeoutInMillis
-) {}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/Scanner.java b/src/main/java/com/it_jaros/jscanner/scan/Scanner.java
deleted file mode 100644
index 4c47aef..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/Scanner.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package com.it_jaros.jscanner.scan;
-
-import com.it_jaros.jscanner.scan.domain.ScanResult;
-import com.it_jaros.jscanner.scan.engine.*;
-
-import java.time.Duration;
-import java.util.concurrent.*;
-import java.util.function.Consumer;
-
-public class Scanner implements AutoCloseable {
-
- private final CancelledToken cancelledToken = new CancelledToken();
- private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
- private final ScanOptions scanOptions;
- private final Semaphore socketLimit;
-
- public Scanner(ScanOptions options) {
- this.scanOptions = options;
- this.socketLimit = new Semaphore(scanOptions.socketLimit());
- }
-
- /**
- * Starts a given scan.
- *
- * @param scan
- */
- public void runScan(final Scan scan, final Consumer<ScanResult> consumer) {
- if (scan == null) {
- throw new IllegalArgumentException("Scan argument cannot be null");
- }
-
- long delayInNanos = TimeUnit.MILLISECONDS.toNanos(Math.max(0, scanOptions.delayInMillis()));
- ScanExecutionContext context = new ScanExecutionContext(
- scanOptions,
- executor,
- socketLimit,
- cancelledToken,
- scan,
- new PortScanRateLimiter(cancelledToken, delayInNanos)
- );
-
- scan.start();
- scan.producerStart();
- final ProducerState<ScanResult> state = new ProducerThread(context).startProducer(
- scan.getHosts().iterator(),
- scanOptions.maxHostsLimit(),
- host -> new ScanHostTask(scan, host, context)
- );
-
- // the main thread is the consumer
- // Let the consumer run as long as the producer runs
- // or if still tasks are pending in pipeline
- // we do not listen to canceled here because we want
- // all results (also partial) collected for the consumer
- // with whatever is there already
- while (state.running().get() || state.inPipeline().get() > 0) {
- try {
- PollState<ScanResult> poll = getHostResult(state);
- if (poll instanceof PollState.Success<ScanResult>(ScanResult value)) {
- consumer.accept(value);
- } else if (poll instanceof PollState.Failure<ScanResult>(Throwable error)) {
- System.err.printf("runScan(): ScanHostTask() failed with error %s -> %s%n", error.getClass().getSimpleName(), error.getMessage());
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
- scan.producerStop();
- scan.stop();
- }
-
- private PollState<ScanResult> getHostResult(ProducerState<ScanResult> state) throws InterruptedException {
- Future<ScanResult> finishedHost = state.completionService().poll(ProducerThread.pollInterval.toMillis(), TimeUnit.MILLISECONDS);
- if (finishedHost == null) {
- return new PollState.Unavailable<>();
- }
-
- try {
- ScanResult result = finishedHost.get();
- return new PollState.Success<>(result);
- } catch (ExecutionException e) {
- return new PollState.Failure<>(e.getCause());
- } finally {
- state.activeWorkers().release();
- state.inPipeline().decrementAndGet();
- }
- }
-
- public boolean awaitTermination(Duration duration) throws InterruptedException {
- return executor.awaitTermination(duration.toMillis(), TimeUnit.MILLISECONDS);
- }
-
- public void cancel() {
- this.cancelledToken.signal();
- executor.shutdown();
- }
-
- public void cancelNow() {
- this.cancelledToken.signal();
- executor.shutdownNow();
- }
-
- @Override
- public void close() throws Exception {
- cancel();
- }
-
-}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/domain/PortList.java b/src/main/java/com/it_jaros/jscanner/scan/domain/PortList.java
deleted file mode 100644
index f145700..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/domain/PortList.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.it_jaros.jscanner.scan.domain;
-
-import java.util.BitSet;
-import java.util.List;
-
-/**
- * Encapsulates a port scan result and is immutable
- */
-public class PortList {
- private final BitSet ports;
-
- public PortList(BitSet ports) {
- this.ports = ports;
- }
-
- public List<Integer> getPorts() {
- return ports.stream().boxed().toList();
- }
-
- public boolean isEmpty() {
- return ports.isEmpty();
- }
-
- public int size() {
- return ports.cardinality();
- }
-}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/domain/PortRange.java b/src/main/java/com/it_jaros/jscanner/scan/domain/PortRange.java
deleted file mode 100644
index 4434763..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/domain/PortRange.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package com.it_jaros.jscanner.scan.domain;
-
-import com.it_jaros.jscanner.scan.engine.PortRangeIterator;
-
-import java.util.BitSet;
-import java.util.Iterator;
-
-public class PortRange {
-
- public static final int MIN_PORT = 1;
- public static final int MAX_PORT = 65535;
- private final BitSet specifiedPorts = new BitSet(MAX_PORT);
-
- public PortRange(String ports) {
- if (ports == null || "".equals(ports)) {
- specifiedPorts.set(MIN_PORT, 1024);
- } else if ("all".equalsIgnoreCase(ports)) {
- specifiedPorts.set(MIN_PORT, MAX_PORT + 1);
- } else {
- parsePortRange(ports);
- }
- }
-
- /**
- * Possible values are 1,10 or 1-10 or a mix of 1,5-10
- * So we treat , stronger and handle them first and then check for -
- *
- * @param ports
- */
- private void parsePortRange(String ports) {
- String[] splitComma = ports.split(",");
- for (String commaValue : splitComma) {
- String[] rangeValue = commaValue.split("-");
-
- // more then two values are not possible
- if (rangeValue.length > 2) {
- throw new IllegalArgumentException(String.format("Argument contains too many '-'' %s", commaValue));
- }
-
- // no range given, only single port
- int port = Integer.parseInt(rangeValue[0]);
- if (rangeValue.length == 1) {
- checkValue(port);
- specifiedPorts.set(port);
- continue;
- }
-
- // range given
- int end = Integer.parseInt(rangeValue[1]);
- checkValues(port, end);
- specifiedPorts.set(port, end + 1);
- }
- }
-
- private void checkValues(int start, int end) {
- checkValue(start);
- checkValue(end);
-
- if (start >= end) {
- throw new IllegalArgumentException(
- String.format("Start value cannot be equal or bigger than end value '%s >= %s'", start, end));
- }
- }
-
- private void checkValue(int port) {
- if (port < MIN_PORT) {
- throw new IllegalArgumentException(
- String.format("Port value is too low: %s < %s", port, MIN_PORT));
- }
-
- if (port > MAX_PORT) {
- throw new IllegalArgumentException(
- String.format("Port value is too high: %s > %s", port, MAX_PORT));
- }
- }
-
- public Iterator<Integer> iterator() {
- return new PortRangeIterator(specifiedPorts);
- }
-} \ No newline at end of file
diff --git a/src/main/java/com/it_jaros/jscanner/scan/domain/PortResult.java b/src/main/java/com/it_jaros/jscanner/scan/domain/PortResult.java
deleted file mode 100644
index 9e70cc5..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/domain/PortResult.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.it_jaros.jscanner.scan.domain;
-
-import java.io.IOException;
-
-public class PortResult {
- private int port;
- private PortState state;
- private byte[] banner;
- private Exception exception;
-
- public int getPort() {
- return port;
- }
-
- public void setPort(int port) {
- this.port = port;
- }
-
- public PortState getState() {
- return state;
- }
-
- public void setState(PortState state) {
- this.state = state;
- }
-
- public byte[] getBanner() {
- return banner;
- }
-
- public void setBanner(byte[] banner) {
- this.banner = banner;
- }
-
- public void setException(IOException e) {
- exception = e;
- }
-
- public Exception getException() {
- return exception;
- }
-
- public static PortResult empty() {
- PortResult portResult = new PortResult();
- portResult.setState(PortState.UNKNOWN);
- return portResult;
- }
-}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/domain/PortState.java b/src/main/java/com/it_jaros/jscanner/scan/domain/PortState.java
deleted file mode 100644
index 6db4b2e..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/domain/PortState.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.it_jaros.jscanner.scan.domain;
-
-public enum PortState {
- CLOSED,
- FILTERED,
- OPEN,
- UNKNOWN
-}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/domain/ScanFailure.java b/src/main/java/com/it_jaros/jscanner/scan/domain/ScanFailure.java
deleted file mode 100644
index db33465..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/domain/ScanFailure.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.it_jaros.jscanner.scan.domain;
-
-import com.it_jaros.jscanner.scan.ExceptionInfo;
-
-public record ScanFailure(
- Integer port,
- ExceptionInfo exception
-) {
-}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/domain/ScanResult.java b/src/main/java/com/it_jaros/jscanner/scan/domain/ScanResult.java
deleted file mode 100644
index e18e75d..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/domain/ScanResult.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.it_jaros.jscanner.scan.domain;
-
-import com.it_jaros.jscanner.scan.service.ServiceType;
-
-import java.util.BitSet;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-public record ScanResult(
- String host,
- PortList openPorts,
- PortList filteredPorts,
- Map<Integer, ServiceType> bannerRecognition,
- List<ScanFailure> errors
-) {
- public static ScanResult empty(String host) {
- return new ScanResult(
- host,
- new PortList(new BitSet(PortRange.MAX_PORT)),
- new PortList(new BitSet(PortRange.MAX_PORT)),
- Collections.emptyMap(),
- Collections.emptyList()
- );
- }
-}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/CancelledToken.java b/src/main/java/com/it_jaros/jscanner/scan/engine/CancelledToken.java
deleted file mode 100644
index 57993e5..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/engine/CancelledToken.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.it_jaros.jscanner.scan.engine;
-
-public class CancelledToken {
- private volatile boolean cancelled = false;
-
- public void signal() {
- if (!cancelled) {
- this.cancelled = true;
- }
- }
-
- public boolean isCancelled() {
- return cancelled;
- }
-}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/PollState.java b/src/main/java/com/it_jaros/jscanner/scan/engine/PollState.java
deleted file mode 100644
index 54ec767..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/engine/PollState.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.it_jaros.jscanner.scan.engine;
-
-/**
- * A sealed interface representing the three possible outcomes of a CompletionService
- * poll operation: successful result, task failure, or not ready yet.
- * Makes error handling explicit — Failure and Unavailable are distinct and compile-time
- * required to handle via exhaustiveness checking in switch statements.
- */
-public sealed interface PollState<T> {
- record Success<T>(T value) implements PollState<T> {}
- record Failure<T>(Throwable error) implements PollState<T> {}
- record Unavailable<T>() implements PollState<T> {}
-}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/PortRangeIterator.java b/src/main/java/com/it_jaros/jscanner/scan/engine/PortRangeIterator.java
deleted file mode 100644
index 0673f0f..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/engine/PortRangeIterator.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.it_jaros.jscanner.scan.engine;
-
-import java.util.BitSet;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-import static com.it_jaros.jscanner.scan.domain.PortRange.MAX_PORT;
-import static com.it_jaros.jscanner.scan.domain.PortRange.MIN_PORT;
-
-public class PortRangeIterator implements Iterator<Integer> {
-
- private int currentPortCursor;
- private int done = 0;
- private final BitSet availablePorts = new BitSet(MAX_PORT);
-
- public PortRangeIterator(BitSet specifiedPorts) {
- availablePorts.or(specifiedPorts);
- currentPortCursor = availablePorts.nextSetBit(MIN_PORT);
- }
-
- @Override
- public Integer next() {
- int p = availablePorts.nextSetBit(currentPortCursor);
- if (p > 0) {
- availablePorts.clear(p);
- currentPortCursor = p + 1;
- done++;
- return p;
- }
-
- throw new NoSuchElementException("Reached end of port range");
- }
-
- @Override
- public boolean hasNext() {
- return availablePorts.nextSetBit(currentPortCursor) > 0;
- }
-}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/PortResultAccumulator.java b/src/main/java/com/it_jaros/jscanner/scan/engine/PortResultAccumulator.java
deleted file mode 100644
index 0cc26b0..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/engine/PortResultAccumulator.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.it_jaros.jscanner.scan.engine;
-
-import com.it_jaros.jscanner.scan.ExceptionInfo;
-import com.it_jaros.jscanner.scan.domain.*;
-import com.it_jaros.jscanner.scan.service.ServiceDetector;
-import com.it_jaros.jscanner.scan.service.ServiceType;
-
-import java.util.*;
-
-final class PortResultAccumulator {
- private final String host;
- private final BitSet openPorts = new BitSet(PortRange.MAX_PORT);
- private final BitSet filteredPorts = new BitSet(PortRange.MAX_PORT);
- private final Map<Integer, ServiceType> serviceTypes = new HashMap<>();
- private final List<ScanFailure> scanFailures = new ArrayList<>();
-
- PortResultAccumulator(String host) {
- this.host = host;
- }
-
- void add(PortResult portResult) {
- switch (portResult.getState()) {
- case OPEN -> {
- openPorts.set(portResult.getPort());
- serviceTypes.put(portResult.getPort(), ServiceDetector.detect(portResult.getBanner()));
- }
- case FILTERED -> {
- filteredPorts.set(portResult.getPort());
- }
- default -> {
- // intentional no-op for uncovered port states
- }
- }
- Exception e = portResult.getException();
- if (e != null) {
- scanFailures.add(new ScanFailure(portResult.getPort(), ExceptionInfo.from(e)));
- }
- }
-
- ScanResult build() {
- return new ScanResult(
- host,
- new PortList(openPorts),
- new PortList(filteredPorts),
- Collections.unmodifiableMap(serviceTypes),
- Collections.unmodifiableList(scanFailures)
- );
- }
-}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/PortScanRateLimiter.java b/src/main/java/com/it_jaros/jscanner/scan/engine/PortScanRateLimiter.java
deleted file mode 100644
index 92b3bcb..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/engine/PortScanRateLimiter.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.it_jaros.jscanner.scan.engine;
-
-import java.util.concurrent.locks.LockSupport;
-
-/**
- * Per-host rate limiter. Each ScanHostTask creates its own instance and
- * shares it with all its ScanPortTasks via constructor.
- *
- * Java allows one inner class to access another's private members, so this works.
- */
-public class PortScanRateLimiter {
- private final Object lock = new Object();
- private volatile long nextAllowedTime;
- private final long delayInNanos;
- private final CancelledToken cancelledToken;
-
- public PortScanRateLimiter(CancelledToken cancelledToken, long delayInNanos) {
- this.cancelledToken = cancelledToken;
- this.delayInNanos = delayInNanos;
- }
-
- void apply() { if (delayInNanos <= 0) {
- return;
- }
- synchronized (lock) {
- if (cancelledToken.isCancelled()) {
- return;
- }
- long now = System.nanoTime();
- if (nextAllowedTime > now) {
- LockSupport.parkNanos(nextAllowedTime - now);
- now = System.nanoTime(); // re-read after waking
- }
- nextAllowedTime = now + delayInNanos;
- }
- }
-}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/ProducerState.java b/src/main/java/com/it_jaros/jscanner/scan/engine/ProducerState.java
deleted file mode 100644
index c49ea3e..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/engine/ProducerState.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.it_jaros.jscanner.scan.engine;
-
-import java.util.concurrent.CompletionService;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-public record ProducerState<OUTPUT>(
- AtomicBoolean running,
- AtomicInteger inPipeline,
- Semaphore activeWorkers,
- CompletionService<OUTPUT> completionService
-) {}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/ProducerThread.java b/src/main/java/com/it_jaros/jscanner/scan/engine/ProducerThread.java
deleted file mode 100644
index e7bcbcc..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/engine/ProducerThread.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package com.it_jaros.jscanner.scan.engine;
-
-import java.time.Duration;
-import java.util.Iterator;
-import java.util.concurrent.*;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Function;
-
-public class ProducerThread {
-
- public static final Duration pollInterval = Duration.ofSeconds(1);
-
- private final ExecutorService executor;
- private final CancelledToken cancelledToken;
-
- public ProducerThread(ScanExecutionContext context) {
- this.executor = context.executorService();
- this.cancelledToken = context.cancelledToken();
- }
-
- /**
- * This method helps to cleanup the code a bit and remove redundancy
- * The producer for providing hosts and the one for providing ports
- * are similar and the small differences can be handled using a function
- *
- * @param queue
- * @param maxWorkers
- * @param taskFactory
- * @param <INPUT>
- * @param <OUTPUT>
- * @return
- */
- public <INPUT, OUTPUT> ProducerState<OUTPUT> startProducer(
- Iterator<INPUT> queue,
- int maxWorkers,
- Function<INPUT, Callable<OUTPUT>> taskFactory
- ) {
- final AtomicInteger inPipeline = new AtomicInteger(0);
- final AtomicBoolean running = new AtomicBoolean(true);
- final Semaphore activeWorkers = new Semaphore(maxWorkers);
- CompletionService<OUTPUT> completionService = new ExecutorCompletionService<>(executor);
- executor.submit(() -> {
- try {
- while (!cancelledToken.isCancelled() && queue.hasNext()) {
- // get semaphore and remember if task got submitted
- // so in case we fail to submit we release the semaphore
- activeWorkers.acquire();
- boolean isTaskSubmitted = false;
- try {
- // just in case something
- // changed while waiting
- if (cancelledToken.isCancelled()) {
- break;
- }
-
- // get next item and create callable
- // using lambda expression
- final INPUT item = queue.next();
- Callable<OUTPUT> task = taskFactory.apply(item);
- inPipeline.incrementAndGet();
- try {
- completionService.submit(task);
- isTaskSubmitted = true;
- } catch (Throwable e) {
- inPipeline.decrementAndGet();
- throw e;
- }
- } finally {
- if (!isTaskSubmitted) {
- activeWorkers.release();
- }
- }
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- } finally {
- running.set(false);
- }
- });
- return new ProducerState<>(running, inPipeline, activeWorkers, completionService);
- }
-}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/ScanExecutionContext.java b/src/main/java/com/it_jaros/jscanner/scan/engine/ScanExecutionContext.java
deleted file mode 100644
index 38e9cdb..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/engine/ScanExecutionContext.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.it_jaros.jscanner.scan.engine;
-
-import com.it_jaros.jscanner.scan.Scan;
-import com.it_jaros.jscanner.scan.ScanOptions;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Semaphore;
-
-public record ScanExecutionContext(
- ScanOptions scanOptions,
- ExecutorService executorService,
- Semaphore socketLimit,
- CancelledToken cancelledToken,
- Scan scan,
- PortScanRateLimiter portScanRateLimiter
-) {}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/ScanHostTask.java b/src/main/java/com/it_jaros/jscanner/scan/engine/ScanHostTask.java
deleted file mode 100644
index 7a01d54..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/engine/ScanHostTask.java
+++ /dev/null
@@ -1,123 +0,0 @@
-package com.it_jaros.jscanner.scan.engine;
-
-import com.it_jaros.jscanner.scan.Scan;
-import com.it_jaros.jscanner.scan.domain.PortRange;
-import com.it_jaros.jscanner.scan.domain.PortResult;
-import com.it_jaros.jscanner.scan.domain.ScanResult;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.LockSupport;
-
-public class ScanHostTask implements Callable<ScanResult> {
-
- private final Scan scan;
- private final String host; // input parameter
- private final ScanExecutionContext context;
- private final CancelledToken cancelledToken;
- private final boolean disableOnlineCheck;
- private final int maxWorkersPerHost;
- private final int timeoutInMillis;
- private final long delayInNanos;
-
- public ScanHostTask(Scan scan, String host, ScanExecutionContext context) {
- this.scan = scan;
- this.host = host;
- this.context = context;
- this.cancelledToken = context.cancelledToken();
- this.delayInNanos = TimeUnit.MILLISECONDS.toNanos(Math.max(0, context.scanOptions().delayInMillis()));
- this.disableOnlineCheck = context.scanOptions().disableOnlineCheck();
- this.maxWorkersPerHost = context.scanOptions().maxWorkersPerHost();
- this.timeoutInMillis = context.scanOptions().timeoutInMillis();
- }
-
- @Override
- public ScanResult call() {
- try {
- scan.hostStart();
- if (context.cancelledToken().isCancelled()) {
- return ScanResult.empty(host);
- }
- return scanHostPorts();
- } finally {
- scan.hostFinish();
- }
- }
-
- private ScanResult scanHostPorts() {
- if (!disableOnlineCheck) {
- boolean isHostOnline = checkHostOnline();
- if (!isHostOnline) {
- // Unreachable host
- return ScanResult.empty(host);
- }
- // online check also sends packets to the target system.
- // in order not to violate set delay time
- // we wait here too
- LockSupport.parkNanos(delayInNanos);
- }
-
- final PortRange portRange = new PortRange(scan.getPorts());
- // producer thread
- scan.producerStart();
- ProducerState<PortResult> state = new ProducerThread(context).startProducer(
- portRange.iterator(),
- maxWorkersPerHost,
- port -> new ScanPortTask(host, port, context)
- );
-
- // consumer is the main thread
- // we run as long as the producer is running OR
- // as long as things are in pipeline waiting to be processed
- // ONLY exception is when cancelled is set
- final PortResultAccumulator accumulator = new PortResultAccumulator(host);
- while (!cancelledToken.isCancelled() && (state.running().get() || state.inPipeline().get() > 0)) {
- try {
- PollState<PortResult> poll = getPortResult(state);
- if (poll instanceof PollState.Success<PortResult>(PortResult value)) {
- accumulator.add(value);
- } else if (poll instanceof PollState.Failure(Throwable error)) {
- System.err.printf("scanHostPorts(%s): ScanPortTask() failed for with error %s -> %s%n", host, error.getClass().getSimpleName(), error.getMessage());
- }
- } catch (InterruptedException ignored) {
- Thread.currentThread().interrupt();
- }
- }
- scan.producerStop();
-
- return accumulator.build();
- }
-
- private boolean checkHostOnline() {
- try {
- return InetAddress.getByName(host).isReachable(timeoutInMillis);
- } catch (IOException e) {
- // we ignore this error because it means that the host is probably not online
- }
-
- return false;
- }
-
- private PollState<PortResult> getPortResult(ProducerState<PortResult> state) throws InterruptedException {
- Future<PortResult> portResultFuture = state.completionService().poll(ProducerThread.pollInterval.toMillis(), TimeUnit.MILLISECONDS);
- if (portResultFuture == null) {
- return new PollState.Unavailable<>();
- }
-
- PortResult portResult;
- try {
- portResult = portResultFuture.get();
- return new PollState.Success<>(portResult);
- } catch (ExecutionException e) {
- Throwable cause = e.getCause();
- return new PollState.Failure<>(cause);
- } finally {
- state.activeWorkers().release();
- state.inPipeline().decrementAndGet();
- }
- }
-}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/ScanPortTask.java b/src/main/java/com/it_jaros/jscanner/scan/engine/ScanPortTask.java
deleted file mode 100644
index 873ec95..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/engine/ScanPortTask.java
+++ /dev/null
@@ -1,135 +0,0 @@
-package com.it_jaros.jscanner.scan.engine;
-
-import com.it_jaros.jscanner.scan.Scan;
-import com.it_jaros.jscanner.scan.domain.PortResult;
-import com.it_jaros.jscanner.scan.domain.PortState;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.net.ConnectException;
-import java.net.InetSocketAddress;
-import java.net.NoRouteToHostException;
-import java.nio.ByteBuffer;
-import java.nio.channels.SocketChannel;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.LockSupport;
-
-public class ScanPortTask implements Callable<PortResult> {
-
- private static final int READ_BUFFER_SIZE = 1024;
-
- private final CancelledToken cancelledToken;
- private final PortScanRateLimiter portScanRateLimiter; // per-host shared limiter
- private final Scan scan;
- private final Semaphore socketLimit;
- private final String host;
- private final boolean bannerRecognition;
- private final int port;
- private final long timeoutInNanos;
-
- ScanPortTask(String host, int port, ScanExecutionContext context) {
- this.host = host;
- this.port = port;
- this.bannerRecognition = context.scanOptions().bannerRecognition();
- this.cancelledToken = context.cancelledToken();
- this.portScanRateLimiter = context.portScanRateLimiter();
- this.scan = context.scan();
- this.socketLimit = context.socketLimit();
- this.timeoutInNanos = TimeUnit.MILLISECONDS.toNanos(context.scanOptions().timeoutInMillis());
- }
-
- @Override
- public PortResult call() throws Exception {
- try {
- socketLimit.acquire();
- scan.portStart();
- portScanRateLimiter.apply();
- if (cancelledToken.isCancelled()) {
- return PortResult.empty();
- }
- return checkPort();
- } finally {
- scan.portFinish();
- socketLimit.release();
- }
- }
-
- private PortResult checkPort() {
- PortResult result = new PortResult();
- result.setPort(port);
- result.setState(PortState.UNKNOWN);
- try(SocketChannel socketChannel = SocketChannel.open()) {
- socketChannel.configureBlocking(false);
- socketChannel.connect(new InetSocketAddress(host, port));
- final long deadlineNanos = System.nanoTime() + timeoutInNanos;
- final long waitInNanos = TimeUnit.MILLISECONDS.toNanos(1000);
- boolean isConnected = socketChannel.finishConnect();
- while (!cancelledToken.isCancelled() && !isConnected) {
- long remainingNanos = deadlineNanos - System.nanoTime();
- if (remainingNanos <= 0) {
- break;
- }
- LockSupport.parkNanos(Math.min(waitInNanos, remainingNanos));
- isConnected = socketChannel.finishConnect();
- }
-
- if (cancelledToken.isCancelled()) {
- return result;
- }
-
- if (isConnected) {
- result.setState(PortState.OPEN);
- if (bannerRecognition) {
- result.setBanner(getBanner(socketChannel));
- }
- } else {
- result.setState(PortState.FILTERED);
- }
- } catch (ConnectException ignored) {
- result.setState(PortState.CLOSED);
- } catch (NoRouteToHostException ignored) {
- // NoRouteToHostException: this can be safely ignored because the port is closed if a host is unreachable
- // Will happen a lot when scanning for open ports, so not needed
- } catch (IOException e) {
- result.setException(e);
- }
-
- return result;
- }
-
- private byte[] getBanner(SocketChannel socketChannel) {
- try {
- return tryReadFrom(socketChannel);
- } catch (IOException ignore) {
- // ignore
- }
-
- return null;
- }
-
- private byte[] tryReadFrom(SocketChannel socketChannel) throws IOException {
- byte[] buffer = new byte[READ_BUFFER_SIZE];
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-
- boolean timedout = false;
- long deadlineNanos = System.nanoTime() + timeoutInNanos;
- while (!timedout) {
- int bytesRead = socketChannel.read(ByteBuffer.wrap(buffer));
- if (bytesRead == -1) {
- break;
- }
- if (bytesRead > 0) {
- byteArrayOutputStream.write(buffer, 0, bytesRead);
- }
- long remaining = deadlineNanos - System.nanoTime();
- if (remaining <= 0) {
- timedout = true;
- } else {
- LockSupport.parkNanos(Math.min(remaining, TimeUnit.MILLISECONDS.toNanos(1000)));
- }
- }
- return byteArrayOutputStream.toByteArray();
- }
-}
diff --git a/src/main/java/com/it_jaros/jscanner/scan/service/ServiceDetector.java b/src/main/java/com/it_jaros/jscanner/scan/service/ServiceDetector.java
deleted file mode 100644
index 93e05d4..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/service/ServiceDetector.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.it_jaros.jscanner.scan.service;
-
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-import java.util.regex.Pattern;
-
-public class ServiceDetector {
- private static final List<ServicePattern> patterns = List.of(
- new ServicePattern(ServiceType.DNS, Pattern.compile("(?i)\\bDNS\\b")),
- new ServicePattern(ServiceType.DNS, Pattern.compile("(?i)\\bBIND\\b")),
- new ServicePattern(ServiceType.DNS, Pattern.compile("(?i)\\bNSD\\b")),
- new ServicePattern(ServiceType.DNS, Pattern.compile("(?i)\\bPowerDNS\\b")),
- new ServicePattern(ServiceType.DNS, Pattern.compile("(?i)\\bUnbound\\b")),
- new ServicePattern(ServiceType.FTP, Pattern.compile("(?i)^220.*\\bFTP\\b")),
- new ServicePattern(ServiceType.HTTP, Pattern.compile("(?i)\\bServer:\\s*")),
- new ServicePattern(ServiceType.HTTP, Pattern.compile("(?i)^HTTP/")),
- new ServicePattern(ServiceType.IMAP, Pattern.compile("(?i)^\\* OK.*\\bIMAP\\b")),
- new ServicePattern(ServiceType.POP3, Pattern.compile("(?i)^\\+OK.*\\bPOP3\\b")),
- new ServicePattern(ServiceType.SMTP, Pattern.compile("(?i)^220.*\\bESMTP\\b")),
- new ServicePattern(ServiceType.SMTP, Pattern.compile("(?i)^220.*\\bSMTP\\b")),
- new ServicePattern(ServiceType.SSH, Pattern.compile("(?i)^SSH-"))
- );
-
- public static ServiceType detect(byte[] banner) {
- if (banner == null || banner.length == 0) {
- return ServiceType.UNKNOWN;
- }
-
- String bannerString = new String(banner, StandardCharsets.UTF_8);
- for (ServicePattern servicePattern : patterns) {
- if (servicePattern.matches(bannerString)) {
- return servicePattern.serviceType();
- }
- }
-
- return ServiceType.UNKNOWN;
- }
-
- private record ServicePattern(
- ServiceType serviceType,
- Pattern pattern
- ) {
- boolean matches(String banner) {
- return pattern.matcher(banner).find();
- }
- }
-} \ No newline at end of file
diff --git a/src/main/java/com/it_jaros/jscanner/scan/service/ServiceType.java b/src/main/java/com/it_jaros/jscanner/scan/service/ServiceType.java
deleted file mode 100644
index 86b282a..0000000
--- a/src/main/java/com/it_jaros/jscanner/scan/service/ServiceType.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.it_jaros.jscanner.scan.service;
-
-public enum ServiceType {
- HTTP, FTP, SMTP, IMAP, POP3, UNKNOWN, DNS, SSH
-}