Skip to content

Instantly share code, notes, and snippets.

@baphael
Last active May 15, 2023 12:34
Show Gist options
  • Save baphael/046f20894d7e15009ab6184e6eda49ca to your computer and use it in GitHub Desktop.
Save baphael/046f20894d7e15009ab6184e6eda49ca to your computer and use it in GitHub Desktop.
Netstat overlay to monitor TCP sockets usage (polling)
#!/usr/bin/env bash
# Simple IPv4 PCRE pattern
IP_P="(\d{1,3}\.){3}\d{1,3}"
echo -n "Check privileges... "
if (( $(id -u) )); then
echo KO
echo "This script must be executed as superuser in order to write in /tmp and to listen to all sockets."
exit 1
fi
echo OK
echo -n "Check dependencies... "
netstat="$(which netstat)"
if [[ -z ${netstat} ]]; then
echo KO
echo "This script relies on netstat and it seems it is not installed. To install it, run 'apt install net-tools' as superuser."
exit 2
fi
echo OK
function usage() {
SELF="$(basename ${0})"
cat <<EOF
Description: Retrieves distinct IPv4 addresses that connect to localhost (on some ports or all).
Usage: "${SELF}" [-p|--port PORT] [-l|--local] [-i|--interval SECONDS] [-o|--output FILE] [-h|--help] [-v|--verbose]
Press ^C [CTRL+c] to stop
Optional arguments:
-h, --help Display this help message and exit.
-p, --port PORT Port to check. Can be used multiple times to check multiple ports. Default is all ports.
-l, --include-lhost Include localhost connexions.
-i, --interval SECONDS Frequency at which checks are made. Cannot be below 1 nor above 59. Should be below 10 for optimal results. Default is 2.
-o, --output FILE Output file. Defaults to STDOUT.
-v, --verbose Verbose output (only to STDOUT).
Examples:
"${SELF}" -p 80 -p 443 -i 5 # Check who connects to my web services
"${SELF}" -p 3306 -p 5432 -l -o my_db_clients.log # Check who connects to my MySQL/PostgreSQL
"${SELF}" -v -p 0-1024 # Check who connects to all my below-1024 ports
Notes:
Compact arguments notation is not supported (e.g. : "${SELF} -vlp 80" will not work).
EOF
}
echo -n "Parse arguments... "
# Arguments parsing
MY_OWN_IPS=($(ip a | grep -oP "${IP_P}(?=/\d+)"))
PORTS=()
INTERVAL=2
OUTPUT=""
VERBOSE=0
DEBUG=0
while (( $# )); do
case $1 in
-l|--include-lhost)
MY_OWN_IPS=()
shift
;;
-p|--port)
if (( $(echo "${2}" | grep -Pc "^\d+?$") )); then
PORTS+=( "${2}" )
elif (( $(echo "${2}" | grep -Pc "^\d+-\d+?$") )); then
port_range_lower_bound=$(echo "${2}"|cut -d- -f1)
port_range_upper_bound=$(echo "${2}"|cut -d- -f2)
if (( ! (${port_range_lower_bound} < ${port_range_upper_bound}) )); then
echo "Illegal port range. Assertion violated: ${port_range_lower_bound} < ${port_range_upper_bound} ! This port range will be ignored."
fi
PORTS+=($(seq ${port_range_lower_bound} ${port_range_upper_bound}))
else
echo KO
echo "Illegal port value ${2} !"
exit 3
fi
shift
shift
;;
-i|--interval)
if (( $(echo "${2}" | grep -Pc "^\d+$") )); then
if (( "${2}" >= 10 )); then
echo "WARNING: for optimal results, delay value should be < 10 (seconds) !"
fi
if (( "${2}" < 1 || "${2}" > 59 )); then
echo KO
echo "Assertion violated: 1 < ${2} < 59 !"
exit 4
fi
INTERVAL="${2}"
fi
shift
shift
;;
-o|--output)
touch "${2}"
if (( ! $? )) && [[ -f "${2}" ]]; then
OUTPUT="${2}"
else
echo KO
echo "Could not write to output file."
exit 5
fi
shift
shift
;;
-h|--help)
usage
exit 6
;;
-v|--verbose)
VERBOSE=1
shift
;;
-d|--debug)
DEBUG=1
shift
;;
*)
echo KO
echo "Unknown argument ${1} !"
usage
exit 7
;;
esac
done
echo OK
if (( ${VERBOSE} )); then
echo "OUTPUT: ${OUTPUT:-STDOUT}"
echo "MONITORED PORTS: ${PORTS[@]:-*}"
echo "VERBOSE MODE: $((( ${VERBOSE} )) && echo En || echo Dis)abled"
echo "INTERVAL: ${INTERVAL:-2} seconds"
echo "SHOW LOCAL CONNEXIONS: $((( ${#MY_OWN_IPS[@]} )) && echo No || echo Yes)"
fi
# Build ports regexp
PORTS_REGEXP=""
if (( ${#PORTS[@]} )); then
PORTS_REGEXP=":("
for port in ${PORTS[@]}; do
PORTS_REGEXP+="${port}|"
done
PORTS_REGEXP="${PORTS_REGEXP%|})\b"
fi
(( ${VERBOSE} )) && echo "MONITORED PORTS REGEXP: ${PORTS_REGEXP}"
TMP_FILE="/tmp/whobinds_$(date +%s%N).log"
touch "${TMP_FILE}"
(( ${VERBOSE} )) && echo "TMP FILE: ${TMP_FILE}"
function cleanup() {
echo -n "Graceful stop... "
rm -rf "${TMP_FILE}" 2>/dev/null
echo OK
exit 10
}
trap cleanup SIGHUP SIGINT SIGQUIT SIGABRT
echo "Running..."
echo "Press [ctrl+c] to interrupt."
while true; do
unset is_local lhost lport rhost output_str
# Every INTERVAL seconds, we check which IPs connect to specified port(s)
for lhost_lport_rhost_rport in $(netstat -ntp | grep -P "${PORTS_REGEXP}" | grep -oP "(${IP_P}:\d+\s*){2}" | sed "s/\s*$//" | tr -s " " | tr " " "|" | sort | uniq); do
(( ${DEBUG} )) && echo lhost_lport_rhost_rport:${lhost_lport_rhost_rport}
lhost=$(echo "${lhost_lport_rhost_rport}" | grep -oP "^${IP_P}(?=:\d+\|${IP_P}:\d+$)"); (( ${DEBUG} )) && echo lhost:$lhost
lport=$(echo "${lhost_lport_rhost_rport}" | grep -oP "^${IP_P}:\K\d+(?=\|${IP_P}:\d+$)"); (( ${DEBUG} )) && echo lport:$lport
rhost=$(echo "${lhost_lport_rhost_rport}" | grep -oP "^${IP_P}:\d+\|\K${IP_P}(?=:\d+$)"); (( ${DEBUG} )) && echo rhost $rhost
rport=$(echo "${lhost_lport_rhost_rport}" | grep -oP "^${IP_P}:\d+\|${IP_P}:\K\d+$"); (( ${DEBUG} )) && echo rport $rport
output_str="${lhost}:${lport} <--> ${rhost}:${rport}"
# For each of those IPs, we check if it was already seen
if (( ! $(grep -cow "^${lhost_lport_rhost_rport}$" "${TMP_FILE}") )); then
# If not, we check if it's one of our own IP
is_local=$(echo ${MY_OWN_IPS[@]} | grep -cow "${rhost}")
if (( ! ${is_local} )); then
# And if it's not or if local addresses are to be incuded, then we have a match !
echo "${lhost_lport_rhost_rport}" >> "${TMP_FILE}"
echo "${output_str}" | tee -a "${OUTPUT}" 2>/dev/null
fi
fi
done
sleep ${INTERVAL}
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment