blob: a090800a54469f7e0a97d5cce4542b299e1724d4 [file] [log] [blame]
/*
* 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);
}
}