|  | /* | 
|  | * Copyright (C) 2017 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  | package com.android.server.wifi; | 
|  |  | 
|  |  | 
|  | import android.annotation.NonNull; | 
|  | import android.content.Context; | 
|  | import android.hardware.wifi.hostapd.V1_0.HostapdStatus; | 
|  | import android.hardware.wifi.hostapd.V1_0.HostapdStatusCode; | 
|  | import android.hardware.wifi.hostapd.V1_0.IHostapd; | 
|  | import android.hidl.manager.V1_0.IServiceManager; | 
|  | import android.hidl.manager.V1_0.IServiceNotification; | 
|  | import android.net.wifi.WifiConfiguration; | 
|  | import android.os.HwRemoteBinder; | 
|  | import android.os.RemoteException; | 
|  | import android.util.Log; | 
|  |  | 
|  | import com.android.internal.R; | 
|  | import com.android.internal.annotations.VisibleForTesting; | 
|  | import com.android.server.wifi.WifiNative.HostapdDeathEventHandler; | 
|  | import com.android.server.wifi.util.NativeUtil; | 
|  |  | 
|  | import javax.annotation.concurrent.ThreadSafe; | 
|  |  | 
|  | /** | 
|  | * To maintain thread-safety, the locking protocol is that every non-static method (regardless of | 
|  | * access level) acquires mLock. | 
|  | */ | 
|  | @ThreadSafe | 
|  | public class HostapdHal { | 
|  | private static final String TAG = "HostapdHal"; | 
|  |  | 
|  | private final Object mLock = new Object(); | 
|  | private boolean mVerboseLoggingEnabled = false; | 
|  | private final boolean mEnableAcs; | 
|  | private final boolean mEnableIeee80211AC; | 
|  |  | 
|  | // Hostapd HAL interface objects | 
|  | private IServiceManager mIServiceManager = null; | 
|  | private IHostapd mIHostapd; | 
|  | private HostapdDeathEventHandler mDeathEventHandler; | 
|  |  | 
|  | private final IServiceNotification mServiceNotificationCallback = | 
|  | new IServiceNotification.Stub() { | 
|  | public void onRegistration(String fqName, String name, boolean preexisting) { | 
|  | synchronized (mLock) { | 
|  | if (mVerboseLoggingEnabled) { | 
|  | Log.i(TAG, "IServiceNotification.onRegistration for: " + fqName | 
|  | + ", " + name + " preexisting=" + preexisting); | 
|  | } | 
|  | if (!initHostapdService()) { | 
|  | Log.e(TAG, "initalizing IHostapd failed."); | 
|  | hostapdServiceDiedHandler(); | 
|  | } else { | 
|  | Log.i(TAG, "Completed initialization of IHostapd."); | 
|  | } | 
|  | } | 
|  | } | 
|  | }; | 
|  | private final HwRemoteBinder.DeathRecipient mServiceManagerDeathRecipient = | 
|  | cookie -> { | 
|  | synchronized (mLock) { | 
|  | Log.w(TAG, "IServiceManager died: cookie=" + cookie); | 
|  | hostapdServiceDiedHandler(); | 
|  | mIServiceManager = null; // Will need to register a new ServiceNotification | 
|  | } | 
|  | }; | 
|  | private final HwRemoteBinder.DeathRecipient mHostapdDeathRecipient = | 
|  | cookie -> { | 
|  | synchronized (mLock) { | 
|  | Log.w(TAG, "IHostapd/IHostapd died: cookie=" + cookie); | 
|  | hostapdServiceDiedHandler(); | 
|  | } | 
|  | }; | 
|  |  | 
|  |  | 
|  | public HostapdHal(Context context) { | 
|  | mEnableAcs = context.getResources().getBoolean(R.bool.config_wifi_softap_acs_supported); | 
|  | mEnableIeee80211AC = | 
|  | context.getResources().getBoolean(R.bool.config_wifi_softap_ieee80211ac_supported); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Enable/Disable verbose logging. | 
|  | * | 
|  | * @param enable true to enable, false to disable. | 
|  | */ | 
|  | void enableVerboseLogging(boolean enable) { | 
|  | synchronized (mLock) { | 
|  | mVerboseLoggingEnabled = enable; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Link to death for IServiceManager object. | 
|  | * @return true on success, false otherwise. | 
|  | */ | 
|  | private boolean linkToServiceManagerDeath() { | 
|  | synchronized (mLock) { | 
|  | if (mIServiceManager == null) return false; | 
|  | try { | 
|  | if (!mIServiceManager.linkToDeath(mServiceManagerDeathRecipient, 0)) { | 
|  | Log.wtf(TAG, "Error on linkToDeath on IServiceManager"); | 
|  | hostapdServiceDiedHandler(); | 
|  | mIServiceManager = null; // Will need to register a new ServiceNotification | 
|  | return false; | 
|  | } | 
|  | } catch (RemoteException e) { | 
|  | Log.e(TAG, "IServiceManager.linkToDeath exception", e); | 
|  | mIServiceManager = null; // Will need to register a new ServiceNotification | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Registers a service notification for the IHostapd service, which triggers intialization of | 
|  | * the IHostapd | 
|  | * @return true if the service notification was successfully registered | 
|  | */ | 
|  | public boolean initialize() { | 
|  | synchronized (mLock) { | 
|  | if (mVerboseLoggingEnabled) { | 
|  | Log.i(TAG, "Registering IHostapd service ready callback."); | 
|  | } | 
|  | mIHostapd = null; | 
|  | if (mIServiceManager != null) { | 
|  | // Already have an IServiceManager and serviceNotification registered, don't | 
|  | // don't register another. | 
|  | return true; | 
|  | } | 
|  | try { | 
|  | mIServiceManager = getServiceManagerMockable(); | 
|  | if (mIServiceManager == null) { | 
|  | Log.e(TAG, "Failed to get HIDL Service Manager"); | 
|  | return false; | 
|  | } | 
|  | if (!linkToServiceManagerDeath()) { | 
|  | return false; | 
|  | } | 
|  | /* TODO(b/33639391) : Use the new IHostapd.registerForNotifications() once it | 
|  | exists */ | 
|  | if (!mIServiceManager.registerForNotifications( | 
|  | IHostapd.kInterfaceName, "", mServiceNotificationCallback)) { | 
|  | Log.e(TAG, "Failed to register for notifications to " | 
|  | + IHostapd.kInterfaceName); | 
|  | mIServiceManager = null; // Will need to register a new ServiceNotification | 
|  | return false; | 
|  | } | 
|  | } catch (RemoteException e) { | 
|  | Log.e(TAG, "Exception while trying to register a listener for IHostapd service: " | 
|  | + e); | 
|  | hostapdServiceDiedHandler(); | 
|  | mIServiceManager = null; // Will need to register a new ServiceNotification | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Link to death for IHostapd object. | 
|  | * @return true on success, false otherwise. | 
|  | */ | 
|  | private boolean linkToHostapdDeath() { | 
|  | synchronized (mLock) { | 
|  | if (mIHostapd == null) return false; | 
|  | try { | 
|  | if (!mIHostapd.linkToDeath(mHostapdDeathRecipient, 0)) { | 
|  | Log.wtf(TAG, "Error on linkToDeath on IHostapd"); | 
|  | hostapdServiceDiedHandler(); | 
|  | return false; | 
|  | } | 
|  | } catch (RemoteException e) { | 
|  | Log.e(TAG, "IHostapd.linkToDeath exception", e); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Initialize the IHostapd object. | 
|  | * @return true on success, false otherwise. | 
|  | */ | 
|  | private boolean initHostapdService() { | 
|  | synchronized (mLock) { | 
|  | try { | 
|  | mIHostapd = getHostapdMockable(); | 
|  | } catch (RemoteException e) { | 
|  | Log.e(TAG, "IHostapd.getService exception: " + e); | 
|  | return false; | 
|  | } | 
|  | if (mIHostapd == null) { | 
|  | Log.e(TAG, "Got null IHostapd service. Stopping hostapd HIDL startup"); | 
|  | return false; | 
|  | } | 
|  | if (!linkToHostapdDeath()) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Add and start a new access point. | 
|  | * | 
|  | * @param ifaceName Name of the interface. | 
|  | * @param config Configuration to use for the AP. | 
|  | * @return true on success, false otherwise. | 
|  | */ | 
|  | public boolean addAccessPoint(@NonNull String ifaceName, @NonNull WifiConfiguration config) { | 
|  | synchronized (mLock) { | 
|  | final String methodStr = "addAccessPoint"; | 
|  | IHostapd.IfaceParams ifaceParams = new IHostapd.IfaceParams(); | 
|  | ifaceParams.ifaceName = ifaceName; | 
|  | ifaceParams.hwModeParams.enable80211N = true; | 
|  | ifaceParams.hwModeParams.enable80211AC = mEnableIeee80211AC; | 
|  | try { | 
|  | ifaceParams.channelParams.band = getBand(config); | 
|  | } catch (IllegalArgumentException e) { | 
|  | Log.e(TAG, "Unrecognized apBand " + config.apBand); | 
|  | return false; | 
|  | } | 
|  | if (mEnableAcs) { | 
|  | ifaceParams.channelParams.enableAcs = true; | 
|  | ifaceParams.channelParams.acsShouldExcludeDfs = true; | 
|  | } else { | 
|  | // Downgrade IHostapd.Band.BAND_ANY to IHostapd.Band.BAND_2_4_GHZ if ACS | 
|  | // is not supported. | 
|  | // We should remove this workaround once channel selection is moved from | 
|  | // ApConfigUtil to here. | 
|  | if (ifaceParams.channelParams.band == IHostapd.Band.BAND_ANY) { | 
|  | Log.d(TAG, "ACS is not supported on this device, using 2.4 GHz band."); | 
|  | ifaceParams.channelParams.band = IHostapd.Band.BAND_2_4_GHZ; | 
|  | } | 
|  | ifaceParams.channelParams.enableAcs = false; | 
|  | ifaceParams.channelParams.channel = config.apChannel; | 
|  | } | 
|  |  | 
|  | IHostapd.NetworkParams nwParams = new IHostapd.NetworkParams(); | 
|  | // TODO(b/67745880) Note that config.SSID is intended to be either a | 
|  | // hex string or "double quoted". | 
|  | // However, it seems that whatever is handing us these configurations does not obey | 
|  | // this convention. | 
|  | nwParams.ssid.addAll(NativeUtil.stringToByteArrayList(config.SSID)); | 
|  | nwParams.isHidden = config.hiddenSSID; | 
|  | nwParams.encryptionType = getEncryptionType(config); | 
|  | nwParams.pskPassphrase = (config.preSharedKey != null) ? config.preSharedKey : ""; | 
|  | if (!checkHostapdAndLogFailure(methodStr)) return false; | 
|  | try { | 
|  | HostapdStatus status = mIHostapd.addAccessPoint(ifaceParams, nwParams); | 
|  | return checkStatusAndLogFailure(status, methodStr); | 
|  | } catch (RemoteException e) { | 
|  | handleRemoteException(e, methodStr); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Remove a previously started access point. | 
|  | * | 
|  | * @param ifaceName Name of the interface. | 
|  | * @return true on success, false otherwise. | 
|  | */ | 
|  | public boolean removeAccessPoint(@NonNull String ifaceName) { | 
|  | synchronized (mLock) { | 
|  | final String methodStr = "removeAccessPoint"; | 
|  | if (!checkHostapdAndLogFailure(methodStr)) return false; | 
|  | try { | 
|  | HostapdStatus status = mIHostapd.removeAccessPoint(ifaceName); | 
|  | return checkStatusAndLogFailure(status, methodStr); | 
|  | } catch (RemoteException e) { | 
|  | handleRemoteException(e, methodStr); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Registers a death notification for hostapd. | 
|  | * @return Returns true on success. | 
|  | */ | 
|  | public boolean registerDeathHandler(@NonNull HostapdDeathEventHandler handler) { | 
|  | if (mDeathEventHandler != null) { | 
|  | Log.e(TAG, "Death handler already present"); | 
|  | } | 
|  | mDeathEventHandler = handler; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Deregisters a death notification for hostapd. | 
|  | * @return Returns true on success. | 
|  | */ | 
|  | public boolean deregisterDeathHandler() { | 
|  | if (mDeathEventHandler == null) { | 
|  | Log.e(TAG, "No Death handler present"); | 
|  | } | 
|  | mDeathEventHandler = null; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Clear internal state. | 
|  | */ | 
|  | private void clearState() { | 
|  | synchronized (mLock) { | 
|  | mIHostapd = null; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Handle hostapd death. | 
|  | */ | 
|  | private void hostapdServiceDiedHandler() { | 
|  | synchronized (mLock) { | 
|  | clearState(); | 
|  | if (mDeathEventHandler != null) { | 
|  | mDeathEventHandler.onDeath(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Signals whether Initialization completed successfully. | 
|  | */ | 
|  | public boolean isInitializationStarted() { | 
|  | synchronized (mLock) { | 
|  | return mIServiceManager != null; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Signals whether Initialization completed successfully. | 
|  | */ | 
|  | public boolean isInitializationComplete() { | 
|  | synchronized (mLock) { | 
|  | return mIHostapd != null; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Terminate the hostapd daemon. | 
|  | */ | 
|  | public void terminate() { | 
|  | synchronized (mLock) { | 
|  | final String methodStr = "terminate"; | 
|  | if (!checkHostapdAndLogFailure(methodStr)) return; | 
|  | try { | 
|  | mIHostapd.terminate(); | 
|  | } catch (RemoteException e) { | 
|  | handleRemoteException(e, methodStr); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Wrapper functions to access static HAL methods, created to be mockable in unit tests | 
|  | */ | 
|  | @VisibleForTesting | 
|  | protected IServiceManager getServiceManagerMockable() throws RemoteException { | 
|  | synchronized (mLock) { | 
|  | return IServiceManager.getService(); | 
|  | } | 
|  | } | 
|  |  | 
|  | @VisibleForTesting | 
|  | protected IHostapd getHostapdMockable() throws RemoteException { | 
|  | synchronized (mLock) { | 
|  | return IHostapd.getService(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static int getEncryptionType(WifiConfiguration localConfig) { | 
|  | int encryptionType; | 
|  | switch (localConfig.getAuthType()) { | 
|  | case WifiConfiguration.KeyMgmt.NONE: | 
|  | encryptionType = IHostapd.EncryptionType.NONE; | 
|  | break; | 
|  | case WifiConfiguration.KeyMgmt.WPA_PSK: | 
|  | encryptionType = IHostapd.EncryptionType.WPA; | 
|  | break; | 
|  | case WifiConfiguration.KeyMgmt.WPA2_PSK: | 
|  | encryptionType = IHostapd.EncryptionType.WPA2; | 
|  | break; | 
|  | default: | 
|  | // We really shouldn't default to None, but this was how NetworkManagementService | 
|  | // used to do this. | 
|  | encryptionType = IHostapd.EncryptionType.NONE; | 
|  | break; | 
|  | } | 
|  | return encryptionType; | 
|  | } | 
|  |  | 
|  | private static int getBand(WifiConfiguration localConfig) { | 
|  | int bandType; | 
|  | switch (localConfig.apBand) { | 
|  | case WifiConfiguration.AP_BAND_2GHZ: | 
|  | bandType = IHostapd.Band.BAND_2_4_GHZ; | 
|  | break; | 
|  | case WifiConfiguration.AP_BAND_5GHZ: | 
|  | bandType = IHostapd.Band.BAND_5_GHZ; | 
|  | break; | 
|  | case WifiConfiguration.AP_BAND_ANY: | 
|  | bandType = IHostapd.Band.BAND_ANY; | 
|  | break; | 
|  | default: | 
|  | throw new IllegalArgumentException(); | 
|  | } | 
|  | return bandType; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns false if Hostapd is null, and logs failure to call methodStr | 
|  | */ | 
|  | private boolean checkHostapdAndLogFailure(String methodStr) { | 
|  | synchronized (mLock) { | 
|  | if (mIHostapd == null) { | 
|  | Log.e(TAG, "Can't call " + methodStr + ", IHostapd is null"); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns true if provided status code is SUCCESS, logs debug message and returns false | 
|  | * otherwise | 
|  | */ | 
|  | private boolean checkStatusAndLogFailure(HostapdStatus status, | 
|  | String methodStr) { | 
|  | synchronized (mLock) { | 
|  | if (status.code != HostapdStatusCode.SUCCESS) { | 
|  | Log.e(TAG, "IHostapd." + methodStr + " failed: " + status.code | 
|  | + ", " + status.debugMessage); | 
|  | return false; | 
|  | } else { | 
|  | if (mVerboseLoggingEnabled) { | 
|  | Log.d(TAG, "IHostapd." + methodStr + " succeeded"); | 
|  | } | 
|  | return true; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private void handleRemoteException(RemoteException e, String methodStr) { | 
|  | synchronized (mLock) { | 
|  | hostapdServiceDiedHandler(); | 
|  | Log.e(TAG, "IHostapd." + methodStr + " failed with exception", e); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static void logd(String s) { | 
|  | Log.d(TAG, s); | 
|  | } | 
|  |  | 
|  | private static void logi(String s) { | 
|  | Log.i(TAG, s); | 
|  | } | 
|  |  | 
|  | private static void loge(String s) { | 
|  | Log.e(TAG, s); | 
|  | } | 
|  | } |