commit 987b857c6dd725ea2f675dee5c42a8157ce6a06a
parent 5b4e1909c7f0f3f6398448b438a8c22ef61b4ae0
Author: Matthias Jaros <jaros@mailbox.org>
Date: Mon, 6 Jul 2026 15:42:44 +0200
Reworked exception handling. Collect now only weird exceptions and print
them before results to stderr
Diffstat:
6 files changed, 70 insertions(+), 13 deletions(-)
diff --git a/src/main/java/com/it_jaros/jscanner/ExceptionInfo.java b/src/main/java/com/it_jaros/jscanner/ExceptionInfo.java
@@ -0,0 +1,13 @@
+package com.it_jaros.jscanner;
+
+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/PortResult.java b/src/main/java/com/it_jaros/jscanner/PortResult.java
@@ -1,9 +1,12 @@
package com.it_jaros.jscanner;
+import java.io.IOException;
+
public class PortResult {
private int port;
private PortState state;
private String banner;
+ private Exception exception;
public int getPort() {
return port;
@@ -28,4 +31,12 @@ public class PortResult {
public void setBanner(String banner) {
this.banner = banner;
}
+
+ public void setException(IOException e) {
+ exception = e;
+ }
+
+ public Exception getException() {
+ return exception;
+ }
}
diff --git a/src/main/java/com/it_jaros/jscanner/ProgressBar.java b/src/main/java/com/it_jaros/jscanner/ProgressBar.java
@@ -30,15 +30,36 @@ public class ProgressBar {
public void printResult(ScanResult result, boolean showFilteredPorts) {
synchronized (outputLock) {
- if (result.openPorts().isEmpty() && (!showFilteredPorts || result.filteredPorts().isEmpty())) {
+ if (result.openPorts().isEmpty()
+ && (!showFilteredPorts || result.filteredPorts().isEmpty())
+ && result.errors().isEmpty()) {
return;
}
clearStatusLine();
+ printErrors(result);
printPortsResults(result, showFilteredPorts);
}
}
+ private void printErrors(ScanResult result) {
+ System.err.printf(
+ "error: host scan failed | host=%s | errors: %d%n",
+ result.host(),
+ result.errors().size()
+ );
+ for (ScanFailure error : result.errors()) {
+ System.err.printf(
+ "port: %s: %s %s%n",
+ error.port(),
+ error.exception().type(),
+ error.exception().message()
+ );
+ }
+
+ printStatusLine(scan);
+ }
+
private void printPortsResults(ScanResult result, boolean showFilteredPorts) {
if (result.openPorts().isEmpty() && (!showFilteredPorts || result.filteredPorts().isEmpty())) {
return;
diff --git a/src/main/java/com/it_jaros/jscanner/ScanFailure.java b/src/main/java/com/it_jaros/jscanner/ScanFailure.java
@@ -0,0 +1,7 @@
+package com.it_jaros.jscanner;
+
+public record ScanFailure(
+ Integer port,
+ ExceptionInfo exception
+) {
+}
diff --git a/src/main/java/com/it_jaros/jscanner/ScanResult.java b/src/main/java/com/it_jaros/jscanner/ScanResult.java
@@ -1,6 +1,7 @@
package com.it_jaros.jscanner;
import java.util.BitSet;
+import java.util.List;
public record ScanResult(String host, BitSet openPorts, BitSet filteredPorts,
- java.util.HashMap<Integer, ServiceType> bannerRecognition) {}
+ java.util.HashMap<Integer, ServiceType> bannerRecognition, List<ScanFailure> errors) {}
diff --git a/src/main/java/com/it_jaros/jscanner/Scanner.java b/src/main/java/com/it_jaros/jscanner/Scanner.java
@@ -107,7 +107,7 @@ public class Scanner implements AutoCloseable {
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
- System.out.printf("%s -> %s%n", e.getClass().getSimpleName(), e.getMessage());
+ System.err.printf("%s -> %s%n", e.getClass().getSimpleName(), e.getMessage());
}
}
@@ -129,10 +129,10 @@ public class Scanner implements AutoCloseable {
if (!disableOnlineCheck && !checkHostOnline(host)) {
// Unreachable host
- return new ScanResult(host, openPorts, filteredPorts, serviceTypes);
+ return new ScanResult(host, openPorts, filteredPorts, serviceTypes, List.of());
}
- List<Throwable> errors = new ArrayList<>();
+ List<ScanFailure> scanFailures = new ArrayList<>();
final AtomicLong portSlotFactory = new AtomicLong(System.nanoTime());
// producer thread
ProducerState<PortResult> state = startProducer(
@@ -173,6 +173,13 @@ public class Scanner implements AutoCloseable {
// sonarcube glücklich machen
}
}
+ Exception e = portResult.getException();
+ if (e != null) {
+ scanFailures.add(new ScanFailure(portResult.getPort(), ExceptionInfo.from(e)));
+ }
+ } catch (ExecutionException e) {
+ // something more serious did not work
+ System.err.printf("%s -> %s%n", e.getClass().getSimpleName(), e.getMessage());
} finally {
scan.getThreadCounter().dec();
state.activeWorkers().release();
@@ -180,16 +187,10 @@ public class Scanner implements AutoCloseable {
}
} catch (InterruptedException ignored) {
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, serviceTypes);
+ return new ScanResult(host, openPorts, filteredPorts, serviceTypes, scanFailures);
}
/**
@@ -295,9 +296,11 @@ public class Scanner implements AutoCloseable {
result.setState(PortState.FILTERED);
} catch (ConnectException ignored) {
result.setState(PortState.CLOSED);
- } catch (IOException ignored) {
+ } 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);
} finally {
scan.getSocketCounter().dec();
socketLimit.release();