/* * Copyright (C) 2016 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.aware; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.wifi.V1_0.NanDataPathChannelCfg; import android.hardware.wifi.V1_0.NanStatusType; import android.net.ConnectivityManager; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MatchAllNetworkSpecifier; import android.net.NetworkAgent; import android.net.NetworkCapabilities; import android.net.NetworkFactory; import android.net.NetworkInfo; import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.RouteInfo; import android.net.wifi.aware.WifiAwareAgentNetworkSpecifier; import android.net.wifi.aware.WifiAwareManager; import android.net.wifi.aware.WifiAwareNetworkSpecifier; import android.net.wifi.aware.WifiAwareUtils; import android.os.IBinder; import android.os.INetworkManagementService; import android.os.Looper; import android.os.ServiceManager; import android.os.SystemClock; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wifi.util.WifiPermissionsWrapper; import libcore.util.HexEncoding; import java.io.FileDescriptor; import java.io.PrintWriter; import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.Arrays; import java.util.Enumeration; import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; /** * Manages Aware data-path lifetime: interface creation/deletion, data-path setup and tear-down. * The Aware network configuration is: * - transport = TRANSPORT_WIFI_AWARE * - capabilities = NET_CAPABILITY_NOT_VPN * - network specifier generated by DiscoverySession.createNetworkSpecifier(...) or * WifiAwareManager.createNetworkSpecifier(...). */ public class WifiAwareDataPathStateManager { private static final String TAG = "WifiAwareDataPathStMgr"; private static final boolean DBG = false; private static final boolean VDBG = false; // STOPSHIP if true private static final String AWARE_INTERFACE_PREFIX = "aware_data"; private static final String NETWORK_TAG = "WIFI_AWARE_FACTORY"; private static final String AGENT_TAG_PREFIX = "WIFI_AWARE_AGENT_"; private static final int NETWORK_FACTORY_SCORE_AVAIL = 1; private static final int NETWORK_FACTORY_BANDWIDTH_AVAIL = 1; private static final int NETWORK_FACTORY_SIGNAL_STRENGTH_AVAIL = 1; private final WifiAwareStateManager mMgr; public NetworkInterfaceWrapper mNiWrapper = new NetworkInterfaceWrapper(); private static final NetworkCapabilities sNetworkCapabilitiesFilter = new NetworkCapabilities(); private final Set mInterfaces = new HashSet<>(); private final Map mNetworkRequestsCache = new ArrayMap<>(); private Context mContext; private WifiAwareMetrics mAwareMetrics; private WifiPermissionsWrapper mPermissionsWrapper; private Looper mLooper; private WifiAwareNetworkFactory mNetworkFactory; public INetworkManagementService mNwService; public WifiAwareDataPathStateManager(WifiAwareStateManager mgr) { mMgr = mgr; } /** * Initialize the Aware data-path state manager. Specifically register the network factory with * connectivity service. */ public void start(Context context, Looper looper, WifiAwareMetrics awareMetrics, WifiPermissionsWrapper permissionsWrapper) { if (VDBG) Log.v(TAG, "start"); mContext = context; mAwareMetrics = awareMetrics; mPermissionsWrapper = permissionsWrapper; mLooper = looper; sNetworkCapabilitiesFilter.clearAll(); sNetworkCapabilitiesFilter.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE); sNetworkCapabilitiesFilter .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED); sNetworkCapabilitiesFilter.setNetworkSpecifier(new MatchAllNetworkSpecifier()); sNetworkCapabilitiesFilter.setLinkUpstreamBandwidthKbps(NETWORK_FACTORY_BANDWIDTH_AVAIL); sNetworkCapabilitiesFilter.setLinkDownstreamBandwidthKbps(NETWORK_FACTORY_BANDWIDTH_AVAIL); sNetworkCapabilitiesFilter.setSignalStrength(NETWORK_FACTORY_SIGNAL_STRENGTH_AVAIL); mNetworkFactory = new WifiAwareNetworkFactory(looper, context, sNetworkCapabilitiesFilter); mNetworkFactory.setScoreFilter(NETWORK_FACTORY_SCORE_AVAIL); mNetworkFactory.register(); IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); mNwService = INetworkManagementService.Stub.asInterface(b); } private Map.Entry getNetworkRequestByNdpId(int ndpId) { for (Map.Entry entry : mNetworkRequestsCache.entrySet()) { if (entry.getValue().ndpId == ndpId) { return entry; } } return null; } private Map.Entry getNetworkRequestByCanonicalDescriptor(CanonicalConnectionInfo cci) { for (Map.Entry entry : mNetworkRequestsCache.entrySet()) { if (entry.getValue().getCanonicalDescriptor().equals(cci)) { return entry; } } return null; } /** * Create all Aware data-path interfaces which are possible on the device - based on the * capabilities of the firmware. */ public void createAllInterfaces() { if (VDBG) Log.v(TAG, "createAllInterfaces"); if (mMgr.getCapabilities() == null) { Log.e(TAG, "createAllInterfaces: capabilities aren't initialized yet!"); return; } for (int i = 0; i < mMgr.getCapabilities().maxNdiInterfaces; ++i) { String name = AWARE_INTERFACE_PREFIX + i; if (mInterfaces.contains(name)) { Log.e(TAG, "createAllInterfaces(): interface already up, " + name + ", possibly failed to delete - deleting/creating again to be safe"); mMgr.deleteDataPathInterface(name); // critical to remove so that don't get infinite loop if the delete fails again mInterfaces.remove(name); } mMgr.createDataPathInterface(name); } } /** * Delete all Aware data-path interfaces which are currently up. */ public void deleteAllInterfaces() { if (VDBG) Log.v(TAG, "deleteAllInterfaces"); if (mMgr.getCapabilities() == null) { Log.e(TAG, "deleteAllInterfaces: capabilities aren't initialized yet!"); return; } for (int i = 0; i < mMgr.getCapabilities().maxNdiInterfaces; ++i) { String name = AWARE_INTERFACE_PREFIX + i; mMgr.deleteDataPathInterface(name); } } /** * Called when firmware indicates the an interface was created. */ public void onInterfaceCreated(String interfaceName) { if (VDBG) Log.v(TAG, "onInterfaceCreated: interfaceName=" + interfaceName); if (mInterfaces.contains(interfaceName)) { Log.w(TAG, "onInterfaceCreated: already contains interface -- " + interfaceName); } mInterfaces.add(interfaceName); } /** * Called when firmware indicates the an interface was deleted. */ public void onInterfaceDeleted(String interfaceName) { if (VDBG) Log.v(TAG, "onInterfaceDeleted: interfaceName=" + interfaceName); if (!mInterfaces.contains(interfaceName)) { Log.w(TAG, "onInterfaceDeleted: interface not on list -- " + interfaceName); } mInterfaces.remove(interfaceName); } /** * Response to initiating data-path request. Indicates that request is successful (not * complete!) and is now in progress. * * @param networkSpecifier The network specifier provided as part of the initiate request. * @param ndpId The ID assigned to the data-path. */ public void onDataPathInitiateSuccess(WifiAwareNetworkSpecifier networkSpecifier, int ndpId) { if (VDBG) { Log.v(TAG, "onDataPathInitiateSuccess: networkSpecifier=" + networkSpecifier + ", ndpId=" + ndpId); } AwareNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier); if (nnri == null) { Log.w(TAG, "onDataPathInitiateSuccess: network request not found for networkSpecifier=" + networkSpecifier); mMgr.endDataPath(ndpId); return; } if (nnri.state != AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE) { Log.w(TAG, "onDataPathInitiateSuccess: network request in incorrect state: state=" + nnri.state); mNetworkRequestsCache.remove(networkSpecifier); mMgr.endDataPath(ndpId); return; } nnri.state = AwareNetworkRequestInformation.STATE_WAIT_FOR_CONFIRM; nnri.ndpId = ndpId; } /** * Response to an attempt to set up a data-path (on the initiator side). * * @param networkSpecifier The network specifier provided as part of the initiate request. * @param reason Failure reason. */ public void onDataPathInitiateFail(WifiAwareNetworkSpecifier networkSpecifier, int reason) { if (VDBG) { Log.v(TAG, "onDataPathInitiateFail: networkSpecifier=" + networkSpecifier + ", reason=" + reason); } AwareNetworkRequestInformation nnri = mNetworkRequestsCache.remove(networkSpecifier); if (nnri == null) { Log.w(TAG, "onDataPathInitiateFail: network request not found for networkSpecifier=" + networkSpecifier); return; } if (nnri.state != AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE) { Log.w(TAG, "onDataPathInitiateFail: network request in incorrect state: state=" + nnri.state); } mNetworkRequestsCache.remove(networkSpecifier); mAwareMetrics.recordNdpStatus(reason, networkSpecifier.isOutOfBand(), nnri.startTimestamp); } /** * Notification (unsolicited/asynchronous) that a peer has requested to set up a data-path * connection with us. * * @param pubSubId The ID of the discovery session context for the data-path - or 0 if not * related to a discovery session. * @param mac The discovery MAC address of the peer. * @param ndpId The locally assigned ID for the data-path. * @return The network specifier of the data-path (or null if none/error) */ public WifiAwareNetworkSpecifier onDataPathRequest(int pubSubId, byte[] mac, int ndpId) { if (VDBG) { Log.v(TAG, "onDataPathRequest: pubSubId=" + pubSubId + ", mac=" + String.valueOf( HexEncoding.encode(mac)) + ", ndpId=" + ndpId); } WifiAwareNetworkSpecifier networkSpecifier = null; AwareNetworkRequestInformation nnri = null; for (Map.Entry entry : mNetworkRequestsCache.entrySet()) { /* * Checking that the incoming request (from the Initiator) matches the request * we (the Responder) already have set up. The rules are: * - The discovery session (pub/sub ID) must match. * - The peer MAC address (if specified - i.e. non-null) must match. A null peer MAC == * accept (otherwise matching) requests from any peer MAC. * - The request must be pending (i.e. we could have completed requests for the same * parameters) */ if (entry.getValue().pubSubId != 0 && entry.getValue().pubSubId != pubSubId) { continue; } if (entry.getValue().peerDiscoveryMac != null && !Arrays.equals( entry.getValue().peerDiscoveryMac, mac)) { continue; } if (entry.getValue().state != AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST) { continue; } networkSpecifier = entry.getKey(); nnri = entry.getValue(); break; } if (nnri == null) { Log.w(TAG, "onDataPathRequest: can't find a request with specified pubSubId=" + pubSubId + ", mac=" + String.valueOf(HexEncoding.encode(mac))); if (DBG) { Log.d(TAG, "onDataPathRequest: network request cache = " + mNetworkRequestsCache); } mMgr.respondToDataPathRequest(false, ndpId, "", null, null, false); return null; } if (nnri.state != AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST) { Log.w(TAG, "onDataPathRequest: request " + networkSpecifier + " is incorrect state=" + nnri.state); mMgr.respondToDataPathRequest(false, ndpId, "", null, null, false); mNetworkRequestsCache.remove(networkSpecifier); return null; } nnri.interfaceName = selectInterfaceForRequest(nnri); if (nnri.interfaceName == null) { Log.w(TAG, "onDataPathRequest: request " + networkSpecifier + " no interface available"); mMgr.respondToDataPathRequest(false, ndpId, "", null, null, false); mNetworkRequestsCache.remove(networkSpecifier); return null; } nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE; nnri.ndpId = ndpId; nnri.startTimestamp = SystemClock.elapsedRealtime(); mMgr.respondToDataPathRequest(true, ndpId, nnri.interfaceName, nnri.networkSpecifier.pmk, nnri.networkSpecifier.passphrase, nnri.networkSpecifier.isOutOfBand()); return networkSpecifier; } /** * Called on the RESPONDER when the response to data-path request has been completed. * * @param ndpId The ID of the data-path (NDP) * @param success Whether or not the 'RespondToDataPathRequest' operation was a success. */ public void onRespondToDataPathRequest(int ndpId, boolean success, int reasonOnFailure) { if (VDBG) { Log.v(TAG, "onRespondToDataPathRequest: ndpId=" + ndpId + ", success=" + success); } WifiAwareNetworkSpecifier networkSpecifier = null; AwareNetworkRequestInformation nnri = null; for (Map.Entry entry : mNetworkRequestsCache.entrySet()) { if (entry.getValue().ndpId == ndpId) { networkSpecifier = entry.getKey(); nnri = entry.getValue(); break; } } if (nnri == null) { Log.w(TAG, "onRespondToDataPathRequest: can't find a request with specified ndpId=" + ndpId); if (DBG) { Log.d(TAG, "onRespondToDataPathRequest: network request cache = " + mNetworkRequestsCache); } return; } if (!success) { Log.w(TAG, "onRespondToDataPathRequest: request " + networkSpecifier + " failed responding"); mMgr.endDataPath(ndpId); mNetworkRequestsCache.remove(networkSpecifier); mAwareMetrics.recordNdpStatus(reasonOnFailure, networkSpecifier.isOutOfBand(), nnri.startTimestamp); return; } if (nnri.state != AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE) { Log.w(TAG, "onRespondToDataPathRequest: request " + networkSpecifier + " is incorrect state=" + nnri.state); mMgr.endDataPath(ndpId); mNetworkRequestsCache.remove(networkSpecifier); return; } nnri.state = AwareNetworkRequestInformation.STATE_WAIT_FOR_CONFIRM; } /** * Notification (unsolicited/asynchronous) that the data-path (which we've been setting up) * is possibly (if {@code accept} is {@code true}) ready for use from the firmware's * perspective - now can do L3 configuration. * * @param ndpId Id of the data-path * @param mac The MAC address of the peer's data-path (not discovery interface). Only * valid * if {@code accept} is {@code true}. * @param accept Indicates whether the data-path setup has succeeded (been accepted) or * failed (been rejected). * @param reason If {@code accept} is {@code false} provides a reason code for the * rejection/failure. * @param message The message provided by the peer as part of the data-path setup * process. * @return The network specifier of the data-path or a null if none/error. */ public WifiAwareNetworkSpecifier onDataPathConfirm(int ndpId, byte[] mac, boolean accept, int reason, byte[] message) { if (VDBG) { Log.v(TAG, "onDataPathConfirm: ndpId=" + ndpId + ", mac=" + String.valueOf( HexEncoding.encode(mac)) + ", accept=" + accept + ", reason=" + reason); } Map.Entry nnriE = getNetworkRequestByNdpId(ndpId); if (nnriE == null) { Log.w(TAG, "onDataPathConfirm: network request not found for ndpId=" + ndpId); if (accept) { mMgr.endDataPath(ndpId); } return null; } WifiAwareNetworkSpecifier networkSpecifier = nnriE.getKey(); AwareNetworkRequestInformation nnri = nnriE.getValue(); // validate state if (nnri.state != AwareNetworkRequestInformation.STATE_WAIT_FOR_CONFIRM) { Log.w(TAG, "onDataPathConfirm: invalid state=" + nnri.state); mNetworkRequestsCache.remove(networkSpecifier); if (accept) { mMgr.endDataPath(ndpId); } return networkSpecifier; } if (accept) { nnri.state = AwareNetworkRequestInformation.STATE_CONFIRMED; nnri.peerDataMac = mac; NetworkInfo networkInfo = new NetworkInfo(ConnectivityManager.TYPE_NONE, 0, NETWORK_TAG, ""); NetworkCapabilities networkCapabilities = new NetworkCapabilities( sNetworkCapabilitiesFilter); LinkProperties linkProperties = new LinkProperties(); boolean interfaceUsedByAnotherNdp = isInterfaceUpAndUsedByAnotherNdp(nnri); if (!interfaceUsedByAnotherNdp) { try { mNwService.setInterfaceUp(nnri.interfaceName); mNwService.enableIpv6(nnri.interfaceName); } catch (Exception e) { // NwService throws runtime exceptions for errors Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri + ": can't configure network - " + e); mMgr.endDataPath(ndpId); nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING; return networkSpecifier; } } else { if (VDBG) { Log.v(TAG, "onDataPathConfirm: interface already configured: " + nnri.interfaceName); } } if (!mNiWrapper.configureAgentProperties(nnri, nnri.equivalentSpecifiers, ndpId, networkInfo, networkCapabilities, linkProperties)) { return networkSpecifier; } nnri.networkAgent = new WifiAwareNetworkAgent(mLooper, mContext, AGENT_TAG_PREFIX + nnri.ndpId, new NetworkInfo(ConnectivityManager.TYPE_NONE, 0, NETWORK_TAG, ""), networkCapabilities, linkProperties, NETWORK_FACTORY_SCORE_AVAIL, nnri); nnri.networkAgent.sendNetworkInfo(networkInfo); mAwareMetrics.recordNdpStatus(NanStatusType.SUCCESS, networkSpecifier.isOutOfBand(), nnri.startTimestamp); nnri.startTimestamp = SystemClock.elapsedRealtime(); // update time-stamp for duration mAwareMetrics.recordNdpCreation(nnri.uid, mNetworkRequestsCache); } else { if (DBG) { Log.d(TAG, "onDataPathConfirm: data-path for networkSpecifier=" + networkSpecifier + " rejected - reason=" + reason); } mNetworkRequestsCache.remove(networkSpecifier); mAwareMetrics.recordNdpStatus(reason, networkSpecifier.isOutOfBand(), nnri.startTimestamp); } return networkSpecifier; } /** * Notification (unsolicited/asynchronous) from the firmware that the specified data-path has * been terminated. * * @param ndpId The ID of the terminated data-path. */ public void onDataPathEnd(int ndpId) { if (VDBG) Log.v(TAG, "onDataPathEnd: ndpId=" + ndpId); Map.Entry nnriE = getNetworkRequestByNdpId(ndpId); if (nnriE == null) { if (DBG) { Log.d(TAG, "onDataPathEnd: network request not found for ndpId=" + ndpId); } return; } tearDownInterfaceIfPossible(nnriE.getValue()); if (nnriE.getValue().state == AwareNetworkRequestInformation.STATE_CONFIRMED || nnriE.getValue().state == AwareNetworkRequestInformation.STATE_TERMINATING) { mAwareMetrics.recordNdpSessionDuration(nnriE.getValue().startTimestamp); } mNetworkRequestsCache.remove(nnriE.getKey()); mNetworkFactory.tickleConnectivityIfWaiting(); } /** * Called whenever Aware comes down. Clean up all pending and up network requeests and agents. */ public void onAwareDownCleanupDataPaths() { if (VDBG) Log.v(TAG, "onAwareDownCleanupDataPaths"); for (AwareNetworkRequestInformation nnri : mNetworkRequestsCache.values()) { tearDownInterfaceIfPossible(nnri); } mNetworkRequestsCache.clear(); } /** * Called when timed-out waiting for confirmation of the data-path setup (i.e. * onDataPathConfirm). Started on the initiator when executing the request for the data-path * and on the responder when received a request for data-path (in both cases only on success * - i.e. when we're proceeding with data-path setup). */ public void handleDataPathTimeout(NetworkSpecifier networkSpecifier) { if (VDBG) Log.v(TAG, "handleDataPathTimeout: networkSpecifier=" + networkSpecifier); AwareNetworkRequestInformation nnri = mNetworkRequestsCache.remove(networkSpecifier); if (nnri == null) { if (DBG) { Log.d(TAG, "handleDataPathTimeout: network request not found for networkSpecifier=" + networkSpecifier); } return; } mAwareMetrics.recordNdpStatus(NanStatusType.INTERNAL_FAILURE, nnri.networkSpecifier.isOutOfBand(), nnri.startTimestamp); mMgr.endDataPath(nnri.ndpId); nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING; } private class WifiAwareNetworkFactory extends NetworkFactory { // Request received while waiting for confirmation that a canonically identical data-path // (NDP) is in the process of being terminated private boolean mWaitingForTermination = false; WifiAwareNetworkFactory(Looper looper, Context context, NetworkCapabilities filter) { super(looper, context, NETWORK_TAG, filter); } public void tickleConnectivityIfWaiting() { if (mWaitingForTermination) { if (VDBG) Log.v(TAG, "tickleConnectivityIfWaiting: was waiting!"); mWaitingForTermination = false; reevaluateAllRequests(); } } @Override public boolean acceptRequest(NetworkRequest request, int score) { if (VDBG) { Log.v(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request + ", score=" + score); } if (!mMgr.isUsageEnabled()) { if (VDBG) { Log.v(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request + " -- Aware disabled"); } return false; } if (mInterfaces.isEmpty()) { Log.w(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request + " -- No Aware interfaces are up"); return false; } NetworkSpecifier networkSpecifierBase = request.networkCapabilities.getNetworkSpecifier(); if (!(networkSpecifierBase instanceof WifiAwareNetworkSpecifier)) { Log.w(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request + " - not a WifiAwareNetworkSpecifier"); return false; } WifiAwareNetworkSpecifier networkSpecifier = (WifiAwareNetworkSpecifier) networkSpecifierBase; // look up specifier - are we being called again? AwareNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier); if (nnri != null) { if (DBG) { Log.d(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request + " - already in cache with state=" + nnri.state); } if (nnri.state == AwareNetworkRequestInformation.STATE_TERMINATING) { mWaitingForTermination = true; return false; } // seems to happen after a network agent is created - trying to rematch all // requests again!? return true; } nnri = AwareNetworkRequestInformation.processNetworkSpecifier(networkSpecifier, mMgr, mPermissionsWrapper); if (nnri == null) { Log.e(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request + " - can't parse network specifier"); return false; } // check to see if a canonical version exists Map.Entry primaryRequest = getNetworkRequestByCanonicalDescriptor(nnri.getCanonicalDescriptor()); if (primaryRequest != null) { if (VDBG) { Log.v(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request + ", already has a primary request=" + primaryRequest.getKey() + " with state=" + primaryRequest.getValue().state); } if (primaryRequest.getValue().state == AwareNetworkRequestInformation.STATE_TERMINATING) { mWaitingForTermination = true; } else { primaryRequest.getValue().updateToSupportNewRequest(networkSpecifier); } return false; } mNetworkRequestsCache.put(networkSpecifier, nnri); return true; } @Override protected void needNetworkFor(NetworkRequest networkRequest, int score) { if (VDBG) { Log.v(TAG, "WifiAwareNetworkFactory.needNetworkFor: networkRequest=" + networkRequest + ", score=" + score); } NetworkSpecifier networkSpecifierObj = networkRequest.networkCapabilities.getNetworkSpecifier(); WifiAwareNetworkSpecifier networkSpecifier = null; if (networkSpecifierObj instanceof WifiAwareNetworkSpecifier) { networkSpecifier = (WifiAwareNetworkSpecifier) networkSpecifierObj; } AwareNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier); if (nnri == null) { Log.e(TAG, "WifiAwareNetworkFactory.needNetworkFor: networkRequest=" + networkRequest + " not in cache!?"); return; } if (nnri.state != AwareNetworkRequestInformation.STATE_IDLE) { if (DBG) { Log.d(TAG, "WifiAwareNetworkFactory.needNetworkFor: networkRequest=" + networkRequest + " - already in progress"); // TODO: understand how/when can be called again/while in progress (seems // to be related to score re-calculation after a network agent is created) } return; } if (nnri.networkSpecifier.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR) { nnri.interfaceName = selectInterfaceForRequest(nnri); if (nnri.interfaceName == null) { Log.w(TAG, "needNetworkFor: request " + networkSpecifier + " no interface available"); mNetworkRequestsCache.remove(networkSpecifier); return; } mMgr.initiateDataPathSetup(networkSpecifier, nnri.peerInstanceId, NanDataPathChannelCfg.CHANNEL_NOT_REQUESTED, selectChannelForRequest(nnri), nnri.peerDiscoveryMac, nnri.interfaceName, nnri.networkSpecifier.pmk, nnri.networkSpecifier.passphrase, nnri.networkSpecifier.isOutOfBand()); nnri.state = AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE; nnri.startTimestamp = SystemClock.elapsedRealtime(); } else { nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST; } } @Override protected void releaseNetworkFor(NetworkRequest networkRequest) { if (VDBG) { Log.v(TAG, "WifiAwareNetworkFactory.releaseNetworkFor: networkRequest=" + networkRequest); } NetworkSpecifier networkSpecifierObj = networkRequest.networkCapabilities.getNetworkSpecifier(); WifiAwareNetworkSpecifier networkSpecifier = null; if (networkSpecifierObj instanceof WifiAwareNetworkSpecifier) { networkSpecifier = (WifiAwareNetworkSpecifier) networkSpecifierObj; } AwareNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier); if (nnri == null) { Log.e(TAG, "WifiAwareNetworkFactory.releaseNetworkFor: networkRequest=" + networkRequest + " not in cache!?"); return; } if (nnri.networkAgent != null) { if (VDBG) { Log.v(TAG, "WifiAwareNetworkFactory.releaseNetworkFor: networkRequest=" + networkRequest + ", nnri=" + nnri + ": agent already created - deferring ending data-path to agent" + ".unwanted()"); } return; } /* * Since there's no agent it means we're in the process of setting up the NDP. * However, it is possible that there were other equivalent requests for this NDP. We * should keep going in that case. */ nnri.removeSupportForRequest(networkSpecifier); if (nnri.equivalentSpecifiers.isEmpty()) { if (VDBG) { Log.v(TAG, "releaseNetworkFor: there are no further requests, networkRequest=" + networkRequest); } if (nnri.ndpId != 0) { // 0 is never a valid ID! if (VDBG) Log.v(TAG, "releaseNetworkFor: in progress NDP being terminated"); mMgr.endDataPath(nnri.ndpId); nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING; } } else { if (VDBG) { Log.v(TAG, "releaseNetworkFor: equivalent requests exist - not terminating " + "networkRequest=" + networkRequest); } } } } private class WifiAwareNetworkAgent extends NetworkAgent { private NetworkInfo mNetworkInfo; private AwareNetworkRequestInformation mAwareNetworkRequestInfo; WifiAwareNetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, NetworkCapabilities nc, LinkProperties lp, int score, AwareNetworkRequestInformation anri) { super(looper, context, logTag, ni, nc, lp, score); mNetworkInfo = ni; mAwareNetworkRequestInfo = anri; } @Override protected void unwanted() { if (VDBG) { Log.v(TAG, "WifiAwareNetworkAgent.unwanted: request=" + mAwareNetworkRequestInfo); } mMgr.endDataPath(mAwareNetworkRequestInfo.ndpId); mAwareNetworkRequestInfo.state = AwareNetworkRequestInformation.STATE_TERMINATING; // Will get a callback (on both initiator and responder) when data-path actually // terminated. At that point will inform the agent and will clear the cache. } void reconfigureAgentAsDisconnected() { if (VDBG) { Log.v(TAG, "WifiAwareNetworkAgent.reconfigureAgentAsDisconnected: request=" + mAwareNetworkRequestInfo); } mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, ""); sendNetworkInfo(mNetworkInfo); } } private void tearDownInterfaceIfPossible(AwareNetworkRequestInformation nnri) { if (VDBG) Log.v(TAG, "tearDownInterfaceIfPossible: nnri=" + nnri); if (!TextUtils.isEmpty(nnri.interfaceName)) { boolean interfaceUsedByAnotherNdp = isInterfaceUpAndUsedByAnotherNdp(nnri); if (interfaceUsedByAnotherNdp) { if (VDBG) { Log.v(TAG, "tearDownInterfaceIfPossible: interfaceName=" + nnri.interfaceName + ", still in use - not turning down"); } } else { try { mNwService.setInterfaceDown(nnri.interfaceName); } catch (Exception e) { // NwService throws runtime exceptions for errors Log.e(TAG, "tearDownInterfaceIfPossible: nnri=" + nnri + ": can't bring interface down - " + e); } } } if (nnri.networkAgent != null) { nnri.networkAgent.reconfigureAgentAsDisconnected(); } } private boolean isInterfaceUpAndUsedByAnotherNdp(AwareNetworkRequestInformation nri) { for (AwareNetworkRequestInformation lnri : mNetworkRequestsCache.values()) { if (lnri == nri) { continue; } if (nri.interfaceName.equals(lnri.interfaceName) && ( lnri.state == AwareNetworkRequestInformation.STATE_CONFIRMED || lnri.state == AwareNetworkRequestInformation.STATE_TERMINATING)) { return true; } } return false; } /** * Select one of the existing interfaces for the new network request. A request is canonical * (otherwise it wouldn't be executed). * * Construct a list of all interfaces currently used to communicate to the peer. The remaining * interfaces are available for use for this request - if none are left then the request should * fail (signaled to the caller by returning a null). */ private String selectInterfaceForRequest(AwareNetworkRequestInformation req) { SortedSet potential = new TreeSet<>(mInterfaces); Set used = new HashSet<>(); if (VDBG) { Log.v(TAG, "selectInterfaceForRequest: req=" + req + ", mNetworkRequestsCache=" + mNetworkRequestsCache); } for (AwareNetworkRequestInformation nnri : mNetworkRequestsCache.values()) { if (nnri == req) { continue; } if (Arrays.equals(req.peerDiscoveryMac, nnri.peerDiscoveryMac)) { used.add(nnri.interfaceName); } } if (VDBG) { Log.v(TAG, "selectInterfaceForRequest: potential=" + potential + ", used=" + used); } for (String ifName: potential) { if (!used.contains(ifName)) { return ifName; } } Log.e(TAG, "selectInterfaceForRequest: req=" + req + " - no interfaces available!"); return null; } /** * Select a channel for the network request. * * TODO (b/38209409): The value from this function isn't currently used - the channel selection * is delegated to the HAL. */ private int selectChannelForRequest(AwareNetworkRequestInformation req) { return 2437; } /** * Aware network request. State object: contains network request information/state through its * lifetime. */ @VisibleForTesting public static class AwareNetworkRequestInformation { static final int STATE_IDLE = 100; static final int STATE_WAIT_FOR_CONFIRM = 101; static final int STATE_CONFIRMED = 102; static final int STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE = 103; static final int STATE_RESPONDER_WAIT_FOR_REQUEST = 104; static final int STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE = 105; static final int STATE_TERMINATING = 106; public int state; public int uid; public String interfaceName; public int pubSubId = 0; public int peerInstanceId = 0; public byte[] peerDiscoveryMac = null; public int ndpId = 0; // 0 is never a valid ID! public byte[] peerDataMac; public WifiAwareNetworkSpecifier networkSpecifier; public long startTimestamp = 0; // request is made (initiator) / get request (responder) public WifiAwareNetworkAgent networkAgent; /* A collection of specifiers which are equivalent to the current request and are * supported by it's agent. This list DOES include the original (first) network specifier * (which is stored separately above). */ public Set equivalentSpecifiers = new HashSet<>(); void updateToSupportNewRequest(WifiAwareNetworkSpecifier ns) { if (VDBG) Log.v(TAG, "updateToSupportNewRequest: ns=" + ns); if (equivalentSpecifiers.add(ns) && state == STATE_CONFIRMED) { if (networkAgent == null) { Log.wtf(TAG, "updateToSupportNewRequest: null agent in CONFIRMED state!?"); return; } networkAgent.sendNetworkCapabilities(getNetworkCapabilities()); } } void removeSupportForRequest(WifiAwareNetworkSpecifier ns) { if (VDBG) Log.v(TAG, "removeSupportForRequest: ns=" + ns); equivalentSpecifiers.remove(ns); // we will not update the agent: // 1. this will only get called before the agent is created // 2. connectivity service does not allow (WTF) updates with reduced capabilities } private NetworkCapabilities getNetworkCapabilities() { NetworkCapabilities nc = new NetworkCapabilities(sNetworkCapabilitiesFilter); nc.setNetworkSpecifier(new WifiAwareAgentNetworkSpecifier(equivalentSpecifiers.toArray( new WifiAwareNetworkSpecifier[equivalentSpecifiers.size()]))); return nc; } /** * Returns a canonical descriptor for the network request. */ CanonicalConnectionInfo getCanonicalDescriptor() { return new CanonicalConnectionInfo(peerDiscoveryMac, networkSpecifier.pmk, networkSpecifier.sessionId, networkSpecifier.passphrase); } static AwareNetworkRequestInformation processNetworkSpecifier(WifiAwareNetworkSpecifier ns, WifiAwareStateManager mgr, WifiPermissionsWrapper permissionWrapper) { int uid, pubSubId = 0; int peerInstanceId = 0; byte[] peerMac = ns.peerMac; if (VDBG) { Log.v(TAG, "processNetworkSpecifier: networkSpecifier=" + ns); } // type: always valid if (ns.type < 0 || ns.type > WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_MAX_VALID) { Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns + ", invalid 'type' value"); return null; } // role: always valid if (ns.role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR && ns.role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) { Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns + " -- invalid 'role' value"); return null; } if (ns.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR && ns.type != WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB && ns.type != WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB) { Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns + " -- invalid 'type' value for INITIATOR (only IB and OOB are " + "permitted)"); return null; } // look up network specifier information in Aware state manager WifiAwareClientState client = mgr.getClient(ns.clientId); if (client == null) { Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns + " -- not client with this id -- clientId=" + ns.clientId); return null; } uid = client.getUid(); // validate the role (if session ID provided: i.e. session 1xx) if (ns.type == WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB || ns.type == WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER) { WifiAwareDiscoverySessionState session = client.getSession(ns.sessionId); if (session == null) { Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns + " -- no session with this id -- sessionId=" + ns.sessionId); return null; } if ((session.isPublishSession() && ns.role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) || ( !session.isPublishSession() && ns.role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR)) { Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns + " -- invalid role for session type"); return null; } if (ns.type == WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB) { pubSubId = session.getPubSubId(); WifiAwareDiscoverySessionState.PeerInfo peerInfo = session.getPeerInfo( ns.peerId); if (peerInfo == null) { Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns + " -- no peer info associated with this peer id -- peerId=" + ns.peerId); return null; } peerInstanceId = peerInfo.mInstanceId; try { peerMac = peerInfo.mMac; if (peerMac == null || peerMac.length != 6) { Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns + " -- invalid peer MAC address"); return null; } } catch (IllegalArgumentException e) { Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns + " -- invalid peer MAC address -- e=" + e); return null; } } } // validate UID if (ns.requestorUid != uid) { Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns.toString() + " -- UID mismatch to clientId's uid=" + uid); return null; } // validate permission if PMK is used (SystemApi) if (ns.pmk != null && ns.pmk.length != 0) { if (permissionWrapper.getUidPermission(Manifest.permission.CONNECTIVITY_INTERNAL, ns.requestorUid) != PackageManager.PERMISSION_GRANTED) { Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns.toString() + " -- UID doesn't have permission to use PMK API"); return null; } } // validate passphrase & PMK (if provided) if (!TextUtils.isEmpty(ns.passphrase)) { // non-null indicates usage if (!WifiAwareUtils.validatePassphrase(ns.passphrase)) { Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns.toString() + " -- invalid passphrase length: " + ns.passphrase.length()); return null; } } if (ns.pmk != null && !WifiAwareUtils.validatePmk(ns.pmk)) { // non-null indicates usage Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns.toString() + " -- invalid pmk length: " + ns.pmk.length); return null; } // create container and populate AwareNetworkRequestInformation nnri = new AwareNetworkRequestInformation(); nnri.state = AwareNetworkRequestInformation.STATE_IDLE; nnri.uid = uid; nnri.pubSubId = pubSubId; nnri.peerInstanceId = peerInstanceId; nnri.peerDiscoveryMac = peerMac; nnri.networkSpecifier = ns; nnri.equivalentSpecifiers.add(ns); return nnri; } @Override public String toString() { StringBuilder sb = new StringBuilder("AwareNetworkRequestInformation: "); sb.append("state=").append(state).append(", ns=").append(networkSpecifier).append( ", uid=").append(uid).append(", interfaceName=").append(interfaceName).append( ", pubSubId=").append(pubSubId).append(", peerInstanceId=").append( peerInstanceId).append(", peerDiscoveryMac=").append( peerDiscoveryMac == null ? "" : String.valueOf(HexEncoding.encode(peerDiscoveryMac))).append( ", ndpId=").append(ndpId).append(", peerDataMac=").append( peerDataMac == null ? "" : String.valueOf(HexEncoding.encode(peerDataMac))).append( ", startTimestamp=").append(startTimestamp).append(", equivalentSpecifiers=["); for (WifiAwareNetworkSpecifier ns: equivalentSpecifiers) { sb.append(ns.toString()).append(", "); } return sb.append("]").toString(); } } /** * A canonical (unique) descriptor of the peer connection. */ static class CanonicalConnectionInfo { CanonicalConnectionInfo(byte[] peerDiscoveryMac, byte[] pmk, int sessionId, String passphrase) { this.peerDiscoveryMac = peerDiscoveryMac; this.pmk = pmk; this.sessionId = sessionId; this.passphrase = passphrase; } public final byte[] peerDiscoveryMac; /* * Security configuration matching: * - open: pmk/passphrase = null * - pmk: pmk != null, passphrase = null * - passphrase: passphrase != null, sessionId used (==0 for OOB), pmk=null */ public final byte[] pmk; public final int sessionId; public final String passphrase; @Override public int hashCode() { return Objects.hash(Arrays.hashCode(peerDiscoveryMac), Arrays.hashCode(pmk), sessionId, passphrase); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof CanonicalConnectionInfo)) { return false; } CanonicalConnectionInfo lhs = (CanonicalConnectionInfo) obj; return Arrays.equals(peerDiscoveryMac, lhs.peerDiscoveryMac) && Arrays.equals(pmk, lhs.pmk) && TextUtils.equals(passphrase, lhs.passphrase) && sessionId == lhs.sessionId; } @Override public String toString() { StringBuilder sb = new StringBuilder("CanonicalConnectionInfo: ["); sb.append("peerDiscoveryMac=").append(peerDiscoveryMac == null ? "" : String.valueOf(HexEncoding.encode(peerDiscoveryMac))).append("pmk=").append( pmk == null ? "" : "*").append("sessionId=").append(sessionId).append( "passphrase=").append(passphrase == null ? "" : "*").append("]"); return sb.toString(); } } /** * Enables mocking. */ @VisibleForTesting public class NetworkInterfaceWrapper { /** * Configures network agent properties: link-local address, connected status, interface * name. Delegated to enable mocking. */ public boolean configureAgentProperties(AwareNetworkRequestInformation nnri, Set networkSpecifiers, int ndpId, NetworkInfo networkInfo, NetworkCapabilities networkCapabilities, LinkProperties linkProperties) { // find link-local address InetAddress linkLocal = null; NetworkInterface ni; try { ni = NetworkInterface.getByName(nnri.interfaceName); } catch (SocketException e) { Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri + ": can't get network interface - " + e); mMgr.endDataPath(ndpId); nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING; return false; } if (ni == null) { Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri + ": can't get network interface (null)"); mMgr.endDataPath(ndpId); nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING; return false; } Enumeration addresses = ni.getInetAddresses(); while (addresses.hasMoreElements()) { InetAddress ip = addresses.nextElement(); if (ip instanceof Inet6Address && ip.isLinkLocalAddress()) { linkLocal = ip; break; } } if (linkLocal == null) { Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri + ": no link local addresses"); mMgr.endDataPath(ndpId); nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING; return false; } // configure agent networkInfo.setIsAvailable(true); networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null); networkCapabilities.setNetworkSpecifier(new WifiAwareAgentNetworkSpecifier( networkSpecifiers.toArray(new WifiAwareNetworkSpecifier[0]))); linkProperties.setInterfaceName(nnri.interfaceName); linkProperties.addLinkAddress(new LinkAddress(linkLocal, 64)); linkProperties.addRoute( new RouteInfo(new IpPrefix("fe80::/64"), null, nnri.interfaceName)); return true; } } /** * Dump the internal state of the class. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("WifiAwareDataPathStateManager:"); pw.println(" mInterfaces: " + mInterfaces); pw.println(" sNetworkCapabilitiesFilter: " + sNetworkCapabilitiesFilter); pw.println(" mNetworkRequestsCache: " + mNetworkRequestsCache); pw.println(" mNetworkFactory:"); mNetworkFactory.dump(fd, pw, args); } }