Wifi笔记 | Wifi热点的开启

代码基于Android P

Android使用wpa_supplicant作为daemon来控制WIFI,它是一个安全中间件,代码位于external/wpa_supplicant,为各种无线网卡提供统一的安全机制。当然在这里只是介绍一下wpa_supplicant和 hostapd,研究分析的部分主要还是应用层和java框架层,有时也会涉及Native层。

wpa_supplicant_8主要有三个子目录 :

  • hostapd:当手机进入Soft AP模式时,手机将扮演AP的角色,需要hostapd来提供AP的功能,也就是wifi热点的实现。
  • wpa_supplicant:Station模式,也叫Managed模式,这是平时最常见的使用wifi连接AP的情况。
  • src:hostapd和wpa_supplicant中都包含一些通用的数据结构和处理方法,这些内容都放在此src目录中。
Hostapd简介

hostapd能够使得无线网卡切换为master模式,模拟AP(通常可以认为是路由器)功能,也就是我们说的软AP(Soft AP)。
Hostapd的功能就是作为AP的认证服务器,负责控制管理stations(通常可以认为带无线网卡的PC)的接入和认证。
通过Hostapd可以将无线网卡切换为AP/Master模式,通过修改配置文件,可以建立一个开放式的(不加密)的,WEP,WPA或WPA2的无线网络。并且通过修改配置文件可以设置无线网卡的各种参数,包括频率,信号,beacon包时间间隔,是否发送beacon包,如果响应探针请求等等。

  • 当用以wifi的时候,作为station,工作在managed模式,有后台进程wpa_supplicant对认证管理。

  • 工作在sofap的时候,作为AP, 工作在master模式,由后台进程hostapd做认证处理。

  • 作为station,用户通过UI启动WIFI,由framework层,JNI,hardware层加载WIFI的驱动,加载firmware。驱动加载成功后,启动wpa_supplicant,设置mad地址,设置为managed模式,通过ioctl命令启动scan,启动DHCPD获取IP地址,链接到对应的AP,建立连接后同时sock通信。

  • 作为softap模式的时候,和station模式的流程差不多,只不过不是启动wpa_supplicant,启动hostapd的后台管理进程。

由framework层,JNI,hardware层加载softap的驱动,加载firmware。驱动加载成功后,启动hostap,设置mad地址,设置为master模式,设置BSSID,启动DHCPD获取IP地址。等待station来连接。

流程图

Android P之后,Wifi模块增加了packages/apps/Settings/src/com/android/settings/wifi/tether/路径,相当于把Wifi热点独立放到了tether文件夹下面,并添加了WifiTetherSettings.java类,对应着Wifi热点的界面,而Android P之前是没有的,Wifi热点界面之前是对应在TetherSettings的一部分,有些厂商也还是会改到TetherSettings上。

1.1 创建WifiTetherSwitchBarController

1
2
3
4
5
6
7
8
9
10
11
12
13
//packages/apps/Settings/src/com/android/settings/wifi/tether/WifiTetherSettings.java
@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    // Assume we are in a SettingsActivity. This is only safe because we currently use
    // SettingsActivity as base for all preference fragments.
    final SettingsActivity activity = (SettingsActivity) getActivity();
    final SwitchBar switchBar = activity.getSwitchBar();
    mSwitchBarController = new WifiTetherSwitchBarController(activity,
            new SwitchBarController(switchBar));
    getLifecycle().addObserver(mSwitchBarController);
    switchBar.show();
}

1.2 初始化mSwitchBarController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//packages/apps/Settings/src/com/android/settings/wifi/tether/WifiTetherSwitchBarController.java
public class WifiTetherSwitchBarController implements SwitchWidgetController.OnSwitchChangeListener,
        LifecycleObserver, OnStart, OnStop, DataSaverBackend.Listener {
  //...
}
//packages/apps/Settings/src/com/android/settings/widget/SwitchWidgetController.java
public interface OnSwitchChangeListener {
    /**
    * Called when the checked state of the Switch has changed.
    *
    * @param isChecked The new checked state of switchView.
    *
    * @return true to update the state of the switch with the new value.
    */
    boolean onSwitchToggled(boolean isChecked);
}
//packages/apps/Settings/src/com/android/settings/wifi/tether/WifiTetherSwitchBarController.java

public boolean onSwitchToggled(boolean isChecked) {
    if (!isChecked) {
        stopTether();
    } else if (!mWifiManager.isWifiApEnabled()) {
        startTether();
    }
    return true;
}
void startTether() {
    mSwitchBar.setEnabled(false);
    mConnectivityManager.startTethering(TETHERING_WIFI, true /* showProvisioningUi */,
            mOnStartTetheringCallback, new Handler(Looper.getMainLooper()));
}
1.3 启动 mConnectivityManager.startTethering

注意: android O开始通过mConnectivityManager.startTethering来启动热点了,之前都是通过WifiManager的setWifiApEnable的方法,该方法现在也已废弃。

1
2
3
4
5
6
7
8
9
10
11
12
13
//frameworks/base/core/java/android/net/ConnectivityManager.java
/**
* Convenient overload for
* {@link #startTethering(int, boolean, OnStartTetheringCallback, Handler)} which passes a null
* handler to run on the current thread's {@link Looper}.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
public void startTethering(int type, boolean showProvisioningUi,
        final OnStartTetheringCallback callback) {
    startTethering(type, showProvisioningUi, callback, null);
}

1.4 startTethering
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* Runs tether provisioning for the given type if needed and then starts tethering if
* the check succeeds. If no carrier provisioning is required for tethering, tethering is
* enabled immediately. If provisioning fails, tethering will not be enabled. It also
* schedules tether provisioning re-checks if appropriate.
*
* @param type The type of tethering to start. Must be one of
*         {@link ConnectivityManager.TETHERING_WIFI},
*         {@link ConnectivityManager.TETHERING_USB}, or
*         {@link ConnectivityManager.TETHERING_BLUETOOTH}.
* @param showProvisioningUi a boolean indicating to show the provisioning app UI if there
*         is one. This should be true the first time this function is called and also any time
*         the user can see this UI. It gives users information from their carrier about the
*         check failing and how they can sign up for tethering if possible.
* @param callback an {@link OnStartTetheringCallback} which will be called to notify the caller
*         of the result of trying to tether.
* @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
public void startTethering(int type, boolean showProvisioningUi,
        final OnStartTetheringCallback callback, Handler handler) {
    Preconditions.checkNotNull(callback, "OnStartTetheringCallback cannot be null.");
 
    ResultReceiver wrappedCallback = new ResultReceiver(handler) {
        @Override
        protected void onReceiveResult(int resultCode, Bundle resultData) {
            if (resultCode == TETHER_ERROR_NO_ERROR) {
                callback.onTetheringStarted();
            } else {
                callback.onTetheringFailed();
            }
        }
    };
 
    try {
        String pkgName = mContext.getOpPackageName();
        Log.i(TAG, "startTethering caller:" + pkgName);
        mService.startTethering(type, wrappedCallback, showProvisioningUi, pkgName);
    } catch (RemoteException e) {
        Log.e(TAG, "Exception trying to start tethering.", e);
        wrappedCallback.send(TETHER_ERROR_SERVICE_UNAVAIL, null);
    }
}
1.5 mService.startTethering
1
2
3
4
5
6
7
8
9
10
11
12
//frameworks/base/services/core/java/com/android/server/ConnectivityService.java

@Override
public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi,
        String callerPkg) {
    ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
    if (!isTetheringSupported()) {
        receiver.send(ConnectivityManager.TETHER_ERROR_UNSUPPORTED, null);
        return;
    }
    mTethering.startTethering(type, receiver, showProvisioningUi);
}
1.6 mTethering.startTethering
1
2
3
4
5
6
7
8
9
10
11
12
13
//frameworks/base/services/core/java/com/android/server/connectivity/Tethering.java
public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) {
    if (!isTetherProvisioningRequired()) {
        enableTetheringInternal(type, true, receiver);
        return;
    }
 
    if (showProvisioningUi) {
        runUiTetherProvisioningAndEnable(type, receiver);
    } else {
        runSilentTetherProvisioningAndEnable(type, receiver);
    }
}
1.7 Tethering.enableTetheringInternal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* Enables or disables tethering for the given type. This should only be called once
* provisioning has succeeded or is not necessary. It will also schedule provisioning rechecks
* for the specified interface.
*/
private void enableTetheringInternal(int type, boolean enable, ResultReceiver receiver) {
    boolean isProvisioningRequired = enable && isTetherProvisioningRequired();
    int result;
    switch (type) {
        case TETHERING_WIFI:
            result = setWifiTethering(enable);
            if (isProvisioningRequired && result == TETHER_ERROR_NO_ERROR) {
                scheduleProvisioningRechecks(type);
            }
            sendTetherResult(receiver, result);
            break;
        case TETHERING_USB:
            result = setUsbTethering(enable);
            if (isProvisioningRequired && result == TETHER_ERROR_NO_ERROR) {
                scheduleProvisioningRechecks(type);
            }
            sendTetherResult(receiver, result);
            break;
        case TETHERING_BLUETOOTH:
            setBluetoothTethering(enable, receiver);
            break;
        default:
            Log.w(TAG, "Invalid tether type.");
            sendTetherResult(receiver, TETHER_ERROR_UNKNOWN_IFACE);
    }
}

1.8 Tethering.setWifiTethering
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private int setWifiTethering(final boolean enable) {
    int rval = TETHER_ERROR_MASTER_ERROR;
    final long ident = Binder.clearCallingIdentity();
    try {
        synchronized (mPublicSync) {
            mWifiTetherRequested = enable;
            final WifiManager mgr = getWifiManager();
            if ((enable && mgr.startSoftAp(null /* use existing wifi config */)) ||
                (!enable && mgr.stopSoftAp())) {
                rval = TETHER_ERROR_NO_ERROR;
            }
        }
    } finally {
        Binder.restoreCallingIdentity(ident);
    }
    return rval;
}
1.9 mgr.startSoftAp
1
2
3
4
5
6
7
8
//frameworks/base/wifi/java/android/net/wifi/WifiManager.java
public boolean startSoftAp(@Nullable WifiConfiguration wifiConfig) {
    try {
        return mService.startSoftAp(wifiConfig);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

startSoftAp --> startSoftApInternal --> mWifiController.sendMessage --> makeSoftApManager-->
frameworks/opt/net/wifi/service/java/com/android/server/wifi/SoftApManager.java
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiInjector.java

1.10 SoftApStateMachine是SoftApManager的内部类。

函数中调用Native的 stopSoftAp 将通过Native调用Wifi的相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Override
public boolean processMessage(Message message) {
    switch (message.what) {
        case CMD_START:
            mApInterfaceName = mWifiNative.setupInterfaceForSoftApMode(
                    mWifiNativeInterfaceCallback);
            if (TextUtils.isEmpty(mApInterfaceName)) {
                Log.e(TAG, "setup failure when creating ap interface.");
                updateApState(WifiManager.WIFI_AP_STATE_FAILED,
                        WifiManager.WIFI_AP_STATE_DISABLED,
                        WifiManager.SAP_START_FAILURE_GENERAL);
                mWifiMetrics.incrementSoftApStartResult(
                        false, WifiManager.SAP_START_FAILURE_GENERAL);
                break;
            }
            updateApState(WifiManager.WIFI_AP_STATE_ENABLING,
            WifiManager.WIFI_AP_STATE_DISABLED, 0);
            int result = startSoftAp((WifiConfiguration) message.obj);
            if (result != SUCCESS) {
                int failureReason = WifiManager.SAP_START_FAILURE_GENERAL;
                if (result == ERROR_NO_CHANNEL) {
                    failureReason = WifiManager.SAP_START_FAILURE_NO_CHANNEL;
                }
                updateApState(WifiManager.WIFI_AP_STATE_FAILED,
                        WifiManager.WIFI_AP_STATE_ENABLING,
                        failureReason);
                stopSoftAp();
                mWifiMetrics.incrementSoftApStartResult(false, failureReason);
                break;
            }
            transitionTo(mStartedState);
            break;
            default:
                // Ignore all other commands.
                break;
            }
 
            return HANDLED;
    }
}
1.11 mWifiNative.startSoftAp

mWifiNative.startSoftAp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* Start a soft AP instance with the given configuration.
* @param config AP configuration
* @return integer result code
*/
private int startSoftAp(WifiConfiguration config) {
    if (config == null || config.SSID == null) {
        Log.e(TAG, "Unable to start soft AP without valid configuration");
        return ERROR_GENERIC;
    }
 
    // Make a copy of configuration for updating AP band and channel.
    WifiConfiguration localConfig = new WifiConfiguration(config);
 
    int result = ApConfigUtil.updateApChannelConfig(
            mWifiNative, mCountryCode,
            mWifiApConfigStore.getAllowed2GChannel(), localConfig);
 
    if (result != SUCCESS) {
        Log.e(TAG, "Failed to update AP band and channel");
        return result;
    }
 
    // Setup country code if it is provided.
    if (mCountryCode != null) {
        // Country code is mandatory for 5GHz band, return an error if failed to set
        // country code when AP is configured for 5GHz band.
        if (!mWifiNative.setCountryCodeHal(
                mApInterfaceName, mCountryCode.toUpperCase(Locale.ROOT))
                && config.apBand == WifiConfiguration.AP_BAND_5GHZ) {
            Log.e(TAG, "Failed to set country code, required for setting up "
                    + "soft ap in 5GHz");
            return ERROR_GENERIC;
        }
    }
    if (localConfig.hiddenSSID) {
        Log.d(TAG, "SoftAP is a hidden network");
    }
    if (!mWifiNative.startSoftAp(mApInterfaceName, localConfig, mSoftApListener)) {
        Log.e(TAG, "Soft AP start failed");
        return ERROR_GENERIC;
    }
    Log.d(TAG, "Soft AP is started");
 
    return SUCCESS;
}
1.12 mWifiNative.startSoftAp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiNative.java
/**
* Start Soft AP operation using the provided configuration.
*
* @param ifaceName Name of the interface.
* @param config Configuration to use for the soft ap created.
* @param listener Callback for AP events.
* @return true on success, false otherwise.
*/
public boolean startSoftAp(
        @NonNull String ifaceName, WifiConfiguration config, SoftApListener listener) {
    if (!mWificondControl.startHostapd(ifaceName, listener)) {
        Log.e(TAG, "Failed to start hostapd");
        mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHostapd();
        return false;
    }
    if (!waitForHostapdConnection()) {
        Log.e(TAG, "Failed to establish connection to hostapd");
        mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHostapd();
        return false;
    }
    if (!mHostapdHal.registerDeathHandler(new HostapdDeathHandlerInternal())) {
        Log.e(TAG, "Failed to register hostapd death handler");
        mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHostapd();
        return false;
    }
    if (!mHostapdHal.addAccessPoint(ifaceName, config)) {
        Log.e(TAG, "Failed to add acccess point");
        mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHostapd();
        return false;
    }
    return true;
}

以上函数处理了

  • mWificondControl.startHostapd
  • waitForHostapdConnection
  • mHostapdHal.registerDeathHandler
  • mHostapdHal.addAccessPoint

就以mWificondControl.startHostapd为主线进行分析。

1.13 mWificondControl.startHostapd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//frameworks/opt/net/wifi/service/java/com/android/server/wifi/WificondControl.java
public boolean startHostapd(@NonNull String ifaceName,
                              SoftApListener listener) {
    IApInterface iface = getApInterface(ifaceName);
    if (iface == null) {
        Log.e(TAG, "No valid ap interface handler");
        return false;
    }
    try {
        IApInterfaceEventCallback  callback = new ApInterfaceEventCallback(listener);
        mApInterfaceListeners.put(ifaceName, callback);
        boolean success = iface.startHostapd(callback);
        if (!success) {
            Log.e(TAG, "Failed to start hostapd.");
            return false;
        }
    } catch (RemoteException e) {
        Log.e(TAG, "Exception in starting soft AP: " + e);
        return false;
    }
    return true;
}
1.14 iface.startHostapd
1
2
3
4
5
6
7
//system/connectivity/wificond/aidl/android/net/wifi/IApInterface.aidl
boolean startHostapd(IApInterfaceEventCallback callback);

//system/connectivity/wificond/ap_interface_binder.h
binder::Status startHostapd(
    const sp<net::wifi::IApInterfaceEventCallback>& callback,
     bool* out_success) override;
1.15 ApInterfaceBinder::startHostapd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//system/connectivity/wificond/ap_interface_binder.cpp
binder::Status ApInterfaceBinder::startHostapd(
    const sp<IApInterfaceEventCallback>& callback, bool* out_success) {
    *out_success = false;
    if (!impl_) {
        LOG(WARNING) << "Cannot start hostapd on dead ApInterface.";
        return binder::Status::ok();
    }
    *out_success = impl_->StartHostapd();
    if (*out_success) {
        ap_interface_event_callback_ = callback;
    }
    return binder::Status::ok();
}
1.16 impl_->StartHostapd()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//system/connectivity/wificond/ap_interface_impl.h

#ifndef WIFICOND_AP_INTERFACE_IMPL_H_
#define WIFICOND_AP_INTERFACE_IMPL_H_
 
#include <string>
#include <vector>
 
#include <android-base/macros.h>
#include <wifi_system/hostapd_manager.h>
#include <wifi_system/interface_tool.h>
 
#include "wificond/net/netlink_manager.h"
 
#include "android/net/wifi/IApInterface.h"
 
namespace android {
namespace wificond {
 
class ApInterfaceBinder;
class NetlinkUtils;
 
// Holds the guts of how we control network interfaces capable of exposing an AP
// via hostapd.  Because remote processes may hold on to the corresponding
// binder object past the lifetime of the local object, we are forced to
// keep this object separate from the binder representation of itself.
class ApInterfaceImpl {
 public:
  ApInterfaceImpl(const std::string& interface_name,
                  uint32_t interface_index,
                  NetlinkUtils* netlink_utils,
                  wifi_system::InterfaceTool* if_tool,
                  wifi_system::HostapdManager* hostapd_manager);
  ~ApInterfaceImpl();
 
  // Get a pointer to the binder representing this ApInterfaceImpl.
  android::sp<android::net::wifi::IApInterface> GetBinder() const;
 
  bool StartHostapd();
  bool StopHostapd();
  std::string GetInterfaceName() { return interface_name_; }
  int GetNumberOfAssociatedStations() const;
  void Dump(std::stringstream* ss) const;
 
 private:
  const std::string interface_name_;
  const uint32_t interface_index_;
  NetlinkUtils* const netlink_utils_;
  wifi_system::InterfaceTool* const if_tool_;
  wifi_system::HostapdManager* const hostapd_manager_;
  const android::sp<ApInterfaceBinder> binder_;
 
  // Number of associated stations.
  int number_of_associated_stations_;
 
  void OnStationEvent(StationEvent event,
                      const std::vector<uint8_t>& mac_address);
 
  void OnChannelSwitchEvent(uint32_t frequency, ChannelBandwidth bandwidth);
 
  DISALLOW_COPY_AND_ASSIGN(ApInterfaceImpl);
};
 
}  // namespace wificond
}  // namespace android
 
#endif  // WIFICOND_AP_INTERFACE_IMPL_H_
1.17 ApInterfaceImpl::StartHostapd()
1
2
3
4
//system/connectivity/wificond/ap_interface_impl.cpp
bool ApInterfaceImpl::StartHostapd() {
    return hostapd_manager_->StartHostapd();
}

总结: 工作在sofap的时候,作为AP, 工作在master模式,由后台进程hostapd做认证处理。

完~~~

文 | 力卉编程