WIFI连网失败为哪般

2003年,英特尔推出迅驰(centrino)技术,低功耗的Pentium-M CPU和芯片组加上无线网卡三件套捆绑销售,走遍天下,是英特尔历史上的辉煌之作。从此之后,WIFI成为移动计算的标配。

ff02020c3d0ac1b65fae2ba67f3be78b.jpeg

20多年后的今天,虽然WIFI技术已经非常成熟,但因为WIFI硬件种类繁多,软件代码量大且复杂,所以WIFI的问题还是挺多的。根据格蠹这几年的经验,每次适配新硬件平台或者操作系统时,最常出问题的就是WIFI。

比如,前些天在给幽兰代码本适配Debian 12时,又出现WIFI问题。系统启动到Debian的桌面后,在系统托盘区展开WIFI列表,可以找到很多热点(AP),但是选择一个热点,输入密码后,Debian显示一条恼人的错误:连接失败。附带一条模糊的消息:网络连接激活失败。

9f2bd06a2b4f6813f580b4db83172677.png

对于这样的WIFI问题,调试起来难度是很大的,主要原因是故障的范围大,从底层硬件,到WIFI固件,到WIFI内核驱动,到网络协议栈,到用户空间的网络服务,再到网络服务的客户端界面。

这么大范围,首先要决定从哪里寻找突破口。格蠹的小伙伴一度怀疑是内核驱动的问题,但是我觉得更可能是用户空间的问题。因为既然可以找到那么多的热点,说明内核驱动已经工作的挺好了。

顺手牵羊

本周三上午,我开始准备《RK3588驱动实战》系列直播第四讲的讲义,主题刚好是“WIFI常见问题和排查方法”。

79c9e285900d037e69f95627549c17fc.png

借着这个机会,我一边整理以往调试WIFI问题的资料,一边调试Debian的WIFI问题。

wpa_supplicant

根据直觉,我把主要的战场放在用户空间。

因为错误信息中包含“网络连接激活失败”,所以我首先想到的是wpa_supplicant。 

WPA(Wi-Fi Protected Access)是WIFI认证和授权协议, wpa_supplicant是实现这个协议的一个开源项目,主要作者是下面照片中的Jouni Malinen。很可能是因为这个开源项目被广泛使用,Jouni 在2012年获得了ieee802组织的表彰。

df37ba4bfac49fef91ff34b6e5b95d8c.png

(照片来自ieee802组织官网)

wpa_supplicant一般是以后台服务的形式运行的。大多数Linux系统中,都可以看到这个服务:

geduer@ulan:~/ndb$ ps -A | grep wpa
    600 ?        00:00:00 wpa_supplicant
geduer@ulan:~/ndb$

它也支持以命令行方式运行,特别是可以跟上调试选项-d,这样它就会输出很详细的调试信息。

root@ulan:/etc/wpa_supplicant# wpa_supplicant -i wlan0 -c /etc/wpa_supplicant/gd.conf -d
wpa_supplicant v2.9
random: getrandom() support available
Successfully initialized wpa_supplicant
Initializing interface 'wlan0' conf '/etc/wpa_supplicant/gd.conf' driver 'default' ctrl_interface 'N/A' bridge 'N/A'
Configuration file '/etc/wpa_supplicant/gd.conf' -> '/etc/wpa_supplicant/gd.conf'
Reading configuration file '/etc/wpa_supplicant/gd.conf'
ctrl_interface='/run/wpa_supplicant'
ctrl_interface_group='wheel'
update_config=1
ap_scan=1
country='US'
Priority group 0
   id=0 ssid='MYSSID'
nl80211: Using driver-based roaming
nl80211: TDLS supported
nl80211: Supported cipher 00-0f-ac:1
nl80211: Supported cipher 00-0f-ac:5
nl80211: Supported cipher 00-0f-ac:2
nl80211: Supported cipher 00-0f-ac:4
nl80211: Supported cipher 00-0f-ac:6

nm出场

我一边寻找wpa_supplicant的问题, 一边写晚上的讲义,一边在Debian官网等网站上查找资料,寻找其它线索。

不知不觉中,我的注意力慢慢集中到另一个角色:NetworkManager,简称nm。

nm是freedesktop旗下的开源项目,主要功能就是管理和设置网络,被很多种Linux发行版广泛使用。

nm有很多组件,简单来说,常用的有三个:

- nm后台服务,一般就叫NetworkManager

- nm配置界面,一般称为nm-gui

- nm命令行,名叫nmcli

对于调试来说,nmcli功能强大,使用方便,是我的首选。

我先用nmcli观察幽兰太乙版本的网络设备,里面显示wlan0设备连接到格蠹的AP-gsl(GEDU Shanghai Lab)。

geduer@ulan:~/wpa-2.9$ nmcli d
DEVICE         TYPE      STATE      CONNECTION
wlan0          wifi      connected  gsl
docker0        bridge    unmanaged  --
lo             loopback  unmanaged  --
p2p-dev-wlan0  wifi-p2p  unmanaged  --

在有问题的Debian 12上执行相同命令,得到的信息是:

geduer@ulan:~/rkwifi/bcmdhd$ sudo nmcli d
DEVICE         TYPE      STATE   CONNECTION
lo             loopback  已断开  --
wlan0          wifi      已断开  --
p2p-dev-wlan0  wifi-p2p  已断开  --

我继续用nmcli命令行来连接WIFI热点:

sudo gdb nmcli dev wifi hotspot ifname wlan0 ssid gsl password "xxx"

得到一条更精确的错误信息:

Error: Connection activation failed: 设备配置未就绪.

这条信息在图形界面的激活失败基础上,说出了进一步的原因:设备配置未就绪。可谓“发前人所未发”

配置未就绪?什么配置呢?

难道是NetworkManger的配置吗?顺着这条线索我找到了/etc下的nm配置文件。

f421543e181a7468ada13c82b4c91b8a.png

上面是Debian系统上的,再打开没有故障的太乙镜像,居然不一样:

geduer@ulan:/etc/NetworkManager$ cat NetworkManager.conf
[main]
plugins=ifupdown,keyfile


[ifupdown]
managed=false


[device]
wifi.scan-rand-mac-address=no

睁大眼睛对比两个配置文件,没问题的太乙版本多两行。

把多的两行复制到Debian版本,然后重启nm服务:

sudo systemctl restart NetworkManager

重启服务后,Debian界面上的wifi图标浮现出三个圆点,忽明忽暗,表示在工作,与之前过一会就出现错误信息不同,这一次,圆点隐去后,完整的WIFI图标被加亮了!

4ec7cf60a7c1217a6aebb4daa08028e2.png

搁置几周的Debian的WIFI问题解决了,我第一时间把这个好消息发到了兰舍群里,兰友们纷纷点赞。

f486bcdfcf36d42c1d3400d960549215.png

回味无穷

这个问题解决后,我继续下载了nm的源代码,搜索到了设备配置未就绪对应的英文错误信息。

#: src/libnmc-base/nm-client-utils.c:358
msgid "The device could not be readied for configuration"
msgstr "设备配置未就绪"

这个错误信息对应的常量是:

NM_DEVICE_STATE_REASON_CONFIG_FAILED

这个常量的值为4。

typedef enum {
    NM_DEVICE_STATE_REASON_NONE                           = 0,
    NM_DEVICE_STATE_REASON_UNKNOWN                        = 1,
    NM_DEVICE_STATE_REASON_NOW_MANAGED                    = 2,
    NM_DEVICE_STATE_REASON_NOW_UNMANAGED                  = 3,
    NM_DEVICE_STATE_REASON_CONFIG_FAILED                  = 4,
    NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE          = 5,
    NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED              = 6,
    NM_DEVICE_STATE_REASON_NO_SECRETS                     = 7,
    NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT          = 8,
    NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED       = 9,
    NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED              = 10,
    NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT             = 11,
    NM_DEVICE_STATE_REASON_PPP_START_FAILED               = 12,
    NM_DEVICE_STATE_REASON_PPP_DISCONNECT                 = 13,
    NM_DEVICE_STATE_REASON_PPP_FAILED                     = 14,
    NM_DEVICE_STATE_REASON_DHCP_START_FAILED              = 15,
    NM_DEVICE_STATE_REASON_DHCP_ERROR                     = 16,

顺着英文错误信息,我查看了有关的代码。

5c850615657927cf4b69397957d19f6a.png

我还上了gdb,辅助阅读代码。因为涉及到很多模块,下载符号文件花了很多时间。

(gdb) bt
#0  0x000000557c57b3e4 in nm_device_state_changed ()
#1  0x000000557c443efc in ?? ()
#2  0x000000557c44ad40 in ?? ()
#3  0x0000007f8d31a914 in g_cclosure_marshal_VOID__OBJECTv (
    closure=0x558db3df60, return_value=<optimized out>, instance=0x7f7c002180,
    args=<error reading variable: Cannot access memory at address 0x0>,
    marshal_data=<optimized out>, n_params=<optimized out>,
    param_types=<optimized out>) at ../../../gobject/gmarshal.c:1910
#4  0x0000007f8d317430 in _g_closure_invoke_va (
    closure=closure@entry=0x558db3df60, return_value=return_value@entry=0x0,
    instance=0x7f7c002180, instance@entry=0x7fd97b1000, args=..., n_params=1,
    param_types=0x558db33b10) at ../../../gobject/gclosure.c:895
#5  0x0000007f8d3318ac in g_signal_emit_valist (instance=0x7fd97b1000,
    instance@entry=0x7f7c002180, signal_id=2368937272, signal_id@entry=43,
    detail=detail@entry=0, var_args=...) at ../../../gobject/gsignal.c:3456
#6  0x0000007f8d331cec in g_signal_emit_by_name (instance=0x7f7c002180,
    detailed_signal=0x7f89c07678 "device-added")
    at ../../../gobject/gsignal.c:3648
#7  0x0000007f8d3171f8 in g_closure_invoke (closure=0x558dbd4520,
    return_value=return_value@entry=0x0, n_param_values=2,
    param_values=param_values@entry=0x7fd97b1310,
invocation_hint=invocation_hint@entry=0x7fd97b12d8)

nm的核心代码是用c语言编写的,代码质量看起来很不错。

/* Note: the lifetime of the activation connection is always bound to the profiles visibility
         * via NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY.
         *
         * This only makes a difference, if the profile actually has "connection.permissions"
         * set to limit visibility (which is not the case for externally managed, generated profiles).
         *
         * If we assume a previously active connection whose lifetime was unbound, we now bind it
         * after restart. That is not correct, and can mean that the profile becomes subject to
         * deactivation after restart (if the user logs out).
         *
         * This should be improved, but it's unclear how. */
        active = _new_active_connection(
            self,
            FALSE,
            sett_conn,
            NULL,
            NULL,
            NULL,
            device,
            subject,
            activation_type_assume ? NM_ACTIVATION_TYPE_ASSUME : NM_ACTIVATION_TYPE_EXTERNAL,
            activation_type_assume ? NM_ACTIVATION_REASON_ASSUME : NM_ACTIVATION_REASON_EXTERNAL,
            NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY,
            &error);


        if (!active) {
            _LOGW(LOGD_DEVICE,
                  "assume: assumed connection %s failed to activate: %s",
                  nm_dbus_object_get_path(NM_DBUS_OBJECT(sett_conn)),
                  error->message);
            g_error_free(error);


            if (was_unmanaged) {
                nm_device_state_changed(device,
                                        NM_DEVICE_STATE_UNAVAILABLE,
                                        NM_DEVICE_STATE_REASON_CONFIG_FAILED);
            }

nm的代码量很大,如同一头大象。借着这次WIFI问题的机会浏览一番,只是蜻蜓点水。这再次印证了我常说的,今天的关键复杂度在“量”。

值得一提的是,搜索导致问题的配置项:

wifi.scan-rand-mac-address=no

在Debian的官方wiki里就有这样的建议:

Troubleshooting & Tips for NetworkManager
WiFi can scan, but not connect using NetworkManager (Debian 9 Stretch)
If you find that your wireless network device can scan, but will not complete connecting, try turning off MAC address randomization.


Write inside /etc/NetworkManager/NetworkManager.conf:




[device]
wifi.scan-rand-mac-address=no
After doing this, restart NetworkManager with service NetworkManager restart

看来这是Debian官方知道的问题,既然如此,为什么不像Ubuntu那样默认包含这个设置呢?系统软件研发挑战很多,我主讲的《RK3588系统软件开发训练营》正在招生,感兴趣的同行欢迎当面切磋。

71caba33f23b502d216ac24c4b2c0133.png

写文章很辛苦,恳请各位读者点击“在看”,也欢迎转发)

*************************************************

正心诚意,格物致知,以人文情怀审视软件,以软件技术改变人生

扫描下方二维码或者在微信中搜索“盛格塾”小程序,可以阅读更多文章和有声读物

9643218fa79f7c5aaace2a4eba88da47.png

也欢迎关注格友公众号

577644a45c39808e4f7c68be52fe4d55.jpeg

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值