Compare commits

...

4 commits

Author SHA1 Message Date
46226f0d78 IP-Scan: echte CIDR-Auswertung statt hardcodiertem /24 [apk]
All checks were successful
Build APK / build-apk (push) Successful in 4m11s
Der Scan lief stur ueber (1..254) einer aus dem Subnetz-String abgeschnittenen
base — die Netzmaske wurde komplett ignoriert. Ein /23, /22 oder /16 wurde
also nur zu einem Viertel/Achtel etc. gescannt.

Jetzt: hostsInSubnet() parst die CIDR (ip/praefix), berechnet Netz- und
Broadcast-Adresse und liefert ALLE Host-IPs des Bereichs. Unterstuetzt /0..32
(praktisch bis /16 = 65534 Hosts), /31+/32 ohne Netz/Broadcast-Sonderfall.

Sammel-Build: enthaelt ausserdem Titelleisten-Fix, App-Icon, Debug-Log,
IP-Scan-Logging.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 19:57:32 +02:00
c81871f010 IP-Scan instrumentiert: loggt Eingabe-Subnetz vs. gescanntes Subnetz
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 19:49:49 +02:00
d389ee924d App-Icon: eigenes NetDiag-Radar-Motiv [apk]
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 19:44:22 +02:00
06cc5910ab Titelleiste: CSS-Kommentar killte die .safe-top-Regel [apk]
Der Kommentar ueber .safe-top enthielt die Zeichenfolge pb-*/px-* — das
darin steckende */ schliesst den CSS-Kommentar vorzeitig. Die .safe-top-
Regel landete dadurch hinter kaputtem Selektor-Text und wurde vom Parser
komplett verworfen (.safe-bottom danach blieb heil).

Folge: header padding-top = 0 statt 12px, Titel klebte oben.
Verifiziert per WebView-DevTools im Emulator: headerPadTop jetzt 12px.

Fix: Kommentar ohne */ umformuliert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 19:22:10 +02:00
19 changed files with 67 additions and 11 deletions

View file

@ -82,14 +82,18 @@ class NetDiagScannerPlugin : Plugin() {
@PluginMethod @PluginMethod
fun ipScan(call: PluginCall) { fun ipScan(call: PluginCall) {
val subnet = call.getString("subnet") ?: return call.reject("subnet fehlt") val subnet = call.getString("subnet") ?: return call.reject("subnet fehlt")
val base = subnet.substringBeforeLast('.', "192.168.1") val hosts = hostsInSubnet(subnet)
if (hosts.isEmpty()) {
return call.reject("Subnetz ungültig oder zu groß (max /16): $subnet")
}
io.launch { io.launch {
try { try {
// Parallel-Ping über das gesamte /24 // Parallel-Ping über ALLE Host-Adressen des Subnetzes — CIDR-genau,
// also exakt der Bereich, den die Netzmaske aufspannt (/24, /23, /22 …).
val alive = withContext(Dispatchers.IO) { val alive = withContext(Dispatchers.IO) {
(1..254).map { host -> hosts.map { ipInt ->
async { async {
val ip = "$base.$host" val ip = intToIpv4(ipInt)
if (InetAddress.getByName(ip).isReachable(350)) ip else null if (InetAddress.getByName(ip).isReachable(350)) ip else null
} }
}.awaitAll().filterNotNull() }.awaitAll().filterNotNull()
@ -112,6 +116,51 @@ class NetDiagScannerPlugin : Plugin() {
} }
} }
/**
* Alle Host-IPs (als Int) eines CIDR-Subnetzes.
* "192.168.1.0/24" -> .1 bis .254, "10.0.0.0/22" -> 1022 Hosts usw.
* Ohne Praefix wird /24 angenommen. Netz- und Broadcast-Adresse sind
* ausgenommen (ausser /31, /32). Leer bei ungueltig oder > /16.
*/
private fun hostsInSubnet(cidr: String): List<Int> {
val parts = cidr.trim().split('/')
val ipInt = ipv4ToInt(parts[0].trim()) ?: return emptyList()
val prefix = if (parts.size > 1) (parts[1].trim().toIntOrNull() ?: 24) else 24
if (prefix < 0 || prefix > 32) return emptyList()
val mask = if (prefix == 0) 0 else (-1 shl (32 - prefix))
val network = ipInt and mask
val broadcast = network or mask.inv()
val out = ArrayList<Int>()
if (prefix >= 31) {
var i = network
while (true) { out.add(i); if (i == broadcast) break; i++ }
return out
}
val count = (broadcast.toLong() and 0xFFFFFFFFL) - (network.toLong() and 0xFFFFFFFFL) - 1L
if (count < 1L || count > 65534L) return emptyList()
var i = network + 1
val last = broadcast - 1
while (true) { out.add(i); if (i == last) break; i++ }
return out
}
/** "192.168.1.50" -> 32-Bit-Int (big-endian), null bei ungueltig */
private fun ipv4ToInt(s: String): Int? {
val o = s.split('.')
if (o.size != 4) return null
var v = 0
for (part in o) {
val n = part.toIntOrNull() ?: return null
if (n < 0 || n > 255) return null
v = (v shl 8) or n
}
return v
}
/** 32-Bit-Int (big-endian) -> "192.168.1.50" */
private fun intToIpv4(i: Int): String =
"${(i shr 24) and 0xFF}.${(i shr 16) and 0xFF}.${(i shr 8) and 0xFF}.${i and 0xFF}"
/* --------------------------------------------------------------------- */ /* --------------------------------------------------------------------- */
/* Port-Scan */ /* Port-Scan */
/* --------------------------------------------------------------------- */ /* --------------------------------------------------------------------- */

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="ic_launcher_background">#FFFFFF</color> <color name="ic_launcher_background">#0d1117</color>
</resources> </resources>

View file

@ -15,11 +15,10 @@ body {
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
} }
/* Sichere Bereiche (Notch / Statusleiste). /* Sichere Bereiche (Notch / Statusleiste): enthalten bewusst den
Enthalten bewusst den Basis-Innenabstand (0.75rem) sonst überschreibt Basis-Innenabstand 0.75rem, sonst ueberschreibt diese Klasse das
diese Klasse das padding-top/-bottom von Tailwind py-* (unlayered CSS Tailwind-Padding und der Inhalt klebt an der Statusleiste. Elemente
schlägt @layer utilities) und der Inhalt klebt an Statusleiste/Notch. mit pb- und px- kombinieren, nicht mit py-. Siehe KB 551. */
Elemente daher mit pb-*/px-* statt py-* kombinieren. Siehe KB #551. */
.safe-top { .safe-top {
padding-top: calc(0.75rem + env(safe-area-inset-top)); padding-top: calc(0.75rem + env(safe-area-inset-top));
} }

View file

@ -9,6 +9,7 @@
*/ */
import { scanner } from '../../scanner'; import { scanner } from '../../scanner';
import { debugLog } from '../../debuglog.svelte';
import type { Tool } from '../types'; import type { Tool } from '../types';
export const ipScanTool: Tool = { export const ipScanTool: Tool = {
@ -58,7 +59,14 @@ export const ipScanTool: Tool = {
ctx.protocol.subnet = subnet; ctx.protocol.subnet = subnet;
} }
debugLog.add(
'info',
`IP-Scan: Dialog-Eingabe="${String(ctx.params.subnet ?? '')}", ` +
`Protokoll-Subnetz="${String(ctx.protocol.subnet ?? '')}" → ` +
`gescannt wird "${subnet}" (Quelle: ${source})`,
);
const { devices } = await scanner.ipScan({ subnet }); const { devices } = await scanner.ipScan({ subnet });
debugLog.add('info', `IP-Scan Ergebnis: ${devices.length} Geräte in ${subnet}`);
const via = source === 'adapter' ? ' (Adapter erkannt)' : ''; const via = source === 'adapter' ? ' (Adapter erkannt)' : '';
return { return {
label: `${devices.length} Geräte im Netz ${subnet}${via}`, label: `${devices.length} Geräte im Netz ${subnet}${via}`,