From b94fe1bd813d9d5c8bd156996dd515d46dcb5fed Mon Sep 17 00:00:00 2001 From: tievolu <78606440+tievolu@users.noreply.github.com> Date: Tue, 6 Sep 2022 13:33:46 +0100 Subject: [PATCH] Overhaul handling of interfaces Several significant changes: 1. UCI SQM configuration has been removed. Only tc is used to modify bandwidths. 2. Interface direction configuration has been removed because it is now redundant. 3. Interfaces can now be specified as-is - either regular interfaces or IFB interfaces. 4. Each configured interface is now checked as follows: - If upload, check interface as-is for an SQM instance - If download, and interface is not already specified for upload, check interface as-is for an SQM instance - If download and no SQM instance found for interface as-is, check for an SQM instance on a corresponding IFB interface --- sqm-autorate.pl | 260 ++++++++++++++++++++++-------------------------- 1 file changed, 117 insertions(+), 143 deletions(-) diff --git a/sqm-autorate.pl b/sqm-autorate.pl index 3392d7c..4b49e71 100644 --- a/sqm-autorate.pl +++ b/sqm-autorate.pl @@ -209,29 +209,25 @@ my $config_file = "sqm-autorate.conf"; my %config_properties = &get_config_properties($config_file); -# Upload interfaces and directions +# Upload interfaces my $index = 0; -my %ul_interface_directions; +my @ul_interfaces = (); while( - exists($config_properties{"ul_interface." . $index}) && - exists($config_properties{"ul_interface_direction." . $index}) + exists($config_properties{"ul_interface." . $index}) ) { my $if_name = $config_properties{"ul_interface." . $index}; - my $if_direction = $config_properties{"ul_interface_direction." . $index}; - $ul_interface_directions{$if_name} = $if_direction; + push(@ul_interfaces, $if_name); $index++ } -# Download interfaces and directions +# Download interfaces $index = 0; -my %dl_interface_directions; +my @dl_interfaces = (); while( - exists($config_properties{"dl_interface." . $index}) && - exists($config_properties{"dl_interface_direction." . $index}) + exists($config_properties{"dl_interface." . $index}) ) { my $if_name = $config_properties{"dl_interface." . $index}; - my $if_direction = $config_properties{"dl_interface_direction." . $index}; - $dl_interface_directions{$if_name} = $if_direction; + push(@dl_interfaces, $if_name); $index++; } @@ -432,9 +428,9 @@ my $sender_thread = &create_sender_thread($icmp_interval, $fd); &output(0, "INIT: Created ICMP Sender thread: " . $sender_thread->tid); -# Get the current bandwidth values from UCI -$current_bandwidth_ul = &get_current_bandwidth_from_uci("upload"); -$current_bandwidth_dl = &get_current_bandwidth_from_uci("download"); +# Get the current bandwidth values from tc +$current_bandwidth_ul = &get_current_bandwidth_from_tc("upload"); +$current_bandwidth_dl = &get_current_bandwidth_from_tc("download"); # Check whether the current bandwidths are within the min/max range, and if # not, fix them. This can happen if the min/max settings are modified @@ -936,53 +932,48 @@ sub check_config { my $fatal_error = 0; if ($dryrun) { - &output(0, "WARNING: Dry run - configuration changes will not be applied"); + &output(0, "INIT: WARNING: Dry run - configuration changes will not be applied"); } - foreach my $dl_interface (keys(%dl_interface_directions)) { - if ($dl_interface =~ /^ifb4/) { - my $corrected_dl_interface =~ s/^ifb4//g; - &output(0, "WARNING: Download IFB interface \"$dl_interface\" specified in config file. Correcting to \"$corrected_dl_interface\" (tc settings will be applied to \"$dl_interface\" automatically)."); - delete($dl_interface_directions{$dl_interface}); - $dl_interface_directions{$corrected_dl_interface} = "ingress"; - } + if (&check_interfaces() == -1) { + $fatal_error = 1; } if (!defined($wan_interface) || $wan_interface eq "") { - &output(0, "ERROR: WAN interface (\"wan_interface\") not set"); + &output(0, "INIT: ERROR: WAN interface (\"wan_interface\") not set"); $fatal_error = 1; } elsif (scalar(&get_wan_bytes()) == 0) { - &output(0, "ERROR: Failed to get statistics for WAN interface \"$wan_interface\" from /proc/net/dev"); + &output(0, "INIT: ERROR: Failed to get statistics for WAN interface \"$wan_interface\" from /proc/net/dev"); $fatal_error = 1; } if (!defined($dl_bw_minimum)) { - &output(0, "ERROR: Minimum download bandwidth (\"dl_bw_minimum\") not set"); + &output(0, "INIT: ERROR: Minimum download bandwidth (\"dl_bw_minimum\") not set"); $fatal_error = 1; } if (!defined($dl_bw_standard)) { - &output(0, "ERROR: Standard download bandwidth (\"dl_bw_standard\") not set"); + &output(0, "INIT: ERROR: Standard download bandwidth (\"dl_bw_standard\") not set"); $fatal_error = 1; } if (!defined($dl_bw_maximum)) { - &output(0, "ERROR: Maximum download bandwidth (\"dl_bw_maximum\") not set"); + &output(0, "INIT: ERROR: Maximum download bandwidth (\"dl_bw_maximum\") not set"); $fatal_error = 1; } if (!defined($ul_bw_minimum)) { - &output(0, "ERROR: Minimum upload bandwidth (\"ul_bw_minimum\")not set"); + &output(0, "INIT: ERROR: Minimum upload bandwidth (\"ul_bw_minimum\")not set"); $fatal_error = 1; } if (!defined($ul_bw_standard)) { - &output(0, "ERROR: Standard upload bandwidth (\"ul_bw_standard\") not set"); + &output(0, "INIT: ERROR: Standard upload bandwidth (\"ul_bw_standard\") not set"); $fatal_error = 1; } if (!defined($ul_bw_maximum)) { - &output(0, "ERROR: Maximum upload bandwidth (\"ul_bw_maximum\") not set"); + &output(0, "INIT: ERROR: Maximum upload bandwidth (\"ul_bw_maximum\") not set"); $fatal_error = 1; } @@ -991,141 +982,141 @@ sub check_config { defined($ul_bw_minimum) && defined($ul_bw_standard) && defined($ul_bw_maximum) ) { if ($dl_bw_minimum >= $dl_bw_maximum) { - &output(0, "ERROR: Minimum download bandwidth (\"dl_bw_minimum\") must be lower than maximum download bandwidth (\"dl_bw_maximum\")"); + &output(0, "INIT: ERROR: Minimum download bandwidth (\"dl_bw_minimum\") must be lower than maximum download bandwidth (\"dl_bw_maximum\")"); $fatal_error = 1; } if ($ul_bw_minimum >= $ul_bw_maximum) { - &output(0, "ERROR: Minimum upload bandwidth (\"ul_bw_minimum\") must be lower than maximum upload bandwidth (\"ul_bw_maximum\")"); + &output(0, "INIT: ERROR: Minimum upload bandwidth (\"ul_bw_minimum\") must be lower than maximum upload bandwidth (\"ul_bw_maximum\")"); $fatal_error = 1; } if ($dl_bw_standard <= $dl_bw_minimum || $dl_bw_standard >= $dl_bw_maximum) { - &output(0, "Standard download bandwidth (\"dl_bw_standard\") must be between minimum and maximum download bandwidths (\"dl_bw_minimum\" and \"dl_bw_maximum\")"); + &output(0, "INIT: ERROR: Standard download bandwidth (\"dl_bw_standard\") must be between minimum and maximum download bandwidths (\"dl_bw_minimum\" and \"dl_bw_maximum\")"); $fatal_error = 1; } if ($ul_bw_standard <= $ul_bw_minimum || $ul_bw_standard >= $ul_bw_maximum) { - &output(0, "ERROR: Standard upload bandwidth (\"ul_bw_standard\") must be between minimum and maximum upload bandwidths (\"ul_bw_minimum\" and \"ul_bw_maximum\")"); + &output(0, "INIT: ERROR: Standard upload bandwidth (\"ul_bw_standard\") must be between minimum and maximum upload bandwidths (\"ul_bw_minimum\" and \"ul_bw_maximum\")"); $fatal_error = 1; } } if ($max_recent_results < 1) { - &output(0, "ERROR: Maximum recent results (\"max_recent_results\") must be greater than 0"); + &output(0, "INIT: ERROR: Maximum recent results (\"max_recent_results\") must be greater than 0"); $fatal_error = 1; } if ($icmp_offset_samples < 5) { - &output(0, "ERROR: ICMP offset samples (\"icmp_offset_samples \") must be 5 or greater"); + &output(0, "INIT: ERROR: ICMP offset samples (\"icmp_offset_samples \") must be 5 or greater"); $fatal_error = 1; } if ($increase_load_threshold_pc < 0 || $increase_load_threshold_pc > 100) { - &output(0, "ERROR: Increase load threshold percentage (\"increase_load_threshold_pc\") must be between 0 and 100"); + &output(0, "INIT: ERROR: Increase load threshold percentage (\"increase_load_threshold_pc\") must be between 0 and 100"); $fatal_error = 1; } if ($increase_delay_after_decrease < 0) { - &output(0, "ERROR: Increase delay after increase (\"increase_delay_after_decrease\") cannot be negative"); + &output(0, "INIT: ERROR: Increase delay after increase (\"increase_delay_after_decrease\") cannot be negative"); $fatal_error = 1; } if ($increase_delay_after_decrease < 0) { - &output(0, "ERROR: Increase delay after decrease (\"increase_delay_after_decrease\") cannot be negative"); + &output(0, "INIT: ERROR: Increase delay after decrease (\"increase_delay_after_decrease\") cannot be negative"); $fatal_error = 1; } if ($relax_delay < 0) { - &output(0, "ERROR: Relaxation delay (\"relax_delay\") cannot be negative"); + &output(0, "INIT: ERROR: Relaxation delay (\"relax_delay\") cannot be negative"); $fatal_error = 1; } if ($relax_pc < 0) { - &output(0, "ERROR: Relaxation percentage step (\"relax_pc\") cannot be negative"); + &output(0, "INIT: ERROR: Relaxation percentage step (\"relax_pc\") cannot be negative"); $fatal_error = 1; } if ($relax_load_threshold_pc < 0 || $relax_load_threshold_pc > 100) { - &output(0, "ERROR: Relaxation load threshold percentage (\"relax_load_threshold_pc\") must be between 0 and 100"); + &output(0, "INIT: ERROR: Relaxation load threshold percentage (\"relax_load_threshold_pc\") must be between 0 and 100"); $fatal_error = 1; } if ($icmp_interval <= 0) { - &output(0, "ERROR: ICMP interval (\"icmp_interval\") must be greater than 0"); + &output(0, "INIT: ERROR: ICMP interval (\"icmp_interval\") must be greater than 0"); $fatal_error = 1; } if ($icmp_timeout <= 0) { - &output(0, "ERROR: ICMP timeout (\"icmp_timeout\") must be greater than 0"); + &output(0, "INIT: ERROR: ICMP timeout (\"icmp_timeout\") must be greater than 0"); $fatal_error = 1; } if ($latency_check_interval < 0) { - &output(0, "ERROR: Latency check interval (\"latency_check_interval\") cannot be negative"); + &output(0, "INIT: ERROR: Latency check interval (\"latency_check_interval\") cannot be negative"); $fatal_error = 1; } if ($latency_check_summary_interval < 0) { - &output(0, "ERROR: Latency check summary interval (\"latency_check_summary_interval\") cannot be negative"); + &output(0, "INIT: ERROR: Latency check summary interval (\"latency_check_summary_interval\") cannot be negative"); $fatal_error = 1; } if ($bad_ping_pc < 0 || $bad_ping_pc > 100) { - &output(0, "ERROR: Bad ping percentage (\"bad_ping_pc\") must be between 0 and 100"); + &output(0, "INIT: ERROR: Bad ping percentage (\"bad_ping_pc\") must be between 0 and 100"); $fatal_error = 1; } if ($icmp_offset_samples < 0) { - &output(0, "ERROR: ICMP offset samples (\"icmp_offset_samples\") cannot be negative"); + &output(0, "INIT: ERROR: ICMP offset samples (\"icmp_offset_samples\") cannot be negative"); $fatal_error = 1; } if ($ul_max_idle_latency < 0) { - &output(0, "ERROR: Maximum idle upload latency (\"ul_max_idle_latency\") cannot be negative"); + &output(0, "INIT: ERROR: Maximum idle upload latency (\"ul_max_idle_latency\") cannot be negative"); $fatal_error = 1; } if ($dl_max_idle_latency < 0) { - &output(0, "ERROR: Maximum idle download latency (\"dl_max_idle_latency\") cannot be negative"); + &output(0, "INIT: ERROR: Maximum idle download latency (\"dl_max_idle_latency\") cannot be negative"); $fatal_error = 1; } if (!defined($ul_max_loaded_latency) || $ul_max_loaded_latency < 0) { - &output(0, "ERROR: Maximum loaded upload latency (\"ul_max_loaded_latency\") must be set to positive value"); + &output(0, "INIT: ERROR: Maximum loaded upload latency (\"ul_max_loaded_latency\") must be set to positive value"); $fatal_error = 1; } if (!defined($dl_max_loaded_latency) || $dl_max_loaded_latency < 0) { - &output(0, "ERROR: Maximum loaded download latency (\"dl_max_loaded_latency\") must be set to positive value"); + &output(0, "INIT: ERROR: Maximum loaded download latency (\"dl_max_loaded_latency\") must be set to positive value"); $fatal_error = 1; } if ($ul_bw_idle_threshold < 0 || $ul_bw_idle_threshold >= $ul_bw_maximum) { - &output(0, "ERROR: Upload idle bandwidth threshold (\"ul_bw_idle_threshold\") must be between 0 and maximum upload bandwidth (\"ul_bw_maximum\")"); + &output(0, "INIT: ERROR: Upload idle bandwidth threshold (\"ul_bw_idle_threshold\") must be between 0 and maximum upload bandwidth (\"ul_bw_maximum\")"); $fatal_error = 1; } if ($dl_bw_idle_threshold < 0 || $dl_bw_idle_threshold >= $dl_bw_maximum) { - &output(0, "ERROR: Download idle bandwidth threshold (\"dl_bw_idle_threshold\") must be between 0 and maximum download bandwidth (\"dl_bw_maximum\")"); + &output(0, "INIT: ERROR: Download idle bandwidth threshold (\"dl_bw_idle_threshold\") must be between 0 and maximum download bandwidth (\"dl_bw_maximum\")"); $fatal_error = 1; } if (!defined($reflectors_csv_file) || $reflectors_csv_file eq "") { - &output(0, "ERROR: No reflector CSV file specified (\"reflectors_csv_file\"). Download one for your region from https://github.com/tievolu/timestamp-reflectors"); + &output(0, "INIT: ERROR: No reflector CSV file specified (\"reflectors_csv_file\"). Download one for your region from https://github.com/tievolu/timestamp-reflectors"); $fatal_error = 1; } elsif (! -e $reflectors_csv_file) { - &output(0, "ERROR: Specified reflector CSV file does not exist: $reflectors_csv_file"); + &output(0, "INIT: ERROR: Specified reflector CSV file does not exist: $reflectors_csv_file"); $fatal_error = 1; } if ($number_of_reflectors <= 0) { - &output(0, "ERROR: Number of reflectors (\"number_of_reflectors\") must be greater than 0"); + &output(0, "INIT: ERROR: Number of reflectors (\"number_of_reflectors\") must be greater than 0"); $fatal_error = 1; } if ($reflector_strikeout_threshold < 0) { - &output(0, "ERROR: Reflector strikeout threshold (\"reflector_strikeout_threshold\") cannot be less than 0"); + &output(0, "INIT: ERROR: Reflector strikeout threshold (\"reflector_strikeout_threshold\") cannot be less than 0"); $fatal_error = 1; } @@ -1144,6 +1135,52 @@ sub check_config { } } +# Check that SQM instances exist for the configured interfaces +sub check_interfaces { + my @qdiscs = split(/\n/, &run_sys_command("tc -d qdisc")); + + # Return value of 1 means success. -1 will be returned if we hit a problem. + my $return_value = 1; + + # Upload interfaces + UL_INTERFACE: for (my $i = 0; $i < scalar(@ul_interfaces); $i++) { + foreach my $qdisc (@qdiscs) { + # Check specified interface as-is + if ($qdisc =~ / dev $ul_interfaces[$i] /) { + &output(0, "INIT: Found upload SQM instance on " . $ul_interfaces[$i]); + next UL_INTERFACE; + } + } + + &output(0, "INIT: ERROR: No upload SQM instance found for $ul_interfaces[$i] - check configuration"); + $return_value = -1; + } + + # Download interfaces + DL_INTERFACE: for (my $i = 0; $i < scalar(@dl_interfaces); $i++) { + foreach my $qdisc (@qdiscs) { + # Check interface as-is if this interface is not already configured as an upload interface + if (!grep(/$dl_interfaces[$i]/, @ul_interfaces) && $qdisc =~ / dev $dl_interfaces[$i] /) { + &output(0, "INIT: Found download SQM instance on " . $dl_interfaces[$i]); + next DL_INTERFACE; + } + + # If this is a non-IFB interface, check for a corresponding IFB interface + if ($dl_interfaces[$i] !~ /^ifb4/ && $qdisc =~ / dev ifb4$dl_interfaces[$i] /) { + # Found IFB interface - modify configuration accordingly + $dl_interfaces[$i] = "ifb4" . $dl_interfaces[$i]; + &output(0, "INIT: Found download SQM instance on " . $dl_interfaces[$i]); + next DL_INTERFACE; + } + } + + &output(0, "INIT: ERROR: No download SQM instance found for $dl_interfaces[$i] - check configuration"); + $return_value = -1; + } + + return $return_value; +} + # Print the specified message to the console and log file, if enabled. # If $send_to_syslog == 1 the message is also sent to the syslog, unless $use_syslog == 0. # If $dryrun == 1 messages are only printed to the console. @@ -3068,7 +3105,7 @@ sub is_struckout { # Returns the current bandwidth for the specified direction (download|upload) # The value returned here is the value maintained by this script. -# Use &get_current_bandwidth_from_uci() to refresh the value from UCI. +# Use &get_current_bandwidth_from_tc() to refresh the value from tc. sub get_current_bandwidth { my ($direction) = @_; @@ -3083,55 +3120,29 @@ sub get_current_bandwidth { # Returns the current bandwidth for the specified direction (download|upload) # from the SQM config. We should only need to do this once during initialisation. -sub get_current_bandwidth_from_uci { +sub get_current_bandwidth_from_tc { my ($direction) = @_; - - &check_direction($direction); - - # Take the first interface configured for the specified direction - # (The bandwidth should be the same on all of them) + my $interface = ""; - my $direction_uci = ""; if ($direction eq "download") { - my @dl_interfaces = keys(%dl_interface_directions); $interface = $dl_interfaces[0]; - my $interface_dir = $dl_interface_directions{$interface}; - - # UCI uses "download" for ingress, "upload" for egress - if ($interface_dir eq "ingress") { - $direction_uci = "download"; - } elsif ($interface_dir eq "egress") { - $direction_uci = "upload"; - } else { - &fatal_error("Invalid interface direction for $interface: $interface_dir"); - } } if ($direction eq "upload") { - my @ul_interfaces = keys(%ul_interface_directions); $interface = $ul_interfaces[0]; - my $interface_dir = $ul_interface_directions{$interface}; - - # UCI uses "download" for ingress, "upload" for egress - if ($interface_dir eq "ingress") { - $direction_uci = "download"; - } elsif ($interface_dir eq "egress") { - $direction_uci = "upload"; - } else { - &fatal_error("Invalid interface direction for $interface: $interface_dir"); - } } - - # Get the bandwidth from UCI - my $uci_command = "uci get sqm.$interface.$direction_uci"; - my $result = &run_sys_command($uci_command); - if ($direction eq "upload") { - $current_bandwidth_ul = $result; - } else { - $current_bandwidth_dl = $result; + my @qdiscs = split(/\n/, &run_sys_command("tc -d qdisc")); + foreach my $qdisc (@qdiscs) { + if ($qdisc =~ / dev $interface .* bandwidth (\d+)(K|M)bit/) { + my $bw = $1; + my $bw_units = $2; + if ($bw_units eq "K") { + return $bw; + } elsif ($bw_units eq "M") { + return $bw * 1000; + } + } } - - return $result; } # Get the maximum allowed latency for the specified direction when bandwidth usage is significant @@ -3916,14 +3927,14 @@ sub set_bandwidth { my $errors = ""; if ($direction eq "upload") { - foreach my $interface (keys(%ul_interface_directions)) { - $errors .= &set_bandwidth_for_interface($interface, $ul_interface_directions{$interface}, $new_bandwidth); + foreach my $interface (@ul_interfaces) { + $errors .= &set_bandwidth_for_interface($interface, $new_bandwidth); } } if ($direction eq "download") { - foreach my $interface (keys(%dl_interface_directions)) { - $errors .= &set_bandwidth_for_interface($interface, $dl_interface_directions{$interface}, $new_bandwidth); + foreach my $interface (@dl_interfaces) { + $errors .= &set_bandwidth_for_interface($interface, $new_bandwidth); } } @@ -3954,51 +3965,14 @@ sub set_bandwidth { # Returns any errors, or an empty string if successful # Note: the bandwidth must be an integer value in kilobits/s sub set_bandwidth_for_interface { - my ($interface, $direction_if, $bandwidth) = @_; + my ($interface, $bandwidth) = @_; my $errors = ""; - # Use ingress/egress terminology in the logs to keep things clear. - # However, UCI uses "upload" and "download". - my $direction_uci = ""; - if ($direction_if eq "ingress") { - $direction_uci = "download"; - } elsif ($direction_if eq "egress") { - $direction_uci = "upload"; - } else { - return "Bad interface direction specified: $direction_if"; - } - - # Set the new bandwidth in UCI so that the changes will show up in the - # GUI and survive a reboot. Should complete silently. - - my $uci_command = "uci set sqm.$interface.$direction_uci=$bandwidth"; - if ($debug_bw_changes) { &output(0, "Applying new $direction_if bandwidth $bandwidth Kb/s to $interface: $uci_command"); } - my $uci_errors = &run_sys_command($uci_command); - chomp($uci_errors); - - # Check whether we had any errors. - if ($uci_errors eq "") { - # No errors - commit the change - &run_sys_command("uci commit"); - } else { - $errors = $uci_errors; - if ($debug_bw_changes) { &output(0, $uci_errors); } - } - - # Now use tc to change the bandwidth on the fly, without restarting SQM. + # Use tc to change the bandwidth on the fly, without restarting SQM. # The tc command should complete silently. - - # Ingress SQM qdiscs operate on virtual Intermediary Function Block (IFB) interfaces - # Which receive ingress packets on their ingress side, and then send them on to - # the kernel on their egress side. The SQM instance operates on this egress side. - if ($direction_if eq "ingress" && $interface !~ /^ifb4/) { - $interface = "ifb4" . $interface; - $direction_if = "egress"; - } - my $tc_command = "tc qdisc change root dev $interface cake bandwidth " . $bandwidth . "Kbit"; - if ($debug_bw_changes) { &output(0, "Applying new $direction_if bandwidth $bandwidth Kb/s to $interface: $tc_command"); } + if ($debug_bw_changes) { &output(0, "Applying new bandwidth $bandwidth Kb/s to $interface: $tc_command"); } my $tc_errors .= &run_sys_command($tc_command); chomp($tc_errors);