mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
Two races could prevent DHCP-learned default routes from being installed at boot: 1. The signal from the DHCP client script could be lost leaving conf.d updated but frr.conf stale. 2. Even when the signal was received, 'vtysh -b' could fail because the FRR daemon chain (mgmtd→zebra→staticd) writes PID files before being fully operational, causing netd to give up with no retry. Fix both by refactoring netd to use libev: - Use an inotify watch of CONF_DIR, so netd reacts directly to file changes without depending on signal delivery. - On backend_apply() failure, schedule a retry in 5s and call pidfile() unconditionally so dependent services are not blocked waiting for the Finit condition 'pid/netd' to be satisfied. We also take this opportunity to rename /etc/netd/conf.d/ to /etc/net.d/ The extra /etc/netd/ directory level served no purpose — nothing else lives there. Flatten to /etc/net.d/ which reads more naturally as the drop-in directory for network configuration snippets. Also, reduce logging a bit since each netd backend already logs success or fail which is sufficient to know that a configuration change has been applied or not. Finally, with the new inotify processing in netd it's redundant to call 'initctl reload netd' from the udhcpc script, in fact it will only cause unnecessary overhead, so we drop it. Fixes #1438 Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
251 lines
5.8 KiB
Bash
Executable File
251 lines
5.8 KiB
Bash
Executable File
#!/bin/sh
|
|
# This script expect a system with resolvconf (openresolv) and iproute2
|
|
|
|
[ -z "$1" ] && echo "Error: should be called from udhcpc" && exit 1
|
|
|
|
ACTION="$1"
|
|
IP_CACHE="/var/lib/misc/${interface}.cache"
|
|
RESOLV_CONF="/run/resolvconf/interfaces/${interface}.conf"
|
|
NTPFILE="/run/chrony/dhcp-sources.d/${interface}.sources"
|
|
NAME="/etc/net.d/${interface}-dhcp.conf"
|
|
NEXT="${NAME}+"
|
|
|
|
[ -n "$broadcast" ] && BROADCAST="broadcast $broadcast"
|
|
[ -n "$subnet" ] || subnet=32
|
|
[ -n "$metric" ] || metric=5
|
|
|
|
# Handle stateful DHCPv6 like DHCPv4
|
|
[ -n "$ipv6" ] && ip="$ipv6/128"
|
|
|
|
if [ -z "${IF_WAIT_DELAY}" ]; then
|
|
IF_WAIT_DELAY=10
|
|
fi
|
|
|
|
log()
|
|
{
|
|
logger -I $$ -t udhcpc -p user.notice "${interface}: $*"
|
|
}
|
|
|
|
dbg()
|
|
{
|
|
logger -I $$ -t udhcpc -p user.debug "${interface}: $*"
|
|
}
|
|
|
|
err()
|
|
{
|
|
logger -I $$ -t udhcpc -p user.err "${interface}: $*"
|
|
}
|
|
|
|
wait_for_ipv6_default_route()
|
|
{
|
|
dbg "waiting for IPv6 default route to be installed."
|
|
while [ $IF_WAIT_DELAY -gt 0 ]; do
|
|
if ip -6 route list proto dhcp dev $interface | grep -q default; then
|
|
return
|
|
fi
|
|
sleep 1
|
|
printf "."
|
|
: $((IF_WAIT_DELAY -= 1))
|
|
done
|
|
err "Timed out waiting for IPv6 default route!"
|
|
}
|
|
|
|
# Check if a DHCP option was requested in the parameter request list
|
|
# Returns: 0 if requested, 1 if not requested or config unavailable
|
|
was_option_requested()
|
|
{
|
|
local opt_num="$1"
|
|
local config="/etc/finit.d/available/dhcp-client-${interface}.conf"
|
|
|
|
if [ ! -f "$config" ]; then
|
|
dbg "config file not found: $config"
|
|
return 1
|
|
fi
|
|
|
|
# Extract udhcpc command line and check for -O <option_num>
|
|
if grep -q -- "-O ${opt_num}\b" "$config"; then
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# RFC3442: If the DHCP server returns both a Classless
|
|
# Static Routes option and a Router option, the DHCP
|
|
# client MUST ignore the Router option.
|
|
set_dhcp_routes()
|
|
{
|
|
echo "# Generated by udhcpc" > "$NEXT"
|
|
echo "" >> "$NEXT"
|
|
if [ -n "$staticroutes" ]; then
|
|
if was_option_requested 121; then
|
|
# format: dest1/mask gw1 ... destn/mask gwn
|
|
set -- $staticroutes
|
|
while [ -n "$1" -a -n "$2" ]; do
|
|
dbg "adding route $1 via $2 metric $metric tag 100"
|
|
cat >> "$NEXT" <<-EOF
|
|
route {
|
|
prefix = "$1"
|
|
nexthop = "$2"
|
|
distance = $metric
|
|
tag = 100
|
|
}
|
|
|
|
EOF
|
|
shift 2
|
|
done
|
|
else
|
|
log "ignoring unrequested staticroutes (option 121)"
|
|
fi
|
|
elif [ -n "$router" ] ; then
|
|
if was_option_requested 3; then
|
|
for i in $router ; do
|
|
cat >> "$NEXT" <<-EOF
|
|
route {
|
|
prefix = "0.0.0.0/0"
|
|
nexthop = "$i"
|
|
distance = $metric
|
|
tag = 100
|
|
}
|
|
|
|
EOF
|
|
done
|
|
else
|
|
log "ignoring unrequested router (option 3)"
|
|
fi
|
|
fi
|
|
|
|
# Reduce changes needed by comparing with previous route(s)
|
|
cmp -s "$NAME" "$NEXT" && return
|
|
mv "$NEXT" "$NAME"
|
|
}
|
|
|
|
clr_dhcp_routes()
|
|
{
|
|
log "deleting DHCP routes"
|
|
[ -f "$NAME" ] || return
|
|
rm "$NAME"
|
|
}
|
|
|
|
clr_dhcp_addresses()
|
|
{
|
|
addrs=$(ip -j addr show dev $interface \
|
|
| jq -c '.[0].addr_info[] | select(.family == "inet") | select(.protocol == "dhcp")')
|
|
|
|
for addr in $addrs; do
|
|
ip="$(echo "$addr" | jq -r '."local"')"
|
|
prefix="$(echo "$addr" | jq -r '."prefixlen"')"
|
|
log "removing $ip/$prefix"
|
|
ip addr del "$ip/$prefix" dev "$interface"
|
|
done
|
|
}
|
|
|
|
log "action $ACTION"
|
|
case "$ACTION" in
|
|
deconfig)
|
|
clr_dhcp_addresses
|
|
clr_dhcp_routes
|
|
|
|
# drop info from this interface
|
|
rm -f "$RESOLV_CONF"
|
|
rm -f "$NTPFILE"
|
|
if [ -f "/etc/hostname.d/90-dhcp-${interface}" ]; then
|
|
log "removing /etc/hostname.d/90-dhcp-${interface}"
|
|
rm -f "/etc/hostname.d/90-dhcp-${interface}"
|
|
/usr/libexec/infix/hostname dhcp
|
|
fi
|
|
if [ -x /usr/sbin/avahi-autoipd ]; then
|
|
/usr/sbin/avahi-autoipd -c $interface && /usr/sbin/avahi-autoipd -k $interface
|
|
fi
|
|
;;
|
|
|
|
leasefail|nak)
|
|
if [ -x /usr/sbin/avahi-autoipd ]; then
|
|
/usr/sbin/avahi-autoipd -c $interface || /usr/sbin/avahi-autoipd -wD $interface --no-chroot
|
|
fi
|
|
;;
|
|
|
|
renew|bound)
|
|
if [ -x /usr/sbin/avahi-autoipd ]; then
|
|
/usr/sbin/avahi-autoipd -c $interface && /usr/sbin/avahi-autoipd -k $interface
|
|
fi
|
|
|
|
if /bin/ip addr add dev $interface $ip/$subnet $BROADCAST proto dhcp; then
|
|
echo "$ip" > "$IP_CACHE"
|
|
fi
|
|
if [ -n "$ipv6" ] ; then
|
|
wait_for_ipv6_default_route
|
|
fi
|
|
|
|
set_dhcp_routes
|
|
|
|
# set hostname if given and requested
|
|
if [ -n "$hostname" ]; then
|
|
if was_option_requested 12; then
|
|
log "received DHCP hostname: $hostname"
|
|
mkdir -p /etc/hostname.d
|
|
echo "$hostname" > "/etc/hostname.d/90-dhcp-${interface}"
|
|
/usr/libexec/infix/hostname dhcp
|
|
else
|
|
log "ignoring unrequested hostname (option 12): $hostname"
|
|
fi
|
|
fi
|
|
|
|
# drop info from this interface
|
|
truncate -s 0 "$RESOLV_CONF"
|
|
|
|
# prefer rfc3397 domain search list (option 119) if available
|
|
search_list=""
|
|
if [ -n "$search" ]; then
|
|
if was_option_requested 119; then
|
|
search_list=$search
|
|
else
|
|
log "ignoring unrequested search (option 119): $search"
|
|
fi
|
|
elif [ -n "$domain" ]; then
|
|
if was_option_requested 15; then
|
|
search_list=$domain
|
|
else
|
|
log "ignoring unrequested domain (option 15): $domain"
|
|
fi
|
|
fi
|
|
|
|
if [ -n "$search_list" ]; then
|
|
dbg "adding search $search_list"
|
|
echo "search $search_list # $interface" >> $RESOLV_CONF
|
|
fi
|
|
|
|
if [ -n "$dns" ]; then
|
|
if was_option_requested 6; then
|
|
for i in $dns ; do
|
|
dbg "adding dns $i"
|
|
echo "nameserver $i # $interface" >> $RESOLV_CONF
|
|
resolvconf -u
|
|
done
|
|
else
|
|
log "ignoring unrequested dns (option 6): $dns"
|
|
fi
|
|
fi
|
|
|
|
if [ -n "$ntpsrv" ]; then
|
|
if was_option_requested 42; then
|
|
truncate -s 0 "$NTPFILE"
|
|
for srv in $ntpsrv; do
|
|
dbg "got NTP server $srv"
|
|
echo "server $srv iburst" >> "$NTPFILE"
|
|
done
|
|
chronyc reload sources >/dev/null
|
|
else
|
|
log "ignoring unrequested ntpsrv (option 42): $ntpsrv"
|
|
fi
|
|
fi
|
|
esac
|
|
|
|
HOOK_DIR="$0.d"
|
|
for hook in "${HOOK_DIR}/"*; do
|
|
[ -f "${hook}" -a -x "${hook}" ] || continue
|
|
"${hook}" "$ACTION"
|
|
done
|
|
|
|
exit 0
|