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