#!/bin/bash
# Needs bash for printf -v VARNAME
# -----------------------------------------------------------------------------
#
# Copyright (c) 2011..2018 bwLehrpool-Projektteam
#
# This program/file is free software distributed under the GPL version 2.
# See https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
#
# If you have any feedback please consult https://bwlehrpool.de and
# send your feedback to support@bwlehrpool.de.
#
# General information about bwLehrpool can be found at https://bwlehrpool.de
#
# -----------------------------------------------------------------------------
#
# Mini-Linux Toolkit
#
# -----------------------------------------------------------------------------
# DO NOT RENAME/MOVE THIS FILE!
# The slx-network dracut module checks for its existence
. /opt/openslx/config
declare -rg primary="${SLX_BRIDGE:-br0}"
declare -rg RESOLV_CONF="/opt/openslx/resolv.conf"
declare -rg THIS_RESOLV="/run/network/${interface}.resolv"
declare -rg flag="/run/network/primary-dhcp.flag"
if [ "$1" = "--lock-hostname-updates" ]; then
# If we change the hostname while Xorg is running, this might
# cause issues, depending on how it's set up. So we run this
# script with the lock argument to touch the flag file right
# before we try to launch lightdm/Xorg. In case the DHCP request
# is too slow, this means we'll run with the wrong hostname
# (potentially), but that's better than delaying the bootup
# unnecessarily.
# So also note that if you ever change this script's name/location
# (JUST DON'T), you'd need to update all call sites, i.e. the
# udhcpc startup script, and the lightdm service drop-in.
mkdir -p /run/network
touch "$flag"
exit 0
fi
shopt -s extglob
rebuild_resolv_conf () {
# Don't do anything if the active resolv.conf is not ours
# Also this will not run resolvconf update.d... No idea if we should still do so...
[ -L "/etc/resolv.conf" ] || return 0
[ "$(readlink -f "/etc/resolv.conf")" = "${RESOLV_CONF}" ] || return 0
# Fake file to merge in search domains from /opt/openslx/config
echo "search $SLX_NET_SEARCH" > "/run/network/zzzzz-openslx.resolv"
# Use extglob trickery to make sure br0/$primary stuff comes first
# Then print them in a first-come-first-served manner.
# Print nameserver entries one per line, print only first domain value,
# group everything else together into one line.
# Skip domain entirely if any search lines are found.
cat "/run/network/$primary.resolv" /run/network/!("$primary").resolv \
| gawk '{
if ( $1 ~ /^[a-z]+$/ ) {
for (i = 2; i <= NF; ++i) {
if (done[$1][$i])
continue
done[$1][$i] = 1
output[$1][++idx] = $i
}
} else {
print $0
}
}
END {
for (s in output) {
if (s == "nameserver") {
for (t in output[s]) {
print s " " output[s][t]
}
} else if (s == "domain" && isarray(output["search"])) {
} else {
printf "%s", s
for (t in output[s]) {
printf " %s", output[s][t]
if (s == "domain")
break
}
print ""
}
}
}' \
> "$RESOLV_CONF" 2> /dev/null
# add support for resolv.conf update scripts // see man(8) resolvconf
for s in /etc/resolvconf/update.d/*.sh; do
[ -f "$s" ] && [ -x "$s" ] && "$s"
done
}
escape_search() {
sed -e 's/[]\/()$*.^|[]/\\&/g' <<<"$@"
}
escape_replace() {
sed -e 's/[\/&]/\\&/g' <<<"$@"
}
check_env() {
if [ -z "$ip" ] || [ -z "$subnet" ] || [ -z "$interface" ]; then
echo "$1 event with missing data" >&2
echo "ip = '$ip'" >&2
echo "subnet = '$subnet'" >&2
echo "interface = '$interface'" >&2
exit 1
fi
}
ipc() {
# Use the busybox ipcalc explicity as it's incompatible with the perl-one
busybox ipcalc -s "$@"
}
mkdir -p "/run/network"
case "$1" in
bound|renew)
check_env "$1"
mkdir -p "/run/dhcpc"
if [ "$interface" != "$primary" ]; then
echo 1 > /proc/sys/net/ipv4/conf/all/arp_filter
echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
fi
# Set address on interface
alt_table= # Use separate routing table?
if [ "$interface" != "$primary" ]; then
pri_net="$( ip addr show dev "${primary}" | awk '$1 == "inet" {print $2; exit}' )"
[[ "$pri_net" == */* ]] || pri_net="${pri_net}/32"
pri_net="$( ipc -n "$pri_net" | sed 's/^.*=//' )/${pri_net#*/}"
this_net="$( ipc -n "$ip" "$subnet" | sed 's/^.*=//' )/$( ipc -p "$ip" "$subnet" | sed 's/^.*=//' )"
[ "$pri_net" = "$this_net" ] && alt_table=yes
fi
if [ -z "$alt_table" ]; then
# default table
ip addr add "$ip/$( ipc -p "$ip" "$subnet" | sed 's/^.*=//' )" dev "$interface"
# Set default route, if given
if [ -n "$router" ]; then
# Only replace route if it's the same interface as the current default route, or we don't have any
current="$( ip route show | awk '{ if ($1 == "default") {print $5; exit 0}}' )"
if [ -z "$current" ] || [ "$interface" = "$current" ]; then
ip route replace default via "$router"
fi
fi
else
# alt table - determine which one
mkdir -p /etc/iproute2
touch /etc/iproute2/rt_tables
alt_table="$( awk -v "iface=$interface" \
'$1 ~ /^[0-9]+$/ && $2 == iface {print $1; exit}' \
/etc/iproute2/rt_tables )"
if [ -z "$alt_table" ]; then
alt_table="$( awk '$1 ~ /^[0-9]+$/ { a[$1] = 1 }
END {
for (i = 1; i < 255; ++i) {
if (!a[i]) { print i; exit }
}
print 1
}' /etc/iproute2/rt_tables )"
echo "$alt_table $interface" >> /etc/iproute2/rt_tables
fi
ip addr add "$ip/$( ipc -p "$ip" "$subnet" | sed 's/.*=//' )" dev "$interface" noprefixroute
ip route add "$this_net" dev "$interface" scope link src "$ip" table "$interface"
ip rule add from "$ip" table "$interface"
# Set default route, if given
if [ -n "$router" ]; then
ip route replace default via "$router" table "$interface"
fi
fi
# get domain, hostname and thus fqdn from DNS
dns_fqdn=$(busybox timeout 3 rdns "$ip")
dns_short="${dns_fqdn%%.*}"
# check if it is fqdn
if [ "$dns_fqdn" == "$dns_short" ]; then
unset dns_fqdn dns_short
fi
# Update resolver configuration file
conf_file="$(mktemp)"
# Own domain suffix
if [ -n "$domain" ]; then
:
elif [ -n "$dns_fqdn" ]; then
domain="${dns_fqdn#*.}"
elif [ -n "$SLX_NET_DOMAIN" ]; then
domain="$SLX_NET_DOMAIN"
fi
if [ -n "$domain" ]; then
echo "domain ${domain%% *}" >> "${conf_file}"
fi
# Add domain to list of search domains if not in there yet
if [ -n "$domain" ] && [ -n "$search" ]; then
FOUND=no
for sd in $search; do
[ "x$sd" = "x$domain" ] && FOUND=yes
done
[ "$FOUND" = "no" ] && search="$domain $search"
elif [ -n "$domain" ]; then
search="$domain"
fi
# Search domains
if [ -n "$search" ]; then
echo "search $search" >> "${conf_file}"
elif [ -n "$SLX_NET_SEARCH" ]; then
echo "search $SLX_NET_SEARCH" >> "${conf_file}"
elif [ -n "$SLX_NET_DOMAIN" ]; then
echo "search $SLX_NET_DOMAIN" >> "${conf_file}"
fi
for i in $dns; do
echo "$0: Adding DNS $i"
echo "nameserver $i" >> "${conf_file}"
done
# Manual handling required :-(
mv -f "$conf_file" "$THIS_RESOLV"
rebuild_resolv_conf
# Things that should only happen for the main interface that was used for booting
if [ "$interface" = "$primary" ]; then
# Update IP
sed -i "s/^\(SLX_PXE_CLIENT_IP=\).*$/\1'$ip'/" /opt/openslx/config
# Write DOMAIN and SEARCH to /opt/openslx/config if empty
if [ -z "$SLX_NET_DOMAIN" ] && [ -n "$domain" ]; then
echo "SLX_NET_DOMAIN='$domain'" >> /opt/openslx/config
fi
if [ -z "$SLX_NET_SEARCH" ] && [ -n "$search" ]; then
echo "SLX_NET_SEARCH='$search'" >> /opt/openslx/config
fi
# Same for WINS servers
if [ -z "$SLX_NET_WINS" ] && [ -n "$wins" ]; then
echo "SLX_NET_WINS='$wins'" >> /opt/openslx/config
fi
# Only update hostname if network is not ready yet
# later on this might cause issues
if ! [ -e "$flag" ]; then
# Fallback for hostname
if [ -z "$dns_fqdn" ] && [ -n "$domain" ] && [ -n "$hostname" ]; then
# fallback to what the dhcp told us
dns_fqdn="${hostname}.${domain}"
fi
if [ -z "$dns_fqdn" ]; then
# Try currently set fqdn
dns_fqdn="$( hostname -f )"
fi
if [ -z "$dns_fqdn" ]; then
if [ -s "/etc/hostname" ]; then
dns_fqdn="$( head -n 1 /etc/hostname )"
else
# Final fallback, nothing valid found
dns_fqdn="noname-${ip//./-}.invalid"
fi
fi
# finally, if dns_fqdn was set to anything, apply it
if [ -n "$dns_fqdn" ]; then
dns_short="${dns_fqdn%%.*}"
echo "$dns_short" > "/proc/sys/kernel/hostname"
echo "$dns_short" > "/etc/hostname"
if [ -z "$SLX_HOSTNAME" ]; then
echo "# Config written by openslx-dhcp-script (1)" >> /opt/openslx/config
echo "SLX_HOSTNAME='$dns_short'" >> /opt/openslx/config
elif [ "$SLX_HOSTNAME" != "$dns_short" ]; then
sed -i "s/^\(SLX_HOSTNAME=\).*$/\1'$dns_short'/" /opt/openslx/config
fi
fi
# Update /etc/issue for proper spacing
/opt/openslx/scripts/openslx-create_issue
touch "$flag"
# end "network not ready yet"
else
# Network already going, make sure we don't change the primary hostname in /etc/hosts
hostname=
domain=
dns_fqdn=
fi
# Remove any stray addresses; we expect the primary interface to only have one
# address supplied via DHCP. We do this after adding the new one, obviously.
rem_list=$( ip -o addr show "$interface" | awk '{ for (i=1;i<NF;++i) if ($i == "inet") print $(i+1) }' | grep -v "^${ip}/" )
if [ -n "$rem_list" ]; then
echo "PRIMARY: Removing $rem_list since we just got assigned $ip"
echo 1 > "/proc/sys/net/ipv4/conf/$interface/promote_secondaries"
for addr in $rem_list; do
ip addr del "$addr" dev "$interface"
sed -i "/^$(escape_search "${addr%/*}")(\s|$)/d" /etc/hosts
done
fi
fi # end "primary only"
# Hostname in /etc/hosts
touch "/etc/hosts"
hostlist=""
[ -n "$dns_fqdn" ] && hostlist="$dns_fqdn"
[ -n "$hostname" ] && [ -n "$domain" ] && [ "x${hostname}.${domain}" != "x$dns_fqdn" ] && hostlist="$hostlist ${hostname}.${domain}"
if [ -n "$hostlist" ]; then
for host in $hostlist; do
host_short="${host%%.*}"
[ "x$host_short" = "x$host" ] && host_short=""
sed -i -r "s/\s$(escape_search "$host")(\s|$)/ /g" /etc/hosts
[ -n "$host_short" ] && sed -i -r "s/\s$(escape_search "$host_short")(\s|$)/ /g" /etc/hosts
if grep -q -E "^$ip\s" /etc/hosts; then
sed -i "s/^$(escape_search "$ip")\s.*/$(escape_replace "$ip $host $host_short")/g" /etc/hosts
else
echo "$ip $host $host_short" >> /etc/hosts
fi
done
fi
# Get rid of orphaned lines in /etc/hosts
sed -i -r '/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\s*$/d' /etc/hosts
if [ -n "$dns" ] && [ -z "$SLX_DNS" ]; then
# Write to openslx-config
echo "# Config written by openslx-dhcp-script (2)" >> /opt/openslx/config
echo "SLX_DNS='$dns'" >> /opt/openslx/config
fi
# Rewrite nat1 dhcpd config for VMs, and restart dnsmasq if it changed
/opt/openslx/scripts/runvirt-start_dhcpd
;;
deconfig)
check_env "$1"
if [ "$interface" = "$primary" ]; then
echo "Ignoring deconfig for primary interface"
else
echo 1 > "/proc/sys/net/ipv4/conf/$interface/promote_secondaries"
clientip=${ip%%:*}
ip addr del "$clientip/$(ipc -p "$clientip" "$subnet" | sed s/.*=//)" dev "$interface"
ip route flush table "$interface"
sed -i "/^$(escape_search "$ip")(\s|$)/d" /etc/hosts
fi
# Manual handling required :-(
rm -f -- "$THIS_RESOLV"
rebuild_resolv_conf
;;
leasefail)
echo "$0: Lease failed: $message" >&2
;;
nak)
echo "$0: Received a NAK: $message" >&2
;;
*)
echo "$0: Unknown udhcpc command: $1" >&2
exit 1
;;
esac
exit 0