From ece6b4caccc85aaf699de9b51c5aaec830d3304f Mon Sep 17 00:00:00 2001 From: Joel Low Date: Mon, 1 May 2023 11:01:46 +0800 Subject: [PATCH 1/8] config: Clarify config values and their relationships This shows how the different values must be set relative to each other so that the configs make sense. In the long term this should be implemented as checks in the config loading code and emit warnings, but documenting them is better than status quo at the moment. Signed-off-by: Joel Low --- openwrt/usteer/files/etc/config/usteer | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openwrt/usteer/files/etc/config/usteer b/openwrt/usteer/files/etc/config/usteer index dc06b7c..325af28 100644 --- a/openwrt/usteer/files/etc/config/usteer +++ b/openwrt/usteer/files/etc/config/usteer @@ -44,6 +44,7 @@ config usteer #option seen_policy_timeout 30000 # Minimum number of stations delta between APs before load balancing policy is active + # No effect unless load_balancing_threshold is above 0 #option load_balancing_threshold 0 # Minimum number of stations delta between bands before band steering policy is active @@ -62,9 +63,11 @@ config usteer #option probe_steering 0 # Minimum signal-to-noise ratio or signal level (dBm) to allow connections + # Related: should be equal to or higher than `min_snr` and `band_steering_min_snr` #option min_connect_snr 0 # Minimum signal-to-noise ratio or signal level (dBm) to remain connected + # Related: should be equal to or lower than `min_connect_snr` and lower than `band_steering_min_snr` #option min_snr 0 # Timeout after which a station with snr < min_snr will be kicked @@ -90,8 +93,9 @@ config usteer # as a roam #option roam_process_timeout 5000 - # Minimum signal-to-noise ratio or signal level (dBm) before attempting to trigger + # Maximum signal-to-noise ratio or signal level (dBm) before attempting to trigger # client scans for roaming + # Related: should be higher than `roam_trigger_snr` #option roam_scan_snr 0 # Maximum number of client roaming scan trigger attempts @@ -104,8 +108,9 @@ config usteer # Minimum time (ms) between client roaming scan trigger attempts #option roam_scan_interval 10000 - # Minimum signal-to-noise ratio or signal level (dBm) before attempting to trigger + # Maximum signal-to-noise ratio or signal level (dBm) before attempting to trigger # forced client roaming + # Related: should be lower than `roam_scan_snr` #option roam_trigger_snr 0 # Minimum time (ms) between client roaming trigger attempts @@ -141,6 +146,7 @@ config usteer # Minimal SNR or absolute signal a device has to maintain over band_steering_interval to be # steered to a higher frequency band + # Related: should be above `min_connect_snr` and `min_snr` #option band_steering_min_snr -60 # SNR difference that the signal must be better compared to signal was on connection to node. From f0bf78dfc86df4d7e6f862a7fb1ded99036ba3d7 Mon Sep 17 00:00:00 2001 From: Nils Hendrik Rottgardt Date: Thu, 5 Mar 2026 10:50:57 +0100 Subject: [PATCH 2/8] policy: correction of load threshold determination Load threshold is not evaluated correctly as the result of the comparison can be false only. - policy: load comparison of actual and target node Signed-off-by: Nils Hendrik Rottgardt --- policy.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/policy.c b/policy.c index 16c4363..f0e20b9 100644 --- a/policy.c +++ b/policy.c @@ -55,7 +55,7 @@ better_signal_strength(int signal_cur, int signal_new) } static bool -below_load_threshold(struct usteer_node *node) +above_load_threshold(struct usteer_node *node) { return node->n_assoc >= config.load_kick_min_clients && node->load > config.load_kick_threshold; @@ -64,7 +64,7 @@ below_load_threshold(struct usteer_node *node) static bool has_better_load(struct usteer_node *node_cur, struct usteer_node *node_new) { - return !below_load_threshold(node_cur) && below_load_threshold(node_new); + return above_load_threshold(node_cur) && !above_load_threshold(node_new); } bool @@ -107,8 +107,7 @@ is_better_candidate(struct sta_info *si_cur, struct sta_info *si_new) if (better_signal_strength(current_signal, new_signal)) reasons |= (1 << UEV_SELECT_REASON_SIGNAL); - if (has_better_load(current_node, new_node) && - !has_better_load(new_node, current_node)) + if (has_better_load(current_node, new_node)) reasons |= (1 << UEV_SELECT_REASON_LOAD); return reasons; From b841f0fa6a8816a6327a4b666f0fa83575eb47f0 Mon Sep 17 00:00:00 2001 From: Nils Hendrik Rottgardt Date: Fri, 6 Mar 2026 00:49:56 +0100 Subject: [PATCH 3/8] Changed default config values to optimise setup and performance - main,; usteer config: consolidated initial config values - main:, usteer config: added roam_scan_snr and signal_diff_threshold default values - README: added config recommendation Signed-off-by: Nils Hendrik Rottgardt default Signed-off-by: Nils Hendrik Rottgardt --- README.md | 6 ++++++ main.c | 21 ++++++++++++++------- openwrt/usteer/files/etc/config/usteer | 14 +++++++------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 502ac0d..ae62ab4 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,12 @@ usteer is available from the OpenWrt packages feed and can be installed on devic opkg update; opkg install usteer ``` +You can find the complete documentation for setting everything up and getting it running here:[OpenWRT documentation](https://openwrt.org/docs/guide-user/network/wifi/usteer). + +### Config recommendation +The default settings are the best in most environments (many users overdo optimizations to it) to support roaming and band steering. At best nothing has to be configured but take care that usteer communication is not done on a public network. + + ## Submitting patches usteer patches are welcome on the openwrt-devel mailing list. diff --git a/main.c b/main.c index bd6ae4b..639a682 100644 --- a/main.c +++ b/main.c @@ -84,6 +84,12 @@ void usteer_init_defaults(void) { memset(&config, 0, sizeof(config)); + config.syslog = true; + config.debug_level = MSG_FATAL; + + config.ipv6 = false; + config.local_mode = 0; + config.sta_block_timeout = 30 * 1000; config.local_sta_timeout = 120 * 1000; config.measurement_report_timeout = 120 * 1000; @@ -91,6 +97,7 @@ void usteer_init_defaults(void) config.max_retry_band = 5; config.max_neighbor_reports = 8; config.seen_policy_timeout = 30 * 1000; + config.signal_diff_threshold = 8; config.band_steering_threshold = 5; config.load_balancing_threshold = 0; config.remote_update_interval = 1000; @@ -99,22 +106,24 @@ void usteer_init_defaults(void) config.aggressiveness = 3; config.reassociation_delay = 30; - config.steer_reject_timeout = 60000; + config.steer_reject_timeout = 60 * 1000; - config.band_steering_interval = 30000; + config.band_steering_interval = 30 * 1000; config.band_steering_min_snr = -60; config.band_steering_signal_threshold = 5; - config.link_measurement_interval = 30000; + config.link_measurement_interval = 30 * 1000; config.probe_steering = 0; - config.roam_kick_delay = 10000; + config.roam_kick_delay = 10 * 1000; config.roam_process_timeout = 5 * 1000; config.roam_scan_tries = 3; config.roam_scan_timeout = 0; config.roam_scan_interval = 10 * 1000; - config.roam_trigger_interval = 60 * 1000; + config.roam_scan_snr = -65; + config.roam_trigger_interval = 30 * 1000; + config.roam_trigger_snr = 0; config.min_snr_kick_delay = 5 * 1000; @@ -123,8 +132,6 @@ void usteer_init_defaults(void) config.load_kick_delay = 10 * 1000; config.load_kick_min_clients = 10; config.load_kick_reason_code = 5; /* WLAN_REASON_DISASSOC_AP_BUSY */ - - config.debug_level = MSG_FATAL; } void usteer_update_time(void) diff --git a/openwrt/usteer/files/etc/config/usteer b/openwrt/usteer/files/etc/config/usteer index 325af28..627bed2 100644 --- a/openwrt/usteer/files/etc/config/usteer +++ b/openwrt/usteer/files/etc/config/usteer @@ -5,13 +5,13 @@ config usteer option 'network' 'lan' # Log messages to syslog (0/1) - option 'syslog' '1' + #option 'syslog' '1' # Disable network communication (0/1) - option local_mode '0' + #option local_mode '0' # Use IPv6 for remote exchange - option 'ipv6' '0' + #option 'ipv6' '0' # Minimum level of logged messages # 0 = fatal @@ -20,7 +20,7 @@ config usteer # 3 = some debug messages # 4 = network packet information # 5 = all debug messages - option 'debug_level' '2' + #option 'debug_level' '2' # Maximum number of neighbor reports set for a node #option max_neighbor_reports 8 @@ -96,7 +96,7 @@ config usteer # Maximum signal-to-noise ratio or signal level (dBm) before attempting to trigger # client scans for roaming # Related: should be higher than `roam_trigger_snr` - #option roam_scan_snr 0 + #option roam_scan_snr -65 # Maximum number of client roaming scan trigger attempts #option roam_scan_tries 3 @@ -114,13 +114,13 @@ config usteer #option roam_trigger_snr 0 # Minimum time (ms) between client roaming trigger attempts - #option roam_trigger_interval 60000 + #option roam_trigger_interval 30000 # Timeout (ms) for client roam requests. usteer will kick the client after this times out. #option roam_kick_delay 10000 # Minimum signal strength difference until AP steering policy is active - #option signal_diff_threshold 0 + #option signal_diff_threshold 8 # Initial delay (ms) before responding to probe requests (to allow other APs to see packets as well) #option initial_connect_delay 0 From 96ea195a0ed22b6e29acee452edb37093204bc04 Mon Sep 17 00:00:00 2001 From: Nils Hendrik Rottgardt Date: Thu, 19 Mar 2026 20:08:58 +0100 Subject: [PATCH 4/8] Avoid creating an unneccessary ip4/ip6 socket in local mode - main: local_mode initalisation to false explicitly - remote: avoid socket creation in local mode Signed-off-by: Nils Hendrik Rottgardt --- main.c | 1 + remote.c | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/main.c b/main.c index 639a682..7f08fad 100644 --- a/main.c +++ b/main.c @@ -100,6 +100,7 @@ void usteer_init_defaults(void) config.signal_diff_threshold = 8; config.band_steering_threshold = 5; config.load_balancing_threshold = 0; + config.local_mode = 0; config.remote_update_interval = 1000; config.initial_connect_delay = 0; config.remote_node_timeout = 10; diff --git a/remote.c b/remote.c index bf58ea3..e1ac83d 100644 --- a/remote.c +++ b/remote.c @@ -749,6 +749,9 @@ static void usteer_reload_timer(struct uloop_timeout *t) { close(remote_fd.fd); } + if (config.local_mode) + return; + if (config.ipv6) { remote_fd.fd = usteer_create_v6_socket(); remote_fd.cb = interface_recv_v6; @@ -765,6 +768,8 @@ static void usteer_reload_timer(struct uloop_timeout *t) { int usteer_interface_init(void) { + if (config.local_mode) + return -1; if (usteer_init_local_id()) return -1; From df58091347e829165f7f452dfe058503b6f0bcbc Mon Sep 17 00:00:00 2001 From: Nils Hendrik Rottgardt Date: Sun, 22 Mar 2026 23:28:11 +0100 Subject: [PATCH 5/8] aggressiveness mac list: correction for ubus get_config and user handlebility - sta: moved aggressivemess update to correct location - sta: easied entry of mac as it is case insensitve now - ubus: added aggressivness to connected_clients api Signed-off-by: Nils Hendrik Rottgardt --- sta.c | 25 ++++++++++++++++++------- ubus.c | 7 ++++++- usteer.h | 2 ++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/sta.c b/sta.c index 059e987..1327cd6 100644 --- a/sta.c +++ b/sta.c @@ -17,6 +17,8 @@ * Copyright (C) 2020 John Crispin */ +#include +#include #include "usteer.h" static int @@ -81,11 +83,10 @@ usteer_sta_update_aggressiveness(struct sta *sta) { struct blob_attr *cur; int rem; - char sta_mac[18]; + uint8_t *config_addr; char config_entry[20]; char config_mac[18]; - - sprintf(sta_mac, MAC_ADDR_FMT, MAC_ADDR_DATA(sta->addr)); + sta->aggressiveness = config.aggressiveness; blobmsg_for_each_attr(cur, config.aggressiveness_mac_list, rem) { strcpy(config_entry, blobmsg_get_string(cur)); @@ -93,13 +94,25 @@ usteer_sta_update_aggressiveness(struct sta *sta) continue; strncpy(config_mac, config_entry, 18); config_mac[17] = '\0'; - if (strcmp(config_mac, sta_mac) != 0) + config_addr = (uint8_t *) ether_aton(config_mac); + if (memcmp(config_addr, sta->addr, 6) != 0) continue; sta->aggressiveness = config_entry[18] - '0'; + MSG(DEBUG, "Set aggressiveness for station " MAC_ADDR_FMT " from config to %d\n", + MAC_ADDR_DATA(sta->addr), sta->aggressiveness); break; } } +void +usteer_sta_load_each_aggressiveness() +{ + struct sta *sta; + avl_for_each_element(&stations, sta, avl) { + usteer_sta_update_aggressiveness(sta); + } +} + struct sta_info * usteer_sta_info_get(struct sta *sta, struct usteer_node *node, bool *create) { @@ -129,8 +142,6 @@ usteer_sta_info_get(struct sta *sta, struct usteer_node *node, bool *create) si->created = current_time; *create = true; - usteer_sta_update_aggressiveness(sta); - /* Node is by default not connected. */ usteer_sta_disconnected(si); @@ -168,7 +179,7 @@ usteer_sta_get(const uint8_t *addr, bool create) avl_insert(&stations, &sta->avl); INIT_LIST_HEAD(&sta->nodes); INIT_LIST_HEAD(&sta->measurements); - + usteer_sta_update_aggressiveness(sta); return sta; } diff --git a/ubus.c b/ubus.c index dca6f9a..09f6af3 100644 --- a/ubus.c +++ b/ubus.c @@ -163,8 +163,8 @@ struct cfg_item { _cfg(U32, remote_node_timeout), \ _cfg(BOOL, assoc_steering), \ _cfg(U32, aggressiveness), \ + _cfg(U32, reassociation_delay), \ _cfg(ARRAY_CB, aggressiveness_mac_list), \ - _cfg(U32, aggressive_disassoc_timer), \ _cfg(I32, min_connect_snr), \ _cfg(I32, min_snr), \ _cfg(U32, min_snr_kick_delay), \ @@ -186,6 +186,7 @@ struct cfg_item { _cfg(U32, load_kick_reason_code), \ _cfg(U32, band_steering_interval), \ _cfg(I32, band_steering_min_snr), \ + _cfg(U32, band_steering_signal_threshold), \ _cfg(U32, link_measurement_interval), \ _cfg(ARRAY_CB, interfaces), \ _cfg(STRING_CB, node_up_script), \ @@ -285,6 +286,7 @@ usteer_ubus_set_config(struct ubus_context *ctx, struct ubus_object *obj, } } + usteer_sta_load_each_aggressiveness(); usteer_interface_init(); return 0; @@ -472,6 +474,9 @@ usteer_ubus_get_connected_clients(struct ubus_context *ctx, struct ubus_object * } blobmsg_close_array(&b, a); + /* Aggressiveness */ + blobmsg_add_u32(&b, "aggressiveness", si->sta->aggressiveness); + blobmsg_close_table(&b, s); } diff --git a/usteer.h b/usteer.h index 407d02d..eeb5d75 100644 --- a/usteer.h +++ b/usteer.h @@ -371,6 +371,8 @@ int usteer_ubus_bss_transition_request(struct sta_info *si, struct sta *usteer_sta_get(const uint8_t *addr, bool create); struct sta_info *usteer_sta_info_get(struct sta *sta, struct usteer_node *node, bool *create); +void usteer_sta_load_each_aggressiveness(); + bool usteer_sta_supports_beacon_measurement_mode(struct sta_info *si, enum usteer_beacon_measurement_mode mode); bool usteer_sta_supports_link_measurement(struct sta_info *si); From 373571ec1bb1eeaa16a950bcb8dce8634926e1a6 Mon Sep 17 00:00:00 2001 From: Nils Hendrik Rottgardt Date: Mon, 23 Mar 2026 11:56:07 +0100 Subject: [PATCH 6/8] band_steering: ignore stations without 5Ghz support Signed-off-by: Nils Hendrik Rottgardt --- band_steering.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/band_steering.c b/band_steering.c index 9f28603..a2869db 100644 --- a/band_steering.c +++ b/band_steering.c @@ -26,6 +26,11 @@ void usteer_band_steering_sta_update(struct sta_info *si) } return; } + if (si->node->freq >= 4000) + return; + if (!si->sta->seen_5ghz) + return; + if (config.band_steering_signal_threshold) { if (si->connected != STA_NOT_CONNECTED && si->band_steering.signal_threshold == NO_SIGNAL) @@ -106,6 +111,10 @@ void usteer_band_steering_perform_steer(struct usteer_local_node *ln) ln->band_steering_interval = 0; list_for_each_entry(si, &ln->node.sta_info, node_list) { + /* Check if client supports 5Ghz */ + if (!si->sta->seen_5ghz) + continue; + /* Check if client is eligable to be steerd */ if (!usteer_policy_can_perform_roam(si)) continue; From 2bf9ad8866756e95ba71290020539f8a2fc53bef Mon Sep 17 00:00:00 2001 From: Nils Hendrik Rottgardt Date: Tue, 31 Mar 2026 22:16:39 +0200 Subject: [PATCH 7/8] usteer: optimized order of options in default config Signed-off-by: Nils Hendrik Rottgardt --- openwrt/usteer/files/etc/config/usteer | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openwrt/usteer/files/etc/config/usteer b/openwrt/usteer/files/etc/config/usteer index 627bed2..32bfca0 100644 --- a/openwrt/usteer/files/etc/config/usteer +++ b/openwrt/usteer/files/etc/config/usteer @@ -83,12 +83,12 @@ config usteer # 4 = BSS-transition-request with disassociation imminent, timer and forced disassociation #option aggressiveness 3 - # Timeout (s in "1024ms") a station is requested to avoid reassociation after bss transition - #option reassociation_delay 30 - # List of MACs (lower case) to set aggressiveness per station (ff:ff:ff:ff:ff,2) #list aggressiveness_mac_list '' + # Timeout (s in "1024ms") a station is requested to avoid reassociation after bss transition + #option reassociation_delay 30 + # Timeout (in ms) after which a association following a disassociation is not seen # as a roam #option roam_process_timeout 5000 From fcb36c8850dbc39fee3dfffe507d74c1b80c075d Mon Sep 17 00:00:00 2001 From: Nils Hendrik Rottgardt Date: Fri, 24 Jul 2026 16:17:55 +0200 Subject: [PATCH 8/8] remote: Add diagnostic logging for blob parsing to identify Kirkwood alignment issues This commit adds debug logging for incomming network messages. The issue shows IDs with consistent 0008 bytes on Kirkwood while IPQ40xx platforms parse correctly. This suggests a blob_attr struct alignment or padding issue. Fixes issue [#8](https://github.com/NilsRo/usteer-ng/issues/8): Usteer and usteer-ng on Kirkwood platform seems to parse messages badly. Signed-off-by: Nils Hendrik Rottgardt --- main.c | 24 ++++++++++++++++++++++++ remote.c | 20 ++++++++++++++++---- utils.h | 1 + 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/main.c b/main.c index 7f08fad..ba473ee 100644 --- a/main.c +++ b/main.c @@ -80,6 +80,30 @@ void debug_msg_cont(int level, const char *format, ...) va_end(ap); } + +/* DEBUG: Hex dump helper function for Kirkwood diagnostics */ +void debug_hexdump(const char *prefix, const void *data, int len) +{ + const unsigned char *buf = (const unsigned char *)data; + int i, j; + char hex_str[64], ascii_str[32]; + + MSG(NETWORK, "%s (len=%d):\n", prefix, len); + + for (i = 0; i < len; i += 16) { + hex_str[0] = '\0'; + ascii_str[0] = '\0'; + + for (j = 0; j < 16 && i + j < len; j++) { + unsigned char c = buf[i + j]; + sprintf(hex_str + strlen(hex_str), "%02x ", c); + sprintf(ascii_str + strlen(ascii_str), "%c", (c >= 32 && c < 127) ? c : '.'); + } + + MSG(NETWORK, " %04x: %-48s | %s\n", i, hex_str, ascii_str); + } +} + void usteer_init_defaults(void) { memset(&config, 0, sizeof(config)); diff --git a/remote.c b/remote.c index e1ac83d..27e555e 100644 --- a/remote.c +++ b/remote.c @@ -47,6 +47,7 @@ struct interface { int ifindex; }; + static void interfaces_update_cb(struct vlist_tree *tree, struct vlist_node *node_new, @@ -318,6 +319,9 @@ interface_recv_msg(struct interface *iface, char *addr_str, void *buf, int len) if (config.local_mode) return; + /* DEBUG 1: Hexdump first 64 bytes of raw packet for pattern analysis */ + debug_hexdump("RAW PACKET START", data, (blob_pad_len(data) < 64) ? blob_pad_len(data) : 64); + if (blob_pad_len(data) != len) { MSG(DEBUG, "Invalid message length (header: %d, real: %d)\n", blob_pad_len(data), len); return; @@ -357,8 +361,12 @@ interface_find_by_ifindex(int index) static void interface_recv_v4(struct uloop_fd *u, unsigned int events) { - static char buf[APMGR_BUFLEN]; - static char cmsg_buf[( CMSG_SPACE(sizeof(struct in_pktinfo)) + sizeof(int)) + 1]; + /* Keep receive buffers aligned for blob parsing and cmsghdr access on + * stricter ARM targets such as Kirkwood. + */ + static unsigned char buf[APMGR_BUFLEN] __attribute__((aligned(sizeof(unsigned long)))); + static unsigned char cmsg_buf[(CMSG_SPACE(sizeof(struct in_pktinfo)) + sizeof(int)) + 1] + __attribute__((aligned(sizeof(unsigned long)))); static struct sockaddr_in sin; char addr_str[INET_ADDRSTRLEN]; static struct iovec iov = { @@ -420,8 +428,12 @@ interface_recv_v4(struct uloop_fd *u, unsigned int events) static void interface_recv_v6(struct uloop_fd *u, unsigned int events){ - static char buf[APMGR_BUFLEN]; - static char cmsg_buf[( CMSG_SPACE(sizeof(struct in6_pktinfo)) + sizeof(int)) + 1]; + /* Keep receive buffers aligned for blob parsing and cmsghdr access on + * stricter ARM targets such as Kirkwood. + */ + static unsigned char buf[APMGR_BUFLEN] __attribute__((aligned(sizeof(unsigned long)))); + static unsigned char cmsg_buf[(CMSG_SPACE(sizeof(struct in6_pktinfo)) + sizeof(int)) + 1] + __attribute__((aligned(sizeof(unsigned long)))); static struct sockaddr_in6 sin; static struct iovec iov = { .iov_base = buf, diff --git a/utils.h b/utils.h index 75e1e2d..d151c5d 100644 --- a/utils.h +++ b/utils.h @@ -44,6 +44,7 @@ enum usteer_debug { extern void log_msg(char *msg); extern void debug_msg(int level, const char *func, int line, const char *format, ...); extern void debug_msg_cont(int level, const char *format, ...); +extern void debug_hexdump(const char *prefix, const void *data, int len); #define __usteer_init __attribute__((constructor))