Some checks failed
Build APK / build-apk (push) Failing after 11m29s
SvelteKit + Capacitor 6 Netzwerk-Diagnose-App: - Tool-Plattform (IP-Scan, Port, Ping, WLAN, DHCP, SNMP, Traceroute, Stresstest, iperf) - Offline-First SQLite-Cache + idempotenter Dolibarr-Sync - Natives Kotlin-Plugin NetDiagScanner (ARP, Ping, Ports, WLAN, DHCP, SNMP, Traceroute) - Backbutton-Single-Instance-Modul, Auto-Updater, Toast-System - Auftrags-/Kunden-Übersicht nach Baustellen-App-Muster - CI: [apk]-Tag → Forgejo Runner → Package Registry netdiag-apk Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
138 lines
4.9 KiB
Kotlin
138 lines
4.9 KiB
Kotlin
package de.data_it_solution.netdiag
|
|
|
|
import java.io.ByteArrayOutputStream
|
|
import java.net.DatagramPacket
|
|
import java.net.DatagramSocket
|
|
import java.net.InetAddress
|
|
|
|
/**
|
|
* Minimaler SNMP-v2c-GET-Client.
|
|
*
|
|
* Reicht für das Auslesen einzelner OIDs (Link-Speed, Fehlerzähler) von
|
|
* gemanagten Switches. Implementiert nur so viel BER-Kodierung wie nötig.
|
|
*/
|
|
object Snmp {
|
|
|
|
/**
|
|
* Eine OID per SNMP v2c GET abfragen.
|
|
*
|
|
* @return Wert als String oder null bei Fehler/Timeout
|
|
*/
|
|
fun get(host: String, community: String, oid: String): String? {
|
|
return try {
|
|
val request = buildGetRequest(community, oid)
|
|
val socket = DatagramSocket()
|
|
socket.soTimeout = 2500
|
|
socket.use {
|
|
it.send(DatagramPacket(request, request.size, InetAddress.getByName(host), 161))
|
|
val buf = ByteArray(2048)
|
|
val resp = DatagramPacket(buf, buf.size)
|
|
it.receive(resp)
|
|
parseFirstValue(buf, resp.length)
|
|
}
|
|
} catch (e: Exception) {
|
|
null
|
|
}
|
|
}
|
|
|
|
/* ---- BER-Kodierung ---- */
|
|
|
|
private fun tlv(tag: Int, value: ByteArray): ByteArray {
|
|
val out = ByteArrayOutputStream()
|
|
out.write(tag)
|
|
when {
|
|
value.size < 0x80 -> out.write(value.size)
|
|
value.size < 0x100 -> { out.write(0x81); out.write(value.size) }
|
|
else -> { out.write(0x82); out.write(value.size shr 8); out.write(value.size and 0xFF) }
|
|
}
|
|
out.write(value)
|
|
return out.toByteArray()
|
|
}
|
|
|
|
private fun integer(v: Int): ByteArray {
|
|
val bytes = when {
|
|
v == 0 -> byteArrayOf(0)
|
|
v < 0x80 -> byteArrayOf(v.toByte())
|
|
v < 0x8000 -> byteArrayOf((v shr 8).toByte(), v.toByte())
|
|
else -> byteArrayOf((v shr 24).toByte(), (v shr 16).toByte(), (v shr 8).toByte(), v.toByte())
|
|
}
|
|
return tlv(0x02, bytes)
|
|
}
|
|
|
|
private fun octetString(s: String): ByteArray = tlv(0x04, s.toByteArray())
|
|
|
|
private fun oid(o: String): ByteArray {
|
|
val parts = o.trim().trimStart('.').split('.').map { it.toInt() }
|
|
val out = ByteArrayOutputStream()
|
|
out.write(parts[0] * 40 + parts[1]) // erste zwei Subidentifier zusammengefasst
|
|
for (i in 2 until parts.size) {
|
|
var v = parts[i]
|
|
if (v < 0x80) {
|
|
out.write(v)
|
|
} else {
|
|
val stack = ArrayDeque<Int>()
|
|
stack.addFirst(v and 0x7F)
|
|
v = v shr 7
|
|
while (v > 0) { stack.addFirst((v and 0x7F) or 0x80); v = v shr 7 }
|
|
stack.forEach { out.write(it) }
|
|
}
|
|
}
|
|
return tlv(0x06, out.toByteArray())
|
|
}
|
|
|
|
private fun buildGetRequest(community: String, oidStr: String): ByteArray {
|
|
val varbind = tlv(0x30, oid(oidStr) + tlv(0x05, ByteArray(0))) // OID + NULL
|
|
val varbindList = tlv(0x30, varbind)
|
|
val requestId = (System.currentTimeMillis() and 0x7FFF).toInt()
|
|
val pdu = tlv(
|
|
0xA0, // GetRequest-PDU
|
|
integer(requestId) + integer(0) + integer(0) + varbindList,
|
|
)
|
|
val message = tlv(
|
|
0x30,
|
|
integer(1) + octetString(community) + pdu, // version 1 = SNMPv2c
|
|
)
|
|
return message
|
|
}
|
|
|
|
/* ---- Antwort parsen: ersten Variablen-Wert herausziehen ---- */
|
|
|
|
private fun parseFirstValue(buf: ByteArray, len: Int): String? {
|
|
var i = 0
|
|
// Durch die Struktur navigieren bis zum ersten primitiven Wert nach einer OID
|
|
var lastWasOid = false
|
|
while (i < len) {
|
|
val tag = buf[i].toInt() and 0xFF
|
|
i++
|
|
if (i >= len) break
|
|
var l = buf[i].toInt() and 0xFF
|
|
i++
|
|
if (l and 0x80 != 0) {
|
|
val n = l and 0x7F
|
|
l = 0
|
|
for (k in 0 until n) { l = (l shl 8) or (buf[i].toInt() and 0xFF); i++ }
|
|
}
|
|
// Konstruierte Typen (SEQUENCE, PDU) aufsteigen
|
|
if (tag == 0x30 || tag == 0xA2 || tag == 0xA0) continue
|
|
if (tag == 0x06) { lastWasOid = true; i += l; continue }
|
|
if (lastWasOid) {
|
|
return when (tag) {
|
|
0x02, 0x41, 0x42, 0x43, 0x44, 0x46 -> { // INTEGER, Counter, Gauge, TimeTicks ...
|
|
var v = 0L
|
|
for (k in 0 until l) v = (v shl 8) or (buf[i + k].toLong() and 0xFF)
|
|
v.toString()
|
|
}
|
|
0x04 -> String(buf, i, l) // OCTET STRING
|
|
0x05 -> "" // NULL -> kein Wert
|
|
else -> {
|
|
val sb = StringBuilder()
|
|
for (k in 0 until l) sb.append("%02X".format(buf[i + k]))
|
|
sb.toString()
|
|
}
|
|
}
|
|
}
|
|
i += l
|
|
}
|
|
return null
|
|
}
|
|
}
|