commit 05cc7ca4b808686a34ddd1bc359745130452cc96
parent 4b74b6c6ea4e1ac2675fed1087df26a005f417dc
Author: Matthias Jaros <jaros@mailbox.org>
Date: Mon, 23 Mar 2026 12:30:57 +0100
Added feature to distinguish between blocked by firewall and open ports
Diffstat:
7 files changed, 61 insertions(+), 43 deletions(-)
diff --git a/src/main/java/com/it_jaros/network_scanner/App.java b/src/main/java/com/it_jaros/network_scanner/App.java
@@ -1,5 +1,6 @@
package com.it_jaros.network_scanner;
+import java.util.BitSet;
import java.util.List;
import java.util.stream.Collectors;
@@ -24,13 +25,21 @@ public class App {
}
private static void printResult(ScanResult result) {
- String ports = result.openPorts().stream().mapToObj(String::valueOf).collect(Collectors.joining(","));
- String output = "[" + result.target() + "]:";
- if (ports.isEmpty()) {
+ if (result.openPorts().isEmpty() && result.blockedPorts().isEmpty()) {
return;
- } else {
- output = output + ports;
}
- System.out.print(output + "\n");
+
+ System.out.println(result.target());
+ if (!result.openPorts().isEmpty()) {
+ System.out.println("\topen:\t" + map(result.openPorts()));
+ }
+ if (!result.blockedPorts().isEmpty()) {
+ System.out.println("\tblocked:\t" + map(result.blockedPorts()));
+ }
+ System.out.println();
+ }
+
+ private static String map(BitSet ports) {
+ return ports.stream().mapToObj(String::valueOf).collect(Collectors.joining(","));
}
}
diff --git a/src/main/java/com/it_jaros/network_scanner/PortResult.java b/src/main/java/com/it_jaros/network_scanner/PortResult.java
@@ -0,0 +1,4 @@
+package com.it_jaros.network_scanner;
+
+public record PortResult(int port, PortState state) {
+}
diff --git a/src/main/java/com/it_jaros/network_scanner/PortState.java b/src/main/java/com/it_jaros/network_scanner/PortState.java
@@ -0,0 +1,7 @@
+package com.it_jaros.network_scanner;
+
+public enum PortState {
+ OPEN,
+ CLOSED,
+ BLOCKED
+}
diff --git a/src/main/java/com/it_jaros/network_scanner/Progress.java b/src/main/java/com/it_jaros/network_scanner/Progress.java
@@ -2,4 +2,4 @@ package com.it_jaros.network_scanner;
import java.util.concurrent.atomic.AtomicInteger;
-public record Progress(String host, int total, AtomicInteger done, AtomicInteger open) {}
-\ No newline at end of file
+public record Progress(String host, int total, AtomicInteger done, AtomicInteger open, AtomicInteger blocked) {}
+\ No newline at end of file
diff --git a/src/main/java/com/it_jaros/network_scanner/ProgressBar.java b/src/main/java/com/it_jaros/network_scanner/ProgressBar.java
@@ -59,11 +59,12 @@ public class ProgressBar {
System.out.println(createBar(
progress.host(),
progress.open().get(),
+ progress.blocked().get(),
progress.done().get(),
progress.total()));
}
- public String createBar(String label, int open, int done, int total) {
+ public String createBar(String label, int open, int blocked, int done, int total) {
int width = 30;
double pct = total == 0 ? 1.0 : (done / (double) total);
int filled = (int) (pct * width);
@@ -71,7 +72,7 @@ public class ProgressBar {
String brackets = "[" + "#".repeat(filled) + "-".repeat(width - filled) + "]";
int percent = (int) (pct * 100);
- return String.format("%-40s open=%s %s %3d%% (%d/%d)", label, open, brackets, percent, done, total);
+ return String.format("%-40s open: %s blocked: %s %s %3d%% (%d/%d)", label, open, blocked, brackets, percent, done, total);
}
public void stop() {
diff --git a/src/main/java/com/it_jaros/network_scanner/ScanResult.java b/src/main/java/com/it_jaros/network_scanner/ScanResult.java
@@ -2,4 +2,4 @@ package com.it_jaros.network_scanner;
import java.util.BitSet;
-public record ScanResult(String target, BitSet openPorts) {}
+public record ScanResult(String target, BitSet openPorts, BitSet blockedPorts) {}
diff --git a/src/main/java/com/it_jaros/network_scanner/Scanner.java b/src/main/java/com/it_jaros/network_scanner/Scanner.java
@@ -1,19 +1,11 @@
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.net.*;
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.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.LockSupport;
@@ -69,7 +61,11 @@ public class Scanner {
}
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;
}
@@ -78,23 +74,26 @@ public class Scanner {
AtomicLong portSlotFactory = new AtomicLong(System.nanoTime());
Semaphore maxWorkers = new Semaphore(maxWorkersPerHost);
BitSet openPorts = new BitSet(PortRange.MAX_PORT);
+ BitSet blockedPorts = new BitSet(PortRange.MAX_PORT);
- Progress progress = new Progress(target, portRange.getTotal(), new AtomicInteger(), new AtomicInteger());
+ Progress progress = new Progress(target, portRange.getTotal(), new AtomicInteger(), new AtomicInteger(), new AtomicInteger() );
progressBar.submit(progress);
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
- List<Future<Integer>> futures = new ArrayList<>();
+ List<Future<PortResult>> 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) {
+ PortState state = getPortState(target, currentPort);
+ if (state.equals(PortState.OPEN)) {
progress.open().incrementAndGet();
+ } else if (state.equals(PortState.BLOCKED)) {
+ progress.blocked().incrementAndGet();
}
- return port;
+ return new PortResult(currentPort, state);
} finally {
maxWorkers.release();
progress.done().incrementAndGet();
@@ -103,12 +102,12 @@ public class Scanner {
}
List<Throwable> errors = new ArrayList<>();
- for (Future<Integer> f : futures) {
+ for (Future<PortResult> 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);
+ PortResult portResult = f.get();
+ switch (portResult.state()) {
+ case OPEN -> openPorts.set(portResult.port());
+ case BLOCKED -> blockedPorts.set(portResult.port());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
@@ -122,11 +121,7 @@ public class Scanner {
}
}
- return new ScanResult(target, openPorts);
- }
-
- private int scanPort(String target, final int currentPort) {
- return isTargetPortOpen(target, currentPort) ? currentPort : -1;
+ return new ScanResult(target, openPorts, blockedPorts);
}
private void waitForSlot(AtomicLong scanSlotFactory) {
@@ -138,21 +133,23 @@ public class Scanner {
}
}
- private boolean isTargetPortOpen(String target, int port) {
+ private PortState getPortState(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) {
+ return PortState.OPEN;
+ } catch (NoRouteToHostException ignored) {
+ // this can be safely ignored because the port is closed if a host is unreachable
+ } catch (ConnectException ignored) {
+ return PortState.BLOCKED;
+ } catch (IOException 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 {
+ } finally {
counter.dec();
socketLimit.release();
}
- return false;
+ return PortState.CLOSED;
}
}