STA 连接请求到 wpa_supplicant

从 connectToNetwork 到拿到 IP:OpenHarmony STA 连接 wpa_supplicant 的完整链路

一次 STA 连接请求从应用层走到 wpa_supplicant,再经事件回调回到框架层、最后通过 DHCP 拿到 IP 的完整调用链。

一、这条链路到底有多长

一次用户主动连接,下行调用链大致如下:

应用 / JS API
  -> WifiDeviceImpl
  -> WifiDeviceProxy(IPC 客户端)
  -> WifiDeviceStub / WifiDeviceServiceImpl(SA 侧)
  -> IStaService / StaService
  -> StaStateMachine(消息驱动)
  -> WifiStaHalInterface
  -> WifiHdiWpaClient
  -> HDI IWpaInterface(wpa_interface_service)
  -> wpa_supplicant
  -> Wi-Fi 驱动 / 固件

连接过程中还有一条“上行事件链”,负责把 supplicant 的结果传回框架:

wpa_supplicant 事件
  -> HDI IWpaCallback
  -> wifi_hdi_wpa_callback
  -> WifiEventCallback
  -> StaMonitor
  -> StaStateMachine(InternalMessage)

连接成功且 802.11 关联完成后,还会再开一条“拿 IP”的支线:

StaStateMachine::GetIpState
  -> RegisterDhcpClientCallBack / RegisterDhcpClientReportCallBack
  -> StartDhcpClient(RouterConfig)
  -> DHCP Client SA
  -> DhcpResultNotify 回调
  -> WIFI_SVR_CMD_STA_DHCP_RESULT_NOTIFY_EVENT
  -> StaStateMachine -> LinkedState / NetSupplierInfo 更新

二、一次连接请求的下行链路

2.1 应用层与 Native Framework

connectToNetwork(networkId) 为例,应用层最终会走到 WifiDeviceImpl::ConnectToNetwork,它并不直接操作状态机,而是拿到 IPC 代理后转发:

ErrCode WifiDeviceImpl::ConnectToNetwork(int networkId, bool isCandidate)
{
    std::lock_guard<std::mutex> lock(mutex_);
    RETURN_IF_FAIL(GetWifiDeviceProxy());
    return client_->ConnectToNetwork(networkId, isCandidate);
}

WifiDeviceProxy::ConnectToNetwork 通过 Remote()->SendRequest() 发送 DevInterfaceCode::WIFI_SVR_CMD_CONNECT_TOConnectToDevice 对应 WIFI_SVR_CMD_CONNECT2_TO。进程边界在这里体现得最明显:应用进程和 Wi-Fi SA 不在同一个进程里。

SA 侧由 WifiDeviceStub::OnConnectTo / OnConnect2To 接收,再转到 WifiDeviceServiceImplWifiDeviceServiceImpl::ConnectToNetwork 会做这些事:

  1. 检查多 VAP 冲突,比如 P2P/热点是否占用了 Wi-Fi。
  2. 校验权限,例如 VerifyWifiConnectionPermission、企业 Wi-Fi 连接权限、候选网络配置权限。
  3. WifiServiceManager 拿到 IStaService
  4. 调用 pService->ConnectToNetwork(networkId)pService->ConnectToDevice(config)

从这里开始,请求进入 STA 服务模块。

2.2 StaService:把“连接”变成状态机消息

StaService 是 STA 服务的管理者,但它不直接去连网。它的核心动作是“把请求变成状态机消息”。

ConnectToDevice 的路径:

ErrCode StaService::ConnectToDevice(const WifiDeviceConfig &config) const
{
    ...
    int netWorkId = AddDeviceConfig(config);
    ...
    pStaStateMachine->SendMessage(
        WIFI_SVR_CMD_STA_CONNECT_NETWORK, netWorkId, NETWORK_SELECTED_BY_USER);
    return WIFI_OPT_SUCCESS;
}

AddDeviceConfig 会把配置加密、保存到 WifiSettings,并分配或复用 networkId。所以 ConnectToDevice 本质是“先保存配置,再按 networkId 发起连接”。

ConnectToNetwork 的路径:

ErrCode StaService::ConnectToNetwork(int networkId, int type) const
{
    ...
    WifiDeviceConfig config;
    WifiSettings::GetInstance().GetDeviceConfig(networkId, config, m_instId);
    ...
    pStaStateMachine->SendMessage(
        WIFI_SVR_CMD_STA_CONNECT_SAVED_NETWORK, networkId, type);
    return WIFI_OPT_SUCCESS;
}

两条路径最终都会进入 StaStateMachine::InitState::StartConnectEvent,由它调用真正执行连接的 StartConnectToNetwork

2.3 StaStateMachine:准备 supplicant 里的网络

StartConnectToNetwork 是下行链路里最值得展开的一段,主要步骤:

1. 随机 MAC 处理(ConfigRandMacSelfCure / SetRandomMac)
2. 从 WifiSettings 取出 WifiDeviceConfig
3. ClearDeviceConfig:清掉 wpa_supplicant 里旧的网络
4. GetNextNetworkId:等价于 HDI AddNetwork,拿到新的 supplicant networkId
5. ConvertDeviceCfg:把 WifiDeviceConfig 转成 WifiHalDeviceConfig
6. WifiStaHalInterface::SetDeviceConfig(0, ...) 写入 supplicant 网络参数
7. WifiStaHalInterface::Connect(0, ifaceName) 发起连接

代码里有一个容易看晕的细节:这里传的是 WPA_DEFAULT_NETWORKID,它在 6.1 Release 源码里就是 0

#define WPA_DEFAULT_NETWORKID 0

因为前面刚执行过 ClearDeviceConfig,新 AddNetwork 出来的网络通常就是 0 号网络,所以 SetDeviceConfig(0, ...)Connect(0, ...) 指向的就是刚创建的这个网络。

ConvertDeviceCfg 还会做几件容易被忽略的事:

  • 把框架层的 keyMgmt 转换成 supplicant 认识的 key_mgmt 字符串。
  • WPA2/WPA3 混合网络时通过 GetSuitableKeyMgmtForWpaMixed 选择合适的 key_mgmt
  • SAE 网络强制 PMF,并收紧协议与加密算法组合。
  • WEP、EAP、WAPI 的参数字段补齐。

2.4 WifiStaHalInterface 与 WifiHdiWpaClient

WifiStaHalInterface 是框架层看到的“HAL 门面”,实际实现并不直接打开 wpa_supplicant 的 control socket,而是通过 WifiHdiWpaClient 走 HDI:

WifiErrorNo WifiStaHalInterface::Connect(int networkId, const std::string &ifaceName)
{
    ...
    return mHdiWpaClient->ReqConnect(networkId, ifaceName.c_str());
}

WifiHdiWpaClient 再调用 HDI C 接口:

WifiErrorNo WifiHdiWpaClient::ReqConnect(int networkId, const char *ifaceName)
{
    return HdiWpaStaConnect(networkId, ifaceName);
}

HdiWpaStaConnect 最终调用的不是“connect”,而是 supplicant 语义里的 SELECT_NETWORK

int32_t result = wpaObj->SelectNetwork(wpaObj, ifaceName, networkId);

IWpaInterface 是 HDI v2_0wpa_interface_service 暴露的接口。HdiWpaStart 会通过 HDIDeviceManagerGet + LoadDevice("wpa_interface_service") 加载服务,再用 IWpaInterfaceGetInstance 拿到 IWpaInterface 实例。

网络参数写入链路也是类似的:

StaStateMachine::ConvertDeviceCfg
  -> WifiStaHalInterface::SetDeviceConfig
  -> WifiHdiWpaClient::SetDeviceConfig
  -> 组装 SetNetworkConfig[]
  -> HdiWpaStaSetNetwork
  -> IWpaInterface::SetNetwork(ifaceName, networkId, fieldName, value)

wifi_hdi_wpa_sta_impl.c 里维护了一张字段映射表,把框架层枚举映射成 wpa_supplicant 配置项:

ssid          -> ssid
psk           -> psk / sae_password
keyMgmt       -> key_mgmt
eap           -> eap
identity      -> identity
password      -> password
phase2        -> phase2
bssid         -> bssid
wep_key0..3   -> wep_key0..wep_key3
priority      -> priority
scan_ssid     -> scan_ssid
ieee80211w    -> ieee80211w
proto/pairwise/group -> proto / pairwise / group

这里也能解释为什么框架层连接前要先做“参数翻译”:supplicant 只认自己的 network 配置项,框架层的 WifiDeviceConfig 不能直接塞给 supplicant。

三、上行事件:wpa_supplicant 如何告诉框架“我连上了”

连接是异步的,SelectNetwork 返回成功只代表 supplicant 接受了请求,不代表已经关联成功。真正的结果通过事件回调返回。

3.1 StaMonitor 注册回调

StaMonitor::InitStaMonitor 会构造一个 WifiEventCallback,通过 WifiStaHalInterface::RegisterStaEventCallback 注册到 HAL 层:

WifiEventCallback callBack = {
    [this](int status, int code, const std::string &bssid, int locallyGenerated) {
        this->OnConnectChangedCallBack(status, code, bssid, locallyGenerated);
    },
    ...
};
WifiStaHalInterface::GetInstance().RegisterStaEventCallback(callBack, ifaceName);

3.2 HDI 回调到 WifiEventCallback

wifi_hdi_wpa_callback.cpp 实现 HDI 侧的 IWpaCallback,例如:

  • OnEventConnected -> 解析 HdiWpaConnectParam 中的 bssid,转成字符串后回调 cbk.onConnectChanged(HAL_WPA_CB_CONNECTED, ...)
  • OnEventDisconnected -> 解析断开原因和 bssid,回调 cbk.onConnectChanged(HAL_WPA_CB_DISCONNECTED, ...)
  • 还有 OnEventBssidChangedOnEventStateChangedOnEventAssociateRejectOnEventAuthTimeout 等事件。

3.3 StaMonitor 把事件变成状态机消息

StaMonitor::OnConnectChangedCallBack 不直接改状态,而是把事件转成 InternalMessage

case HAL_WPA_CB_CONNECTED: {
    InternalMessagePtr msg = pStaStateMachine->CreateMessage(
        WIFI_SVR_CMD_STA_NETWORK_CONNECTION_EVENT);
    msg->SetParam1(code);
    msg->AddStringMessageBody(bssid);
    pStaStateMachine->SendMessage(msg);
    break;
}

断连事件则变成 WIFI_SVR_CMD_STA_NETWORK_DISCONNECTION_EVENT

StaStateMachine 收到 WIFI_SVR_CMD_STA_NETWORK_CONNECTION_EVENT 后,会更新 linkedInfo、清理连接超时定时器,并切换到 GetIpState,进入“拿 IP”阶段。

四、最后一公里:DHCP 客户端拿 IP

802.11 关联成功不代表可以上网,OpenHarmony 还会走 DHCP 获取 IPv4/IPv6 地址。

4.1 进入 GetIpState

StaStateMachine::GetIpState::GoInState 做的事:

  1. 通过 WifiNetAgent::OnStaMachineUpdateNetSupplierInfo 更新网络供应商信息。
  2. 上报 CONNECT_OBTAINING_IP 连接状态。
  3. 读取 WifiDeviceConfigassignMethod,判断是 DHCP 还是静态 IP。
  4. 调用 HandlePreDhcpSetup,例如关闭 supplicant 的 power save / suspend。
  5. 注册 DHCP 回调,然后 StartDhcpClient(config)

4.2 DHCP 回调注册与启动

DHCP 客户端 API 在 foundation/communication/dhcp/interfaces/kits/c/dhcp_c_api.h

RegisterDhcpClientCallBack(ifname.c_str(), &dhcpclientCallBack_);
RegisterDhcpClientReportCallBack(ifname.c_str(), &dhcpClientReport_);
StartDhcpClient(config);

回调绑定:

dhcpclientCallBack_.OnIpSuccessChanged = DhcpResultNotify::OnSuccess;
dhcpclientCallBack_.OnIpFailChanged = DhcpResultNotify::OnFailed;

RouterConfig 里会带上接口名、bssid、是否需要 IPv4/IPv6、是否禁止使用缓存 IP 等信息。真正跑 DHCP 状态机的是独立的 DHCP Client SA,不在 Wi-Fi 服务进程里。

4.3 DHCP 结果回到状态机

拿到 IP 后,DhcpResultNotify::OnSuccess -> OnSuccessDhcpResult

  1. 保存 IPv4/IPv6 结果到状态机内部的 DhcpIpv4Result / DhcpIpv6Result
  2. 调用 DhcpResultNotifyEvent(DhcpReturnCode::DHCP_RESULT, ipType)
  3. 发送 WIFI_SVR_CMD_STA_DHCP_RESULT_NOTIFY_EVENT 给状态机。

GetIpState::DealDhcpResultNotify 再根据结果处理:

  • DHCP_RESULT -> DealDhcpResult(ipType)
  • DHCP_JUMP -> 跳到 LinkedState,连接完成。
  • DHCP_FAIL -> 按 DHCP 失败处理,可能触发断开和失败原因上报。
  • DHCP_OFFER_REPORT -> 处理 OFFER 上报。

另外还启动了 30 秒的 CMD_START_GET_DHCP_IP_TIMEOUT 定时器,超时走 DealGetDhcpIpv4Timeout,最终会断开并上报 DISCONNECT_BY_DHCP_FAIL

拿到 IP 后调用 HandlePostDhcpSetup 恢复 power save / suspend 设置,再通过 WifiNetAgentNetSupplierInfo 更新给网络管理,上层网络栈才能认为 Wi-Fi 这条链路真正可用。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐