Skip to content

Commit

Permalink
Added plugin and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Darren Spruell committed Dec 30, 2015
1 parent 1810d6d commit 55c9aaf
Show file tree
Hide file tree
Showing 2 changed files with 274 additions and 1 deletion.
83 changes: 82 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,83 @@
# check_nids_interfaces
Nagios plugin designed to passively check capture interfaces receiving desired traffic flows

Nagios plugin designed to passively check capture interfaces receiving desired
traffic flows.

## Overview

**check_nids_interfaces** is a Nagios plugin designed to test NIDS sensor
network capture interfaces for specified traffic in order to check if the
interface is receiving monitored flows. The plugin's purpose is to aid with
the problem of monitoring what is effectively a passive service; when network
flows cease to arrive at passive monitoring devices, there is seldom a
noticable service outage. Even a partial outage may go undetected, if at least
a single link is functioning and feeding traffic to the device. This plugin is
designed to make passive traffic inspection a monitorable service.

## How it works

The plugin is a shell script that conforms to the Nagios plugin API.
Specified network interfaces are checked in sequence by attaching *tcpdump(8)*
to them and using a BPF to listen for desired traffic. If the number of
interfaces that do present that traffic don't meet the given thresholds,
WARNING or CRITICAL status is returned along with output summarizing the
issue. Here's an example:

$ check_nids_interfaces -p 80/tcp -w 7 -c 6 eth6 eth7 eth8 eth9 eth10 eth11 eth12 eth13
INTERFACES OK - 8 interface(s) with traffic (eth6 eth7 eth8 eth9 eth10 eth11 eth12 eth13), 0 without (-)

## Requirements

- Bash (version 4)
- tcpdump
- [bash_colors][bc] terminal color library

## Setup

1. Copy the plugin into the plugins directory.
2. Install a copy of the bash_colors somewhere in `PATH` (using the name
`bash_colors`).

This plugin is designed to be run as an unprivileged user on Linux, so on that
platform the *tcpdump(8)* binary may have the proper capabilities(7) set,
typically with the following command:

# setcap cap_net_raw,cap_net_admin=eip /usr/sbin/tcpdump

Otherwise, and on other platforms, it will work under *sudo(8)* instead.

## Usage notes

Run the script with the `-h` (help) option to see usage information. In the
standard case, the plugin must be provided with a traffic filter
specification, warning and critical thresholds, and a list of interface names.

The traffic filter specification may be given using the `-p` option as a
"protocol spec", using the standard service specification for a TCP or UDP
port in the form *port/proto*, as in *80/tcp*. This is then converted to a
valid BPF expression. Alternatively, an explicit BPF filter may be given using
the `-f` option, as in `-f 'tcp and port (80 or 443)'`. Unless the monitored
traffic is passed through devices that filter or otherwise selectively forward
specific traffic, the best strategy is probably to specify a common service
for the monitored segments or listen for any traffic (`-f ip`).

Thresholds should be specified by the number of interfaces that receive
traffic to return the given status. For example, if 8 interfaces are checked,
and thresholds are given as `-w 7 -c 6`, the plugin will return WARNING status
if one interface is not receiving traffic, or CRITICAL if two or more are not.

By default, the plugin captures traffic on the interface for about one second.
Some network segments may not forward desired traffic in high volume, so it
may be desirable to increase the capture duration to greater than one second.
This can be done using the `-d` option, as in `-d 3`. Be aware that while this
increases the possibility of receiving the desired traffic, it also compounds
the time required for the plugin to complete execution, thus increasing the
chance that the plugin could exceed its allotted execution time.

Finally, this plugin is also designed so that it may be run manually using the
`-v' option for verbose output. This makes use of colorized terminal output
using the [bash_colors][bc] library and is useful when spot checking and
troubleshooting monitoring health interactively.

[bc]: https://github.com/maxtsepkov/bash_colors

192 changes: 192 additions & 0 deletions check_nids_interfaces
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#!/usr/bin/env bash
#
# https://github.com/RiskIQ/check_nids_interfaces
#
# Author: Darren Spruell ([email protected])
#
# Test NIDS sensor network interfaces for specified traffic to see if the
# sensor is receiving monitored flows.

set -e
PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin

# Service name to return in plugin check output for Nagios
SERVICENAME="INTERFACES"
# Default pause interval in seconds for capture process to snag traffic
CAPTURE_DURATION=1
# Array of status codes from Nagios plugin API
declare -A STATUS_CODES=([OK]=0 [WARNING]=1 [CRITICAL]=2 [UNKNOWN]=3)

# Load ANSI color routines; relative path searches in PATH
source bash_colors

do_cleanup() {
rm -f /tmp/pcap-????????
}

trap 'do_cleanup; exit' ERR EXIT QUIT TERM
trap 'printf "\n%s\n" Exiting...; do_cleanup; exit' INT

# Return BPF expression to use when testing interface for receipt of "target"
# traffic that will determine whether traffic is transmitting to sensor.
# Explicitly specified BPF is returned as is; "portspecs" are translated to
# simple expressions, and anything else is an error.
get_bpf() {
if [ -n "$BPF" ]; then
FILTER="$BPF"
elif [ -n "$PORTSPEC" ]; then
if ! echo "$PORTSPEC" | egrep -q '[0-9]+/[a-z]+'; then
print_err "given portspec not required format ($PORTSPEC)"
exit ${STATUS_CODES[UNKNOWN]}
fi
FILTER="$(echo "$PORTSPEC" | awk -F/ '{print $2 " and port " $1}')"
else
print_err "BPF or monitor portspec must be specified"
exit ${STATUS_CODES[UNKNOWN]}
fi
print_verbose "$(clr_white "Filtering using BPF: $(clr_escape "$FILTER" $CLR_BOLD $CLR_CYAN)")"
echo "$FILTER"
}

usage()
{
cat <<-EOF
USAGE: $(basename "$0") [options] INTERFACE [...]
Test specified interfaces for receipt of key network traffic.
Options:
-p PORTSPEC:
Use given PORTSPEC to derive the BPF expression to verify receipt
of monitored traffic on interfaces. PORTSPEC should be specified
in the format 'port/proto', e.g. 80/tcp. Either the PORTSPEC or
the FILTER options are required.
-f FILTER:
Explicit BPF filter to use to verify receipt of monitored traffic
on interfaces. This can be used instead of '-p' option to specify
a more complex filter to monitor for target traffic.
-w NUM:
Warning threshold. Return WARNING status when only the
specified number or fewer interfaces see the desired traffic.
-c NUM:
Critical threshold. Return CRITICAL status when only the
specified number or fewer interfaces see the desired traffic.
-d NUM:
Capture duration. Number of seconds to monitor for traffic on each
interface. Defaults to $CAPTURE_DURATION; depending on the flow rate of the
desired traffic it may be required to increase this value.
Be careful not to set this too high as otherwise the check could
time out.
-v: Enable verbose output. Intended for interactive usage.
-h: Display this help output.
EOF
}

# Format error messages on stderr
print_err() {
local msg="$1"
echo "$(basename $0) - ERROR: $msg" >&2
}

# Handle verbose output (also stderr)
print_verbose()
{
local msg="$1"
if [ -n "$VERBOSE" ]; then
echo "$msg" >&2
fi
}

# Test for privileges to invoke tcpdump(8). Pray to Cthulu that this works on
# your platform.
test_capture_priv() {
tcpdump -n -c 1 2>&1 1>/dev/null
}

# Test for receipt of desired traffic on specified interface.
test_iface() {
local iface="$1"
TMPOUT=$(mktemp "${TMPDIR:-/tmp}"/pcap-XXXXXXXX)

# If something goes wrong grabbing the BPF expression, abort
[ -n "$FILTER" ] || FILTER="$(get_bpf)"
local STATUS=$?
[ $STATUS -eq 0 ] || exit $STATUS

tcpdump -s 68 -c 1 -ni "$iface" -w $TMPOUT "$FILTER" 2>/dev/null &
PID=$!
# Grab desired traffic, hopefully
sleep $CAPTURE_DURATION
# Kill the backgrounded tcpdump(8) if it's waiting for packets that didn't
# arrive. This is the failure state for the interface. The '|| :' construct
# is used to bypass abort-on-error logic in script (set -e, ERR trap).
kill $PID 2>/dev/null || :
# Give tcpdump a chance to flush to output file
sleep 1
# Return status based on whether interesting traffic was captured
[ -n "$(tcpdump -nqr $TMPOUT 2>/dev/null)" ]
}

# Parse script arguments
while getopts "p:f:w:c:d:vh" optchar; do
case $optchar in
p) PORTSPEC="${OPTARG}" ;;
f) BPF="${OPTARG}" ;;
w) THRESHOLD_WARN=${OPTARG} ;;
c) THRESHOLD_CRIT=${OPTARG} ;;
d) CAPTURE_DURATION=${OPTARG} ;;
v) VERBOSE=1 ;;
h) usage; exit 0 ;;
esac
done
shift $(($OPTIND - 1))

# Check that tcpdump can be invoked with current privileges. The '&& :'
# construct is used to bypass abort-on-error logic in script (set -e,
# ERR trap).
ERR_MSG="$(test_capture_priv)" && :
if [ $? -ne 0 ]; then
echo $SERVICENAME UNKNOWN - $ERR_MSG >&2
exit ${STATUS_CODES[UNKNOWN]}
fi

if [ $# -eq 0 ]; then
usage; exit 1
elif [ -z "$THRESHOLD_WARN" -o -z "$THRESHOLD_CRIT" ]; then
print_err "warning and critical thresholds required"
usage; exit 1
elif [ "$THRESHOLD_WARN" -ge ${#@} -o "$THRESHOLD_CRIT" -ge ${#@} ]; then
print_err "warning and critical thresholds must be less than number of target interfaces"
usage; exit 1
else
# Load positional arguments into interface array
TAP_IFACES=($*)
fi

print_verbose "$(clr_white "Testing ${#TAP_IFACES[@]} interface(s): $(clr_escape "${TAP_IFACES[*]}" $CLR_BOLD $CLR_CYAN)")"

for iface in ${TAP_IFACES[@]}; do
if test_iface $iface; then
RESULTS_PASS+=("$iface")
print_verbose "$(clr_bold "$(clr_green '[*] Interface '${iface}' has specified traffic')")"
else
RESULTS_FAIL+=("$iface")
print_verbose "$(clr_bold "$(clr_red '[!] Interface '${iface}' missing specified traffic')")"
fi
done

# Exit status will be OK unless a threshold is violated
STATUS="OK"
if [ ${#RESULTS_PASS[*]} -le $THRESHOLD_WARN ]; then
STATUS="WARNING"
fi
if [ ${#RESULTS_PASS[*]} -le $THRESHOLD_CRIT ]; then
STATUS="CRITICAL"
fi

printf "%s %s - %d interface(s) with traffic (%s), %d without (%s)\n" \
"$SERVICENAME" "$STATUS" "${#RESULTS_PASS[*]}" "${RESULTS_PASS[*]:--}" \
"${#RESULTS_FAIL[*]}" "${RESULTS_FAIL[*]:--}"
exit ${STATUS_CODES["$STATUS"]}

0 comments on commit 55c9aaf

Please sign in to comment.