summaryrefslogtreecommitdiffstats
path: root/collectors/cgroups.plugin/cgroup-network-helper.sh.in
diff options
context:
space:
mode:
Diffstat (limited to 'collectors/cgroups.plugin/cgroup-network-helper.sh.in')
-rwxr-xr-xcollectors/cgroups.plugin/cgroup-network-helper.sh.in376
1 files changed, 376 insertions, 0 deletions
diff --git a/collectors/cgroups.plugin/cgroup-network-helper.sh.in b/collectors/cgroups.plugin/cgroup-network-helper.sh.in
new file mode 100755
index 000000000..da9b9162a
--- /dev/null
+++ b/collectors/cgroups.plugin/cgroup-network-helper.sh.in
@@ -0,0 +1,376 @@
+#!/usr/bin/env bash
+# shellcheck disable=SC1117
+
+# cgroup-network-helper.sh
+# detect container and virtual machine interfaces
+#
+# (C) 2023 Netdata Inc.
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This script is called as root (by cgroup-network), with either a pid, or a cgroup path.
+# It tries to find all the network interfaces that belong to the same cgroup.
+#
+# It supports several method for this detection:
+#
+# 1. cgroup-network (the binary father of this script) detects veth network interfaces,
+# by examining iflink and ifindex IDs and switching namespaces
+# (it also detects the interface name as it is used by the container).
+#
+# 2. this script, uses /proc/PID/fdinfo to find tun/tap network interfaces.
+#
+# 3. this script, calls virsh to find libvirt network interfaces.
+#
+
+# -----------------------------------------------------------------------------
+
+# the system path is cleared by cgroup-network
+# shellcheck source=/dev/null
+[ -f /etc/profile ] && source /etc/profile
+export PATH="${PATH}:@sbindir_POST@"
+
+export LC_ALL=C
+
+cmd_line="'${0}' $(printf "'%s' " "${@}")"
+
+# -----------------------------------------------------------------------------
+# logging
+
+PROGRAM_NAME="$(basename "${0}")"
+
+# these should be the same with syslog() priorities
+NDLP_EMERG=0 # system is unusable
+NDLP_ALERT=1 # action must be taken immediately
+NDLP_CRIT=2 # critical conditions
+NDLP_ERR=3 # error conditions
+NDLP_WARN=4 # warning conditions
+NDLP_NOTICE=5 # normal but significant condition
+NDLP_INFO=6 # informational
+NDLP_DEBUG=7 # debug-level messages
+
+# the max (numerically) log level we will log
+LOG_LEVEL=$NDLP_INFO
+
+set_log_min_priority() {
+ case "${NETDATA_LOG_LEVEL,,}" in
+ "emerg" | "emergency")
+ LOG_LEVEL=$NDLP_EMERG
+ ;;
+
+ "alert")
+ LOG_LEVEL=$NDLP_ALERT
+ ;;
+
+ "crit" | "critical")
+ LOG_LEVEL=$NDLP_CRIT
+ ;;
+
+ "err" | "error")
+ LOG_LEVEL=$NDLP_ERR
+ ;;
+
+ "warn" | "warning")
+ LOG_LEVEL=$NDLP_WARN
+ ;;
+
+ "notice")
+ LOG_LEVEL=$NDLP_NOTICE
+ ;;
+
+ "info")
+ LOG_LEVEL=$NDLP_INFO
+ ;;
+
+ "debug")
+ LOG_LEVEL=$NDLP_DEBUG
+ ;;
+ esac
+}
+
+set_log_min_priority
+
+log() {
+ local level="${1}"
+ shift 1
+
+ [[ -n "$level" && -n "$LOG_LEVEL" && "$level" -gt "$LOG_LEVEL" ]] && return
+
+ systemd-cat-native --log-as-netdata --newline="--NEWLINE--" <<EOFLOG
+INVOCATION_ID=${NETDATA_INVOCATION_ID}
+SYSLOG_IDENTIFIER=${PROGRAM_NAME}
+PRIORITY=${level}
+THREAD_TAG=cgroup-network-helper
+ND_LOG_SOURCE=collector
+ND_REQUEST=${cmd_line}
+MESSAGE=${*//\\n/--NEWLINE--}
+
+EOFLOG
+ # AN EMPTY LINE IS NEEDED ABOVE
+}
+
+info() {
+ log "$NDLP_INFO" "${@}"
+}
+
+warning() {
+ log "$NDLP_WARN" "${@}"
+}
+
+error() {
+ log "$NDLP_ERR" "${@}"
+}
+
+fatal() {
+ log "$NDLP_ALERT" "${@}"
+ exit 1
+}
+
+debug() {
+ log "$NDLP_DEBUG" "${@}"
+}
+
+debug=0
+if [ "${NETDATA_CGROUP_NETWORK_HELPER_DEBUG-0}" = "1" ]; then
+ debug=1
+ LOG_LEVEL=$NDLP_DEBUG
+fi
+
+# -----------------------------------------------------------------------------
+# check for BASH v4+ (required for associative arrays)
+
+if [ ${BASH_VERSINFO[0]} -lt 4 ]; then
+ echo >&2 "BASH version 4 or later is required (this is ${BASH_VERSION})."
+ exit 1
+fi
+
+# -----------------------------------------------------------------------------
+# parse the arguments
+
+pid=
+cgroup=
+while [ -n "${1}" ]
+do
+ case "${1}" in
+ --cgroup) cgroup="${2}"; shift 1;;
+ --pid|-p) pid="${2}"; shift 1;;
+ --debug|debug)
+ debug=1
+ LOG_LEVEL=$NDLP_DEBUG
+ ;;
+ *) fatal "Cannot understand argument '${1}'";;
+ esac
+
+ shift
+done
+
+if [ -z "${pid}" ] && [ -z "${cgroup}" ]
+then
+ fatal "Either --pid or --cgroup is required"
+fi
+
+# -----------------------------------------------------------------------------
+
+set_source() {
+ [ ${debug} -eq 1 ] && echo "SRC ${*}"
+}
+
+
+# -----------------------------------------------------------------------------
+# veth interfaces via cgroup
+
+# cgroup-network can detect veth interfaces by itself (written in C).
+# If you seek for a shell version of what it does, check this:
+# https://github.com/netdata/netdata/issues/474#issuecomment-317866709
+
+
+# -----------------------------------------------------------------------------
+# tun/tap interfaces via /proc/PID/fdinfo
+
+# find any tun/tap devices linked to a pid
+proc_pid_fdinfo_iff() {
+ local p="${1}" # the pid
+
+ debug "Searching for tun/tap interfaces for pid ${p}..."
+ set_source "fdinfo"
+ grep "^iff:.*" "${NETDATA_HOST_PREFIX}/proc/${p}/fdinfo"/* 2>/dev/null | cut -f 2
+}
+
+find_tun_tap_interfaces_for_cgroup() {
+ local c="${1}" # the cgroup path
+ [ -d "${c}/emulator" ] && c="${c}/emulator" # check for 'emulator' subdirectory
+ c="${c}/cgroup.procs" # make full path
+
+ # for each pid of the cgroup
+ # find any tun/tap devices linked to the pid
+ if [ -f "${c}" ]
+ then
+ local p
+ for p in $(< "${c}" )
+ do
+ proc_pid_fdinfo_iff "${p}"
+ done
+ else
+ debug "Cannot find file '${c}', not searching for tun/tap interfaces."
+ fi
+}
+
+
+# -----------------------------------------------------------------------------
+# virsh domain network interfaces
+
+virsh_cgroup_to_domain_name() {
+ local c="${1}" # the cgroup path
+
+ debug "extracting a possible virsh domain from cgroup ${c}..."
+
+ # extract for the cgroup path
+ sed -n -e "s|.*/machine-qemu\\\\x2d[0-9]\+\\\\x2d\(.*\)\.scope$|\1|p" \
+ -e "s|.*/machine/qemu-[0-9]\+-\(.*\)\.libvirt-qemu$|\1|p" \
+ -e "s|.*/machine/\(.*\)\.libvirt-qemu$|\1|p" \
+ <<EOF
+${c}
+EOF
+}
+
+virsh_find_all_interfaces_for_cgroup() {
+ local c="${1}" # the cgroup path
+
+ # the virsh command
+ local virsh
+ # shellcheck disable=SC2230
+ virsh="$(which virsh 2>/dev/null || command -v virsh 2>/dev/null)"
+
+ if [ -n "${virsh}" ]
+ then
+ local d
+ d="$(virsh_cgroup_to_domain_name "${c}")"
+ # convert hex to character
+ # e.g.: vm01\x2dweb => vm01-web (https://github.com/netdata/netdata/issues/11088#issuecomment-832618149)
+ d="$(printf '%b' "${d}")"
+
+ if [ -n "${d}" ]
+ then
+ debug "running: virsh domiflist ${d}; to find the network interfaces"
+
+ # 'virsh -r domiflist <domain>' example output
+ # Interface Type Source Model MAC
+ #--------------------------------------------------------------
+ # vnet3 bridge br0 virtio 52:54:00:xx:xx:xx
+ # vnet4 network default virtio 52:54:00:yy:yy:yy
+
+ # match only 'network' interfaces from virsh output
+ set_source "virsh"
+ "${virsh}" -r domiflist "${d}" |\
+ sed -n \
+ -e "s|^[[:space:]]\?\([^[:space:]]\+\)[[:space:]]\+network[[:space:]]\+\([^[:space:]]\+\)[[:space:]]\+[^[:space:]]\+[[:space:]]\+[^[:space:]]\+$|\1 \1_\2|p" \
+ -e "s|^[[:space:]]\?\([^[:space:]]\+\)[[:space:]]\+bridge[[:space:]]\+\([^[:space:]]\+\)[[:space:]]\+[^[:space:]]\+[[:space:]]\+[^[:space:]]\+$|\1 \1_\2|p"
+ else
+ debug "no virsh domain extracted from cgroup ${c}"
+ fi
+ else
+ debug "virsh command is not available"
+ fi
+}
+
+# -----------------------------------------------------------------------------
+# netnsid detected interfaces
+
+netnsid_find_all_interfaces_for_pid() {
+ local pid="${1}"
+ [ -z "${pid}" ] && return 1
+
+ local nsid
+ nsid=$(lsns -t net -p "${pid}" -o NETNSID -nr 2>/dev/null)
+ if [ -z "${nsid}" ] || [ "${nsid}" = "unassigned" ]; then
+ return 1
+ fi
+
+ set_source "netnsid"
+ ip link show |\
+ grep -B 1 -E " link-netnsid ${nsid}($| )" |\
+ sed -n -e "s|^[[:space:]]*[0-9]\+:[[:space:]]\+\([A-Za-z0-9_]\+\)\(@[A-Za-z0-9_]\+\)*:[[:space:]].*$|\1|p"
+}
+
+netnsid_find_all_interfaces_for_cgroup() {
+ local c="${1}" # the cgroup path
+
+ if [ -f "${c}/cgroup.procs" ]; then
+ netnsid_find_all_interfaces_for_pid "$(head -n 1 "${c}/cgroup.procs" 2>/dev/null)"
+ else
+ debug "Cannot find file '${c}/cgroup.procs', not searching for netnsid interfaces."
+ fi
+}
+
+# -----------------------------------------------------------------------------
+
+find_all_interfaces_of_pid_or_cgroup() {
+ local p="${1}" c="${2}" # the pid and the cgroup path
+
+ if [ -n "${pid}" ]
+ then
+ # we have been called with a pid
+
+ proc_pid_fdinfo_iff "${p}"
+ netnsid_find_all_interfaces_for_pid "${p}"
+
+ elif [ -n "${c}" ]
+ then
+ # we have been called with a cgroup
+
+ info "searching for network interfaces of cgroup '${c}'"
+
+ find_tun_tap_interfaces_for_cgroup "${c}"
+ virsh_find_all_interfaces_for_cgroup "${c}"
+ netnsid_find_all_interfaces_for_cgroup "${c}"
+
+ else
+
+ error "Either a pid or a cgroup path is needed"
+ return 1
+
+ fi
+
+ return 0
+}
+
+# -----------------------------------------------------------------------------
+
+# an associative array to store the interfaces
+# the index is the interface name as seen by the host
+# the value is the interface name as seen by the guest / container
+declare -A devs=()
+
+# store all interfaces found in the associative array
+# this will also give the unique devices, as seen by the host
+last_src=
+# shellcheck disable=SC2162
+while read host_device guest_device
+do
+ [ -z "${host_device}" ] && continue
+
+ [ "${host_device}" = "SRC" ] && last_src="${guest_device}" && continue
+
+ # the default guest_device is the host_device
+ [ -z "${guest_device}" ] && guest_device="${host_device}"
+
+ # when we run in debug, show the source
+ debug "Found host device '${host_device}', guest device '${guest_device}', detected via '${last_src}'"
+
+ if [ -z "${devs[${host_device}]}" ] || [ "${devs[${host_device}]}" = "${host_device}" ]; then
+ devs[${host_device}]="${guest_device}"
+ fi
+
+done < <( find_all_interfaces_of_pid_or_cgroup "${pid}" "${cgroup}" )
+
+# print the interfaces found, in the format netdata expects them
+found=0
+for x in "${!devs[@]}"
+do
+ found=$((found + 1))
+ echo "${x} ${devs[${x}]}"
+done
+
+debug "found ${found} network interfaces for pid '${pid}', cgroup '${cgroup}', run as ${USER}, ${UID}"
+
+# let netdata know if we found any
+[ ${found} -eq 0 ] && exit 1
+exit 0