| /* |
| * 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.content.Context; |
| import android.hardware.wifi.V1_0.NanDataPathChannelCfg; |
| import android.net.ConnectivityManager; |
| import android.net.IpPrefix; |
| import android.net.LinkAddress; |
| import android.net.LinkProperties; |
| import android.net.NetworkAgent; |
| import android.net.NetworkCapabilities; |
| import android.net.NetworkFactory; |
| import android.net.NetworkInfo; |
| import android.net.NetworkRequest; |
| import android.net.RouteInfo; |
| import android.net.wifi.aware.WifiAwareManager; |
| import android.os.IBinder; |
| import android.os.INetworkManagementService; |
| import android.os.Looper; |
| import android.os.ServiceManager; |
| import android.text.TextUtils; |
| import android.util.ArrayMap; |
| import android.util.Base64; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import libcore.util.HexEncoding; |
| |
| import org.json.JSONException; |
| import org.json.JSONObject; |
| |
| 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.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * 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(...). |
| * The network specifier is encoded as a JSON string with the key combos described in |
| * {@link WifiAwareManager} as {@code NETWORK_SPECIFIER_TYPE_*}. |
| */ |
| 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; |
| private final NetworkInterfaceWrapper mNiWrapper = new NetworkInterfaceWrapper(); |
| private final NetworkCapabilities mNetworkCapabilitiesFilter = new NetworkCapabilities(); |
| private final Set<String> mInterfaces = new HashSet<>(); |
| private final Map<String, AwareNetworkRequestInformation> mNetworkRequestsCache = |
| new ArrayMap<>(); |
| private Context mContext; |
| private Looper mLooper; |
| private WifiAwareNetworkFactory mNetworkFactory; |
| private 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) { |
| if (VDBG) Log.v(TAG, "start"); |
| |
| mContext = context; |
| mLooper = looper; |
| |
| mNetworkCapabilitiesFilter.clearAll(); |
| mNetworkCapabilitiesFilter.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE); |
| mNetworkCapabilitiesFilter |
| .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) |
| .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) |
| .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) |
| .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED); |
| mNetworkCapabilitiesFilter |
| .setNetworkSpecifier(NetworkCapabilities.MATCH_ALL_REQUESTS_NETWORK_SPECIFIER); |
| mNetworkCapabilitiesFilter.setLinkUpstreamBandwidthKbps(NETWORK_FACTORY_BANDWIDTH_AVAIL); |
| mNetworkCapabilitiesFilter.setLinkDownstreamBandwidthKbps(NETWORK_FACTORY_BANDWIDTH_AVAIL); |
| mNetworkCapabilitiesFilter.setSignalStrength(NETWORK_FACTORY_SIGNAL_STRENGTH_AVAIL); |
| |
| mNetworkFactory = new WifiAwareNetworkFactory(looper, context, mNetworkCapabilitiesFilter); |
| mNetworkFactory.setScoreFilter(NETWORK_FACTORY_SCORE_AVAIL); |
| mNetworkFactory.register(); |
| |
| IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); |
| mNwService = INetworkManagementService.Stub.asInterface(b); |
| } |
| |
| private Map.Entry<String, AwareNetworkRequestInformation> getNetworkRequestByNdpId(int ndpId) { |
| for (Map.Entry<String, AwareNetworkRequestInformation> entry : mNetworkRequestsCache |
| .entrySet()) { |
| if (entry.getValue().ndpId == ndpId) { |
| 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"); |
| |
| for (String name : mInterfaces) { |
| 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(String 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_INITIATOR_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(String 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); |
| } |
| |
| |
| /** |
| * 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 String onDataPathRequest(int pubSubId, byte[] mac, int ndpId) { |
| if (VDBG) { |
| Log.v(TAG, |
| "onDataPathRequest: pubSubId=" + pubSubId + ", mac=" + String.valueOf( |
| HexEncoding.encode(mac)) + ", ndpId=" + ndpId); |
| } |
| |
| String networkSpecifier = null; |
| AwareNetworkRequestInformation nnri = null; |
| for (Map.Entry<String, AwareNetworkRequestInformation> 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. |
| */ |
| if (entry.getValue().pubSubId != 0 && entry.getValue().pubSubId != pubSubId) { |
| continue; |
| } |
| |
| if (entry.getValue().peerDiscoveryMac != null && !Arrays.equals( |
| entry.getValue().peerDiscoveryMac, mac)) { |
| 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); |
| 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); |
| mNetworkRequestsCache.remove(networkSpecifier); |
| return null; |
| } |
| |
| nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE; |
| nnri.ndpId = ndpId; |
| nnri.interfaceName = selectInterfaceForRequest(nnri); |
| mMgr.respondToDataPathRequest(true, ndpId, nnri.interfaceName, nnri.pmk); |
| |
| 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) { |
| if (VDBG) { |
| Log.v(TAG, "onRespondToDataPathRequest: ndpId=" + ndpId + ", success=" + success); |
| } |
| |
| String networkSpecifier = null; |
| AwareNetworkRequestInformation nnri = null; |
| for (Map.Entry<String, AwareNetworkRequestInformation> 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); |
| 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_RESPONDER_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 String 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<String, AwareNetworkRequestInformation> nnriE = getNetworkRequestByNdpId(ndpId); |
| if (nnriE == null) { |
| Log.w(TAG, "onDataPathConfirm: network request not found for ndpId=" + ndpId); |
| if (accept) { |
| mMgr.endDataPath(ndpId); |
| } |
| return null; |
| } |
| |
| String networkSpecifier = nnriE.getKey(); |
| AwareNetworkRequestInformation nnri = nnriE.getValue(); |
| |
| // validate state |
| if (nnri.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR |
| && nnri.state != AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_CONFIRM) { |
| Log.w(TAG, "onDataPathConfirm: INITIATOR in invalid state=" + nnri.state); |
| mNetworkRequestsCache.remove(networkSpecifier); |
| if (accept) { |
| mMgr.endDataPath(ndpId); |
| } |
| return networkSpecifier; |
| } |
| if (nnri.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER |
| && nnri.state != AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_CONFIRM) { |
| Log.w(TAG, "onDataPathConfirm: RESPONDER in invalid state=" + nnri.state); |
| mNetworkRequestsCache.remove(networkSpecifier); |
| if (accept) { |
| mMgr.endDataPath(ndpId); |
| } |
| return networkSpecifier; |
| } |
| |
| if (accept) { |
| nnri.state = (nnri.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR) |
| ? AwareNetworkRequestInformation.STATE_INITIATOR_CONFIRMED |
| : AwareNetworkRequestInformation.STATE_RESPONDER_CONFIRMED; |
| nnri.peerDataMac = mac; |
| |
| NetworkInfo networkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI_P2P, 0, |
| NETWORK_TAG, ""); |
| NetworkCapabilities networkCapabilities = new NetworkCapabilities( |
| mNetworkCapabilitiesFilter); |
| LinkProperties linkProperties = new LinkProperties(); |
| |
| 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); |
| return networkSpecifier; |
| } |
| |
| if (!mNiWrapper.configureAgentProperties(nnri, networkSpecifier, ndpId, networkInfo, |
| networkCapabilities, linkProperties)) { |
| return networkSpecifier; |
| } |
| |
| nnri.networkAgent = new WifiAwareNetworkAgent(mLooper, mContext, |
| AGENT_TAG_PREFIX + nnri.ndpId, |
| new NetworkInfo(ConnectivityManager.TYPE_WIFI_P2P, 0, NETWORK_TAG, ""), |
| networkCapabilities, new LinkProperties(), NETWORK_FACTORY_SCORE_AVAIL, |
| networkSpecifier, ndpId); |
| nnri.networkAgent.sendNetworkInfo(networkInfo); |
| nnri.networkAgent.sendLinkProperties(linkProperties); |
| } else { |
| if (DBG) { |
| Log.d(TAG, "onDataPathConfirm: data-path for networkSpecifier=" + networkSpecifier |
| + " rejected - reason=" + reason); |
| } |
| mNetworkRequestsCache.remove(networkSpecifier); |
| } |
| |
| 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<String, AwareNetworkRequestInformation> nnriE = getNetworkRequestByNdpId(ndpId); |
| if (nnriE == null) { |
| if (DBG) { |
| Log.d(TAG, "onDataPathEnd: network request not found for ndpId=" + ndpId); |
| } |
| return; |
| } |
| |
| tearDownInterface(nnriE.getValue()); |
| mNetworkRequestsCache.remove(nnriE.getKey()); |
| } |
| |
| /** |
| * 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()) { |
| tearDownInterface(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(String 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; |
| } |
| |
| mMgr.endDataPath(nnri.ndpId); |
| } |
| |
| private class WifiAwareNetworkFactory extends NetworkFactory { |
| WifiAwareNetworkFactory(Looper looper, Context context, NetworkCapabilities filter) { |
| super(looper, context, NETWORK_TAG, filter); |
| } |
| |
| @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; |
| } |
| |
| String networkSpecifier = request.networkCapabilities.getNetworkSpecifier(); |
| if (TextUtils.isEmpty(networkSpecifier)) { |
| Log.w(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request |
| + " - empty (or null) NetworkSpecifier"); |
| return false; |
| } |
| |
| // 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!?"); |
| } |
| |
| // seems to happen after a network agent is created - trying to rematch all |
| // requests again!? |
| return true; |
| } |
| |
| // parse network specifier (JSON) & cache |
| // TODO: validate that the client ID actually comes from the correct process and is |
| // not faked? |
| nnri = AwareNetworkRequestInformation.parseNetworkSpecifier(networkSpecifier, mMgr); |
| if (nnri == null) { |
| Log.e(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request |
| + " - can't parse network specifier"); |
| 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); |
| } |
| |
| String networkSpecifier = networkRequest.networkCapabilities.getNetworkSpecifier(); |
| AwareNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier); |
| if (nnri == null) { |
| Log.e(TAG, "WifiAwareNetworkFactory.needNetworkFor: networkRequest=" |
| + networkRequest + " not in cache!?"); |
| return; |
| } |
| |
| if (nnri.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR) { |
| if (nnri.state != AwareNetworkRequestInformation.STATE_INITIATOR_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; |
| } |
| |
| nnri.interfaceName = selectInterfaceForRequest(nnri); |
| mMgr.initiateDataPathSetup(networkSpecifier, nnri.peerId, |
| NanDataPathChannelCfg.REQUEST_CHANNEL_SETUP, selectChannelForRequest(nnri), |
| nnri.peerDiscoveryMac, nnri.interfaceName, nnri.pmk); |
| nnri.state = |
| AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE; |
| } else { |
| if (nnri.state != AwareNetworkRequestInformation.STATE_RESPONDER_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; |
| } |
| |
| nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST; |
| } |
| } |
| |
| @Override |
| protected void releaseNetworkFor(NetworkRequest networkRequest) { |
| if (VDBG) { |
| Log.v(TAG, "WifiAwareNetworkFactory.releaseNetworkFor: networkRequest=" |
| + networkRequest); |
| } |
| |
| String networkSpecifier = networkRequest.networkCapabilities.getNetworkSpecifier(); |
| 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; |
| } |
| |
| if (nnri.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR && nnri.state |
| > AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE) { |
| mMgr.endDataPath(nnri.ndpId); |
| } |
| if (nnri.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER && nnri.state |
| > AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST) { |
| mMgr.endDataPath(nnri.ndpId); |
| } |
| |
| // 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. |
| } |
| } |
| |
| private class WifiAwareNetworkAgent extends NetworkAgent { |
| private NetworkInfo mNetworkInfo; |
| private String mNetworkSpecifier; |
| private int mNdpId; |
| |
| WifiAwareNetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, |
| NetworkCapabilities nc, LinkProperties lp, int score, String networkSpecifier, |
| int ndpId) { |
| super(looper, context, logTag, ni, nc, lp, score); |
| |
| mNetworkInfo = ni; |
| mNetworkSpecifier = networkSpecifier; |
| mNdpId = ndpId; |
| } |
| |
| @Override |
| protected void unwanted() { |
| if (VDBG) { |
| Log.v(TAG, "WifiAwareNetworkAgent.unwanted: networkSpecifier=" + mNetworkSpecifier |
| + ", ndpId=" + mNdpId); |
| } |
| |
| mMgr.endDataPath(mNdpId); |
| |
| // 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: networkSpecifier=" |
| + mNetworkSpecifier + ", ndpId=" + mNdpId); |
| } |
| |
| mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, ""); |
| sendNetworkInfo(mNetworkInfo); |
| } |
| } |
| |
| private void tearDownInterface(AwareNetworkRequestInformation nnri) { |
| if (VDBG) Log.v(TAG, "tearDownInterface: nnri=" + nnri); |
| |
| if (nnri.interfaceName != null && !nnri.interfaceName.isEmpty()) { |
| try { |
| mNwService.setInterfaceDown(nnri.interfaceName); |
| } catch (Exception e) { // NwService throws runtime exceptions for errors |
| Log.e(TAG, |
| "tearDownInterface: nnri=" + nnri + ": can't bring interface down - " + e); |
| } |
| } |
| |
| if (nnri.networkAgent != null) { |
| nnri.networkAgent.reconfigureAgentAsDisconnected(); |
| } |
| } |
| |
| /** |
| * Select one of the existing interfaces for the new network request. |
| * |
| * TODO: for now there is only a single interface - simply pick it. |
| */ |
| private String selectInterfaceForRequest(AwareNetworkRequestInformation req) { |
| Iterator<String> it = mInterfaces.iterator(); |
| if (it.hasNext()) { |
| return it.next(); |
| } |
| |
| Log.e(TAG, "selectInterfaceForRequest: req=" + req + " - but no interfaces available!"); |
| |
| return ""; |
| } |
| |
| /** |
| * Select a channel for the network request. |
| * |
| * TODO: for now simply select channel 6 |
| */ |
| 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_INITIATOR_IDLE = 100; |
| static final int STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE = 101; |
| static final int STATE_INITIATOR_WAIT_FOR_CONFIRM = 102; |
| static final int STATE_INITIATOR_CONFIRMED = 103; |
| |
| static final int STATE_RESPONDER_IDLE = 200; |
| static final int STATE_RESPONDER_WAIT_FOR_REQUEST = 201; |
| static final int STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE = 202; |
| static final int STATE_RESPONDER_WAIT_FOR_CONFIRM = 203; |
| static final int STATE_RESPONDER_CONFIRMED = 204; |
| |
| public int state; |
| public int role; |
| |
| public int uid; |
| public String interfaceName; |
| public int pubSubId = 0; |
| public int peerId = 0; |
| public byte[] peerDiscoveryMac = null; |
| public byte[] pmk = null; |
| public int ndpId; |
| public byte[] peerDataMac; |
| |
| public WifiAwareNetworkAgent networkAgent; |
| |
| static AwareNetworkRequestInformation parseNetworkSpecifier(String networkSpecifier, |
| WifiAwareStateManager mgr) { |
| int type, role, uid, clientId, sessionId = 0, peerId = 0, pubSubId = 0; |
| byte[] peerMac = null; |
| byte[] pmk = null; |
| |
| if (VDBG) { |
| Log.v(TAG, "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier); |
| } |
| |
| try { |
| JSONObject jsonObject = new JSONObject(networkSpecifier); |
| |
| // type: always present |
| type = jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_TYPE); |
| if (type < 0 || type > WifiAwareManager.NETWORK_SPECIFIER_TYPE_MAX_VALID) { |
| Log.e(TAG, "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier |
| + ", invalid 'type' value"); |
| return null; |
| } |
| |
| // role: always present |
| role = jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE); |
| if (role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR |
| && role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) { |
| Log.e(TAG, "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier |
| + " -- invalid 'role' value"); |
| return null; |
| } |
| |
| if (role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR |
| && type != WifiAwareManager.NETWORK_SPECIFIER_TYPE_IB |
| && type != WifiAwareManager.NETWORK_SPECIFIER_TYPE_OOB) { |
| Log.e(TAG, "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier |
| + " -- invalid 'type' value for INITIATOR (only IB and OOB are " |
| + "permitted)"); |
| return null; |
| } |
| |
| // clientId: always present |
| clientId = jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_CLIENT_ID); |
| |
| // sessionId: present for types IB, IB_ANY_PEER |
| if (type == WifiAwareManager.NETWORK_SPECIFIER_TYPE_IB |
| || type == WifiAwareManager.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER) { |
| sessionId = jsonObject.getInt( |
| WifiAwareManager.NETWORK_SPECIFIER_KEY_SESSION_ID); |
| } |
| |
| // peer Id: present for type IB |
| if (type == WifiAwareManager.NETWORK_SPECIFIER_TYPE_IB) { |
| peerId = jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_ID); |
| } |
| |
| // peerMac: present for type OOB |
| if (type == WifiAwareManager.NETWORK_SPECIFIER_TYPE_OOB) { |
| try { |
| peerMac = HexEncoding.decode(jsonObject.getString( |
| WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_MAC).toCharArray(), |
| false); |
| if (peerMac == null || peerMac.length != 6) { |
| Log.e(TAG, "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier |
| + " -- invalid peer MAC address - null or not 6 bytes"); |
| return null; |
| } |
| } catch (IllegalArgumentException e) { |
| Log.e(TAG, "parseNetworkSpecifier: networkSpecifier=" |
| + networkSpecifier + " -- invalid peer MAC address -- e=" + e); |
| return null; |
| } |
| } |
| |
| // pmk: always present (though can be an empty array - equivalent to null) |
| pmk = Base64.decode( |
| jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PMK), |
| Base64.DEFAULT); |
| if (pmk != null && pmk.length == 0) { |
| pmk = null; |
| } |
| } catch (JSONException e) { |
| Log.e(TAG, "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier |
| + " -- invalid JSON format -- e=" + e); |
| return null; |
| } |
| |
| // look up network specifier information in Aware state manager |
| WifiAwareClientState client = mgr.getClient(clientId); |
| if (client == null) { |
| Log.e(TAG, "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier |
| + " -- not client with this id -- clientId=" + clientId); |
| return null; |
| } |
| uid = client.getUid(); |
| |
| // validate the role (if session ID provided: i.e. session 1xx) |
| if (type == WifiAwareManager.NETWORK_SPECIFIER_TYPE_IB |
| || type == WifiAwareManager.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER) { |
| WifiAwareDiscoverySessionState session = client.getSession(sessionId); |
| if (session == null) { |
| Log.e(TAG, |
| "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier |
| + " -- no session with this id -- sessionId=" + sessionId); |
| return null; |
| } |
| |
| if ((session.isPublishSession() |
| && role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) || ( |
| !session.isPublishSession() |
| && role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR)) { |
| Log.e(TAG, "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier |
| + " -- invalid role for session type"); |
| return null; |
| } |
| |
| if (type == WifiAwareManager.NETWORK_SPECIFIER_TYPE_IB) { |
| pubSubId = session.getPubSubId(); |
| String peerMacStr = session.getMac(peerId, null); |
| if (peerMacStr == null) { |
| Log.e(TAG, "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier |
| + " -- no MAC address associated with this peer id -- peerId=" |
| + peerId); |
| return null; |
| } |
| try { |
| peerMac = HexEncoding.decode(peerMacStr.toCharArray(), false); |
| if (peerMac == null || peerMac.length != 6) { |
| Log.e(TAG, "parseNetworkSpecifier: networkSpecifier=" |
| + networkSpecifier + " -- invalid peer MAC address"); |
| return null; |
| } |
| } catch (IllegalArgumentException e) { |
| Log.e(TAG, |
| "parseNetworkSpecifier: networkSpecifier=" + networkSpecifier |
| + " -- invalid peer MAC address -- e=" + e); |
| return null; |
| } |
| } |
| } |
| |
| // create container and populate |
| AwareNetworkRequestInformation nnri = new AwareNetworkRequestInformation(); |
| nnri.state = (role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR) |
| ? AwareNetworkRequestInformation.STATE_INITIATOR_IDLE |
| : AwareNetworkRequestInformation.STATE_RESPONDER_IDLE; |
| nnri.role = role; |
| nnri.uid = uid; |
| nnri.pubSubId = pubSubId; |
| nnri.peerId = peerId; |
| nnri.peerDiscoveryMac = peerMac; |
| nnri.pmk = pmk; |
| |
| return nnri; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder("AwareNetworkRequestInformation: "); |
| sb.append("state=").append(state).append(", role=").append(role).append( |
| ", uid=").append(uid).append(", interfaceName=").append(interfaceName).append( |
| ", pubSubId=").append(pubSubId).append(", peerDiscoveryMac=").append( |
| peerDiscoveryMac == null ? "" |
| : String.valueOf(HexEncoding.encode(peerDiscoveryMac))).append( |
| ", ndpId=").append(ndpId).append(", peerDataMac=").append( |
| peerDataMac == null ? "" : String.valueOf(HexEncoding.encode(peerDataMac))); |
| 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, |
| String networkSpecifier, 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); |
| return false; |
| } |
| if (ni == null) { |
| Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri |
| + ": can't get network interface (null)"); |
| mMgr.endDataPath(ndpId); |
| return false; |
| } |
| Enumeration<InetAddress> 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); |
| return false; |
| } |
| |
| // configure agent |
| networkInfo.setIsAvailable(true); |
| networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null); |
| |
| networkCapabilities.setNetworkSpecifier(networkSpecifier); |
| |
| 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(" mNetworkCapabilitiesFilter: " + mNetworkCapabilitiesFilter); |
| pw.println(" mNetworkRequestsCache: " + mNetworkRequestsCache); |
| pw.println(" mNetworkFactory:"); |
| mNetworkFactory.dump(fd, pw, args); |
| } |
| } |