From 2fa5dcfea0cb8525bb31cd73c8330e0f0b41b5d6 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Fri, 8 Dec 2023 08:09:59 +0100 Subject: [PATCH] board/common: major refactor of udhcpc script - New log() function replaces stdout logging - New set_dhcp_routes() and clr_dhcp_routes() functions - Set all option 121 routes with same metric - Set all option 3 routers with increasing metric (this is what the reference udhcpc scripts do, and RFC says the routers should be listed in order of preference ...) - Clearing routes must, like IP addresses, be done both by interface and protocol. This refactor makes sure to delete any DHCP routes set on the given interface (in case options change) - Use resolvconf per-interface search+nameserver - Cache IP lease so we can ask for it back later - On deconfig|leasefail|nak, make sure to clean up anything that might be lingering from this interface. E.g., we can get leasefail when a server denies our request to prolong a lease. - On renew|bound, refresh routes, and set search+dns + NTP servers Signed-off-by: Joachim Wiberg --- .../rootfs/usr/share/udhcpc/default.script | 218 ++++++++++-------- 1 file changed, 116 insertions(+), 102 deletions(-) diff --git a/board/common/rootfs/usr/share/udhcpc/default.script b/board/common/rootfs/usr/share/udhcpc/default.script index 4eeff79f..d527dd85 100755 --- a/board/common/rootfs/usr/share/udhcpc/default.script +++ b/board/common/rootfs/usr/share/udhcpc/default.script @@ -1,135 +1,149 @@ #!/bin/sh - -# udhcpc script edited by Tim Riker +# This script expect a system with resolvconf (openresolv) and iproute2 [ -z "$1" ] && echo "Error: should be called from udhcpc" && exit 1 ACTION="$1" -RESOLV_CONF="/etc/resolv.conf" +IP_CACHE="/var/lib/misc/${interface}.cache" +RESOLV_CONF="/run/resolvconf/interfaces/${interface}.conf" NTPFILE="/run/chrony/dhcp-sources.d/${interface}.sources" -[ -e $RESOLV_CONF ] || touch $RESOLV_CONF [ -n "$broadcast" ] && BROADCAST="broadcast $broadcast" [ -n "$subnet" ] && NETMASK="$subnet" +[ -n "$metric" ] || metric=100 + # Handle stateful DHCPv6 like DHCPv4 [ -n "$ipv6" ] && ip="$ipv6/128" -[ -n "$ntpsrv" ] && NTP="server $ntpsrv iburst" if [ -z "${IF_WAIT_DELAY}" ]; then IF_WAIT_DELAY=10 fi -wait_for_ipv6_default_route() { - printf "Waiting for IPv6 default route to appear" - while [ $IF_WAIT_DELAY -gt 0 ]; do - if ip -6 route list | grep -q default; then - printf "\n" - return - fi - sleep 1 - printf "." - : $((IF_WAIT_DELAY -= 1)) - done - printf " timeout!\n" +log() +{ + logger -I $$ -t udhcpc -p user.notice "$*" } -flush_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"')" - ip addr del "$ip/$prefix" dev "$interface" - done +wwait_for_ipv6_default_route() +{ + log "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 + log "Timed out witing for IPv6 default route!" } +# 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() +{ + if [ -n "$staticroutes" ]; then + # format: dest1/mask gw1 ... destn/mask gwn + set -- $staticroutes + while [ -n "$1" -a -n "$2" ]; do + log "adding route $1 via $2 dev $interface proto dhcp" + ip route add "$1" via "$2" dev $interface metric $metric proto dhcp + shift 2 + done + elif [ -n "$router" ] ; then + for i in $router ; do + ip route add default via $i dev $interface metric $((metric++)) proto dhcp + done + fi +} + +clr_dhcp_routes() +{ + log "deleting DHCP routes from $interface" + ip route show proto dhcp dev $interface | while read rt via nh dev dev; do + log "removing $rt nh $nh on $dev" + ip route del $rt via $nh dev $dev proto dhcp + done +} + +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 from $interface" + ip addr del "$ip/$prefix" dev "$interface" + done +} + +log "action $ACTION" case "$ACTION" in - deconfig) - flush_dhcp_addresses + deconfig) + clr_dhcp_addresses + clr_dhcp_routes + /bin/ip link set dev $interface up - /bin/ip link set dev $interface up + # drop info from this interface + rm -f "$RESOLV_CONF" + rm -f "$NTPFILE" + if [ -x /usr/sbin/avahi-autoipd ]; then + /usr/sbin/avahi-autoipd -c $interface && /usr/sbin/avahi-autoipd -k $interface + fi + ;; - # drop info from this interface - # resolv.conf may be a symlink to /tmp/, so take care - TMPFILE=$(mktemp) - grep -vE "# $interface\$" $RESOLV_CONF > $TMPFILE - cat $TMPFILE > $RESOLV_CONF - rm -f $TMPFILE - rm -f $NTPFILE - 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 + ;; - leasefail|nak) - if [ -x /usr/sbin/avahi-autoipd ]; then - /usr/sbin/avahi-autoipd -c $interface || /usr/sbin/avahi-autoipd -wD $interface --no-chroot - fi - rm -f $NTPFILE - ;; + renew|bound) + if [ -x /usr/sbin/avahi-autoipd ]; then + /usr/sbin/avahi-autoipd -c $interface && /usr/sbin/avahi-autoipd -k $interface + fi - renew|bound) - if [ -x /usr/sbin/avahi-autoipd ]; then - /usr/sbin/avahi-autoipd -c $interface && /usr/sbin/avahi-autoipd -k $interface - fi - /bin/ip addr add dev $interface $ip/$NETMASK $BROADCAST proto 5 - if [ -n "$ipv6" ] ; then - wait_for_ipv6_default_route - fi + if /bin/ip addr add dev $interface $ip/$NETMASK $BROADCAST proto 5; then + echo "$ip" > "$IP_CACHE" + fi + if [ -n "$ipv6" ] ; then + wait_for_ipv6_default_route + fi - # RFC3442: If the DHCP server returns both a Classless - # Static Routes option and a Router option, the DHCP - # client MUST ignore the Router option. - if [ -n "$staticroutes" ]; then - echo "deleting routers" - route -n | while read dest gw mask flags metric ref use iface; do - [ "$iface" != "$interface" -o "$gw" = "0.0.0.0" ] || \ - route del -net "$dest" netmask "$mask" gw "$gw" dev "$interface" - done + clr_dhcp_routes + set_dhcp_routes - # format: dest1/mask gw1 ... destn/mask gwn - set -- $staticroutes - while [ -n "$1" -a -n "$2" ]; do - ip route add -net "$1" via "$2" dev "$interface" proto 16 - shift 2 - done - elif [ -n "$router" ] ; then - echo "deleting routers" - while route del default gw 0.0.0.0 dev $interface 2> /dev/null; do - : - done + # drop info from this interface + truncate -s 0 "$RESOLV_CONF" - for i in $router ; do - ip route add default via $i dev $interface proto 16 - done - fi + # prefer rfc3397 domain search list (option 119) if available + if [ -n "$search" ]; then + search_list=$search + elif [ -n "$domain" ]; then + search_list=$domain + fi - # drop info from this interface - # resolv.conf may be a symlink to /tmp/, so take care - TMPFILE=$(mktemp) - grep -vE "# $interface\$" $RESOLV_CONF > $TMPFILE - cat $TMPFILE > $RESOLV_CONF - rm -f $TMPFILE + if [ -n "$search_list" ]; then + log "adding search $search_list" + echo "search $search_list # $interface" >> $RESOLV_CONF + fi - # prefer rfc3397 domain search list (option 119) if available - if [ -n "$search" ]; then - search_list=$search - elif [ -n "$domain" ]; then - search_list=$domain - fi + for i in $dns ; do + log "adding dns $i" + echo "nameserver $i # $interface" >> $RESOLV_CONF + resolvconf -u + done - [ -n "$search_list" ] && - echo "search $search_list # $interface" >> $RESOLV_CONF - - for i in $dns ; do - echo adding dns $i - echo "nameserver $i # $interface" >> $RESOLV_CONF - done - - if [ -n "$NTP" ]; then - echo "$NTP" > "$NTPFILE" - chronyc reload sources >/dev/null - fi + if [ -n "$ntpsrv" ]; then + truncate -s 0 "$NTPFILE" + for srv in $ntpsrv; do + log "got NTP server $srv" + echo "server $srv iburst" >> "$NTPFILE" + done + chronyc reload sources >/dev/null + fi esac HOOK_DIR="$0.d"