1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.server.wifi; 17 18 19 import android.annotation.NonNull; 20 import android.content.Context; 21 import android.hardware.wifi.hostapd.V1_0.HostapdStatus; 22 import android.hardware.wifi.hostapd.V1_0.HostapdStatusCode; 23 import android.hardware.wifi.hostapd.V1_0.IHostapd; 24 import android.hidl.manager.V1_0.IServiceManager; 25 import android.hidl.manager.V1_0.IServiceNotification; 26 import android.net.wifi.WifiConfiguration; 27 import android.os.HwRemoteBinder; 28 import android.os.RemoteException; 29 import android.util.Log; 30 31 import com.android.internal.R; 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.server.wifi.WifiNative.HostapdDeathEventHandler; 34 import com.android.server.wifi.util.NativeUtil; 35 36 import javax.annotation.concurrent.ThreadSafe; 37 38 /** 39 * To maintain thread-safety, the locking protocol is that every non-static method (regardless of 40 * access level) acquires mLock. 41 */ 42 @ThreadSafe 43 public class HostapdHal { 44 private static final String TAG = "HostapdHal"; 45 46 private final Object mLock = new Object(); 47 private boolean mVerboseLoggingEnabled = false; 48 private final boolean mEnableAcs; 49 private final boolean mEnableIeee80211AC; 50 51 // Hostapd HAL interface objects 52 private IServiceManager mIServiceManager = null; 53 private IHostapd mIHostapd; 54 private HostapdDeathEventHandler mDeathEventHandler; 55 56 private final IServiceNotification mServiceNotificationCallback = 57 new IServiceNotification.Stub() { 58 public void onRegistration(String fqName, String name, boolean preexisting) { 59 synchronized (mLock) { 60 if (mVerboseLoggingEnabled) { 61 Log.i(TAG, "IServiceNotification.onRegistration for: " + fqName 62 + ", " + name + " preexisting=" + preexisting); 63 } 64 if (!initHostapdService()) { 65 Log.e(TAG, "initalizing IHostapd failed."); 66 hostapdServiceDiedHandler(); 67 } else { 68 Log.i(TAG, "Completed initialization of IHostapd."); 69 } 70 } 71 } 72 }; 73 private final HwRemoteBinder.DeathRecipient mServiceManagerDeathRecipient = 74 cookie -> { 75 synchronized (mLock) { 76 Log.w(TAG, "IServiceManager died: cookie=" + cookie); 77 hostapdServiceDiedHandler(); 78 mIServiceManager = null; // Will need to register a new ServiceNotification 79 } 80 }; 81 private final HwRemoteBinder.DeathRecipient mHostapdDeathRecipient = 82 cookie -> { 83 synchronized (mLock) { 84 Log.w(TAG, "IHostapd/IHostapd died: cookie=" + cookie); 85 hostapdServiceDiedHandler(); 86 } 87 }; 88 89 90 public HostapdHal(Context context) { 91 mEnableAcs = context.getResources().getBoolean(R.bool.config_wifi_softap_acs_supported); 92 mEnableIeee80211AC = 93 context.getResources().getBoolean(R.bool.config_wifi_softap_ieee80211ac_supported); 94 } 95 96 /** 97 * Enable/Disable verbose logging. 98 * 99 * @param enable true to enable, false to disable. 100 */ 101 void enableVerboseLogging(boolean enable) { 102 synchronized (mLock) { 103 mVerboseLoggingEnabled = enable; 104 } 105 } 106 107 /** 108 * Link to death for IServiceManager object. 109 * @return true on success, false otherwise. 110 */ 111 private boolean linkToServiceManagerDeath() { 112 synchronized (mLock) { 113 if (mIServiceManager == null) return false; 114 try { 115 if (!mIServiceManager.linkToDeath(mServiceManagerDeathRecipient, 0)) { 116 Log.wtf(TAG, "Error on linkToDeath on IServiceManager"); 117 hostapdServiceDiedHandler(); 118 mIServiceManager = null; // Will need to register a new ServiceNotification 119 return false; 120 } 121 } catch (RemoteException e) { 122 Log.e(TAG, "IServiceManager.linkToDeath exception", e); 123 mIServiceManager = null; // Will need to register a new ServiceNotification 124 return false; 125 } 126 return true; 127 } 128 } 129 130 /** 131 * Registers a service notification for the IHostapd service, which triggers intialization of 132 * the IHostapd 133 * @return true if the service notification was successfully registered 134 */ 135 public boolean initialize() { 136 synchronized (mLock) { 137 if (mVerboseLoggingEnabled) { 138 Log.i(TAG, "Registering IHostapd service ready callback."); 139 } 140 mIHostapd = null; 141 if (mIServiceManager != null) { 142 // Already have an IServiceManager and serviceNotification registered, don't 143 // don't register another. 144 return true; 145 } 146 try { 147 mIServiceManager = getServiceManagerMockable(); 148 if (mIServiceManager == null) { 149 Log.e(TAG, "Failed to get HIDL Service Manager"); 150 return false; 151 } 152 if (!linkToServiceManagerDeath()) { 153 return false; 154 } 155 /* TODO(b/33639391) : Use the new IHostapd.registerForNotifications() once it 156 exists */ 157 if (!mIServiceManager.registerForNotifications( 158 IHostapd.kInterfaceName, "", mServiceNotificationCallback)) { 159 Log.e(TAG, "Failed to register for notifications to " 160 + IHostapd.kInterfaceName); 161 mIServiceManager = null; // Will need to register a new ServiceNotification 162 return false; 163 } 164 } catch (RemoteException e) { 165 Log.e(TAG, "Exception while trying to register a listener for IHostapd service: " 166 + e); 167 hostapdServiceDiedHandler(); 168 mIServiceManager = null; // Will need to register a new ServiceNotification 169 return false; 170 } 171 return true; 172 } 173 } 174 175 /** 176 * Link to death for IHostapd object. 177 * @return true on success, false otherwise. 178 */ 179 private boolean linkToHostapdDeath() { 180 synchronized (mLock) { 181 if (mIHostapd == null) return false; 182 try { 183 if (!mIHostapd.linkToDeath(mHostapdDeathRecipient, 0)) { 184 Log.wtf(TAG, "Error on linkToDeath on IHostapd"); 185 hostapdServiceDiedHandler(); 186 return false; 187 } 188 } catch (RemoteException e) { 189 Log.e(TAG, "IHostapd.linkToDeath exception", e); 190 return false; 191 } 192 return true; 193 } 194 } 195 196 /** 197 * Initialize the IHostapd object. 198 * @return true on success, false otherwise. 199 */ 200 private boolean initHostapdService() { 201 synchronized (mLock) { 202 try { 203 mIHostapd = getHostapdMockable(); 204 } catch (RemoteException e) { 205 Log.e(TAG, "IHostapd.getService exception: " + e); 206 return false; 207 } 208 if (mIHostapd == null) { 209 Log.e(TAG, "Got null IHostapd service. Stopping hostapd HIDL startup"); 210 return false; 211 } 212 if (!linkToHostapdDeath()) { 213 return false; 214 } 215 } 216 return true; 217 } 218 219 /** 220 * Add and start a new access point. 221 * 222 * @param ifaceName Name of the interface. 223 * @param config Configuration to use for the AP. 224 * @return true on success, false otherwise. 225 */ 226 public boolean addAccessPoint(@NonNull String ifaceName, @NonNull WifiConfiguration config) { 227 synchronized (mLock) { 228 final String methodStr = "addAccessPoint"; 229 IHostapd.IfaceParams ifaceParams = new IHostapd.IfaceParams(); 230 ifaceParams.ifaceName = ifaceName; 231 ifaceParams.hwModeParams.enable80211N = true; 232 ifaceParams.hwModeParams.enable80211AC = mEnableIeee80211AC; 233 try { 234 ifaceParams.channelParams.band = getBand(config); 235 } catch (IllegalArgumentException e) { 236 Log.e(TAG, "Unrecognized apBand " + config.apBand); 237 return false; 238 } 239 if (mEnableAcs) { 240 ifaceParams.channelParams.enableAcs = true; 241 ifaceParams.channelParams.acsShouldExcludeDfs = true; 242 } else { 243 // Downgrade IHostapd.Band.BAND_ANY to IHostapd.Band.BAND_2_4_GHZ if ACS 244 // is not supported. 245 // We should remove this workaround once channel selection is moved from 246 // ApConfigUtil to here. 247 if (ifaceParams.channelParams.band == IHostapd.Band.BAND_ANY) { 248 Log.d(TAG, "ACS is not supported on this device, using 2.4 GHz band."); 249 ifaceParams.channelParams.band = IHostapd.Band.BAND_2_4_GHZ; 250 } 251 ifaceParams.channelParams.enableAcs = false; 252 ifaceParams.channelParams.channel = config.apChannel; 253 } 254 255 IHostapd.NetworkParams nwParams = new IHostapd.NetworkParams(); 256 // TODO(b/67745880) Note that config.SSID is intended to be either a 257 // hex string or "double quoted". 258 // However, it seems that whatever is handing us these configurations does not obey 259 // this convention. 260 nwParams.ssid.addAll(NativeUtil.stringToByteArrayList(config.SSID)); 261 nwParams.isHidden = config.hiddenSSID; 262 nwParams.encryptionType = getEncryptionType(config); 263 nwParams.pskPassphrase = (config.preSharedKey != null) ? config.preSharedKey : ""; 264 if (!checkHostapdAndLogFailure(methodStr)) return false; 265 try { 266 HostapdStatus status = mIHostapd.addAccessPoint(ifaceParams, nwParams); 267 return checkStatusAndLogFailure(status, methodStr); 268 } catch (RemoteException e) { 269 handleRemoteException(e, methodStr); 270 return false; 271 } 272 } 273 } 274 275 /** 276 * Remove a previously started access point. 277 * 278 * @param ifaceName Name of the interface. 279 * @return true on success, false otherwise. 280 */ 281 public boolean removeAccessPoint(@NonNull String ifaceName) { 282 synchronized (mLock) { 283 final String methodStr = "removeAccessPoint"; 284 if (!checkHostapdAndLogFailure(methodStr)) return false; 285 try { 286 HostapdStatus status = mIHostapd.removeAccessPoint(ifaceName); 287 return checkStatusAndLogFailure(status, methodStr); 288 } catch (RemoteException e) { 289 handleRemoteException(e, methodStr); 290 return false; 291 } 292 } 293 } 294 295 /** 296 * Registers a death notification for hostapd. 297 * @return Returns true on success. 298 */ 299 public boolean registerDeathHandler(@NonNull HostapdDeathEventHandler handler) { 300 if (mDeathEventHandler != null) { 301 Log.e(TAG, "Death handler already present"); 302 } 303 mDeathEventHandler = handler; 304 return true; 305 } 306 307 /** 308 * Deregisters a death notification for hostapd. 309 * @return Returns true on success. 310 */ 311 public boolean deregisterDeathHandler() { 312 if (mDeathEventHandler == null) { 313 Log.e(TAG, "No Death handler present"); 314 } 315 mDeathEventHandler = null; 316 return true; 317 } 318 319 /** 320 * Clear internal state. 321 */ 322 private void clearState() { 323 synchronized (mLock) { 324 mIHostapd = null; 325 } 326 } 327 328 /** 329 * Handle hostapd death. 330 */ 331 private void hostapdServiceDiedHandler() { 332 synchronized (mLock) { 333 clearState(); 334 if (mDeathEventHandler != null) { 335 mDeathEventHandler.onDeath(); 336 } 337 } 338 } 339 340 /** 341 * Signals whether Initialization completed successfully. 342 */ 343 public boolean isInitializationStarted() { 344 synchronized (mLock) { 345 return mIServiceManager != null; 346 } 347 } 348 349 /** 350 * Signals whether Initialization completed successfully. 351 */ 352 public boolean isInitializationComplete() { 353 synchronized (mLock) { 354 return mIHostapd != null; 355 } 356 } 357 358 /** 359 * Terminate the hostapd daemon. 360 */ 361 public void terminate() { 362 synchronized (mLock) { 363 final String methodStr = "terminate"; 364 if (!checkHostapdAndLogFailure(methodStr)) return; 365 try { 366 mIHostapd.terminate(); 367 } catch (RemoteException e) { 368 handleRemoteException(e, methodStr); 369 } 370 } 371 } 372 373 /** 374 * Wrapper functions to access static HAL methods, created to be mockable in unit tests 375 */ 376 @VisibleForTesting 377 protected IServiceManager getServiceManagerMockable() throws RemoteException { 378 synchronized (mLock) { 379 return IServiceManager.getService(); 380 } 381 } 382 383 @VisibleForTesting 384 protected IHostapd getHostapdMockable() throws RemoteException { 385 synchronized (mLock) { 386 return IHostapd.getService(); 387 } 388 } 389 390 private static int getEncryptionType(WifiConfiguration localConfig) { 391 int encryptionType; 392 switch (localConfig.getAuthType()) { 393 case WifiConfiguration.KeyMgmt.NONE: 394 encryptionType = IHostapd.EncryptionType.NONE; 395 break; 396 case WifiConfiguration.KeyMgmt.WPA_PSK: 397 encryptionType = IHostapd.EncryptionType.WPA; 398 break; 399 case WifiConfiguration.KeyMgmt.WPA2_PSK: 400 encryptionType = IHostapd.EncryptionType.WPA2; 401 break; 402 default: 403 // We really shouldn't default to None, but this was how NetworkManagementService 404 // used to do this. 405 encryptionType = IHostapd.EncryptionType.NONE; 406 break; 407 } 408 return encryptionType; 409 } 410 411 private static int getBand(WifiConfiguration localConfig) { 412 int bandType; 413 switch (localConfig.apBand) { 414 case WifiConfiguration.AP_BAND_2GHZ: 415 bandType = IHostapd.Band.BAND_2_4_GHZ; 416 break; 417 case WifiConfiguration.AP_BAND_5GHZ: 418 bandType = IHostapd.Band.BAND_5_GHZ; 419 break; 420 case WifiConfiguration.AP_BAND_ANY: 421 bandType = IHostapd.Band.BAND_ANY; 422 break; 423 default: 424 throw new IllegalArgumentException(); 425 } 426 return bandType; 427 } 428 429 /** 430 * Returns false if Hostapd is null, and logs failure to call methodStr 431 */ 432 private boolean checkHostapdAndLogFailure(String methodStr) { 433 synchronized (mLock) { 434 if (mIHostapd == null) { 435 Log.e(TAG, "Can't call " + methodStr + ", IHostapd is null"); 436 return false; 437 } 438 return true; 439 } 440 } 441 442 /** 443 * Returns true if provided status code is SUCCESS, logs debug message and returns false 444 * otherwise 445 */ 446 private boolean checkStatusAndLogFailure(HostapdStatus status, 447 String methodStr) { 448 synchronized (mLock) { 449 if (status.code != HostapdStatusCode.SUCCESS) { 450 Log.e(TAG, "IHostapd." + methodStr + " failed: " + status.code 451 + ", " + status.debugMessage); 452 return false; 453 } else { 454 if (mVerboseLoggingEnabled) { 455 Log.d(TAG, "IHostapd." + methodStr + " succeeded"); 456 } 457 return true; 458 } 459 } 460 } 461 462 private void handleRemoteException(RemoteException e, String methodStr) { 463 synchronized (mLock) { 464 hostapdServiceDiedHandler(); 465 Log.e(TAG, "IHostapd." + methodStr + " failed with exception", e); 466 } 467 } 468 469 private static void logd(String s) { 470 Log.d(TAG, s); 471 } 472 473 private static void logi(String s) { 474 Log.i(TAG, s); 475 } 476 477 private static void loge(String s) { 478 Log.e(TAG, s); 479 } 480 } 481