Home | History | Annotate | Download | only in wifi
      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