/* * 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 static android.net.RouteInfo.RTN_UNICAST; import android.content.Context; import android.hardware.wifi.V1_0.NanDataPathChannelCfg; import android.hardware.wifi.V1_0.NanStatusType; import android.hardware.wifi.V1_2.NanDataPathChannelInfo; import android.net.ConnectivityManager; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MacAddress; import android.net.MatchAllNetworkSpecifier; import android.net.NetworkAgent; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkFactory; import android.net.NetworkProvider; import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.RouteInfo; import android.net.wifi.aware.TlvBufferUtils; import android.net.wifi.aware.WifiAwareAgentNetworkSpecifier; import android.net.wifi.aware.WifiAwareManager; import android.net.wifi.aware.WifiAwareNetworkInfo; import android.net.wifi.aware.WifiAwareNetworkSpecifier; import android.net.wifi.aware.WifiAwareUtils; import android.net.wifi.util.HexEncoding; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.build.SdkLevel; import com.android.server.wifi.Clock; import com.android.server.wifi.util.NetdWrapper; import com.android.server.wifi.util.WifiPermissionsUtil; import com.android.server.wifi.util.WifiPermissionsWrapper; import com.android.wifi.resources.R; import java.io.FileDescriptor; import java.io.PrintWriter; import java.net.DatagramSocket; import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.net.UnknownHostException; import java.nio.ByteOrder; import java.util.Arrays; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; 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 VDBG = false; // STOPSHIP if true private boolean mDbg = false; 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; @VisibleForTesting public static final int ADDRESS_VALIDATION_RETRY_INTERVAL_MS = 1_000; // 1 second @VisibleForTesting public static final int ADDRESS_VALIDATION_TIMEOUT_MS = 5_000; // 5 seconds private final WifiAwareStateManager mMgr; private final Clock mClock; public NetworkInterfaceWrapper mNiWrapper = new NetworkInterfaceWrapper(); private static final NetworkCapabilities sNetworkCapabilitiesFilter = makeNetworkCapabilitiesFilter(); private final Set mInterfaces = new HashSet<>(); private final ArrayMap mNetworkRequestsCache = new ArrayMap<>(); private Context mContext; private WifiAwareMetrics mAwareMetrics; private WifiPermissionsUtil mWifiPermissionsUtil; private WifiPermissionsWrapper mPermissionsWrapper; private Looper mLooper; private Handler mHandler; private WifiAwareNetworkFactory mNetworkFactory; public NetdWrapper mNetdWrapper; // internal debug flag to override API check /* package */ boolean mAllowNdpResponderFromAnyOverride = false; public WifiAwareDataPathStateManager(WifiAwareStateManager mgr, Clock clock) { mMgr = mgr; mClock = clock; } private static NetworkCapabilities makeNetworkCapabilitiesFilter() { NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE) .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING) .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED) .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) .setNetworkSpecifier(new MatchAllNetworkSpecifier()) .setLinkUpstreamBandwidthKbps(NETWORK_FACTORY_BANDWIDTH_AVAIL) .setLinkDownstreamBandwidthKbps(NETWORK_FACTORY_BANDWIDTH_AVAIL) .setSignalStrength(NETWORK_FACTORY_SIGNAL_STRENGTH_AVAIL); if (SdkLevel.isAtLeastS()) { builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); } return builder.build(); } /** * Initialize the Aware data-path state manager. Specifically register the network factory with * connectivity service. */ public void start(Context context, Looper looper, WifiAwareMetrics awareMetrics, WifiPermissionsUtil wifiPermissionsUtil, WifiPermissionsWrapper permissionsWrapper, NetdWrapper netdWrapper) { if (VDBG) Log.v(TAG, "start"); mContext = context; mAwareMetrics = awareMetrics; mWifiPermissionsUtil = wifiPermissionsUtil; mPermissionsWrapper = permissionsWrapper; mNetdWrapper = netdWrapper; mLooper = looper; mHandler = new Handler(mLooper); mNetworkFactory = new WifiAwareNetworkFactory(looper, context, sNetworkCapabilitiesFilter); mNetworkFactory.setScoreFilter(NETWORK_FACTORY_SCORE_AVAIL); mNetworkFactory.register(); } /** * Enable verbose logging. */ public void enableVerboseLogging(boolean verbose) { mDbg = verbose | VDBG; } /** * Get the number of the NDPs is already set up. */ public int getNumOfNdps() { int numOfNdps = 0; for (AwareNetworkRequestInformation requestInformation : mNetworkRequestsCache.values()) { numOfNdps += requestInformation.ndpInfos.size(); } return numOfNdps; } private Map.Entry getNetworkRequestByNdpId(int ndpId) { for (Map.Entry entry : mNetworkRequestsCache.entrySet()) { if (entry.getValue().ndpInfos.contains(ndpId)) { return entry; } } return null; } private Map.Entry getNetworkRequestByCanonicalDescriptor(CanonicalConnectionInfo cci) { if (VDBG) Log.v(TAG, "getNetworkRequestByCanonicalDescriptor: cci=" + cci); for (Map.Entry entry : mNetworkRequestsCache.entrySet()) { if (VDBG) { Log.v(TAG, "getNetworkRequestByCanonicalDescriptor: entry=" + entry.getValue() + " --> cci=" + entry.getValue().getCanonicalDescriptor()); } if (entry.getValue().getCanonicalDescriptor().matches(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 (mDbg) 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 (mDbg) Log.v(TAG, "deleteAllInterfaces"); onAwareDownCleanupDataPaths(); 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 (mDbg) 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 (mDbg) 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. * @return False if has error, otherwise return true */ public boolean onDataPathInitiateSuccess(WifiAwareNetworkSpecifier networkSpecifier, int ndpId) { if (mDbg) { 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 false; } if (nnri.state != AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE) { Log.w(TAG, "onDataPathInitiateSuccess: network request in incorrect state: state=" + nnri.state); mMgr.endDataPath(ndpId); mNetworkRequestsCache.remove(networkSpecifier); declareUnfullfillable(nnri); return false; } NdpInfo ndpInfo = new NdpInfo(ndpId); ndpInfo.state = NdpInfo.STATE_WAIT_FOR_CONFIRM; ndpInfo.peerDiscoveryMac = nnri.specifiedPeerDiscoveryMac; nnri.ndpInfos.put(ndpId, ndpInfo); nnri.state = AwareNetworkRequestInformation.STATE_IN_SETUP; return true; } /** * 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 (mDbg) { 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; } mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri); if (nnri.state != AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE) { Log.w(TAG, "onDataPathInitiateFail: network request in incorrect state: state=" + nnri.state); } mAwareMetrics.recordNdpStatus(reason, networkSpecifier.isOutOfBand(), mClock.getElapsedSinceBootMillis()); } /** * 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. * @param message The app_info HAL field (peer's info: binary blob) * @return False if has error, otherwise return true */ public boolean onDataPathRequest(int pubSubId, byte[] mac, int ndpId, byte[] message) { if (mDbg) { Log.v(TAG, "onDataPathRequest: pubSubId=" + pubSubId + ", mac=" + String.valueOf( HexEncoding.encode(mac)) + ", ndpId=" + ndpId); } // it is also possible that this is an initiator-side data-path request indication (which // happens when the Responder responds). In such a case it will be matched by the NDP ID. Map.Entry nnriE = getNetworkRequestByNdpId(ndpId); if (nnriE != null) { if (VDBG) { Log.v(TAG, "onDataPathRequest: initiator-side indication for " + nnriE); } NdpInfo ndpInfo = nnriE.getValue().ndpInfos.get(ndpId); // potential transmission mechanism for port/transport-protocol information from // Responder (alternative to confirm message) NetworkInformationData.ParsedResults peerServerInfo = NetworkInformationData.parseTlv( message); if (ndpInfo == null) { Log.wtf(TAG, "onDataPathRequest: initiator-side ndpInfo is null?!"); return false; } if (peerServerInfo != null) { if (peerServerInfo.port != 0) { ndpInfo.peerPort = peerServerInfo.port; } if (peerServerInfo.transportProtocol != -1) { ndpInfo.peerTransportProtocol = peerServerInfo.transportProtocol; } if (peerServerInfo.ipv6Override != null) { ndpInfo.peerIpv6Override = peerServerInfo.ipv6Override; } } return false; //ignore this for NDP set up flow: it is used to obtain app_info from Resp } AwareNetworkRequestInformation nnri = null; WifiAwareNetworkSpecifier networkSpecifier = null; for (int i = 0; i < mNetworkRequestsCache.size(); i++) { AwareNetworkRequestInformation requestInfo = mNetworkRequestsCache.valueAt(i); /* * 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 (requestInfo.pubSubId != 0 && requestInfo.pubSubId != pubSubId) { continue; } if (requestInfo.specifiedPeerDiscoveryMac != null) { if (Arrays.equals(requestInfo.specifiedPeerDiscoveryMac, mac) && requestInfo.state == AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST) { // If a peer specific request matches, use it. networkSpecifier = mNetworkRequestsCache.keyAt(i); nnri = requestInfo; break; } continue; } // For Accept any, multiple NDP may setup in the same time. In idle or terminating state // it will not accept any request. if (requestInfo.state != AwareNetworkRequestInformation.STATE_IDLE && requestInfo.state != AwareNetworkRequestInformation.STATE_TERMINATING) { // If an accepts any request matches, continually find if there is a peer specific // one. If there isn't any matched peer specific one, use the accepts any peer // request. networkSpecifier = mNetworkRequestsCache.keyAt(i); nnri = requestInfo; } } if (nnri == null) { Log.w(TAG, "onDataPathRequest: can't find a request with specified pubSubId=" + pubSubId + ", mac=" + String.valueOf(HexEncoding.encode(mac))); if (VDBG) { Log.v(TAG, "onDataPathRequest: network request cache = " + mNetworkRequestsCache); } mMgr.respondToDataPathRequest(false, ndpId, "", null, null, null, false); return false; } if (nnri.interfaceName == null) { nnri.interfaceName = selectInterfaceForRequest(nnri); } if (nnri.interfaceName == null) { Log.w(TAG, "onDataPathRequest: request " + networkSpecifier + " no interface available"); mMgr.respondToDataPathRequest(false, ndpId, "", null, null, null, false); mNetworkRequestsCache.remove(networkSpecifier); mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri); return false; } NdpInfo ndpInfo = new NdpInfo(ndpId); ndpInfo.state = NdpInfo.STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE; ndpInfo.peerDiscoveryMac = mac; ndpInfo.startTimestamp = mClock.getElapsedSinceBootMillis(); nnri.ndpInfos.put(ndpId, ndpInfo); nnri.state = AwareNetworkRequestInformation.STATE_IN_SETUP; mMgr.respondToDataPathRequest(true, ndpId, nnri.interfaceName, nnri.networkSpecifier.pmk, nnri.networkSpecifier.passphrase, NetworkInformationData.buildTlv(nnri.networkSpecifier.port, nnri.networkSpecifier.transportProtocol), nnri.networkSpecifier.isOutOfBand()); return true; } /** * 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 (mDbg) { Log.v(TAG, "onRespondToDataPathRequest: ndpId=" + ndpId + ", success=" + success); } Map.Entry nnriE = getNetworkRequestByNdpId(ndpId); if (nnriE == null) { Log.w(TAG, "onRespondToDataPathRequest: can't find a request with specified ndpId=" + ndpId); if (VDBG) { Log.v(TAG, "onRespondToDataPathRequest: network request cache = " + mNetworkRequestsCache); } return; } WifiAwareNetworkSpecifier networkSpecifier = nnriE.getKey(); AwareNetworkRequestInformation nnri = nnriE.getValue(); NdpInfo ndpInfo = nnri.ndpInfos.get(ndpId); if (!success) { Log.w(TAG, "onRespondToDataPathRequest: request " + networkSpecifier + " failed responding"); mMgr.endDataPath(ndpId); nnri.ndpInfos.remove(ndpId); if (nnri.specifiedPeerDiscoveryMac != null) { mNetworkRequestsCache.remove(networkSpecifier); mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri); } mAwareMetrics.recordNdpStatus(reasonOnFailure, networkSpecifier.isOutOfBand(), ndpInfo.startTimestamp); return; } if (ndpInfo.state != NdpInfo.STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE || nnri.state == AwareNetworkRequestInformation.STATE_TERMINATING) { Log.w(TAG, "onRespondToDataPathRequest: request " + networkSpecifier + " is incorrect state=" + nnri.state); mMgr.endDataPath(ndpId); nnri.ndpInfos.remove(ndpId); if (nnri.specifiedPeerDiscoveryMac != null) { mNetworkRequestsCache.remove(networkSpecifier); mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri); } return; } ndpInfo.state = NdpInfo.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. * @param channelInfo Lists of channels used for this NDP. * @return False if has error, otherwise return true */ public boolean onDataPathConfirm(int ndpId, byte[] mac, boolean accept, int reason, byte[] message, List channelInfo) { if (mDbg) { Log.v(TAG, "onDataPathConfirm: ndpId=" + ndpId + ", mac=" + String.valueOf( HexEncoding.encode(mac)) + ", accept=" + accept + ", reason=" + reason + ", message.length=" + ((message == null) ? 0 : message.length) + ", channelInfo=" + channelInfo); } 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 false; } WifiAwareNetworkSpecifier networkSpecifier = nnriE.getKey(); AwareNetworkRequestInformation nnri = nnriE.getValue(); NdpInfo ndpInfo = nnri.ndpInfos.get(ndpId); // validate state if (ndpInfo.state != NdpInfo.STATE_WAIT_FOR_CONFIRM || nnri.state == AwareNetworkRequestInformation.STATE_TERMINATING) { Log.w(TAG, "onDataPathConfirm: invalid state=" + nnri.state); nnri.ndpInfos.remove(ndpId); if (nnri.specifiedPeerDiscoveryMac != null) { mNetworkRequestsCache.remove(networkSpecifier); mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri); } if (accept) { mMgr.endDataPath(ndpId); } return false; } if (accept) { ndpInfo.peerDataMac = mac; ndpInfo.state = NdpInfo.STATE_CONFIRMED; ndpInfo.channelInfo = channelInfo; nnri.state = AwareNetworkRequestInformation.STATE_CONFIRMED; // NetworkAgent may already be created for accept any peer request, interface should be // ready in that case. if (nnri.networkAgent == null && !isInterfaceUpAndUsedByAnotherNdp(nnri)) { try { mNetdWrapper.setInterfaceUp(nnri.interfaceName); mNetdWrapper.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); if (nnri.specifiedPeerDiscoveryMac != null) { declareUnfullfillable(nnri); } return true; } } else { if (VDBG) { Log.v(TAG, "onDataPathConfirm: interface already configured: " + nnri.interfaceName); } } // only relevant for the initiator if (nnri.networkSpecifier.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR) { NetworkInformationData.ParsedResults peerServerInfo = NetworkInformationData.parseTlv(message); if (peerServerInfo != null) { if (peerServerInfo.port != 0) { ndpInfo.peerPort = peerServerInfo.port; } if (peerServerInfo.transportProtocol != -1) { ndpInfo.peerTransportProtocol = peerServerInfo.transportProtocol; } if (peerServerInfo.ipv6Override != null) { ndpInfo.peerIpv6Override = peerServerInfo.ipv6Override; } } } nnri.startValidationTimestamp = mClock.getElapsedSinceBootMillis(); handleAddressValidation(nnri, ndpInfo, networkSpecifier.isOutOfBand(), mac); } else { if (VDBG) { Log.v(TAG, "onDataPathConfirm: data-path for networkSpecifier=" + networkSpecifier + " rejected - reason=" + reason); } nnri.ndpInfos.remove(ndpId); if (nnri.specifiedPeerDiscoveryMac != null) { mNetworkRequestsCache.remove(networkSpecifier); mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri); } mAwareMetrics.recordNdpStatus(reason, networkSpecifier.isOutOfBand(), ndpInfo.startTimestamp); } return true; } private void getInet6Address(NdpInfo ndpInfo, byte[] mac, String interfaceName) { try { byte[] addr; if (ndpInfo.peerIpv6Override == null) { addr = MacAddress.fromBytes(mac).getLinkLocalIpv6FromEui48Mac().getAddress(); } else { addr = new byte[16]; addr[0] = (byte) 0xfe; addr[1] = (byte) 0x80; addr[8] = ndpInfo.peerIpv6Override[0]; addr[9] = ndpInfo.peerIpv6Override[1]; addr[10] = ndpInfo.peerIpv6Override[2]; addr[11] = ndpInfo.peerIpv6Override[3]; addr[12] = ndpInfo.peerIpv6Override[4]; addr[13] = ndpInfo.peerIpv6Override[5]; addr[14] = ndpInfo.peerIpv6Override[6]; addr[15] = ndpInfo.peerIpv6Override[7]; } ndpInfo.peerIpv6 = Inet6Address.getByAddress(null, addr, NetworkInterface.getByName(interfaceName)); } catch (SocketException | UnknownHostException e) { if (mDbg) { Log.d(TAG, "onDataPathConfirm: error obtaining scoped IPv6 address -- " + e); } ndpInfo.peerIpv6 = null; } } private void handleAddressValidation(AwareNetworkRequestInformation nnri, NdpInfo ndpInfo, boolean isOutOfBand, byte[] mac) { final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder( sNetworkCapabilitiesFilter); LinkProperties linkProperties = new LinkProperties(); getInet6Address(ndpInfo, mac, nnri.interfaceName); if (!(ndpInfo.peerIpv6 != null && mNiWrapper.configureAgentProperties(nnri, ncBuilder, linkProperties) && mNiWrapper.isAddressUsable(linkProperties))) { if (VDBG) { Log.d(TAG, "Failed address validation"); } if (!isAddressValidationExpired(nnri, ndpInfo.ndpId)) { mHandler.postDelayed(() -> handleAddressValidation(nnri, ndpInfo, isOutOfBand, mac), ADDRESS_VALIDATION_RETRY_INTERVAL_MS); } return; } // Network agent may already setup finished. Update peer network info. if (nnri.networkAgent == null) { // Setup first NDP for new networkAgent. final WifiAwareNetworkInfo ni = new WifiAwareNetworkInfo(ndpInfo.peerIpv6, ndpInfo.peerPort, ndpInfo.peerTransportProtocol); ncBuilder.setTransportInfo(ni); if (VDBG) { Log.v(TAG, "onDataPathConfirm: AwareNetworkInfo=" + ni); } final NetworkAgentConfig naConfig = new NetworkAgentConfig.Builder() .setLegacyType(ConnectivityManager.TYPE_NONE) .setLegacyTypeName(NETWORK_TAG) .build(); nnri.networkAgent = new WifiAwareNetworkAgent(mLooper, mContext, AGENT_TAG_PREFIX + ndpInfo.ndpId, ncBuilder.build(), linkProperties, NETWORK_FACTORY_SCORE_AVAIL, naConfig, mNetworkFactory.getProvider(), nnri); mNiWrapper.setConnected(nnri.networkAgent); } mAwareMetrics.recordNdpStatus(NanStatusType.SUCCESS, isOutOfBand, ndpInfo.startTimestamp); mAwareMetrics.recordNdpCreation(nnri.uid, nnri.packageName, mNetworkRequestsCache); } private boolean isAddressValidationExpired(AwareNetworkRequestInformation nnri, int ndpId) { if (mClock.getElapsedSinceBootMillis() - nnri.startValidationTimestamp > ADDRESS_VALIDATION_TIMEOUT_MS) { Log.e(TAG, "Timed-out while waiting for IPv6 address to be usable"); mMgr.endDataPath(ndpId); if (nnri.specifiedPeerDiscoveryMac != null) { declareUnfullfillable(nnri); } return true; } return false; } private void declareUnfullfillable(AwareNetworkRequestInformation nnri) { mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri); nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING; } /** * 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 (mDbg) Log.v(TAG, "onDataPathEnd: ndpId=" + ndpId); Map.Entry nnriE = getNetworkRequestByNdpId(ndpId); if (nnriE == null) { if (VDBG) { Log.v(TAG, "onDataPathEnd: network request not found for ndpId=" + ndpId); } return; } AwareNetworkRequestInformation nnri = nnriE.getValue(); NdpInfo ndpInfo = nnri.ndpInfos.get(ndpId); nnri.ndpInfos.remove(ndpId); if (ndpInfo.state == NdpInfo.STATE_CONFIRMED) { mAwareMetrics.recordNdpSessionDuration(ndpInfo.startTimestamp); } if (nnri.specifiedPeerDiscoveryMac == null && nnri.state != AwareNetworkRequestInformation.STATE_TERMINATING) { return; } if (nnri.ndpInfos.size() == 0) { tearDownInterfaceIfPossible(nnri); mNetworkRequestsCache.remove(nnriE.getKey()); mNetworkFactory.tickleConnectivityIfWaiting(); } } /** * Notification (unsolicited/asynchronous) from the firmware that the channel for the specified * NDP ids has been updated. */ public void onDataPathSchedUpdate(byte[] peerMac, List ndpIds, List channelInfo) { if (mDbg) { Log.v(TAG, "onDataPathSchedUpdate: peerMac=" + MacAddress.fromBytes(peerMac).toString() + ", ndpIds=" + ndpIds + ", channelInfo=" + channelInfo); } for (int ndpId : ndpIds) { Map.Entry nnriE = getNetworkRequestByNdpId(ndpId); if (nnriE == null) { Log.e(TAG, "onDataPathSchedUpdate: ndpId=" + ndpId + " - not found"); continue; } NdpInfo ndpInfo = nnriE.getValue().ndpInfos.get(ndpId); if (!Arrays.equals(peerMac, ndpInfo.peerDiscoveryMac)) { Log.e(TAG, "onDataPathSchedUpdate: ndpId=" + ndpId + ", report NMI=" + MacAddress.fromBytes(peerMac).toString() + " doesn't match NDP NMI=" + MacAddress.fromBytes(ndpInfo.peerDiscoveryMac).toString()); continue; } ndpInfo.channelInfo = channelInfo; } } /** * Called whenever Aware comes down. Clean up all pending and up network requests and agents. */ public void onAwareDownCleanupDataPaths() { if (mDbg) Log.v(TAG, "onAwareDownCleanupDataPaths"); Iterator> it = mNetworkRequestsCache.entrySet().iterator(); while (it.hasNext()) { AwareNetworkRequestInformation nnri = it.next().getValue(); nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING; tearDownInterfaceIfPossible(nnri); it.remove(); } } /** * 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(int ndpId) { if (mDbg) Log.v(TAG, "handleDataPathTimeout: networkSpecifier=" + ndpId); Map.Entry nnriE = getNetworkRequestByNdpId(ndpId); if (nnriE == null) { if (VDBG) { Log.v(TAG, "handleDataPathTimeout: network request not found for networkSpecifier=" + ndpId); } return; } AwareNetworkRequestInformation nnri = nnriE.getValue(); NdpInfo ndpInfo = nnri.ndpInfos.get(ndpId); mAwareMetrics.recordNdpStatus(NanStatusType.INTERNAL_FAILURE, nnri.networkSpecifier.isOutOfBand(), ndpInfo.startTimestamp); mMgr.endDataPath(ndpId); nnri.ndpInfos.remove(ndpId); if (nnri.specifiedPeerDiscoveryMac != null) { mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri); mNetworkRequestsCache.remove(nnri.networkSpecifier); 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) { if (VDBG) { Log.v(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request); } NetworkSpecifier networkSpecifierBase = request.getNetworkSpecifier(); if (!(networkSpecifierBase instanceof WifiAwareNetworkSpecifier)) { Log.w(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request + " - not a WifiAwareNetworkSpecifier"); return false; } if (!mMgr.isUsageEnabled()) { if (VDBG) { Log.v(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request + " -- Aware disabled"); } releaseRequestAsUnfulfillableByAnyFactory(request); return false; } if (mInterfaces.isEmpty()) { Log.w(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request + " -- No Aware interfaces are up"); releaseRequestAsUnfulfillableByAnyFactory(request); return false; } WifiAwareNetworkSpecifier networkSpecifier = (WifiAwareNetworkSpecifier) networkSpecifierBase; // look up specifier - are we being called again? AwareNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier); if (nnri != null) { if (VDBG) { Log.v(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(request, networkSpecifier, mMgr, mWifiPermissionsUtil, mPermissionsWrapper, mAllowNdpResponderFromAnyOverride); if (nnri == null) { Log.e(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request + " - can't parse network specifier"); releaseRequestAsUnfulfillableByAnyFactory(request); 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(request); } return false; } mNetworkRequestsCache.put(networkSpecifier, nnri); mAwareMetrics.recordNdpRequestType(networkSpecifier.type); return true; } @Override protected void needNetworkFor(NetworkRequest networkRequest) { if (mDbg) { Log.v(TAG, "WifiAwareNetworkFactory.needNetworkFor: networkRequest=" + networkRequest); } NetworkSpecifier networkSpecifierObj = networkRequest.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 (VDBG) { Log.v(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); letAppKnowThatRequestsAreUnavailable(nnri); return; } mMgr.initiateDataPathSetup(networkSpecifier, nnri.specifiedPeerInstanceId, NanDataPathChannelCfg.CHANNEL_NOT_REQUESTED, selectChannelForRequest(nnri), nnri.specifiedPeerDiscoveryMac, nnri.interfaceName, nnri.networkSpecifier.pmk, nnri.networkSpecifier.passphrase, nnri.networkSpecifier.isOutOfBand(), null); nnri.state = AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE; } else { nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST; } } @Override protected void releaseNetworkFor(NetworkRequest networkRequest) { if (mDbg) { Log.v(TAG, "WifiAwareNetworkFactory.releaseNetworkFor: networkRequest=" + networkRequest); } NetworkSpecifier networkSpecifierObj = networkRequest.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(networkRequest); if (nnri.equivalentRequests.isEmpty()) { if (mDbg) { Log.v(TAG, "releaseNetworkFor: there are no further requests, networkRequest=" + networkRequest); } if (nnri.ndpInfos.size() != 0) { if (VDBG) Log.v(TAG, "releaseNetworkFor: in progress NDP being terminated"); for (int index = 0; index < nnri.ndpInfos.size(); index++) { mMgr.endDataPath(nnri.ndpInfos.keyAt(index)); } nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING; } else { mNetworkRequestsCache.remove(networkSpecifier); } } else { if (VDBG) { Log.v(TAG, "releaseNetworkFor: equivalent requests exist - not terminating " + "networkRequest=" + networkRequest); } } } void letAppKnowThatRequestsAreUnavailable(AwareNetworkRequestInformation nnri) { for (NetworkRequest nr : nnri.equivalentRequests) { releaseRequestAsUnfulfillableByAnyFactory(nr); } } } /** * Network agent for Wi-Fi Aware. */ @VisibleForTesting public class WifiAwareNetworkAgent extends NetworkAgent { private final AwareNetworkRequestInformation mAwareNetworkRequestInfo; @VisibleForTesting public final NetworkCapabilities mDataPathCapabilities; WifiAwareNetworkAgent(Looper looper, Context context, String logTag, NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config, NetworkProvider provider, AwareNetworkRequestInformation anri) { super(context, looper, logTag, nc, lp, score, config, provider); mAwareNetworkRequestInfo = anri; mDataPathCapabilities = nc; register(); } @Override public void onNetworkUnwanted() { if (mDbg) { Log.v(TAG, "WifiAwareNetworkAgent.unwanted: request=" + mAwareNetworkRequestInfo); } for (int index = 0; index < mAwareNetworkRequestInfo.ndpInfos.size(); index++) { mMgr.endDataPath(mAwareNetworkRequestInfo.ndpInfos.keyAt(index)); } 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 (mDbg) { Log.v(TAG, "WifiAwareNetworkAgent.reconfigureAgentAsDisconnected: request=" + mAwareNetworkRequestInfo); } unregister(); } } 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 (mDbg) { Log.v(TAG, "tearDownInterfaceIfPossible: interfaceName=" + nnri.interfaceName + ", still in use - not turning down"); } } else { try { mNetdWrapper.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) { mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri); } else { 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). * * Criteria: * 1. Select a network interface which is unused. This is because the network stack does not * support multiple networks per interface. * 2. If no network interface is available then (based on a device overlay) either fail (new * behavior) or (preserve legacy behavior) pick an interface which isn't used for * communication to the same peer. */ private String selectInterfaceForRequest(AwareNetworkRequestInformation req) { SortedSet unused = new TreeSet<>(mInterfaces); Set invalid = new HashSet<>(); SortedSet inuse = new TreeSet<>(); if (mDbg) { Log.v(TAG, "selectInterfaceForRequest: req=" + req + ", mNetworkRequestsCache=" + mNetworkRequestsCache); } for (AwareNetworkRequestInformation nnri : mNetworkRequestsCache.values()) { if (nnri == req || nnri.interfaceName == null) { continue; } if (Arrays.equals(req.specifiedPeerDiscoveryMac, nnri.specifiedPeerDiscoveryMac)) { invalid.add(nnri.interfaceName); } else { inuse.add(nnri.interfaceName); } unused.remove(nnri.interfaceName); } if (VDBG) { Log.v(TAG, "selectInterfaceForRequest: unUsed=" + unused + ", inuse=" + inuse + ", invalid" + invalid + ", allInterfaces" + mInterfaces); } // If any interface is unused, pick it first if (!unused.isEmpty()) { return unused.first(); } // If device doesn't allow to make multiple network on same interface, return null. if (!mContext.getResources() .getBoolean(R.bool.config_wifiAllowMultipleNetworksOnSameAwareNdi)) { Log.e(TAG, "selectInterfaceForRequest: req=" + req + " - no interfaces available!"); return null; } for (String iface : inuse) { if (!invalid.contains(iface)) { return iface; } } 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; } private static class NdpInfo { static final int STATE_WAIT_FOR_CONFIRM = 107; static final int STATE_CONFIRMED = 108; static final int STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE = 109; public int state; public byte[] peerDiscoveryMac = null; public int peerInstanceId = 0; public int ndpId = 0; // 0 is never a valid ID! public byte[] peerDataMac; public Inet6Address peerIpv6; public int peerPort = 0; // uninitialized (invalid) value public int peerTransportProtocol = -1; // uninitialized (invalid) value public byte[] peerIpv6Override = null; public List channelInfo; public long startTimestamp = 0; // request is made (initiator) / get request (responder) NdpInfo(int ndpId) { this.ndpId = ndpId; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(", ndpInfo["); sb.append("ndpId=").append(ndpId).append(", peerInstanceId=").append( peerInstanceId).append(", peerDiscoveryMac=").append( peerDiscoveryMac == null ? "" : String.valueOf(HexEncoding.encode(peerDiscoveryMac))) .append(", peerDataMac=").append( peerDataMac == null ? "" : String.valueOf(HexEncoding.encode(peerDataMac))) .append(", peerIpv6=").append(peerIpv6).append( ", peerPort=").append( peerPort).append(", peerTransportProtocol=").append( peerTransportProtocol).append(", startTimestamp=").append( startTimestamp).append(", channelInfo=").append( channelInfo); sb.append("]"); return sb.toString(); } } /** * 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_CONFIRMED = 101; static final int STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE = 102; static final int STATE_RESPONDER_WAIT_FOR_REQUEST = 103; static final int STATE_TERMINATING = 104; static final int STATE_IN_SETUP = 105; public int state; public int uid; public String packageName; public String interfaceName; public int pubSubId = 0; public int specifiedPeerInstanceId = 0; public byte[] specifiedPeerDiscoveryMac = null; public WifiAwareNetworkSpecifier networkSpecifier; public long startValidationTimestamp = 0; // NDP created and starting to validate IPv6 addr public SparseArray ndpInfos = new SparseArray(); public WifiAwareNetworkAgent networkAgent; /* A collection of request which are equivalent to the current request and are * supported by it's agent. This list DOES include the original (first) network request * (whose specifier is also stored separately above). */ public Set equivalentRequests = new HashSet<>(); void updateToSupportNewRequest(NetworkRequest ns) { if (VDBG) Log.v(TAG, "updateToSupportNewRequest: ns=" + ns); if (equivalentRequests.add(ns) && state == STATE_CONFIRMED) { if (networkAgent == null) { Log.wtf(TAG, "updateToSupportNewRequest: null agent in CONFIRMED state!?"); return; } networkAgent.sendNetworkCapabilities(getNetworkCapabilities()); } } void removeSupportForRequest(NetworkRequest ns) { if (VDBG) Log.v(TAG, "removeSupportForRequest: ns=" + ns); equivalentRequests.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() { final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(sNetworkCapabilitiesFilter); builder.setNetworkSpecifier(new WifiAwareAgentNetworkSpecifier( equivalentRequests.stream() .map(NetworkRequest::getNetworkSpecifier) .toArray(WifiAwareNetworkSpecifier[]::new))); if (ndpInfos.size() == 0) { Log.wtf(TAG, "Number of NDPs is 0 when Network agent is created?! " + "AwareNetworkRequestInformation" + this); return builder.setTransportInfo(new WifiAwareNetworkInfo()).build(); } if (ndpInfos.valueAt(0).peerIpv6 != null) { builder.setTransportInfo( new WifiAwareNetworkInfo(ndpInfos.valueAt(0).peerIpv6, ndpInfos.valueAt(0).peerPort, ndpInfos.valueAt(0).peerTransportProtocol)); } return builder.build(); } /** * Returns a canonical descriptor for the network request. */ CanonicalConnectionInfo getCanonicalDescriptor() { return new CanonicalConnectionInfo(specifiedPeerDiscoveryMac, networkSpecifier.pmk, networkSpecifier.sessionId, networkSpecifier.passphrase); } static AwareNetworkRequestInformation processNetworkSpecifier(NetworkRequest request, WifiAwareNetworkSpecifier ns, WifiAwareStateManager mgr, WifiPermissionsUtil wifiPermissionsUtil, WifiPermissionsWrapper permissionWrapper, boolean allowNdpResponderFromAnyOverride) { int uid, pubSubId = 0; int peerInstanceId = 0; String packageName = null; 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(); packageName = client.getCallingPackage(); // API change post 30: allow accepts any peer responder. if (!SdkLevel.isAtLeastS() && !allowNdpResponderFromAnyOverride && !wifiPermissionsUtil.isTargetSdkLessThan( client.getCallingPackage(), Build.VERSION_CODES.P, uid)) { if (ns.type != WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB && ns.type != WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB) { Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns + " -- no ANY specifications allowed for this API level"); return null; } } // validate the port & transportProtocol if (ns.port < 0 || ns.transportProtocol < -1) { Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns + " -- invalid port/transportProtocol"); return null; } if (ns.port != 0 || ns.transportProtocol != -1) { if (ns.role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) { Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns + " -- port/transportProtocol can only be specified on responder"); return null; } if (TextUtils.isEmpty(ns.passphrase) && ns.pmk == null) { Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns + " -- port/transportProtocol can only be specified on secure ndp"); return null; } } // 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 && package name if (request.getRequestorUid() != uid || !TextUtils.equals(request.getRequestorPackageName(), packageName)) { Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns.toString() + " -- UID or package name mismatch to clientId's uid=" + uid + ", packageName=" + packageName); 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.packageName = packageName; nnri.pubSubId = pubSubId; nnri.specifiedPeerInstanceId = peerInstanceId; nnri.specifiedPeerDiscoveryMac = peerMac; nnri.networkSpecifier = ns; nnri.equivalentRequests.add(request); 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(", packageName=").append(packageName) .append(", interfaceName=").append(interfaceName).append( ", pubSubId=").append(pubSubId).append(", specifiedPeerInstanceId=").append( specifiedPeerInstanceId).append(", specifiedPeerDiscoveryMac=").append( specifiedPeerDiscoveryMac == null ? "" : String.valueOf(HexEncoding.encode(specifiedPeerDiscoveryMac))) .append(", equivalentSpecifiers=["); for (NetworkRequest nr : equivalentRequests) { sb.append(nr.toString()).append(", "); } sb.append("]"); sb.append(", NdpInfos["); for (int index = 0; index < ndpInfos.size(); index++) { sb.append(" ").append(index).append(": ").append(ndpInfos.valueAt(index)); } sb.append("]"); return sb.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; public boolean matches(CanonicalConnectionInfo other) { return Arrays.equals(peerDiscoveryMac, other.peerDiscoveryMac) && Arrays.equals(pmk, other.pmk) && TextUtils.equals(passphrase, other.passphrase) && (TextUtils.isEmpty(passphrase) || sessionId == other.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, NetworkCapabilities.Builder ncBuilder, LinkProperties linkProperties) { // find link-local address InetAddress linkLocal = null; NetworkInterface ni; try { ni = NetworkInterface.getByName(nnri.interfaceName); } catch (SocketException e) { Log.v(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri + ": can't get network interface - " + e); return false; } if (ni == null) { Log.v(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri + ": can't get network interface (null)"); 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.v(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri + ": no link local addresses"); return false; } ncBuilder.setRequestorUid(nnri.uid); ncBuilder.setRequestorPackageName(nnri.packageName); ncBuilder.setNetworkSpecifier(new WifiAwareAgentNetworkSpecifier( nnri.equivalentRequests.stream() .map(NetworkRequest::getNetworkSpecifier) .toArray(WifiAwareNetworkSpecifier[]::new))); linkProperties.setInterfaceName(nnri.interfaceName); linkProperties.addLinkAddress(new LinkAddress(linkLocal, 64)); linkProperties.addRoute( new RouteInfo(new IpPrefix("fe80::/64"), null, nnri.interfaceName, RTN_UNICAST)); return true; } /** * Tries binding to the input address to check whether it is configured (and therefore * usable). */ public boolean isAddressUsable(LinkProperties linkProperties) { InetAddress address = linkProperties.getLinkAddresses().get(0).getAddress(); DatagramSocket testDatagramSocket = null; try { testDatagramSocket = new DatagramSocket(0, address); } catch (SocketException e) { if (mDbg) { Log.v(TAG, "Can't create socket on address " + address + " -- " + e); } return false; } finally { if (testDatagramSocket != null) { testDatagramSocket.close(); } } return true; } /** * Tell the network agent the network is now connected. */ public void setConnected(WifiAwareNetworkAgent networkAgent) { networkAgent.markConnected(); } } /** * Utility (hence static) class encapsulating the data structure used to communicate Wi-Fi Aware * specific network capabilities. The TLV is defined as part of the NANv3 spec: * * - Generic Service Protocol * - Port * - Transport protocol */ @VisibleForTesting public static class NetworkInformationData { // All package visible to allow usage in unit testing /* package */ static final int IPV6_LL_TYPE = 0x00; // Table 82 /* package */ static final int SERVICE_INFO_TYPE = 0x01; // Table 83 /* package */ static final byte[] WFA_OUI = {0x50, 0x6F, (byte) 0x9A}; // Table 83 /* package */ static final int GENERIC_SERVICE_PROTOCOL_TYPE = 0x02; // Table 50 /* package */ static final int SUB_TYPE_PORT = 0x00; // Table 127 /* package */ static final int SUB_TYPE_TRANSPORT_PROTOCOL = 0x01; // Table 128 /** * Construct the TLV. */ public static byte[] buildTlv(int port, int transportProtocol) { if (port == 0 && transportProtocol == -1) { return null; } TlvBufferUtils.TlvConstructor tlvc = new TlvBufferUtils.TlvConstructor(1, 2); tlvc.setByteOrder(ByteOrder.LITTLE_ENDIAN); tlvc.allocate(20); // safe size for now tlvc.putRawByteArray(WFA_OUI); tlvc.putRawByte((byte) GENERIC_SERVICE_PROTOCOL_TYPE); if (port != 0) { tlvc.putShort(SUB_TYPE_PORT, (short) port); } if (transportProtocol != -1) { tlvc.putByte(SUB_TYPE_TRANSPORT_PROTOCOL, (byte) transportProtocol); } byte[] subTypes = tlvc.getArray(); tlvc.allocate(20); tlvc.putByteArray(SERVICE_INFO_TYPE, subTypes); return tlvc.getArray(); } static class ParsedResults { ParsedResults(int port, int transportProtocol, byte[] ipv6Override) { this.port = port; this.transportProtocol = transportProtocol; this.ipv6Override = ipv6Override; } public int port = 0; public int transportProtocol = -1; public byte[] ipv6Override = null; } /** * Parse the TLV and returns: * - Null on parsing error * - otherwise */ public static ParsedResults parseTlv(byte[] tlvs) { int port = 0; int transportProtocol = -1; byte[] ipv6Override = null; try { TlvBufferUtils.TlvIterable tlvi = new TlvBufferUtils.TlvIterable(1, 2, tlvs); tlvi.setByteOrder(ByteOrder.LITTLE_ENDIAN); for (TlvBufferUtils.TlvElement tlve : tlvi) { switch (tlve.type) { case IPV6_LL_TYPE: if (tlve.length != 8) { // 8 bytes in IPv6 address Log.e(TAG, "NetworkInformationData: invalid IPv6 TLV -- length: " + tlve.length); return null; } ipv6Override = tlve.getRawData(); break; case SERVICE_INFO_TYPE: Pair serviceInfo = parseServiceInfoTlv( tlve.getRawData()); if (serviceInfo == null) { return null; } port = serviceInfo.first; transportProtocol = serviceInfo.second; break; default: Log.w(TAG, "NetworkInformationData: ignoring unknown T -- " + tlve.type); break; } } } catch (Exception e) { Log.e(TAG, "NetworkInformationData: error parsing TLV -- " + e); return null; } return new ParsedResults(port, transportProtocol, ipv6Override); } /** * Parse the Service Info TLV: * - Returns null on error * - Returns otherwise */ private static Pair parseServiceInfoTlv(byte[] tlv) { int port = 0; int transportProtocol = -1; if (tlv.length < 4) { Log.e(TAG, "NetworkInformationData: invalid SERVICE_INFO_TYPE length"); return null; } if (tlv[0] != WFA_OUI[0] || tlv[1] != WFA_OUI[1] || tlv[2] != WFA_OUI[2]) { Log.e(TAG, "NetworkInformationData: unexpected OUI"); return null; } if (tlv[3] != GENERIC_SERVICE_PROTOCOL_TYPE) { Log.e(TAG, "NetworkInformationData: invalid type -- " + tlv[3]); return null; } TlvBufferUtils.TlvIterable subTlvi = new TlvBufferUtils.TlvIterable(1, 2, Arrays.copyOfRange(tlv, 4, tlv.length)); subTlvi.setByteOrder(ByteOrder.LITTLE_ENDIAN); for (TlvBufferUtils.TlvElement subTlve : subTlvi) { switch (subTlve.type) { case SUB_TYPE_PORT: if (subTlve.length != 2) { Log.e(TAG, "NetworkInformationData: invalid port TLV " + "length -- " + subTlve.length); return null; } port = subTlve.getShort(); if (port < 0) { port += -2 * (int) Short.MIN_VALUE; } if (port == 0) { Log.e(TAG, "NetworkInformationData: invalid port " + port); return null; } break; case SUB_TYPE_TRANSPORT_PROTOCOL: if (subTlve.length != 1) { Log.e(TAG, "NetworkInformationData: invalid transport " + "protocol TLV length -- " + subTlve.length); return null; } transportProtocol = subTlve.getByte(); if (transportProtocol < 0) { transportProtocol += -2 * (int) Byte.MIN_VALUE; } break; default: Log.w(TAG, "NetworkInformationData: ignoring unknown " + "SERVICE_INFO.T -- " + subTlve.type); break; } } return Pair.create(port, transportProtocol); } } /** * 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); } }