Initial commit

This commit is contained in:
ltning 2025-05-22 02:10:50 +02:00
commit bdfeca60a6
3 changed files with 229 additions and 0 deletions

25
README.md Normal file
View file

@ -0,0 +1,25 @@
Script to produce Prometheus-style `.prom` files from `pfctl(8)` output.
By default places output in `/var/tmp/node_exporter`, for the benefit of `node_exporter`.
Currently only collects data from running jails (not the host itself).
## Assumptions
The script first checks for the existence of `/dev/pf`.
Jails with a `/dev/pf` have paths either of the form
/x/y
or
/x/y/root
Both cases are checked, and if `/sbin/pfctl` and `/libexec/ld-elf.so.1` are found, they are used as-is.
If not, the former pattern is expected to be the parent of a wrapped jail, with no libraries or binaries in the regular paths.
The inner jail is expected to have a path prefix of /root relative to the parent; In such cases, the script sets `LD_LIBRARY_PATH`
to `/root/lib` and invokes `/root/libexec/ld-elf.so.1` to execute `/root/sbin/pfctl`.
## Work to be done
- Be more flexible about jail path layouts
- Support collecting pf stats for the host
- Collect per-rule statistics
- Collect per-table statistics

163
pfctl_exporter.sh Normal file
View file

@ -0,0 +1,163 @@
#!/bin/sh
set -e
sleeptime='10'
tmpfile='/var/tmp/node_exporter/.pfctl_exporter.prom.tmp'
dstfile='/var/tmp/node_exporter/pfctl_exporter.prom'
counters_pat='/var/tmp/node_exporter/.counters.pat'
tracking_pat='/var/tmp/node_exporter/.tracking.pat'
create_patterns() {
echo x - ${counters_pat}
sed -E 's/^ *X//' >${counters_pat} << ' COUNTERS'
X^match .*
X^bad_offset .*
X^fragment .*
X^short .*
X^normalize .*
X^memory .*
X^bad_timestamp .*
X^congestion .*
X^ip_option .*
X^proto_cksum .*
X^state_mismatch .*
X^state_insert .*
X^state_limit .*
X^src_limit .*
X^synproxy .*
X^map_failed .*
X^max_states_per_rule .*
X^max_src_states .*
X^max_src_nodes .*
X^max_src_conn .*
X^max_src_conn_rate .*
X^overload_table_insertion .*
X^overload_flush_states .*
X^synfloods_detected .*
COUNTERS
echo x - ${tracking_pat}
sed -E 's/^ *X//' >${tracking_pat} << ' TRACKING'
X^current_entries .*
X^searches .*
X^inserts .*
X^removals .*
TRACKING
}
startup() {
local jail="$1"
if [ -z "${jail}" ] || ! jpath=$(jls -j "${jail}" path 2>/dev/null) ; then
echo "Jail not specified or not running" 1>&2
return 1
fi
if ! [ -c "${jpath}/dev/pf" ] ; then
echo "No /dev/pf in ${jpath}!" 1>&2
return 1
fi
if [ -s "${jpath}/sbin/pfctl" ] &&
[ -s "{jpath}/libexec/ld-elf.so.1" ] ; then
J_LD_LIBRARY_PATH='/lib'
pfctl='/sbin/pfctl'
elif [ "${jpath%%/root}" = "$jpath" ] &&
[ -s "${jpath}/root/sbin/pfctl" ] &&
[ -s "{jpath}/root/libexec/ld-elf.so.1" ]; then
J_LD_LIBRARY_PATH='/root/lib'
pfctl='/root/libexec/ld-elf.so.1 /root/sbin/pfctl'
else
echo "pfctl binary and/or supporting libraries not found in ${jpath}."
return 1
fi
}
jail_collect_stats() {
local jail="$1"
LD_LIBRARY_PATH="${J_LD_LIBRARY_PATH}" jexec "${jail}" $pfctl -vvsi 2>/dev/null | \
tr '-' '_' | \
/usr/bin/sed -E -e 's/([^ ]) ([^ ])/\1_\2/g' -e 's/^ *//g' | \
cut -f 1,2 -w | \
tr '\t' ' '
}
jail_parse_stats() {
local jail="$1"
local stats="$2"
# State tracking table
IFS="$(printf "\n\r")"
for l in $(echo "$stats" | grep -f ${tracking_pat} | head -"$(wc -l < ${tracking_pat} | tr -d ' ')") ; do
unset IFS
set -- $l
name=$1 ; value=$2
echo "jail_pf_states_${name}{name=\"${jail}\"} ${value}"
done
# Source tracking table
IFS="$(printf "\n\r")"
for l in $(echo "$stats" | grep -f ${tracking_pat} | tail -"$(wc -l < ${tracking_pat} | tr -d ' ')") ; do
unset IFS
set -- $l
name=$1 ; value=$2
echo "jail_pf_sources_${name}{name=\"${jail}\"} ${value}"
done
# Syncookies
IFS="$(printf "\n\r")"
tags="name=\"${jail}\""
for l in $(echo "$stats" | grep -E 'highwater|lowwater|mode|active') ; do
unset IFS
set -- $l
name=$1 ; value=${2%%_%}
[ "$name" = 'highwater' ] && name='highwater_pct'
[ "$name" = 'lowwater' ] && name='lowwater_pct'
tags="${tags},${name}=\"${value}\""
done
IFS="$(printf "\n\r")"
for l in $(echo "$stats" | grep -E 'syncookies|halfopen') ; do
unset IFS
set -- $l
name=${1##syncookies_} ; value=$2
echo "jail_pf_syncookies_${name}{${tags}} ${value}"
done
# Counters
IFS="$(printf "\n\r")"
for l in $(echo "$stats" | grep -f ${counters_pat}) ; do
unset IFS
set -- $l
name=$1 ; value=$2
echo "jail_pf_counters{name=\"${jail}\",counter=\"${name}\"} ${value}"
done
}
echo "$(date) - Starting up"
create_patterns
if [ ! -s ${tracking_pat} ] || [ ! -s ${counters_pat} ] ; then
echo "Pattern files not found in $(pwd)"
exit 1
fi
while sleep "$sleeptime" ; do
for j in $(jls name) ; do
echo "Running for ${j}.." 1>&2
if startup "$j" ; then
if stats="$(jail_collect_stats "$j")" ; then
jail_parse_stats "$j" "$stats" >> "$tmpfile"
else
echo "$(date) - Failed to collect stats for ${j} - did jail disappear?"
fi
else
echo "Skipping ${j}.." 1>&2
fi
done
if [ "$?" -eq 0 ] ; then
mv "$tmpfile" "$dstfile"
else
rm -f "$dstfile"
fi
echo "Sleeping.." 1>&2
done
rm -f ${counters_pat} ${tracking_pat}

41
pfctl_maintenance.sh Normal file
View file

@ -0,0 +1,41 @@
#!/bin/sh
set -e
startup() {
local jail="$1"
if [ -z "${jail}" ] || ! jpath=$(jls -j "${jail}" path 2>/dev/null) ; then
echo "Jail not specified or not running" 1>&2
return 1
fi
if ! [ -c "${jpath}/dev/pf" ] ; then
echo "No /dev/pf in ${jpath}!" 1>&2
return 1
fi
if [ "${jpath%%/root}" = "$jpath" ] ; then
J_LD_LIBRARY_PATH='/root/lib'
pfctl='/root/libexec/ld-elf.so.1 /root/sbin/pfctl'
else
J_LD_LIBRARY_PATH='/lib'
pfctl='/sbin/pfctl'
fi
}
jail_expire_table() {
local jail="$1"
local table="$2"
local expiry="$3"
LD_LIBRARY_PATH="${J_LD_LIBRARY_PATH}" jexec "${jail}" $pfctl -T expire "$expiry" -t "$table"
}
echo "$(date) - Starting up"
for j in $(jls name) ; do
echo "Running for ${j}.." 1>&2
if startup "$j" ; then
jail_expire_table "$j" "badhosts" 300
else
echo "Skipping ${j}.." 1>&2
fi
done