@cryptotaxi247 / netdata / commits / baa1e71d3

feat(system-info): add default network interface IP detection (#20764)

Ilya Mashchenko committed Aug 5, 2025 at 22:02 UTC baa1e71d3bcaa5eacf3801a16aebb557cd39e3fe
1 file changed +201 -27
src/daemon/system-info.sh
+201 -27
@@ -15,8 +15,8 @@ ARCHITECTURE="$(uname -m)"
15 virtualization_normalize_name() {
16 vname="$1"
17 case "$vname" in
18 - "User-mode Linux") vname="uml" ;;
19 - "Windows Subsystem for Linux") vname="wsl" ;;
18 + "User-mode Linux") vname="uml" ;;
19 + "Windows Subsystem for Linux") vname="wsl" ;;
20 esac
21
22 echo "$vname" | tr '[:upper:]' '[:lower:]' | sed 's/ /-/g'
@@ -69,7 +69,7 @@ fi
69
70 if [ "${CONTAINER}" = "unknown" ]; then
71 if [ -f /proc/1/sched ]; then
72 - IFS='(, ' read -r process _ < /proc/1/sched
72 + IFS='(, ' read -r process _ </proc/1/sched
73 if [ "${process}" = "netdata" ]; then
74 CONTAINER="container"
75 CONT_DETECTION="process"
@@ -77,7 +77,7 @@ if [ "${CONTAINER}" = "unknown" ]; then
77 fi
78 # ubuntu and debian supply /bin/running-in-container
79 # https://www.apt-browse.org/browse/ubuntu/trusty/main/i386/upstart/1.12.1-0ubuntu4/file/bin/running-in-container
80 - if /bin/running-in-container > /dev/null 2>&1; then
80 + if /bin/running-in-container >/dev/null 2>&1; then
81 CONTAINER="container"
82 CONT_DETECTION="/bin/running-in-container"
83 fi
@@ -134,7 +134,7 @@ elif [ "${KERNEL_NAME}" = "FreeBSD" ]; then
134 KERNEL_VERSION=$(uname -K)
135 else
136 if [ -f "/etc/os-release" ]; then
137 - eval "$(grep -E "^(NAME|ID|ID_LIKE|VERSION|VERSION_ID)=" < /etc/os-release | sed 's/^/CONTAINER_/')"
137 + eval "$(grep -E "^(NAME|ID|ID_LIKE|VERSION|VERSION_ID)=" </etc/os-release | sed 's/^/CONTAINER_/')"
138 CONTAINER_OS_DETECTION="/etc/os-release"
139 fi
140
@@ -149,20 +149,20 @@ else
149 DISTRIB_ID="unknown"
150 DISTRIB_RELEASE="unknown"
151 DISTRIB_CODENAME="unknown"
152 - eval "$(grep -E "^(DISTRIB_ID|DISTRIB_RELEASE|DISTRIB_CODENAME)=" < /etc/lsb-release)"
152 + eval "$(grep -E "^(DISTRIB_ID|DISTRIB_RELEASE|DISTRIB_CODENAME)=" </etc/lsb-release)"
153 if [ "${CONTAINER_NAME}" = "unknown" ]; then CONTAINER_NAME="${DISTRIB_ID}"; fi
154 if [ "${CONTAINER_VERSION}" = "unknown" ]; then CONTAINER_VERSION="${DISTRIB_RELEASE}"; fi
155 if [ "${CONTAINER_ID}" = "unknown" ]; then CONTAINER_ID="${DISTRIB_CODENAME}"; fi
156 fi
157 - if [ -n "$(command -v lsb_release 2> /dev/null)" ]; then
157 + if [ -n "$(command -v lsb_release 2>/dev/null)" ]; then
158 if [ "${CONTAINER_OS_DETECTION}" = "unknown" ]; then
159 CONTAINER_OS_DETECTION="lsb_release"
160 else
161 CONTAINER_OS_DETECTION="Mixed"
162 fi
163 - if [ "${CONTAINER_NAME}" = "unknown" ]; then CONTAINER_NAME="$(lsb_release -is 2> /dev/null)"; fi
164 - if [ "${CONTAINER_VERSION}" = "unknown" ]; then CONTAINER_VERSION="$(lsb_release -rs 2> /dev/null)"; fi
165 - if [ "${CONTAINER_ID}" = "unknown" ]; then CONTAINER_ID="$(lsb_release -cs 2> /dev/null)"; fi
163 + if [ "${CONTAINER_NAME}" = "unknown" ]; then CONTAINER_NAME="$(lsb_release -is 2>/dev/null)"; fi
164 + if [ "${CONTAINER_VERSION}" = "unknown" ]; then CONTAINER_VERSION="$(lsb_release -rs 2>/dev/null)"; fi
165 + if [ "${CONTAINER_ID}" = "unknown" ]; then CONTAINER_ID="$(lsb_release -cs 2>/dev/null)"; fi
166 fi
167 fi
168 fi
@@ -183,7 +183,7 @@ if [ "${CONTAINER}" = "unknown" ] || [ "${CONTAINER}" = "none" ]; then
183 else
184 # Otherwise try and use a user-supplied bind-mount into the container to resolve the host details
185 if [ -e "/host/etc/os-release" ]; then
186 - eval "$(grep -E "^(NAME|ID|ID_LIKE|VERSION|VERSION_ID)=" < /host/etc/os-release | sed 's/^/HOST_/')"
186 + eval "$(grep -E "^(NAME|ID|ID_LIKE|VERSION|VERSION_ID)=" </host/etc/os-release | sed 's/^/HOST_/')"
187 HOST_OS_DETECTION="/host/etc/os-release"
188 fi
189 if [ "${HOST_NAME}" = "unknown" ] || [ "${HOST_VERSION}" = "unknown" ] || [ "${HOST_ID}" = "unknown" ]; then
@@ -196,7 +196,7 @@ else
196 DISTRIB_ID="unknown"
197 DISTRIB_RELEASE="unknown"
198 DISTRIB_CODENAME="unknown"
199 - eval "$(grep -E "^(DISTRIB_ID|DISTRIB_RELEASE|DISTRIB_CODENAME)=" < /etc/lsb-release)"
199 + eval "$(grep -E "^(DISTRIB_ID|DISTRIB_RELEASE|DISTRIB_CODENAME)=" </etc/lsb-release)"
200 if [ "${HOST_NAME}" = "unknown" ]; then HOST_NAME="${DISTRIB_ID}"; fi
201 if [ "${HOST_VERSION}" = "unknown" ]; then HOST_VERSION="${DISTRIB_RELEASE}"; fi
202 if [ "${HOST_ID}" = "unknown" ]; then HOST_ID="${DISTRIB_CODENAME}"; fi
@@ -204,9 +204,9 @@ else
204 fi
205 fi
206
207 -if [ -d "/etc/pve" ] && \
208 - echo "${KERNEL_VERSION}" | grep -q -- '-pve$' && \
209 - command -v pveversion > /dev/null 2>&1; then
207 +if [ -d "/etc/pve" ] &&
208 + echo "${KERNEL_VERSION}" | grep -q -- '-pve$' &&
209 + command -v pveversion >/dev/null 2>&1; then
210 HOST_NAME="Proxmox VE"
211 HOST_ID="proxmox"
212 HOST_ID_LIKE="proxmox"
@@ -231,8 +231,8 @@ lscpu_output=""
231 dmidecode="$(command -v dmidecode)"
232 dmidecode_output=""
233
234 -if [ -n "${lscpu}" ] && lscpu > /dev/null 2>&1; then
235 - lscpu_output="$(LC_NUMERIC=C ${lscpu} 2> /dev/null)"
234 +if [ -n "${lscpu}" ] && lscpu >/dev/null 2>&1; then
235 + lscpu_output="$(LC_NUMERIC=C ${lscpu} 2>/dev/null)"
236 CPU_INFO_SOURCE="lscpu"
237 LCPU_COUNT="$(echo "${lscpu_output}" | grep "^CPU(s):" | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
238 CPU_VENDOR="$(echo "${lscpu_output}" | grep "^Vendor ID:" | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
@@ -248,8 +248,8 @@ if [ -n "${lscpu}" ] && lscpu > /dev/null 2>&1; then
248 possible_cpu_freq="$(echo "${lscpu_output}" | grep "^Model name:" | grep -Eo "[0-9\.]+GHz" | grep -o "^[0-9\.]*" | awk '{print int($0*1000)}')"
249 fi
250 [ -n "$possible_cpu_freq" ] && possible_cpu_freq="${possible_cpu_freq} MHz"
251 -elif [ -n "${dmidecode}" ] && dmidecode -t processor > /dev/null 2>&1; then
252 - dmidecode_output="$(${dmidecode} -t processor 2> /dev/null)"
251 +elif [ -n "${dmidecode}" ] && dmidecode -t processor >/dev/null 2>&1; then
252 + dmidecode_output="$(${dmidecode} -t processor 2>/dev/null)"
253 CPU_INFO_SOURCE="dmidecode"
254 LCPU_COUNT="$(echo "${dmidecode_output}" | grep -F "Thread Count:" | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
255 CPU_VENDOR="$(echo "${dmidecode_output}" | grep -F "Manufacturer:" | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
@@ -262,8 +262,8 @@ else
262 elif [ "${KERNEL_NAME}" = FreeBSD ]; then
263 CPU_INFO_SOURCE="sysctl"
264 LCPU_COUNT="$(sysctl -n kern.smp.cpus)"
265 - if ! possible_cpu_freq=$(sysctl -n machdep.tsc_freq 2> /dev/null); then
266 - possible_cpu_freq=$(sysctl -n hw.model 2> /dev/null | grep -Eo "[0-9\.]+GHz" | grep -o "^[0-9\.]*" | awk '{print int($0*1000)}')
265 + if ! possible_cpu_freq=$(sysctl -n machdep.tsc_freq 2>/dev/null); then
266 + possible_cpu_freq=$(sysctl -n hw.model 2>/dev/null | grep -Eo "[0-9\.]+GHz" | grep -o "^[0-9\.]*" | awk '{print int($0*1000)}')
267 [ -n "$possible_cpu_freq" ] && possible_cpu_freq="${possible_cpu_freq} MHz"
268 fi
269 elif [ "${KERNEL_NAME}" = Darwin ]; then
@@ -286,7 +286,7 @@ else
286 CPU_VENDOR="Apple"
287 fi
288 echo "${CPU_INFO_SOURCE}" | grep -qv sysctl && CPU_INFO_SOURCE="${CPU_INFO_SOURCE} sysctl"
289 - elif uname --version 2> /dev/null | grep -qF 'GNU coreutils'; then
289 + elif uname --version 2>/dev/null | grep -qF 'GNU coreutils'; then
290 CPU_INFO_SOURCE="${CPU_INFO_SOURCE} uname"
291 CPU_MODEL="$(uname -p)"
292 CPU_VENDOR="$(uname -i)"
@@ -433,14 +433,14 @@ else
433 DISK_DETECTION="sysfs"
434 DISK_SIZE="0"
435 for disk in /sys/block/*; do
436 - if [ -r "${disk}/size" ] \
437 - && (echo "${dev_major_whitelist}" | grep -q ":$(cut -f 1 -d ':' "${disk}/dev"):") \
438 - && grep -qv 1 "${disk}/removable"; then
436 + if [ -r "${disk}/size" ] &&
437 + (echo "${dev_major_whitelist}" | grep -q ":$(cut -f 1 -d ':' "${disk}/dev"):") &&
438 + grep -qv 1 "${disk}/removable"; then
439 size="$(($(cat "${disk}/size") * 512))"
440 DISK_SIZE="$((DISK_SIZE + size))"
441 fi
442 done
443 - elif df --version 2> /dev/null | grep -qF "GNU coreutils"; then
443 + elif df --version 2>/dev/null | grep -qF "GNU coreutils"; then
444 DISK_DETECTION="df"
445 DISK_SIZE=$(($(df -x tmpfs -x devtmpfs -x squashfs -l -B1 --output=source,size | tail -n +2 | sort -u -k 1 | awk '{print $2}' | tr '\n' '+' | head -c -1)))
446 else
@@ -470,7 +470,7 @@ CLOUD_TYPE="unknown"
470 CLOUD_INSTANCE_TYPE="unknown"
471 CLOUD_INSTANCE_REGION="unknown"
472
473 -if [ "${VIRTUALIZATION}" != "none" ] && command -v curl > /dev/null 2>&1; then
473 +if [ "${VIRTUALIZATION}" != "none" ] && command -v curl >/dev/null 2>&1; then
474 # Returned HTTP status codes: GCP is 200, AWS is 200, DO is 404.
475 curl --fail -s -m 1 --noproxy "*" http://169.254.169.254 >/dev/null 2>&1
476 ret=$?
@@ -509,6 +509,177 @@ if [ "${VIRTUALIZATION}" != "none" ] && command -v curl > /dev/null 2>&1; then
509 fi
510 fi
511
512 +# -------------------------------------------------------------------------------------------------
513 +# Detect the IP address of the interface on the default route
514 +
515 +get_default_interface_ip() {
516 + DEFAULT_INTERFACE_IP="unknown"
517 + DEFAULT_INTERFACE_NAME="unknown"
518 + DEFAULT_INTERFACE_DETECTION="none"
519 +
520 + # Optional parameter for IP version: "-4" (default) or "-6"
521 + ip_version="${1:--4}"
522 +
523 + # Check if timeout command is available
524 + timeout_cmd=""
525 + if command -v timeout >/dev/null 2>&1; then
526 + timeout_cmd="timeout 2"
527 + fi
528 +
529 + # Find default interface based on OS
530 + default_if=""
531 +
532 + case "${KERNEL_NAME}" in
533 + Linux)
534 + # Ultra-safe: Try /proc first (never hangs - just file reading)
535 + if [ "${ip_version}" = "-4" ] && [ -r /proc/net/route ]; then
536 + # Default route has destination 00000000
537 + default_if=$(awk '$2 == "00000000" && $1 != "lo" {print $1; exit}' /proc/net/route)
538 + [ -n "${default_if}" ] && DEFAULT_INTERFACE_DETECTION="procfs"
539 + elif [ "${ip_version}" = "-6" ] && [ -r /proc/net/ipv6_route ]; then
540 + # IPv6 default route - field 10 is the interface
541 + # Look for ::/0 route (all zeros with prefix 00) that's not on lo
542 + default_if=$(awk '$1 == "00000000000000000000000000000000" && $2 == "00" && $10 != "lo" {print $10; exit}' /proc/net/ipv6_route)
543 + [ -n "${default_if}" ] && DEFAULT_INTERFACE_DETECTION="procfs"
544 + fi
545 +
546 + # Fallback to ip command if /proc didn't work
547 + if [ -z "${default_if}" ] && command -v ip >/dev/null 2>&1; then
548 + if [ "${ip_version}" = "-4" ]; then
549 + # Extract interface after "dev" keyword
550 + default_if=$(${timeout_cmd} ip -o -4 route list default 2>/dev/null | \
551 + awk '{for(i=1;i<=NF;i++) if($i=="dev") {print $(i+1); exit}}')
552 + else
553 + default_if=$(${timeout_cmd} ip -o -6 route list default 2>/dev/null | \
554 + awk '{for(i=1;i<=NF;i++) if($i=="dev") {print $(i+1); exit}}')
555 + fi
556 + [ -n "${default_if}" ] && DEFAULT_INTERFACE_DETECTION="iproute2"
557 + fi
558 +
559 + # Last resort: netstat (if available)
560 + if [ -z "${default_if}" ] && command -v netstat >/dev/null 2>&1; then
561 + if [ "${ip_version}" = "-4" ]; then
562 + default_if=$(${timeout_cmd} netstat -rn 2>/dev/null | \
563 + awk '$1 == "0.0.0.0" && $2 != "0.0.0.0" {print $NF; exit}')
564 + else
565 + default_if=$(${timeout_cmd} netstat -rn -A inet6 2>/dev/null | \
566 + awk '$1 == "::/0" {print $NF; exit}')
567 + fi
568 + [ -n "${default_if}" ] && DEFAULT_INTERFACE_DETECTION="netstat"
569 + fi
570 + ;;
571 +
572 + Darwin)
573 + # macOS specific handling
574 + if [ "${ip_version}" = "-4" ]; then
575 + # Try route first
576 + default_if=$(${timeout_cmd} route -n get default 2>/dev/null | \
577 + awk '/interface:/ {print $2; exit}')
578 + [ -n "${default_if}" ] && DEFAULT_INTERFACE_DETECTION="route"
579 +
580 + # Fallback to netstat
581 + if [ -z "${default_if}" ]; then
582 + default_if=$(${timeout_cmd} netstat -rnf inet 2>/dev/null | \
583 + awk '/^default/ {print $4; exit}')
584 + [ -n "${default_if}" ] && DEFAULT_INTERFACE_DETECTION="netstat"
585 + fi
586 + else
587 + # IPv6 - route doesn't work, use netstat
588 + # Note: may have multiple defaults (utun interfaces)
589 + default_if=$(${timeout_cmd} netstat -rnf inet6 2>/dev/null | \
590 + awk '/^default/ && $4 !~ /^utun/ {print $4; exit}')
591 + if [ -z "${default_if}" ]; then
592 + # If no non-utun default, take first one
593 + default_if=$(${timeout_cmd} netstat -rnf inet6 2>/dev/null | \
594 + awk '/^default/ {print $4; exit}')
595 + fi
596 + [ -n "${default_if}" ] && DEFAULT_INTERFACE_DETECTION="netstat"
597 + fi
598 + ;;
599 +
600 + *BSD)
601 + # FreeBSD, OpenBSD, NetBSD
602 + inet_flag=""
603 + [ "${ip_version}" = "-6" ] && inet_flag="-inet6"
604 +
605 + # route with -n to prevent DNS lookups
606 + default_if=$(${timeout_cmd} route -n get ${inet_flag} default 2>/dev/null | \
607 + awk '/interface:/ {print $2; exit}')
608 + [ -n "${default_if}" ] && DEFAULT_INTERFACE_DETECTION="route"
609 +
610 + # Fallback to netstat
611 + if [ -z "${default_if}" ]; then
612 + if [ "${ip_version}" = "-4" ]; then
613 + default_if=$(${timeout_cmd} netstat -rnf inet 2>/dev/null | \
614 + awk '/^default/ {print $4; exit}')
615 + else
616 + default_if=$(${timeout_cmd} netstat -rnf inet6 2>/dev/null | \
617 + awk '/^default/ {print $4; exit}')
618 + fi
619 + [ -n "${default_if}" ] && DEFAULT_INTERFACE_DETECTION="netstat"
620 + fi
621 + ;;
622 + esac
623 +
624 + # Get IP address from interface
625 + if [ -n "${default_if}" ] && [ "${default_if}" != "lo" ]; then
626 + # Skip if interface is down (Linux only)
627 + if [ "${KERNEL_NAME}" = "Linux" ] && [ -r "/sys/class/net/${default_if}/operstate" ]; then
628 + state=$(cat "/sys/class/net/${default_if}/operstate" 2>/dev/null)
629 + if [ "${state}" = "down" ]; then
630 + return
631 + fi
632 + fi
633 +
634 + if [ "${ip_version}" = "-4" ]; then
635 + # IPv4 handling
636 + # Try ip command first on Linux
637 + if [ "${KERNEL_NAME}" = "Linux" ] && command -v ip >/dev/null 2>&1; then
638 + # With -o flag, inet is field 3, IP is field 4
639 + ip_addr=$(${timeout_cmd} ip -o -4 addr show dev "${default_if}" 2>/dev/null | \
640 + awk '$3 == "inet" && $0 !~ /secondary/ {gsub(/\/.*/, "", $4); print $4; exit}')
641 + fi
642 +
643 + # Fallback to ifconfig (if available)
644 + if [ -z "${ip_addr}" ] && command -v ifconfig >/dev/null 2>&1; then
645 + ip_addr=$(${timeout_cmd} ifconfig "${default_if}" 2>/dev/null | \
646 + awk '/inet / && !/127.0.0.1/ {gsub(/addr:/, "", $2); print $2; exit}')
647 + fi
648 +
649 + # Validate IPv4
650 + if [ -n "${ip_addr}" ]; then
651 + if echo "${ip_addr}" | grep -qE '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' && \
652 + echo "${ip_addr}" | awk -F. '$1<=255 && $2<=255 && $3<=255 && $4<=255 {exit 0} {exit 1}'; then
653 + DEFAULT_INTERFACE_IP="${ip_addr}"
654 + DEFAULT_INTERFACE_NAME="${default_if}"
655 + fi
656 + fi
657 + else
658 + # IPv6 handling
659 + if [ "${KERNEL_NAME}" = "Linux" ] && command -v ip >/dev/null 2>&1; then
660 + # Get global scope IPv6, not link-local
661 + ip_addr=$(${timeout_cmd} ip -o -6 addr show dev "${default_if}" scope global 2>/dev/null | \
662 + awk '$3 == "inet6" && $0 !~ /deprecated/ {gsub(/\/.*/, "", $4); print $4; exit}')
663 + fi
664 +
665 + if [ -z "${ip_addr}" ] && command -v ifconfig >/dev/null 2>&1; then
666 + # Skip fe80:: (link-local) and ::1 (loopback)
667 + ip_addr=$(${timeout_cmd} ifconfig "${default_if}" 2>/dev/null | \
668 + awk '/inet6 / && !/fe80:/ && !/::1/ {sub(/%.*/, "", $2); print $2; exit}')
669 + fi
670 +
671 + # Basic IPv6 validation
672 + if [ -n "${ip_addr}" ] && echo "${ip_addr}" | grep -qE '^[0-9a-fA-F:]+$' && \
673 + echo "${ip_addr}" | grep -q ':'; then
674 + DEFAULT_INTERFACE_IP="${ip_addr}"
675 + DEFAULT_INTERFACE_NAME="${default_if}"
676 + fi
677 + fi
678 + fi
679 +}
680 +
681 +get_default_interface_ip -4
682 +
683 echo "NETDATA_CONTAINER_OS_NAME=${CONTAINER_NAME}"
684 echo "NETDATA_CONTAINER_OS_ID=${CONTAINER_ID}"
685 echo "NETDATA_CONTAINER_OS_ID_LIKE=${CONTAINER_ID_LIKE}"
@@ -542,3 +713,6 @@ echo "NETDATA_SYSTEM_DISK_DETECTION=${DISK_DETECTION}"
713 echo "NETDATA_INSTANCE_CLOUD_TYPE=${CLOUD_TYPE}"
714 echo "NETDATA_INSTANCE_CLOUD_INSTANCE_TYPE=${CLOUD_INSTANCE_TYPE}"
715 echo "NETDATA_INSTANCE_CLOUD_INSTANCE_REGION=${CLOUD_INSTANCE_REGION}"
716 +echo "NETDATA_SYSTEM_DEFAULT_INTERFACE_NAME=${DEFAULT_INTERFACE_NAME}"
717 +echo "NETDATA_SYSTEM_DEFAULT_INTERFACE_IP=${DEFAULT_INTERFACE_IP}"
718 +echo "NETDATA_SYSTEM_DEFAULT_INTERFACE_DETECTION=${DEFAULT_INTERFACE_DETECTION}"