From 7c3d1f919cf2ce0c9cd17e43b5f52ed9295b1abe Mon Sep 17 00:00:00 2001 From: Anatoly Mirin Date: Mon, 3 Jul 2023 12:11:28 +0300 Subject: [PATCH] mcastmngr: fixed mcproxy crash for multiupstream config If more than one upstream iface is defined, mcastmngr runs mcproxy multiple times for each protocol (e.g. igmp) with the same config. So second mcproxy process fails because it can't lock the MRT flag (/proc/sys/net/ipv/default/mc_forwarding) already blocked by the first process. If one good and one bad (no IP address) upstream interfaces are defined, mcastmngr generates wrong filter configuration and mcproxy cannot start at all. Now upstream and downstream ifaces are checked in the 'config_mcproxy_interfaces' function and only good interfaces (with IP) are written to the mcproxy config. Only one mcproxy process is started per protocol (igmp, mld), configured to handle multiple interfaces if necessary. 'get_network_of' function removed. This function fails in some uci configurations for interfaces that refer to a device indirectly. Example - network.wan6.device='@wan' --- mcastmngr/files/linux/lib/mcast/linux.sh | 242 +++++++++++------------ 1 file changed, 112 insertions(+), 130 deletions(-) diff --git a/mcastmngr/files/linux/lib/mcast/linux.sh b/mcastmngr/files/linux/lib/mcast/linux.sh index 272c4746e..6913c4840 100755 --- a/mcastmngr/files/linux/lib/mcast/linux.sh +++ b/mcastmngr/files/linux/lib/mcast/linux.sh @@ -10,70 +10,81 @@ PROG_EXE=/usr/sbin/mcproxy PROG_PARAMS= PROG_PARAMS_SEPARATOR=: -setup_mcast_mode() { - local snooped_interface=$1 - local mcast_mode=$2 - local mcast_flood= +__device_is_bridge() { + local device="$2" + local devsec__="$(uci show network | grep name=.*$device | cut -d'.' -f2)" + local sectype="$(uci -q get network.$devsec__)" + local devtype="$(uci -q get network.$devsec__.type)" + [ "$sectype" != "device" -o "$devtype" != "bridge" ] && return 1 + eval "$1=$devsec__" +} + +device_is_bridge() { + local device="$1" local devsec= - local sectype= - local devtype= - local ports= - if [ -z "$snooped_interface" ]; then - return - fi + __device_is_bridge devsec "$device" || return 1 +} - if [ $mcast_mode == "2" ]; then # disable mcast flood - mcast_flood=0 +device_ports() { + local device="$1" + local devsec= + + if __device_is_bridge devsec "$device"; then + echo "$(uci get network.$devsec.ports)" else - mcast_flood=1 + echo "$device" fi +} - for intf in $snooped_interface; - do - devsec="$(uci show network | grep name=.*$intf | cut -d'.' -f2)" - sectype="$(uci -q get network.$devsec)" - devtype="$(uci -q get network.$devsec.type)" - if [ "$sectype" == "device" -a "$devtype" == "bridge" ]; then - ports="$(uci get network.$devsec.ports)" - for prt in $ports; do - echo $mcast_flood > /sys/class/net/$prt/brport/multicast_flood - done - else - if [ -f /sys/class/net/$intf/brport/multicast_flood ]; then - echo $mcast_flood > /sys/class/net/$intf/brport/multicast_flood - fi - fi +device_has_ip() { + local protocol="$1" + local device="$2" + + # Read the openwrt interface for the device. + # Device can have multiple logical interfaces like wan and wan6 + # but same l3 device + # NB. Don't use 'get_network_of' here. + # This function fails in some uci configurations for interfaces that refer + # to a device indirectly. + local ifaces=$(ubus call network.interface dump | jsonfilter -e "@.interface[@.device='$device'].interface") + for iface in $ifaces; do + local ip= + case "$protocol" in + "igmp") network_get_ipaddr ip "$iface" ;; + "mld") network_get_ipaddr6 ip "$iface" ;; + esac + [ -n "$ip" ] && return done + + return 1 } config_mcproxy_interfaces() { - local upstreams="$1" - local downstreams="$2" - local exceptions="$3" + local protocol="$1" + local upstreams="$2" + local downstreams="$3" + local exceptions="$4" + + if [ -z "$upstreams" ] || [ -z "$downstreams" ]; then + return 1 + fi local str_up="" - if [ -n "$upstreams" ]; then - for upstream in $upstreams; do - str_up="$str_up \"$upstream\"" - done - fi + for upstream in $upstreams; do + device_has_ip "$protocol" "$upstream" || continue + str_up="$str_up \"$upstream\"" + done + [ -z "$str_up" ] && return 1 local str_down="" - if [ -n "$downstreams" ]; then - for downstream in $downstreams; do - str_down="$str_down \"$downstream\"" - done - fi + for downstream in $downstreams; do + device_has_ip "$protocol" "$downstream" || continue + str_down="$str_down \"$downstream\"" + done + [ -z "$str_down" ] && return 1 - if [ ! -z $downstream ]; then - echo -e "pinstance main:$str_up ==>$str_down;\n" >> $CONFFILE - fi - - - if [ -z "$exceptions" ] || [ -z "$upstreams" ]; then - return - fi + echo -e "pinstance main:$str_up ==>$str_down;\n" >> $CONFFILE for excp in $exceptions; do local filter="" @@ -89,34 +100,63 @@ config_mcproxy_interfaces() { ;; esac - for upstream in $upstreams; do - echo "pinstance main upstream \"$upstream\" in blacklist table{$filter };" >> $CONFFILE - echo "pinstance main upstream \"$upstream\" out blacklist table{$filter };" >> $CONFFILE + for upstream in $str_up; do + echo "pinstance main upstream $upstream in blacklist table{$filter };" >> $CONFFILE + echo "pinstance main upstream $upstream out blacklist table{$filter };" >> $CONFFILE done - for downstream in $downstreams; do - echo "pinstance main downstream \"$downstream\" in blacklist table{$filter };" >> $CONFFILE - echo "pinstance main downstream \"$downstream\" out blacklist table{$filter };" >> $CONFFILE + for downstream in $str_down; do + echo "pinstance main downstream $downstream in blacklist table{$filter };" >> $CONFFILE + echo "pinstance main downstream $downstream out blacklist table{$filter };" >> $CONFFILE done done } config_sysfs_mcast_snooping() { - local devsec= - local sectype= - local devtype= - local ports= + local downstreams="$1" for downstream in $downstreams; do - devsec="$(uci show network | grep name=.*$downstream | cut -d'.' -f2)" - sectype="$(uci -q get network.$devsec)" - devtype="$(uci -q get network.$devsec.type)" - if [ "$sectype" == "device" -a "$devtype" == "bridge" ]; then + if device_is_bridge "$downstream"; then echo 1 > /sys/class/net/$downstream/bridge/multicast_snooping fi done } +config_sysfs_mcast_fastleave() { + local downstreams="$1" + local fastleave="$2" + local prt + + for downstream in $downstreams; do + for prt in $(device_ports $downstream); do + if [ -f /sys/class/net/$prt/brport/multicast_fast_leave ]; then + echo $fastleave > /sys/class/net/$prt/brport/multicast_fast_leave + fi + done + done +} + +config_sysfs_mcast_mode() { + local downstreams=$1 + local mcast_mode=$2 + local prt + + local mcast_flood= + if [ $mcast_mode == "2" ]; then # disable mcast flood + mcast_flood=0 + else + mcast_flood=1 + fi + + for downstream in $downstreams; do + for prt in $(device_ports $downstream); do + if [ -f /sys/class/net/$prt/brport/multicast_flood ]; then + echo $mcast_flood > /sys/class/net/$prt/brport/multicast_flood + fi + done + done +} + config_mcproxy_instance() { local protocol="$1" local version="$2" @@ -129,7 +169,6 @@ config_mcproxy_instance() { local exceptions= local upstreams= local downstreams= - local intf_has_ip= local mcast_mode=2 # default value 2 is for blocking mode CONFFILE=/var/etc/mcproxy_"$protocol".conf @@ -178,58 +217,23 @@ config_mcproxy_instance() { mcast_mode=$mld_p_mode fi - # for snooping to work we should enable it on the bridge, doing it from - # here instead of from inside network config - config_sysfs_mcast_snooping - [ -n "$max_groups" ] && echo -e "max_groups $max_groups;" >> $CONFFILE [ -n "$robustness" ] && echo -e "rv $robustness;" >> $CONFFILE [ -n "$query_interval" ] && echo -e "qi $query_interval;" >> $CONFFILE [ -n "$q_resp_interval" ] && echo -e "qri $q_resp_interval;" >> $CONFFILE [ -n "$last_mem_q_int" ] && echo -e "lmqi $last_mem_q_int;" >> $CONFFILE - if [[ -n $fast_leave ]]; then - echo -e "fastleave $fast_leave;\n" >> $CONFFILE - config_sysfs_mcast_fastleave $fast_leave - fi + [ -n "$fast_leave" ] && echo -e "fastleave $fast_leave;\n" >> $CONFFILE - setup_mcast_mode $downstreams $mcast_mode - [ -n "$upstreams" ] && [ -n "$downstreams" ] && - config_mcproxy_interfaces "$upstreams" "$downstreams" "$exceptions" + config_mcproxy_interfaces "$protocol" "$upstreams" "$downstreams" "$exceptions" || return - # In case on proxy, upstreams is a list. Iterating and running the mcproxy - # for each valid upstream interface - for upstream_device in $upstreams; - do - # Read the upstream interface for the upstream device - # upstream device can have multiple logical interfaces like wan and wan6 - # but same l3 device - local upstream_ifaces=$(get_network_of $upstream_device) + # for snooping to work we should enable it on the bridge, doing it from + # here instead of from inside network config + config_sysfs_mcast_snooping "$downstreams" + [ -n $fast_leave ] && + config_sysfs_mcast_fastleave "$downstreams" "$fast_leave" + config_sysfs_mcast_mode "$downstreams" "$mcast_mode" - for iface in $upstream_ifaces; - do - if [ "$protocol" == "igmp" ]; then - network_get_ipaddr upstream_ip $iface - if [ ! -z "${upstream_ip}" ]; then - intf_has_ip=1 - break - fi - fi - - if [ "$protocol" == "mld" ]; then - network_get_ipaddr6 upstream_ip $iface - if [ ! -z "${upstream_ip}" ]; then - intf_has_ip=1 - break - fi - fi - done - - if [ -z "${intf_has_ip}" ]; then - continue - fi - - PROG_PARAMS="${PROG_PARAMS} -f ${CONFFILE}${PROG_PARAMS_SEPARATOR}" - done + PROG_PARAMS="${PROG_PARAMS} -f ${CONFFILE}${PROG_PARAMS_SEPARATOR}" } config_mcproxy() { @@ -242,28 +246,6 @@ config_mcproxy() { fi } -config_sysfs_mcast_fastleave() { - local devsec= - local sectype= - local devtype= - local ports= - - for downstream in $downstreams; do - devsec="$(uci show network | grep name=.*$downstream | cut -d'.' -f2)" - sectype="$(uci -q get network.$devsec)" - devtype="$(uci -q get network.$devsec.type)" - if [ "$sectype" == "device" -a "$devtype" == "bridge" ]; then - ports="$(uci get network.$devsec.ports)" - for prt in $ports; do - echo $1 > /sys/class/net/$prt/brport/multicast_fast_leave - done - else - [[ -f /sys/class/net/$downstream/brport/multicast_fast_leave ]] && echo $1 > /sys/class/net/$downstream/brport/multicast_fast_leave - fi - done - -} - configure_mcast() { config_global_params "set_max_groups_and_sources"