netdiag-app/native-plugin/Snmp.kt
Eduard Wisch bf01b4cd21
Some checks failed
Build APK / build-apk (push) Failing after 11m29s
Initiales Commit — NetDiag App vollständig implementiert [apk]
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>
2026-05-19 12:01:56 +02:00

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
}
}