#!/bin/sh # SPDX-License-Identifier: MIT # # Remove qosify qdiscs and ifb devices left behind after qosify exits. # # A clean stop only does part of this: interface_clear_qdisc() removes the root # qdisc, the bpf filters and the ifb device but not clsact, and main() never # calls qosify_dns_stop(), so ifb-dns stays up. A crash, a SIGKILL or a respawn # loop leaves the rest too. This script deliberately only touches the devices of # enabled qosify sections, the ifb names qosify derives from them and qosify's # own ifb-dns -- never a qdisc or ifb qosify did not create. # # `cleanup wait` is for a package removal: qosify is stopped by its own prerm, # which may run after ours, so the device names are read now and the sweep waits # up to 30 s for qosify to exit. If it is still running, nothing is touched. . /lib/functions.sh . /lib/functions/network.sh # The lock is an flock on an open fd, not a directory plus an EXIT trap: rpcd # SIGKILLs this script at its exec timeout (rpc_file_exec_timeout_cb() in # file.c), the trap never runs, and the leftover directory would then make every # later run exit 0 without doing anything. The kernel drops an flock on process # exit however the process dies. The path differs from the old directory so a # stale one left by an earlier version cannot break the redirect. Without # busybox flock, run unlocked rather than not at all -- the worst a concurrent # run can do is repeat a tc delete. LOCK="/var/lock/qosify-luci-cleanup.lock" mkdir -p /var/lock exec 9>"$LOCK" command -v flock >/dev/null && { flock -n 9 || exit 0; } # Mirrors interface_ifb_name() in qosify: "ifb-" while strlen(dev) + 4 is # below IFNAMSIZ. Longer names take a different branch upstream which we do not # try to reproduce here. ifb_name() { [ "${#1}" -lt 12 ] || return 1 echo "ifb-$1" } qosify_filter() { tc filter show dev "$1" "$2" 2>/dev/null | grep -q ' qosify_' } # qosify's filters sit at QOSIFY_PRIO_BASE (0x110 = 272): its bpf classifier on # egress, and on ingress the classifier plus the DNS and ifb redirects up to 277. # A clean exit removes them and the root qdisc, so they only survive a crash, and # then the root cake is qosify's too. Another shaper's root qdisc, and a clsact # carrying anyone else's filters, are left alone. clear_dev() { local dev="$1" local ifb p [ -n "$dev" ] || return 0 if [ -e "/sys/class/net/$dev" ]; then if qosify_filter "$dev" egress; then tc qdisc del dev "$dev" root 2>/dev/null tc filter del dev "$dev" egress pref 272 2>/dev/null fi if qosify_filter "$dev" ingress; then for p in 272 273 274 275 276 277; do tc filter del dev "$dev" ingress pref "$p" 2>/dev/null done fi [ -n "$(tc filter show dev "$dev" ingress 2>/dev/null)$(tc filter show dev "$dev" egress 2>/dev/null)" ] || tc qdisc del dev "$dev" clsact 2>/dev/null fi # An ifb outlives its parent, so this is not gated on $dev still existing. # That only helps a `config device`, whose name survives the netdev. ifb="$(ifb_name "$dev")" || return 0 [ -e "/sys/class/net/$ifb" ] || return 0 ip link set "$ifb" down 2>/dev/null ip link del "$ifb" 2>/dev/null } # add_interface() in qosify.init returns before reading anything else when # disabled is set, so qosify never touched those devices. section_enabled() { local disabled config_get_bool disabled "$1" disabled 0 [ "$disabled" -eq 0 ] } # `config interface` names a netifd interface, not a device; qosify resolves it # to .l3_device before touching tc, so resolve it the same way. netifd drops # .l3_device when the interface goes down, so a vanished pppoe device leaves no # name to derive the ifb from here. qosify clears that one itself: on the next # up, interface_start() runs interface_clear_qdisc(), which deletes ifb- # before cmd_add_ingress() creates it again. collect_interface() { local cfg="$1" local name dev section_enabled "$cfg" || return 0 config_get name "$cfg" name [ -n "$name" ] || return 0 network_get_device dev "$name" && DEVS="$DEVS $dev" } # `config device` names a netdev directly. collect_device() { local cfg="$1" local name section_enabled "$cfg" || return 0 config_get name "$cfg" name [ -n "$name" ] && DEVS="$DEVS $name" } DEVS="" config_load qosify config_foreach collect_interface interface config_foreach collect_device device if [ "$1" = wait ]; then n=30 while pidof qosify >/dev/null; do [ "$n" -gt 0 ] || exit 0 n=$((n - 1)) sleep 1 done fi for dev in $DEVS; do clear_dev "$dev"; done # ifb-dns is qosify's fixed DNS ifb, not derived from any section, and a stop # leaves it up under the kernel's default fq_codel. Deleting its root qdisc only # re-attaches the default, so drop the device -- only once qosify has exited. pidof qosify >/dev/null || ip link del ifb-dns 2>/dev/null exit 0