diff --git a/README.rst b/README.rst index 2cccd1d4..6f1c5b69 100644 --- a/README.rst +++ b/README.rst @@ -1184,6 +1184,32 @@ config files for external use, eg. docker, etc. username: test password: test +Netconsole Remote Kernel Logging +-------------------------------- + +Netconsole logger could be configured for configfs-enabled kernels +(`CONFIG_NETCONSOLE_DYNAMIC` should be enabled). Configuration applies both in +runtime (if network is already configured), and on-boot after interface +initialization. Notes: + + * receiver could be located only in same L3 domain + (or you need to configure gateway MAC manually) + * receiver's MAC is detected only on configuration time + * using broadcast MAC is not recommended + +.. code-block:: yaml + + parameters: + linux: + system: + netconsole: + enabled: true + port: 514 (optional) + loglevel: debug (optional) + target: + 192.168.0.1: + interface: bond0 + mac: "ff:ff:ff:ff:ff:ff" (optional) Usage ===== diff --git a/linux/files/netconsole b/linux/files/netconsole new file mode 100644 index 00000000..d7e1e673 --- /dev/null +++ b/linux/files/netconsole @@ -0,0 +1,136 @@ +#!/bin/sh +SYSFS_NETCONSOLE="/sys/kernel/config/netconsole" +NETCONSOLE_CONF="/etc/default/netconsole.conf" +NETCONSOLE_PORT="514" + +netconsole_remove() { + for sysfsnc in "${SYSFS_NETCONSOLE}/${interface:-}-"* + do + if [ -e "${sysfsnc}" ] + then + logger -t netconsole "remove ${sysfsnc}" + rmdir "${sysfsnc}" + fi + done +} + +netconsole_remote_mac() +{ + neigh() + { + ip -4 -o neigh show to "${remote_ip}" dev "${interface}" | cut -d\ -f3 + } + remote_mac="$(neigh)" + if [ -n "${remote_mac:-}" ] && [ "${remote_mac:-}" != "INCOMPLETE" ] + then + if [ "${remote_mac:-}" != "FAILED" ] + then + echo "${remote_mac:-}" + return 0 + fi + else + if ping -n -q -c 1 -w 1 -I "${interface}" "${remote_ip}" >/dev/null && remote_mac="$(neigh)" && [ -n "${remote_mac:-}" ] + then + echo "${remote_mac:-}" + return 0 + fi + fi + return 1 +} + +netconsole_add() { + netconsole() { + iface="${1:-}" + remote_ip="${2:-}" + remote_mac="${3:-}" + + if [ "${iface:-}" = "${interface:-}" ] && [ -n "${remote_ip:-}" ] + then + logger -t netconsole "from ${new_ip_address:-}@${interface:-}" + else + return 1 + fi + if [ -n "${remote_mac}" ] || remote_mac="$(netconsole_remote_mac)" + then + logger -t netconsole "to ${remote_ip} ${remote_mac}" + else + return 1 + fi + + sysfsnc="${SYSFS_NETCONSOLE}/${interface}-${remote_ip}" + + if [ -e "${sysfsnc}" ] && [ -z "${old_ip_address:-}" ] + then + old_ip_address="$(cat "${sysfsnc}/local_ip")" + fi + + if [ "${old_ip_address:-}" != "${new_ip_address:-}" ] || ! [ -e "${sysfsnc}" ] + then + logger -t netconsole "setup netconsole" + else + return 1 + fi + + mkdir -p "${sysfsnc}" + if [ "$(cat "${sysfsnc}/enabled")" != "0" ] + then + echo "0" > "${sysfsnc}/enabled" + fi + + if [ -n "${new_ip_address:-}" ] + then + echo "${new_ip_address}" > "${sysfsnc}/local_ip" + fi + echo "${interface}" > "${sysfsnc}/dev_name" + echo "${remote_mac}" > "${sysfsnc}/remote_mac" + echo "${remote_ip}" > "${sysfsnc}/remote_ip" + echo "${PORT:-${NETCONSOLE_PORT}}" > "${sysfsnc}/remote_port" + echo "1" > "${sysfsnc}/enabled" + return 0 + } + + if [ -f "${NETCONSOLE_CONF}" ] + then + modprobe netconsole + mountpoint -q /sys/kernel/config || mount none -t configfs /sys/kernel/config + + if [ -e "${SYSFS_NETCONSOLE}" ] + then + ( + set -x + set +e + . "${NETCONSOLE_CONF}" + ) ||: + fi + fi +} + +netconsole_setup() { + case ${reason:-} in + BOUND|RENEW|REBIND|REBOOT) + netconsole_add + ;; + EXPIRE|FAIL|RELEASE|STOP) + netconsole_remove + ;; + PREINIT) : ;; + *) + if [ "${ADDRFAM:-}" = "inet" ] && [ "${METHOD:-}" = "static" ] + then + export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + interface="${IFACE:-}" + new_ip_address="${IF_ADDRESS:-}" + case ${MODE:-} in + start) + netconsole_add + ;; + stop) + netconsole_remove + ;; + *) : ;; + esac + fi + esac +} + +netconsole_setup diff --git a/linux/files/netconsole.conf b/linux/files/netconsole.conf new file mode 100644 index 00000000..01b2cede --- /dev/null +++ b/linux/files/netconsole.conf @@ -0,0 +1,22 @@ +{%- from "linux/map.jinja" import system with context %} +# default port is 514 +#PORT=6666 +{%- if system.netconsole is mapping and system.netconsole.port is defined %} +PORT="{{ system.netconsole.port }}" +{%- endif %} + +# unicast, could be multiline +#netconsole ens3 192.168.1.32 fa:16:3e:8d:f6:d0 +{%- if system.netconsole is mapping and system.netconsole.target is mapping %} +{%- for target, data in system.netconsole.target.iteritems() %} +{%- if data is mapping %} +netconsole "{{ data.get('interface', '${interface}') }}" "{{ target }}" "{{ data.get('mac', '') }}" +{%- endif %} +{%- endfor %} +{%- endif %} + +# set up dmesg log level +# dmesg -n info +{%- if system.netconsole is mapping and system.netconsole.loglevel is defined %} +dmesg -n "{{ system.netconsole.loglevel }}" +{%- endif %} diff --git a/linux/system/init.sls b/linux/system/init.sls index 84c23653..45700f03 100644 --- a/linux/system/init.sls +++ b/linux/system/init.sls @@ -80,3 +80,6 @@ include: {%- if system.sudo is defined %} - linux.system.sudo {%- endif %} +{%- if system.netconsole is defined %} +- linux.system.netconsole +{%- endif %} diff --git a/linux/system/netconsole.sls b/linux/system/netconsole.sls new file mode 100644 index 00000000..1f6a3da3 --- /dev/null +++ b/linux/system/netconsole.sls @@ -0,0 +1,43 @@ +{% from "linux/map.jinja" import system with context %} +{% if system.enabled and system.netconsole is mapping and system.netconsole.enabled %} + +/etc/dhcp/dhclient-exit-hooks.d/netconsole: + file.managed: + - source: salt://linux/files/netconsole + - makedirs: True + +/etc/network/if-up.d/netconsole: + file.managed: + - source: salt://linux/files/netconsole + - mode: 755 + +/etc/network/if-down.d/netconsole: + file.managed: + - source: salt://linux/files/netconsole + - mode: 755 + +/etc/default/netconsole.conf: + file.managed: + - source: salt://linux/files/netconsole.conf + - template: jinja + +{% if system.netconsole is mapping and system.netconsole.target is mapping %} +{% for target, data in system.netconsole.target.iteritems() %} +{% if data is mapping and data.interface is defined %} +/etc/network/if-up.d/netconsole {{ target }} {{ data.interface }}: + cmd.run: + - name: /etc/network/if-up.d/netconsole + - env: + - IFACE: {{ data.interface }} + - METHOD: static + - ADDRFAM: inet + - MODE: start + - onchanges: + - file: /etc/default/netconsole.conf + - require: + - file: /etc/network/if-up.d/netconsole +{% endif %} +{% endfor %} +{% endif %} + +{% endif %} diff --git a/tests/integration/system/netconsole_spec.rb b/tests/integration/system/netconsole_spec.rb new file mode 100644 index 00000000..f5175082 --- /dev/null +++ b/tests/integration/system/netconsole_spec.rb @@ -0,0 +1,14 @@ + +## NETCONSOLE +# +describe file('/etc/default/netconsole.conf') do + it('should exist') + its('content') { should match /^PORT="514"/} + its('content') { should match /^netconsole "bond0" "192.168.0.1" "ff:ff:ff:ff:ff:ff"/} + its('content') { should match /^dmesg -n "debug"/} +end + +describe file('/etc/dhcp/dhclient-exit-hooks.d/netconsole') do + it('should exist') + its('content') { should match /netconsole_setup/} +end diff --git a/tests/pillar/system.sls b/tests/pillar/system.sls index 216bca5f..426f2dcd 100644 --- a/tests/pillar/system.sls +++ b/tests/pillar/system.sls @@ -265,3 +265,13 @@ linux: - host1 - host2 - .local + + # pillars for netconsole setup + netconsole: + enabled: true + port: 514 + loglevel: debug + target: + 192.168.0.1: + mac: "ff:ff:ff:ff:ff:ff" + interface: bond0