Compare commits
29 Commits
22b6c9c751
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
bb4e04818b
|
|||
|
522861a96e
|
|||
|
733cf3e97b
|
|||
|
d67917c958
|
|||
|
1aee330230
|
|||
|
1c0b61eae2
|
|||
|
b37bc30549
|
|||
|
e84310932e
|
|||
|
d5d807c7c3
|
|||
|
6ec4c02e40
|
|||
|
ce2daf9749
|
|||
|
201456a056
|
|||
|
add8e44e4f
|
|||
|
7d4029fd06
|
|||
|
3aecda95ba
|
|||
|
48346d02e7
|
|||
|
ae0f0aa745
|
|||
|
bec584b42d
|
|||
|
75cac1c93b
|
|||
|
59dabc3565
|
|||
|
df9b79f931
|
|||
|
95c8c276ac
|
|||
|
8af91ec36a
|
|||
|
e8b2555b6b
|
|||
|
b73dda4496
|
|||
|
79855cfbca
|
|||
|
c851ff14e0
|
|||
|
eda4d2725b
|
|||
|
5bcda90b06
|
20
README.md
20
README.md
@@ -1,3 +1,23 @@
|
|||||||
# snmpd-oid-daemon
|
# snmpd-oid-daemon
|
||||||
|
|
||||||
A customizable daemon written in Bash to provide custom OIDs to snmpd.
|
A customizable daemon written in Bash to provide custom OIDs to snmpd.
|
||||||
|
|
||||||
|
# Requirements
|
||||||
|
snmpd-oid-daemon runs with Bash version 4.3 or later.
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
* Copy **snmpd-oid-daemon.sh** to your system where it is accessible by snmpd (e.g. to /usr/local/bin).
|
||||||
|
* Edit **snmpd.conf** and add the following line, replace OID with your custom base OID (e.g. .1.3.6.1.4.1.8072.9999.9999).
|
||||||
|
```
|
||||||
|
pass_persist OID /PATH/TO/snmpd-oid-agent.sh --base-oid OID
|
||||||
|
```
|
||||||
|
* Restart snmpd.
|
||||||
|
|
||||||
|
# Data gathering functions
|
||||||
|
* Take a look at the existing functions to learn which ones already exist and how they are implemented.
|
||||||
|
* Implement your own function or overwrite an existing one in the overload-script if neccessary.
|
||||||
|
* Overwrite the global array DATA_FUNCS in the overload-script to enable/disable functions or change their refresh delay.
|
||||||
|
|
||||||
|
# Known issues
|
||||||
|
* The interface to snmpd does not support type Gauge64. One way arround this is to use String instead and convert to int64 on the receiving end.
|
||||||
|
* The function 'gather_filesum_data' in its current form requires snmpd to run as root which is not always the case. Overload the function (--overload-script) if this is an issue in your case.
|
||||||
|
|||||||
151
snmpd-oid-daemon.sh
Normal file → Executable file
151
snmpd-oid-daemon.sh
Normal file → Executable file
@@ -23,7 +23,7 @@ function usage() {
|
|||||||
Usage: $SCRIPT [-b BASE_OID] [-d] [-m FILE] [-n] [-h]
|
Usage: $SCRIPT [-b BASE_OID] [-d] [-m FILE] [-n] [-h]
|
||||||
|
|
||||||
Mandatory arguments to long options are mandatory for short options too.
|
Mandatory arguments to long options are mandatory for short options too.
|
||||||
-b, --base=BASE_OID base OID to operate on, default is '${BASE_OID}'
|
-b, --base=BASE_OID base OID to operate on, default is '$BASE_OID'
|
||||||
-d, --debug enable debug output
|
-d, --debug enable debug output
|
||||||
-h, --help display this help and exit
|
-h, --help display this help and exit
|
||||||
-m, --debug-marker=FILE debug logs will enabled or disabled during runtime
|
-m, --debug-marker=FILE debug logs will enabled or disabled during runtime
|
||||||
@@ -31,7 +31,7 @@ Mandatory arguments to long options are mandatory for short options too.
|
|||||||
-o, --overload-script=FILE source file to add or overload data gathering functions
|
-o, --overload-script=FILE source file to add or overload data gathering functions
|
||||||
-n, --no-log disable logging
|
-n, --no-log disable logging
|
||||||
-t, --tag mark every line to be logged with the specified tag,
|
-t, --tag mark every line to be logged with the specified tag,
|
||||||
default is '${LOG_TAG}'
|
default is '$LOG_TAG'
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,20 +177,16 @@ function set_oid_list {
|
|||||||
if (( ${#COL_TYPES[@]} == 1 )); then
|
if (( ${#COL_TYPES[@]} == 1 )); then
|
||||||
for row_decl in "${DATA[@]}"; do
|
for row_decl in "${DATA[@]}"; do
|
||||||
declare -a row=$(strip_declaration <<<"$row_decl")
|
declare -a row=$(strip_declaration <<<"$row_decl")
|
||||||
echo $base_oid.$row_id
|
set_oid "$base_oid.$row_id" "${COL_TYPES[0]}" "${row[0]//[$'\n'$'\r']/}"
|
||||||
echo ${COL_TYPES[0]}
|
|
||||||
echo ${row[0]//[$'\n'$'\r']/}
|
|
||||||
((row_id++))
|
((row_id++))
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
for row_decl in "${DATA[@]}"; do
|
for row_decl in "${DATA[@]}"; do
|
||||||
local -a row=$(strip_declaration <<<"$row_decl")
|
local -a row=$(strip_declaration <<<"$row_decl")
|
||||||
col_id=${col_start_idx}
|
col_id=$col_start_idx
|
||||||
type_id=0
|
type_id=0
|
||||||
for value in "${row[@]}"; do
|
for value in "${row[@]}"; do
|
||||||
echo $base_oid.$col_id.$row_id
|
set_oid "$base_oid.$col_id.$row_id" "${COL_TYPES[$type_id]}" "${value//[$'\n'$'\r']/}"
|
||||||
echo ${COL_TYPES[$type_id]}
|
|
||||||
echo ${value//[$'\n'$'\r']/}
|
|
||||||
((col_id++))
|
((col_id++))
|
||||||
((type_id++))
|
((type_id++))
|
||||||
done
|
done
|
||||||
@@ -219,7 +215,6 @@ function submit_oids() {
|
|||||||
echo -n "UPDATE "
|
echo -n "UPDATE "
|
||||||
declare -p oids | strip_declaration
|
declare -p oids | strip_declaration
|
||||||
fi
|
fi
|
||||||
echo ENDOFDATA
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,7 +340,7 @@ if [ -f "$OVERLOAD_SCRIPT" -a -r "$OVERLOAD_SCRIPT" ]; then
|
|||||||
echo "source $OVERLOAD_SCRIPT" >&$LOG
|
echo "source $OVERLOAD_SCRIPT" >&$LOG
|
||||||
source "$OVERLOAD_SCRIPT"
|
source "$OVERLOAD_SCRIPT"
|
||||||
else
|
else
|
||||||
echo "overload script '$OVERLOAD_SCRIPT' does not exist or is not readable"
|
echo "overload script '$OVERLOAD_SCRIPT' does not exist or is not readable" >&$DEBUGLOG
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
@@ -365,12 +360,15 @@ declare -A OIDTYPES
|
|||||||
function clear_cached_oid() {
|
function clear_cached_oid() {
|
||||||
local base_oid=$1
|
local base_oid=$1
|
||||||
local oid
|
local oid
|
||||||
|
local count=0
|
||||||
for oid in ${!OIDDATA[@]}; do
|
for oid in ${!OIDDATA[@]}; do
|
||||||
if [[ $oid == $base_oid.* ]]; then
|
if [[ $oid == $base_oid.* ]]; then
|
||||||
unset OIDDATA[$oid]
|
unset OIDDATA[$oid]
|
||||||
unset OIDTYPES[$oid]
|
unset OIDTYPES[$oid]
|
||||||
|
((count++))
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
echo "cache: removed $count OIDs" >&$DEBUGLOG
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,7 +403,7 @@ function update_oid_cache() {
|
|||||||
break
|
break
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "cache: received invalid line" >&$DEBUGLOG
|
echo "cache: received invalid line: $line" >&2
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
@@ -443,8 +441,7 @@ function return_oid() {
|
|||||||
# Main logic of the daemon.
|
# Main logic of the daemon.
|
||||||
#
|
#
|
||||||
function main() {
|
function main() {
|
||||||
local cmd line oid req next
|
local buf cmd oid req next
|
||||||
local -a args
|
|
||||||
|
|
||||||
echo "waiting for all data gathering functions to return data" >&$LOG
|
echo "waiting for all data gathering functions to return data" >&$LOG
|
||||||
update_oid_cache true
|
update_oid_cache true
|
||||||
@@ -455,28 +452,22 @@ function main() {
|
|||||||
update_oid_cache
|
update_oid_cache
|
||||||
done
|
done
|
||||||
read -r -t 1 -u $STDIN buf
|
read -r -t 1 -u $STDIN buf
|
||||||
local rc=$?
|
rc=$?
|
||||||
if (( rc > 128 )); then
|
if (( rc == 0 )); then
|
||||||
line+=$buf
|
cmd+=${buf}
|
||||||
continue
|
elif (( rc > 128 )); then
|
||||||
elif (( rc == 0 )); then
|
# read timed out
|
||||||
line+=$buf
|
[ -z "$buf" ] && continue
|
||||||
|
echo "< $buf (partial line)" >&$DEBUGLOG
|
||||||
|
cmd+=${buf}
|
||||||
|
# to work around a bug in Bash prior to version 5.3, check if $cmd contains a complete command first before continuing reading
|
||||||
|
# -> bug report: https://lists.gnu.org/archive/html/bug-bash/2024-10/msg00005.html
|
||||||
|
# -> bug fix: https://git.savannah.gnu.org/cgit/bash.git/diff/builtins/read.def?h=devel&id=3ed028ccec871bc8d3b198c1681374b1e37df7cd
|
||||||
|
[[ "${cmd,,}" =~ ^(ping|set|get|getnext)$ ]] || continue
|
||||||
else
|
else
|
||||||
exit 255
|
exit 255
|
||||||
fi
|
fi
|
||||||
echo "< $line" >&$DEBUGLOG
|
echo "< $cmd" >&$DEBUGLOG
|
||||||
if [ -z $cmd ]; then
|
|
||||||
cmd=$line
|
|
||||||
args=()
|
|
||||||
elif [ -z $line ]; then
|
|
||||||
cmd=""
|
|
||||||
args=()
|
|
||||||
snmp_echo NONE
|
|
||||||
continue
|
|
||||||
else
|
|
||||||
args+=("$line")
|
|
||||||
fi
|
|
||||||
line=""
|
|
||||||
|
|
||||||
case "${cmd,,}" in
|
case "${cmd,,}" in
|
||||||
ping)
|
ping)
|
||||||
@@ -485,14 +476,21 @@ function main() {
|
|||||||
;;
|
;;
|
||||||
set)
|
set)
|
||||||
# we need to args here, 'oid' and 'type_and_value'
|
# we need to args here, 'oid' and 'type_and_value'
|
||||||
(( ${#args[@]} < 2 )) && continue
|
|
||||||
cmd=""
|
cmd=""
|
||||||
|
read -r -u $STDIN buf
|
||||||
|
echo "< $buf" >&$DEBUGLOG
|
||||||
|
read -r -u $STDIN buf
|
||||||
|
echo "< $buf" >&$DEBUGLOG
|
||||||
snmp_echo not-writable
|
snmp_echo not-writable
|
||||||
;;
|
;;
|
||||||
get)
|
get)
|
||||||
(( ${#args[@]} < 1 )) && continue
|
|
||||||
cmd=""
|
cmd=""
|
||||||
oid=${args[0]}
|
read -r -u $STDIN oid
|
||||||
|
echo "< $oid" >&$DEBUGLOG
|
||||||
|
if [ -z "$oid" ]; then
|
||||||
|
echo "received empty oid" >&2
|
||||||
|
snmp_echo NONE
|
||||||
|
fi
|
||||||
req_from_oid $oid req || continue
|
req_from_oid $oid req || continue
|
||||||
if [[ ! -v OIDDATA[$req] ]]; then
|
if [[ ! -v OIDDATA[$req] ]]; then
|
||||||
echo "$oid not found" >&$DEBUGLOG
|
echo "$oid not found" >&$DEBUGLOG
|
||||||
@@ -502,9 +500,13 @@ function main() {
|
|||||||
return_oid "$req"
|
return_oid "$req"
|
||||||
;;
|
;;
|
||||||
getnext)
|
getnext)
|
||||||
(( ${#args[@]} < 1 )) && continue
|
|
||||||
cmd=""
|
cmd=""
|
||||||
oid=${args[0]}
|
read -r -u $STDIN oid
|
||||||
|
echo "< $oid" >&$DEBUGLOG
|
||||||
|
if [ -z "$oid" ]; then
|
||||||
|
echo "received empty oid" >&2
|
||||||
|
snmp_echo NONE
|
||||||
|
fi
|
||||||
req_from_oid $oid req || continue
|
req_from_oid $oid req || continue
|
||||||
next=$(printf "%s\n" ${!OIDDATA[@]} $req | sort -V | grep -A1 -E "^$req\$" | tail -n 1)
|
next=$(printf "%s\n" ${!OIDDATA[@]} $req | sort -V | grep -A1 -E "^$req\$" | tail -n 1)
|
||||||
echo "evaluated next candidate: [requested: '$req', next: '$next']" >&$DEBUGLOG
|
echo "evaluated next candidate: [requested: '$req', next: '$next']" >&$DEBUGLOG
|
||||||
@@ -520,8 +522,8 @@ function main() {
|
|||||||
break
|
break
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "invalid command '$cmd'" >&$LOG
|
echo "invalid command '$cmd', exiting ..." >&2
|
||||||
cmd=""
|
break
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
@@ -548,26 +550,77 @@ exec {STDIN}<&0
|
|||||||
# Start main in a sub-shell and create a writable fd to it.
|
# Start main in a sub-shell and create a writable fd to it.
|
||||||
echo "daemon starting (PID: $$)" >&$LOG
|
echo "daemon starting (PID: $$)" >&$LOG
|
||||||
exec {DATAIN}> >(main)
|
exec {DATAIN}> >(main)
|
||||||
pid=$!
|
main_pid=$!
|
||||||
|
|
||||||
trap "echo daemon stopped >&$LOG" EXIT
|
trap "echo daemon stopped >&$LOG" EXIT
|
||||||
|
|
||||||
declare -A timetable
|
declare -A timetable
|
||||||
|
declare -A fdtable
|
||||||
|
declare -A pidtable
|
||||||
first_run=true
|
first_run=true
|
||||||
while :; do
|
while :; do
|
||||||
# Check if main is still alive and exit otherwise.
|
# Check if main is still alive and exit otherwise.
|
||||||
ps -p $pid > /dev/null || break
|
ps -p $main_pid >/dev/null || break
|
||||||
|
|
||||||
[ -v EPOCHSECONDS ] && now=$EPOCHSECONDS || now=$(data +%s)
|
[ -v EPOCHSECONDS ] && now=$EPOCHSECONDS || now=$(date +%s)
|
||||||
for func in "${!DATA_FUNCS[@]}"; do
|
for func in "${!DATA_FUNCS[@]}"; do
|
||||||
if (( now >= ${timetable[$func]:-0} )); then
|
next_update=${timetable[$func]:-$now}
|
||||||
|
if (( now >= next_update )); then
|
||||||
delay=${DATA_FUNCS[$func]}
|
delay=${DATA_FUNCS[$func]}
|
||||||
next_update=$((now + delay))
|
|
||||||
timetable[$func]=$next_update
|
|
||||||
$first_run && echo "starting $func (refresh every $delay seconds)" >&$LOG
|
$first_run && echo "starting $func (refresh every $delay seconds)" >&$LOG
|
||||||
echo "executing $func, scheduled next refresh at $(date -d @$next_update)" >&$DEBUGLOG
|
fd=${fdtable[$func]:--1}
|
||||||
# execute data gathering function and pipe its output to main
|
if (( fd == -1 )); then
|
||||||
$func >&$DATAIN
|
exec {data}< <($func </dev/null)
|
||||||
|
pid=$!
|
||||||
|
echo "gather: executed $func (PID $pid, FD $data)" >&$DEBUGLOG
|
||||||
|
fdtable[$func]=$data
|
||||||
|
pidtable[$func]=$pid
|
||||||
|
else
|
||||||
|
pid=${pidtable[$func]}
|
||||||
|
echo "gather: skip executing $func, it is still running (PID $pid, FD $fd)" >&2
|
||||||
|
fi
|
||||||
|
((next_update+=delay))
|
||||||
|
timetable[$func]=$next_update
|
||||||
|
echo "gathe: scheduled next execution of $func at $(date -d @$next_update)" >&$DEBUGLOG
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for func in "${!fdtable[@]}"; do
|
||||||
|
fd=${fdtable[$func]}
|
||||||
|
(( fd == -1 )) && continue
|
||||||
|
if ! $first_run; then
|
||||||
|
read -t 0 -u $fd
|
||||||
|
(( $? == 0 )) || continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
data=$(timeout 5 cat <&$fd)
|
||||||
|
rc=$?
|
||||||
|
eval "exec $fd>&-"
|
||||||
|
fdtable[$func]=-1
|
||||||
|
|
||||||
|
pid=${pidtable[$func]}
|
||||||
|
if (( rc == 124 )); then
|
||||||
|
echo "gather: timeout receiving data from $func (PID $pid, FD $fd), sending SIGTERM" >&2
|
||||||
|
kill -SIGTERM $pid
|
||||||
|
sleep 1
|
||||||
|
if ps -p $pid >/dev/null; then
|
||||||
|
echo "gather: unable to terminate $func (PID $pid, FD $fd), sending SIGKILL" >&2
|
||||||
|
kill -SIGKILL $pid
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait $pid &>/dev/null
|
||||||
|
rc=$?
|
||||||
|
# the wait function in older Bash versions prior to 5.1 always returns 127 if the
|
||||||
|
# sub-process already exited at this point
|
||||||
|
(( rc == 127 )) && rc=0
|
||||||
|
|
||||||
|
echo "gather: $func (PID $pid, FD $fd) exited with rc = $rc" >&$DEBUGLOG
|
||||||
|
if (( rc == 0 )) && [ -n "$data" ]; then
|
||||||
|
echo "gather: sending data to cache" >&$DEBUGLOG
|
||||||
|
echo "$data" 1>&$DATAIN
|
||||||
|
echo "ENDOFDATA" >&$DATAIN
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user