1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
#!/bin/bash
set -eu
# Copyright: 2018 1&1 IONOS Cloud GmbH
# Authors: Daniel Swarbrick <daniel.swarbrick@cloud.ionos.com>
# Benjamin Drung <benjamin.drung@cloud.ionos.com>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
CONFIGFS_PATH=/sys/kernel/config
NETCONSOLE_PATH=$CONFIGFS_PATH/netconsole
is_ip_address() {
local address="$1"
local returncode=1
# Check for IPv4 or IPv6 address
if [[ "$address" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}|([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$ ]]; then
returncode=0
fi
return "$returncode"
}
ip_to_mac() {
local address="$1"
local from_interface="$2"
mac=$(ip -o neigh show to "$address" dev "$from_interface" | grep -oE "lladdr [^ ]*" | cut -d ' ' -f 2)
if test -z "$mac"; then
# Run arping to determine the MAC address for the given IP address
if arping -V 2>/dev/null | grep -q iputils; then
# iputils' arping
mac=$(arping -c 1 -f -I "$from_interface" "$address" | sed -n 's/^.* reply from [^ ]* \[\(.*\)\].*$/\1/p' | head -n 1)
mac="${mac,,}"
else
# Assume Thomas Habet's arping
mac=$(arping -c 1 -i "$from_interface" -r "$address" | head -n 1)
fi
fi
echo "$mac"
}
if test "$#" -eq 0; then
echo "${0##*/}: No target specified. Doing nothing." >&2
exit 0
fi
if [ ! -d $CONFIGFS_PATH ]; then
echo "${0##*/}: Kernel config directory $CONFIGFS_PATH not present." >&2
echo "${0##*/}: Loading kernel module 'configfs'..." >&2
modprobe configfs
if command -v udevadm >/dev/null 2>&1; then
udevadm settle
fi
fi
if ! mount | grep -qw "$CONFIGFS_PATH"; then
echo "${0##*/}: configfs not mounted to '$CONFIGFS_PATH'; mounting configfs..." >&2
mount -t configfs configfs $CONFIGFS_PATH
fi
if [ ! -d $NETCONSOLE_PATH ]; then
if modprobe -n --first-time netconsole >/dev/null 2>&1; then
echo "${0##*/}: Loading kernel module 'netconsole'..." >&2
modprobe netconsole
else
echo "${0##*/}: Error: netconsole dynamic config directory '$NETCONSOLE_PATH' not present." >&2
exit 1
fi
fi
for option in "$@"; do
if [[ "$option" =~ ^((\+)?([0-9]+)?@)?(.+)$ ]]; then
if test "${BASH_REMATCH[2]}" = "+"; then
extended=1
else
extended=0
fi
remote_port=${BASH_REMATCH[3]}
target=${BASH_REMATCH[4]}
else
echo "${0##*/}: Failed to parse option '$option'. Expected format: [[+][target-port]@]target-host" >&2
continue
fi
target_path=$NETCONSOLE_PATH/$target
if is_ip_address "$target"; then
to_ip="$target"
else
to_ip=$(LANG=C getent hosts -- "$target" | grep -oE "^([0-9a-fA-F.:]+)" || true)
fi
if test -z "$to_ip"; then
echo "${0##*/}: Skipped host '$target'. No IP found for it." >&2
continue
fi
route=$(ip -o route get "$to_ip")
from_interface=$(echo "$route" | grep -oE "dev [^ ]*" | cut -d ' ' -f 2)
from_ip=$(echo "$route" | grep -oE "src [^ ]*" | cut -d ' ' -f 2)
to_gateway=$(echo "$route" | grep -oE "via [^ ]*" | cut -d ' ' -f 2)
if test -z "$to_gateway"; then
# If there is no "via" in the output of the ip route lookup, then the packets will
# be sent directory to the target (without any additional hops)
to_gateway=$to_ip
fi
to_mac=$(ip_to_mac "$to_gateway" "$from_interface")
if test -z "$to_mac"; then
echo "${0##*/}: $target: Cannot resolve MAC address of $to_gateway." >&2
exit 1
fi
mkdir -p "$target_path"
declare -A changes=( [dev_name]="$from_interface" [local_ip]="$from_ip"
[remote_ip]="$to_ip" [remote_mac]="$to_mac" [extended]="$extended" )
if test -n "$remote_port"; then
changes[remote_port]="$remote_port"
fi
for config in "${!changes[@]}"; do
if test "$(cat "$target_path/$config")" = "${changes[$config]}"; then
# Setting already in the correct state
unset "changes[$config]"
fi
done
enabled=$(cat "$target_path/enabled")
if test "${#changes[@]}" -eq 0 && test "$enabled" -eq 1; then
echo "${0##*/}: $target already configured correctly." >&2
else
echo "${0##*/}: $target: Configure sending logs to $to_ip via MAC $to_mac from $from_ip on interface '$from_interface'." >&2
if test "$enabled" -ne 0; then
echo 0 > "$target_path/enabled"
fi
for config in "${!changes[@]}"; do
echo "${changes[$config]}" > "$target_path/$config"
done
echo 1 > "$target_path/enabled"
fi
done
# shellcheck disable=SC2034
read -r console_log_level default_log_level minimum_log_level maximum_log_level < /proc/sys/kernel/printk
log_level=$(( console_log_level > minimum_log_level ? console_log_level - 1 : console_log_level ))
echo "<${log_level}>${0##*/}: Test log message to verify netconsole configuration." > /dev/kmsg
|