| /* |
| * 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); |
| } |
| } |