#!/usr/bin/env bash # # an auxiliary script to produce a "stub" snmp-ups subdriver from # SNMP data from a real agent or from dump files # # Version: 0.16 # # See also: docs/snmp-subdrivers.txt # # Copyright (C) # 2011 - 2012 Arnaud Quette # 2015 - 2022 Eaton (author: Arnaud Quette ) # 2011 - 2024 Jim Klimov # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # TODO: # - Prepend sysDescription (.1.3.6.1.2.1.1.1.0) to have some more visibility # - extend to SNMP v3 (auth.) usage() { echo "Usage: $0 [options] [file]" echo "Options:" echo " -h, --help -- show this message and quit" echo " -n name -- subdriver name (use natural capitalization)" echo " -M DIRLIST -- colon separated list of directories to also search for MIBs" echo " -k -- keep temporary files (for debugging)" echo "" echo "mode 1: get SNMP data from a real agent" echo " -H host_address -- SNMP host IP address or name" echo " -c community -- SNMP v1 community name (default: public)" echo " -s XXXX -- override SNMP OID entry point (sysOID). Ex: '.1.3.6.1.4.1.534.10'" echo "" echo "mode 2: get data from files (snmpwalk dumps of 'sysOID' subtree)" echo " -s XXXX -- SNMP OID entry point (sysOID). Ex: '.1.3.6.1.4.1.534.6.6.7'" echo " file1 file2 -- read from files instead of an host (using Net SNMP)" echo " file1: numeric SNMP walk (snmpwalk -On ... )" echo " file2: string SNMP walk (snmpwalk -Os ... )" echo "" echo "mode 3: get data from 1 file (numeric snmpwalk dump of the whole SNMP tree)" echo " The sysOID is extracted from the dump, and only the pointed subtree is used" echo " A MIB file MUST be provided, and is used to produce the string SNMP walk" echo " file1 -- read from file instead of an host (using Net SNMP)" echo " file1: numeric SNMP walk (snmpwalk -On ... )" echo "" echo "Notes:" echo " For both modes, prefer to copy the specific MIB file(s) for your device in the $0 script directory" echo " So that it is automatically taken into account for the string name resolution of OIDs" echo " Otherwise, use \"-M.\" option" echo "" echo "Example:" echo "mode 1: $0 -H 192.168.0.1 -n mibname -c mycommunity" echo "mode 2: (using sysOID .1.3.6.1.4.1.534.6.6.7)" echo " snmpwalk -On -v1 -c mycommunity 192.168.0.1 .1.3.6.1.4.1.534.6.6.7 2>/dev/null 1> numeric-walk-file" echo " snmpwalk -Os -v1 -m ALL -M+. -c mycommunity 192.168.0.1 .1.3.6.1.4.1.534.6.6.7 2>/dev/null 1> string-walk-file" echo " $0 -s .1.3.6.1.4.1.534.6.6.7 numeric-walk-file string-walk-file" echo "mode 3:" echo " snmpwalk -On -v1 -c mycommunity 192.168.0.1 .1 2>/dev/null 1> numeric-walk-file" echo " $0 numeric-walk-file" echo "" echo " You may alos need to install additional packages:" echo " - 'snmp' package (on Debian) for the base commands (snmpget, snmpwalk, snmptranslate)" echo " - 'snmp-mibs-downloader' package (on Debian) to get all standard MIBs" } # variables DRIVER="" KEEP="" HOSTNAME="" MIBS_DIRLIST="+." COMMUNITY="public" DEVICE_SYSOID="" SYSOID="" MODE=0 # constants NAME=gen-snmp-subdriver TMPDIR="${TEMPDIR:-/tmp}" SYSOID_NUMBER=".1.3.6.1.2.1.1.2.0" DEBUG="`mktemp "$TMPDIR/$NAME-DEBUG.XXXXXX"`" DFL_NUMWALKFILE="`mktemp "$TMPDIR/$NAME-NUMWALK.XXXXXX"`" DFL_STRWALKFILE="`mktemp "$TMPDIR/$NAME-STRWALK.XXXXXX"`" TMP_NUMWALKFILE="`mktemp "$TMPDIR/$NAME-TMP-NUMWALK.XXXXXX"`" TMP_STRWALKFILE="`mktemp "$TMPDIR/$NAME-TMP-STRWALK.XXXXXX"`" get_snmp_data() { # 1) get the sysOID (points the mfr specif MIB), apart if there's an override if [ -z "$SYSOID" ] then SYSOID="`snmpget -On -v1 -c "$COMMUNITY" -Ov "$HOSTNAME" "$SYSOID_NUMBER" | cut -d' ' -f2`" echo "sysOID retrieved: ${SYSOID}" else echo "Using the provided sysOID override ($SYSOID)" fi DEVICE_SYSOID="$SYSOID" OID_COUNT=0 while (test "$OID_COUNT" -eq 0) do # 2) get the content of the mfr specif MIB echo "Retrieving SNMP information. This may take some time" snmpwalk -On -v1 -c "$COMMUNITY" "$HOSTNAME" "$SYSOID" 2>/dev/null 1> "$DFL_NUMWALKFILE" snmpwalk -Os -v1 -m ALL -M"$MIBS_DIRLIST" -c "$COMMUNITY" "$HOSTNAME" "$SYSOID" 2>/dev/null 1> "$DFL_STRWALKFILE" # 3) test return value of the walk, and possibly ramp-up the path to get something. # The sysOID mechanism only works if we're pointed somehow in the right direction # i.e. doesn't work if sysOID is .1.3.6.1.4.1.705.1 and data is at .1.3.6.1.4.1.534... # Ex: sysOID = ".1.X.Y.Z" # try with ".1.X.Y.Z", if fails try with .1.X.Y", if fails try with .1.X"... OID_COUNT="`cat $NUMWALKFILE | wc -l`" if [ $OID_COUNT -eq 0 ]; then # ramp-up the provided sysOID by removing the last .x part SYSOID=${SYSOID%.*} echo "Warning: sysOID provided no data! Trying with a level up using $SYSOID" fi done return $OID_COUNT } generate_C() { # create file names, lowercase LDRIVER="`echo "$DRIVER" | tr A-Z a-z`" UDRIVER="`echo "$DRIVER" | tr a-z A-Z`" # keep dashes in name for files CFILE="$LDRIVER-mib.c" HFILE="$LDRIVER-mib.h" # but replace with underscores for the structures and defines LDRIVER="`echo "$LDRIVER" | tr - _`" UDRIVER="`echo "$UDRIVER" | tr - _`" # generate header file # NOTE: with <<-EOF leading TABs are all stripped echo "Creating $HFILE" cat > "$HFILE" <<-EOF /* ${HFILE} - subdriver to monitor ${DRIVER} SNMP devices with NUT * * Copyright (C) * 2011 - 2016 Arnaud Quette * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef ${UDRIVER}_MIB_H #define ${UDRIVER}_MIB_H #include "main.h" #include "snmp-ups.h" extern mib2nut_info_t ${LDRIVER}; #endif /* ${UDRIVER}_MIB_H */ EOF # generate source file # create heading boilerblate # NOTE: with <<-EOF leading TABs are all stripped echo "Creating $CFILE" cat > "$CFILE" <<-EOF /* ${CFILE} - subdriver to monitor ${DRIVER} SNMP devices with NUT * * Copyright (C) * 2011 - 2016 Arnaud Quette * * Note: this subdriver was initially generated as a "stub" by the * gen-snmp-subdriver script. It must be customized! * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "${HFILE}" #define ${UDRIVER}_MIB_VERSION "0.01" #define ${UDRIVER}_SYSOID "${DEVICE_SYSOID}" /* To create a value lookup structure (as needed on the 2nd line of the example * below), use the following kind of declaration, outside of the present snmp_info_t[]: * static info_lkp_t onbatt_info[] = { * info_lkp_default(1, "OB"), * info_lkp_default(2, "OL"), * info_lkp_sentinel * }; */ /* ${UDRIVER} Snmp2NUT lookup table */ static snmp_info_t ${LDRIVER}_mib[] = { /* Data format: * snmp_info_default(info_type, info_flags, info_len, OID, dfl, flags, oid2info), * * info_type: NUT INFO_ or CMD_ element name * info_flags: flags to set in addinfo * info_len: length of strings if ST_FLAG_STRING, multiplier otherwise * OID: SNMP OID or NULL * dfl: default value * flags: snmp-ups internal flags (FIXME: ...) * oid2info: lookup table between OID and NUT values * * Example: * snmp_info_default("input.voltage", 0, 0.1, ".1.3.6.1.4.1.705.1.6.2.1.2.1", "", SU_INPUT_1, NULL), * snmp_info_default("ups.status", ST_FLAG_STRING, SU_INFOSIZE, ".1.3.6.1.4.1.705.1.7.3.0", "", SU_FLAG_OK | SU_STATUS_BATT, onbatt_info), * * To create a value lookup structure (as needed on the 2nd line), use the * following kind of declaration, outside of the present snmp_info_t[]: * static info_lkp_t onbatt_info[] = { * info_lkp_default(1, "OB"), * info_lkp_default(2, "OL"), * info_lkp_sentinel * }; */ /* standard MIB items; if the vendor MIB contains better OIDs for * this (e.g. with daisy-chain support), consider adding those here */ EOF # Same file, indented text (TABs not stripped): cat >> "$CFILE" < /dev/null if [ $? -eq 0 ]; then ST_FLAG_TYPE="ST_FLAG_STRING" SU_INFOSIZE="SU_INFOSIZE" else ST_FLAG_TYPE="0" SU_INFOSIZE="1" fi # get the matching numeric OID NUM_OID="`sed -n "${LINENB}p" "${NUMWALKFILE}" | cut -d' ' -f1`" printf "\t/* ${FULL_STR_OID} */\n\tsnmp_info_default(\"unmapped.${STR_OID}\", ${ST_FLAG_TYPE}, ${SU_INFOSIZE}, \"${NUM_OID}\", NULL, SU_FLAG_OK, NULL),\n" done < "${STRWALKFILE}" >> "${CFILE}" # append footer (TABs not stripped): cat >> "$CFILE" <&2 exit 1 fi done # check that the needed parameters are provided, depending on the mode if [ -z "$NUMWALKFILE" ]; then # mode 1: directly get SNMP data from a real agent echo "Mode 1 selected" MODE=1 NUMWALKFILE="$DFL_NUMWALKFILE" STRWALKFILE="$DFL_STRWALKFILE" # check if Net SNMP is available if [ -z "`command -v snmpget`" -o -z "`command -v snmpwalk`" ] && \ [ -z "`which snmpget`" -o -z "`which snmpwalk`" ]; then echo "Net SNMP not found! snmpget and snmpwalk commands are required." >&2 exit 1 fi # hostname is also mandatory while [ -z "$HOSTNAME" ]; do printf "\n\tPlease enter the SNMP host IP address or name.\n" read -p "SNMP host IP name or address: " HOSTNAME < /dev/tty if echo "$HOSTNAME" | grep -E -q '[^a-zA-Z0-9.-]'; then echo "Please use only letters, digits, dash and period character" HOSTNAME="" fi done # get data from the agent get_snmp_data else # no string walk provided, so mode 3 if [ -z "$STRWALKFILE" ]; then # mode 3: get data from 1 file, # Filter according to sysOID on the specific subtree # Generate the numeric SNMP walk using this output # then use snmptranslate to get the string OIDs and generated the string SNMP walk echo "Mode 3 selected" MODE=3 RAWWALKFILE="$NUMWALKFILE" NUMWALKFILE="$DFL_NUMWALKFILE" STRWALKFILE="$DFL_STRWALKFILE" # check for actual file existence if [ ! -f "$RAWWALKFILE" ]; then echo "SNMP walk dump file is missing on disk. Try --help for more info." >&2 exit 1 fi # Extract the sysOID # Format is "1.3.6.1.2.1.1.2.0 = OID: 1.3.6.1.4.1.4555.1.1.1" DEVICE_SYSOID="`grep 1.3.6.1.2.1.1.2.0 "$RAWWALKFILE" | cut -d' ' -f4`" if [ -n "$DEVICE_SYSOID" ]; then echo "Found sysOID $DEVICE_SYSOID" else echo "SNMP sysOID is missing in file. Try --help for more info." >&2 exit 1 fi # Switch to the entry point, and extract the subtree # Extract the numeric walk echo -n "Extracting numeric SNMP walk..." grep "$DEVICE_SYSOID" "$RAWWALKFILE" | grep -E -v "1.3.6.1.2.1.1.2.0" 2>/dev/null 1> "$NUMWALKFILE" echo " done" # Create the string walk from a translation of the numeric one echo -n "Converting string SNMP walk..." while IFS=' = ' read NUM_OID OID_VALUE do STR_OID="`snmptranslate -Os -m ALL -M+. "$NUM_OID" 2>/dev/null`" # Uncomment the below line to get debug logs #echo "Got: $STR_OID = $OID_VALUE" printf "." echo "$STR_OID = $OID_VALUE" >> "$STRWALKFILE" done < "$NUMWALKFILE" echo " done" else # mode 2: get data from files echo "Mode 2 selected" MODE=2 # get sysOID value from command line, if needed while [ -z "$SYSOID" ]; do echo " Please enter the value of sysOID, as displayed by snmp-ups. For example '.1.3.6.1.4.1.2254.2.4'. You can get it using: snmpget -v1 -c XXX $SYSOID_NUMBER" read -p "Value of sysOID: " SYSOID < /dev/tty if echo "$SYSOID" | grep -E -q '[^0-9.]'; then echo "Please use only the numeric form, with dots and digits" SYSOID="" fi done # check for actual files existence if [ ! -f "$NUMWALKFILE" -o ! -f "$STRWALKFILE" ]; then echo "SNMP walk dump files are missing on disk. Try --help for more info." >&2 exit 1 fi fi fi # delete temporary files: this is called just before exiting. cleanup () { rm -f "$DEBUG $DFL_NUMWALKFILE $TMP_NUMWALKFILE $DFL_STRWALKFILE $TMP_STRWALKFILE" } if [ -n "$KEEP" ]; then trap cleanup EXIT fi # prompt use for name of driver while [ -z "$DRIVER" ]; do echo " Please enter a name for this driver. Use only letters and numbers. Use natural (upper- and lowercase) capitalization, e.g., 'Belkin', 'APC'." read -p "Name of subdriver: " DRIVER < /dev/tty if echo "$DRIVER" | grep -E -q '[^a-zA-Z0-9]'; then echo "Please use only letters and digits" DRIVER="" fi done # remove blank and "End of MIB" lines grep -E -e "^[[:space:]]?$" -e "End of MIB" -v "${NUMWALKFILE}" > "${TMP_NUMWALKFILE}" grep -E -e "^[[:space:]]?$" -e "End of MIB" -v "${STRWALKFILE}" > "${TMP_STRWALKFILE}" NUMWALKFILE="${TMP_NUMWALKFILE}" STRWALKFILE="${TMP_STRWALKFILE}" # FIXME: sanity checks (! -z contents -a same `wc -l`) NUM_OID_COUNT="`cat "$NUMWALKFILE" | wc -l`" STR_OID_COUNT="`cat "$STRWALKFILE" | wc -l`" echo "SNMP OIDs extracted = $NUM_OID_COUNT / $NUM_OID_COUNT" generate_C # Display the remaining tasks cat <