/* * Copyright (C) 2019 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 static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQP3GPPNetwork; import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPDomName; import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPIPAddrAvailability; import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPNAIRealm; import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPRoamingConsortium; import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPVenueName; import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPVenueUrl; import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.HSConnCapability; import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.HSFriendlyName; import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.HSOSUProviders; import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.HSWANMetrics; import android.annotation.NonNull; import android.hardware.wifi.supplicant.V1_0.ISupplicantStaIfaceCallback; import android.net.wifi.SecurityParams; import android.net.wifi.SupplicantState; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.net.wifi.WifiSsid; import android.util.Log; import com.android.server.wifi.hotspot2.AnqpEvent; import com.android.server.wifi.hotspot2.IconEvent; import com.android.server.wifi.hotspot2.WnmData; import com.android.server.wifi.hotspot2.anqp.ANQPElement; import com.android.server.wifi.hotspot2.anqp.ANQPParser; import com.android.server.wifi.hotspot2.anqp.Constants; import com.android.server.wifi.util.NativeUtil; import java.io.IOException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; abstract class SupplicantStaIfaceCallbackImpl extends ISupplicantStaIfaceCallback.Stub { private static final String TAG = SupplicantStaIfaceCallbackImpl.class.getSimpleName(); private final SupplicantStaIfaceHal mStaIfaceHal; private final String mIfaceName; private final Object mLock; private final WifiMonitor mWifiMonitor; // Used to help check for PSK password mismatch & EAP connection failure. private int mStateBeforeDisconnect = State.INACTIVE; private String mCurrentSsid = null; SupplicantStaIfaceCallbackImpl(@NonNull SupplicantStaIfaceHal staIfaceHal, @NonNull String ifaceName, @NonNull Object lock, @NonNull WifiMonitor wifiMonitor) { mStaIfaceHal = staIfaceHal; mIfaceName = ifaceName; mLock = lock; mWifiMonitor = wifiMonitor; } /** * Converts the supplicant state received from HIDL to the equivalent framework state. */ protected static SupplicantState supplicantHidlStateToFrameworkState(int state) { switch (state) { case ISupplicantStaIfaceCallback.State.DISCONNECTED: return SupplicantState.DISCONNECTED; case ISupplicantStaIfaceCallback.State.IFACE_DISABLED: return SupplicantState.INTERFACE_DISABLED; case ISupplicantStaIfaceCallback.State.INACTIVE: return SupplicantState.INACTIVE; case ISupplicantStaIfaceCallback.State.SCANNING: return SupplicantState.SCANNING; case ISupplicantStaIfaceCallback.State.AUTHENTICATING: return SupplicantState.AUTHENTICATING; case ISupplicantStaIfaceCallback.State.ASSOCIATING: return SupplicantState.ASSOCIATING; case ISupplicantStaIfaceCallback.State.ASSOCIATED: return SupplicantState.ASSOCIATED; case ISupplicantStaIfaceCallback.State.FOURWAY_HANDSHAKE: return SupplicantState.FOUR_WAY_HANDSHAKE; case ISupplicantStaIfaceCallback.State.GROUP_HANDSHAKE: return SupplicantState.GROUP_HANDSHAKE; case ISupplicantStaIfaceCallback.State.COMPLETED: return SupplicantState.COMPLETED; default: throw new IllegalArgumentException("Invalid state: " + state); } } /** * Parses the provided payload into an ANQP element. * * @param infoID Element type. * @param payload Raw payload bytes. * @return AnqpElement instance on success, null on failure. */ private ANQPElement parseAnqpElement(Constants.ANQPElementType infoID, ArrayList payload) { synchronized (mLock) { try { return Constants.getANQPElementID(infoID) != null ? ANQPParser.parseElement( infoID, ByteBuffer.wrap(NativeUtil.byteArrayFromArrayList(payload))) : ANQPParser.parseHS20Element( infoID, ByteBuffer.wrap(NativeUtil.byteArrayFromArrayList(payload))); } catch (IOException | BufferUnderflowException e) { Log.e(TAG, "Failed parsing ANQP element payload: " + infoID, e); return null; } } } /** * Parse the ANQP element data and add to the provided elements map if successful. * * @param elementsMap Map to add the parsed out element to. * @param infoID Element type. * @param payload Raw payload bytes. */ private void addAnqpElementToMap(Map elementsMap, Constants.ANQPElementType infoID, ArrayList payload) { synchronized (mLock) { if (payload == null || payload.isEmpty()) return; ANQPElement element = parseAnqpElement(infoID, payload); if (element != null) { elementsMap.put(infoID, element); } } } @Override public void onNetworkAdded(int id) { synchronized (mLock) { mStaIfaceHal.logCallback("onNetworkAdded id=" + id); } } @Override public void onNetworkRemoved(int id) { synchronized (mLock) { mStaIfaceHal.logCallback("onNetworkRemoved id=" + id); // Reset state since network has been removed. mStateBeforeDisconnect = State.INACTIVE; } } /** * Added to plumb the new {@code filsHlpSent} param from the V1.3 callback version. */ public void onStateChanged(int newState, byte[/* 6 */] bssid, int id, ArrayList ssid, boolean filsHlpSent) { synchronized (mLock) { mStaIfaceHal.logCallback("onStateChanged"); SupplicantState newSupplicantState = supplicantHidlStateToFrameworkState(newState); WifiSsid wifiSsid = WifiSsid.createFromByteArray(NativeUtil.byteArrayFromArrayList(ssid)); String bssidStr = NativeUtil.macAddressFromByteArray(bssid); if (newState != State.DISCONNECTED) { // onStateChanged(DISCONNECTED) may come before onDisconnected(), so add this // cache to track the state before the disconnect. mStateBeforeDisconnect = newState; } if (newState == State.ASSOCIATING || newState == State.ASSOCIATED || newState == State.COMPLETED) { mStaIfaceHal.updateOnLinkedNetworkRoaming(mIfaceName, id); } if (newState == State.COMPLETED) { mWifiMonitor.broadcastNetworkConnectionEvent( mIfaceName, mStaIfaceHal.getCurrentNetworkId(mIfaceName), filsHlpSent, wifiSsid, bssidStr); } else if (newState == State.ASSOCIATING) { mCurrentSsid = NativeUtil.encodeSsid(ssid); } mWifiMonitor.broadcastSupplicantStateChangeEvent( mIfaceName, mStaIfaceHal.getCurrentNetworkId(mIfaceName), wifiSsid, bssidStr, newSupplicantState); } } @Override public void onStateChanged(int newState, byte[/* 6 */] bssid, int id, ArrayList ssid) { onStateChanged(newState, bssid, id, ssid, false); } public void onAnqpQueryDone(byte[/* 6 */] bssid, ISupplicantStaIfaceCallback.AnqpData data, ISupplicantStaIfaceCallback.Hs20AnqpData hs20Data, android.hardware.wifi.supplicant.V1_4.ISupplicantStaIfaceCallback.AnqpData dataV14) { Map elementsMap = new HashMap<>(); addAnqpElementToMap(elementsMap, ANQPVenueName, data.venueName); addAnqpElementToMap(elementsMap, ANQPRoamingConsortium, data.roamingConsortium); addAnqpElementToMap( elementsMap, ANQPIPAddrAvailability, data.ipAddrTypeAvailability); addAnqpElementToMap(elementsMap, ANQPNAIRealm, data.naiRealm); addAnqpElementToMap(elementsMap, ANQP3GPPNetwork, data.anqp3gppCellularNetwork); addAnqpElementToMap(elementsMap, ANQPDomName, data.domainName); if (dataV14 != null) { addAnqpElementToMap(elementsMap, ANQPVenueUrl, dataV14.venueUrl); } addAnqpElementToMap(elementsMap, HSFriendlyName, hs20Data.operatorFriendlyName); addAnqpElementToMap(elementsMap, HSWANMetrics, hs20Data.wanMetrics); addAnqpElementToMap(elementsMap, HSConnCapability, hs20Data.connectionCapability); addAnqpElementToMap(elementsMap, HSOSUProviders, hs20Data.osuProvidersList); mWifiMonitor.broadcastAnqpDoneEvent( mIfaceName, new AnqpEvent(NativeUtil.macAddressToLong(bssid), elementsMap)); } @Override public void onAnqpQueryDone(byte[/* 6 */] bssid, ISupplicantStaIfaceCallback.AnqpData data, ISupplicantStaIfaceCallback.Hs20AnqpData hs20Data) { synchronized (mLock) { mStaIfaceHal.logCallback("onAnqpQueryDone"); onAnqpQueryDone(bssid, data, hs20Data, null /* v1.4 element */); } } @Override public void onHs20IconQueryDone(byte[/* 6 */] bssid, String fileName, ArrayList data) { synchronized (mLock) { mStaIfaceHal.logCallback("onHs20IconQueryDone"); mWifiMonitor.broadcastIconDoneEvent( mIfaceName, new IconEvent(NativeUtil.macAddressToLong(bssid), fileName, data.size(), NativeUtil.byteArrayFromArrayList(data))); } } @Override public void onHs20SubscriptionRemediation(byte[/* 6 */] bssid, byte osuMethod, String url) { synchronized (mLock) { mStaIfaceHal.logCallback("onHs20SubscriptionRemediation"); mWifiMonitor.broadcastWnmEvent( mIfaceName, WnmData.createRemediationEvent(NativeUtil.macAddressToLong(bssid), url, osuMethod)); } } @Override public void onHs20DeauthImminentNotice(byte[/* 6 */] bssid, int reasonCode, int reAuthDelayInSec, String url) { synchronized (mLock) { mStaIfaceHal.logCallback("onHs20DeauthImminentNotice"); mWifiMonitor.broadcastWnmEvent( mIfaceName, WnmData.createDeauthImminentEvent(NativeUtil.macAddressToLong(bssid), url, reasonCode == WnmData.ESS, reAuthDelayInSec)); } } @Override public void onDisconnected(byte[/* 6 */] bssid, boolean locallyGenerated, int reasonCode) { synchronized (mLock) { mStaIfaceHal.logCallback("onDisconnected"); if (mStaIfaceHal.isVerboseLoggingEnabled()) { Log.e(TAG, "onDisconnected state=" + mStateBeforeDisconnect + " locallyGenerated=" + locallyGenerated + " reasonCode=" + reasonCode); } WifiConfiguration curConfiguration = mStaIfaceHal.getCurrentNetworkLocalConfig(mIfaceName); if (curConfiguration != null) { if (mStateBeforeDisconnect == State.FOURWAY_HANDSHAKE && WifiConfigurationUtil.isConfigForPskNetwork(curConfiguration) && (!locallyGenerated || reasonCode != ReasonCode.IE_IN_4WAY_DIFFERS)) { mWifiMonitor.broadcastAuthenticationFailureEvent( mIfaceName, WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD, -1); } else if (mStateBeforeDisconnect == State.ASSOCIATED && WifiConfigurationUtil.isConfigForEapNetwork(curConfiguration)) { mWifiMonitor.broadcastAuthenticationFailureEvent( mIfaceName, WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE, -1); } } mWifiMonitor.broadcastNetworkDisconnectionEvent( mIfaceName, locallyGenerated, reasonCode, mCurrentSsid, NativeUtil.macAddressFromByteArray(bssid)); } } private void handleAssocRejectEvent(AssocRejectEventInfo assocRejectInfo) { boolean isWrongPwd = false; WifiConfiguration curConfiguration = mStaIfaceHal.getCurrentNetworkLocalConfig(mIfaceName); if (curConfiguration != null) { if (!assocRejectInfo.timedOut) { Log.d(TAG, "flush PMK cache due to association rejection for config id " + curConfiguration.networkId + "."); mStaIfaceHal.removePmkCacheEntry(curConfiguration.networkId); } // Special handling for WPA3-Personal networks. If the password is // incorrect, the AP will send association rejection, with status code 1 // (unspecified failure). In SAE networks, the password authentication // is not related to the 4-way handshake. In this case, we will send an // authentication failure event up. if (assocRejectInfo.statusCode == StatusCode.UNSPECIFIED_FAILURE) { // Network Selection status is guaranteed to be initialized SecurityParams params = curConfiguration.getNetworkSelectionStatus() .getCandidateSecurityParams(); if (params != null && params.getSecurityType() == WifiConfiguration.SECURITY_TYPE_SAE) { mStaIfaceHal.logCallback("SAE incorrect password"); isWrongPwd = true; } } else if (assocRejectInfo.statusCode == StatusCode.CHALLENGE_FAIL && WifiConfigurationUtil.isConfigForWepNetwork(curConfiguration)) { mStaIfaceHal.logCallback("WEP incorrect password"); isWrongPwd = true; } } if (isWrongPwd) { mWifiMonitor.broadcastAuthenticationFailureEvent( mIfaceName, WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD, -1); } mWifiMonitor.broadcastAssociationRejectionEvent(mIfaceName, assocRejectInfo); mStateBeforeDisconnect = State.INACTIVE; } public void onAssociationRejected(android.hardware.wifi.supplicant.V1_4 .ISupplicantStaIfaceCallback.AssociationRejectionData assocRejectData) { AssocRejectEventInfo assocRejectInfo = new AssocRejectEventInfo(assocRejectData); handleAssocRejectEvent(assocRejectInfo); } @Override public void onAssociationRejected(byte[/* 6 */] bssid, int statusCode, boolean timedOut) { synchronized (mLock) { AssocRejectEventInfo assocRejectInfo = new AssocRejectEventInfo( mCurrentSsid, NativeUtil.macAddressFromByteArray(bssid), statusCode, timedOut); handleAssocRejectEvent(assocRejectInfo); } } @Override public void onAuthenticationTimeout(byte[/* 6 */] bssid) { synchronized (mLock) { mStaIfaceHal.logCallback("onAuthenticationTimeout"); mWifiMonitor.broadcastAuthenticationFailureEvent( mIfaceName, WifiManager.ERROR_AUTH_FAILURE_TIMEOUT, -1); mStateBeforeDisconnect = State.INACTIVE; } } @Override public void onBssidChanged(byte reason, byte[/* 6 */] bssid) { synchronized (mLock) { mStaIfaceHal.logCallback("onBssidChanged"); if (reason == BssidChangeReason.ASSOC_START) { mWifiMonitor.broadcastTargetBssidEvent( mIfaceName, NativeUtil.macAddressFromByteArray(bssid)); } else if (reason == BssidChangeReason.ASSOC_COMPLETE) { mWifiMonitor.broadcastAssociatedBssidEvent( mIfaceName, NativeUtil.macAddressFromByteArray(bssid)); } } } public void onEapFailure(int errorCode) { synchronized (mLock) { mStaIfaceHal.logCallback("onEapFailure"); mWifiMonitor.broadcastAuthenticationFailureEvent( mIfaceName, WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE, errorCode); mStateBeforeDisconnect = State.INACTIVE; } } @Override public void onEapFailure() { onEapFailure(-1); } @Override public void onWpsEventSuccess() { mStaIfaceHal.logCallback("onWpsEventSuccess"); synchronized (mLock) { mWifiMonitor.broadcastWpsSuccessEvent(mIfaceName); } } @Override public void onWpsEventFail(byte[/* 6 */] bssid, short configError, short errorInd) { synchronized (mLock) { mStaIfaceHal.logCallback("onWpsEventFail"); if (configError == WpsConfigError.MSG_TIMEOUT && errorInd == WpsErrorIndication.NO_ERROR) { mWifiMonitor.broadcastWpsTimeoutEvent(mIfaceName); } else { mWifiMonitor.broadcastWpsFailEvent(mIfaceName, configError, errorInd); } } } @Override public void onWpsEventPbcOverlap() { synchronized (mLock) { mStaIfaceHal.logCallback("onWpsEventPbcOverlap"); mWifiMonitor.broadcastWpsOverlapEvent(mIfaceName); } } @Override public void onExtRadioWorkStart(int id) { synchronized (mLock) { mStaIfaceHal.logCallback("onExtRadioWorkStart"); } } @Override public void onExtRadioWorkTimeout(int id) { synchronized (mLock) { mStaIfaceHal.logCallback("onExtRadioWorkTimeout"); } } }