blob: 18ff047a2a7fa4378ad977622bef88d90a7216d7 [file] [log] [blame]
/*
* 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 com.android.internal.annotations.VisibleForTesting;
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 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
/* package */ 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<String> mInterfaces = new HashSet<>();
private final Map<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation>
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() {
return 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)
.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();
}
private Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation>
getNetworkRequestByNdpId(int ndpId) {
for (Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> entry :
mNetworkRequestsCache.entrySet()) {
if (entry.getValue().ndpId == ndpId) {
return entry;
}
}
return null;
}
private Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation>
getNetworkRequestByCanonicalDescriptor(CanonicalConnectionInfo cci) {
if (VDBG) Log.v(TAG, "getNetworkRequestByCanonicalDescriptor: cci=" + cci);
for (Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> 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 (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");
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);
}
mMgr.releaseAwareInterface();
}
/**
* Called when firmware indicates the an interface was created.
*/
public void onInterfaceCreated(String interfaceName) {
if (VDBG) Log.v(TAG, "onInterfaceCreated: interfaceName=" + interfaceName);
if (mInterfaces.contains(interfaceName)) {
Log.w(TAG, "onInterfaceCreated: already contains interface -- " + interfaceName);
}
mInterfaces.add(interfaceName);
}
/**
* Called when firmware indicates the an interface was deleted.
*/
public void onInterfaceDeleted(String interfaceName) {
if (VDBG) Log.v(TAG, "onInterfaceDeleted: interfaceName=" + interfaceName);
if (!mInterfaces.contains(interfaceName)) {
Log.w(TAG, "onInterfaceDeleted: interface not on list -- " + interfaceName);
}
mInterfaces.remove(interfaceName);
}
/**
* Response to initiating data-path request. Indicates that request is successful (not
* complete!) and is now in progress.
*
* @param networkSpecifier The network specifier provided as part of the initiate request.
* @param ndpId The ID assigned to the data-path.
*/
public void onDataPathInitiateSuccess(WifiAwareNetworkSpecifier networkSpecifier, int ndpId) {
if (VDBG) {
Log.v(TAG,
"onDataPathInitiateSuccess: networkSpecifier=" + networkSpecifier + ", ndpId="
+ ndpId);
}
AwareNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier);
if (nnri == null) {
Log.w(TAG, "onDataPathInitiateSuccess: network request not found for networkSpecifier="
+ networkSpecifier);
mMgr.endDataPath(ndpId);
return;
}
if (nnri.state
!= AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE) {
Log.w(TAG, "onDataPathInitiateSuccess: network request in incorrect state: state="
+ nnri.state);
mNetworkRequestsCache.remove(networkSpecifier);
declareUnfullfillableAndEndDp(nnri, ndpId);
return;
}
nnri.state = AwareNetworkRequestInformation.STATE_WAIT_FOR_CONFIRM;
nnri.ndpId = ndpId;
}
/**
* Response to an attempt to set up a data-path (on the initiator side).
*
* @param networkSpecifier The network specifier provided as part of the initiate request.
* @param reason Failure reason.
*/
public void onDataPathInitiateFail(WifiAwareNetworkSpecifier networkSpecifier, int reason) {
if (VDBG) {
Log.v(TAG,
"onDataPathInitiateFail: networkSpecifier=" + networkSpecifier + ", reason="
+ reason);
}
AwareNetworkRequestInformation nnri = mNetworkRequestsCache.remove(networkSpecifier);
if (nnri == null) {
Log.w(TAG, "onDataPathInitiateFail: network request not found for networkSpecifier="
+ networkSpecifier);
return;
}
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(), nnri.startTimestamp);
}
/**
* Notification (unsolicited/asynchronous) that a peer has requested to set up a data-path
* connection with us.
*
* @param pubSubId The ID of the discovery session context for the data-path - or 0 if not
* related to a discovery session.
* @param mac The discovery MAC address of the peer.
* @param ndpId The locally assigned ID for the data-path.
* @param message The app_info HAL field (peer's info: binary blob)
* @return The network specifier of the data-path (or null if none/error)
*/
public WifiAwareNetworkSpecifier onDataPathRequest(int pubSubId, byte[] mac, int ndpId,
byte[] message) {
if (VDBG) {
Log.v(TAG,
"onDataPathRequest: pubSubId=" + pubSubId + ", mac=" + String.valueOf(
HexEncoding.encode(mac)) + ", ndpId=" + ndpId);
}
WifiAwareNetworkSpecifier networkSpecifier = null;
AwareNetworkRequestInformation nnri = null;
for (Map.Entry<WifiAwareNetworkSpecifier, 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.
* - The request must be pending (i.e. we could have completed requests for the same
* parameters)
*/
if (entry.getValue().pubSubId != 0 && entry.getValue().pubSubId != pubSubId) {
continue;
}
if (entry.getValue().peerDiscoveryMac != null && !Arrays.equals(
entry.getValue().peerDiscoveryMac, mac)) {
continue;
}
if (entry.getValue().state
!= AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST) {
continue;
}
networkSpecifier = entry.getKey();
nnri = entry.getValue();
break;
}
// 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<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> nnriE =
getNetworkRequestByNdpId(ndpId);
if (nnriE != null) {
if (VDBG) {
Log.v(TAG,
"onDataPathRequest: initiator-side indication for " + nnriE.getValue());
}
// potential transmission mechanism for port/transport-protocol information from
// Responder (alternative to confirm message)
NetworkInformationData.ParsedResults peerServerInfo = NetworkInformationData.parseTlv(
message);
if (peerServerInfo != null) {
if (peerServerInfo.port != 0) {
nnriE.getValue().peerPort = peerServerInfo.port;
}
if (peerServerInfo.transportProtocol != -1) {
nnriE.getValue().peerTransportProtocol = peerServerInfo.transportProtocol;
}
if (peerServerInfo.ipv6Override != null) {
nnriE.getValue().peerIpv6Override = peerServerInfo.ipv6Override;
}
}
return null; // ignore this for NDP set up flow: it is used to obtain app_info from Resp
}
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 null;
}
if (nnri.peerDiscoveryMac == null) {
// the "accept anyone" request is now specific
nnri.peerDiscoveryMac = mac;
}
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 null;
}
nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE;
nnri.ndpId = ndpId;
nnri.startTimestamp = mClock.getElapsedSinceBootMillis();
mMgr.respondToDataPathRequest(true, ndpId, nnri.interfaceName, nnri.networkSpecifier.pmk,
nnri.networkSpecifier.passphrase,
NetworkInformationData.buildTlv(nnri.networkSpecifier.port,
nnri.networkSpecifier.transportProtocol),
nnri.networkSpecifier.isOutOfBand());
return networkSpecifier;
}
/**
* Called on the RESPONDER when the response to data-path request has been completed.
*
* @param ndpId The ID of the data-path (NDP)
* @param success Whether or not the 'RespondToDataPathRequest' operation was a success.
*/
public void onRespondToDataPathRequest(int ndpId, boolean success, int reasonOnFailure) {
if (VDBG) {
Log.v(TAG, "onRespondToDataPathRequest: ndpId=" + ndpId + ", success=" + success);
}
WifiAwareNetworkSpecifier networkSpecifier = null;
AwareNetworkRequestInformation nnri = null;
for (Map.Entry<WifiAwareNetworkSpecifier, 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 (VDBG) {
Log.v(TAG, "onRespondToDataPathRequest: network request cache = "
+ mNetworkRequestsCache);
}
return;
}
if (!success) {
Log.w(TAG, "onRespondToDataPathRequest: request " + networkSpecifier
+ " failed responding");
mMgr.endDataPath(ndpId);
mNetworkRequestsCache.remove(networkSpecifier);
mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri);
mAwareMetrics.recordNdpStatus(reasonOnFailure, networkSpecifier.isOutOfBand(),
nnri.startTimestamp);
return;
}
if (nnri.state
!= AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE) {
Log.w(TAG, "onRespondToDataPathRequest: request " + networkSpecifier
+ " is incorrect state=" + nnri.state);
mMgr.endDataPath(ndpId);
mNetworkRequestsCache.remove(networkSpecifier);
mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri);
return;
}
nnri.state = AwareNetworkRequestInformation.STATE_WAIT_FOR_CONFIRM;
}
/**
* Notification (unsolicited/asynchronous) that the data-path (which we've been setting up)
* is possibly (if {@code accept} is {@code true}) ready for use from the firmware's
* perspective - now can do L3 configuration.
*
* @param ndpId Id of the data-path
* @param mac The MAC address of the peer's data-path (not discovery interface). Only
* valid
* if {@code accept} is {@code true}.
* @param accept Indicates whether the data-path setup has succeeded (been accepted) or
* failed (been rejected).
* @param reason If {@code accept} is {@code false} provides a reason code for the
* rejection/failure.
* @param message The message provided by the peer as part of the data-path setup
* process.
* @param channelInfo Lists of channels used for this NDP.
* @return The network specifier of the data-path or a null if none/error.
*/
public WifiAwareNetworkSpecifier onDataPathConfirm(int ndpId, byte[] mac, boolean accept,
int reason, byte[] message, List<NanDataPathChannelInfo> channelInfo) {
if (VDBG) {
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<WifiAwareNetworkSpecifier, 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;
}
WifiAwareNetworkSpecifier networkSpecifier = nnriE.getKey();
AwareNetworkRequestInformation nnri = nnriE.getValue();
// validate state
if (nnri.state != AwareNetworkRequestInformation.STATE_WAIT_FOR_CONFIRM) {
Log.w(TAG, "onDataPathConfirm: invalid state=" + nnri.state);
mNetworkRequestsCache.remove(networkSpecifier);
mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri);
if (accept) {
mMgr.endDataPath(ndpId);
}
return networkSpecifier;
}
if (accept) {
nnri.state = AwareNetworkRequestInformation.STATE_CONFIRMED;
nnri.peerDataMac = mac;
nnri.channelInfo = channelInfo;
final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder(
sNetworkCapabilitiesFilter);
LinkProperties linkProperties = new LinkProperties();
boolean interfaceUsedByAnotherNdp = isInterfaceUpAndUsedByAnotherNdp(nnri);
if (!interfaceUsedByAnotherNdp) {
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);
declareUnfullfillableAndEndDp(nnri, ndpId);
return networkSpecifier;
}
} 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) {
nnri.peerPort = peerServerInfo.port;
}
if (peerServerInfo.transportProtocol != -1) {
nnri.peerTransportProtocol = peerServerInfo.transportProtocol;
}
if (peerServerInfo.ipv6Override != null) {
nnri.peerIpv6Override = peerServerInfo.ipv6Override;
}
}
}
try {
if (nnri.peerIpv6Override == null) {
nnri.peerIpv6 = Inet6Address.getByAddress(null,
MacAddress.fromBytes(mac).getLinkLocalIpv6FromEui48Mac().getAddress(),
NetworkInterface.getByName(nnri.interfaceName));
} else {
byte[] addr = new byte[16];
addr[0] = (byte) 0xfe;
addr[1] = (byte) 0x80;
addr[8] = nnri.peerIpv6Override[0];
addr[9] = nnri.peerIpv6Override[1];
addr[10] = nnri.peerIpv6Override[2];
addr[11] = nnri.peerIpv6Override[3];
addr[12] = nnri.peerIpv6Override[4];
addr[13] = nnri.peerIpv6Override[5];
addr[14] = nnri.peerIpv6Override[6];
addr[15] = nnri.peerIpv6Override[7];
nnri.peerIpv6 = Inet6Address.getByAddress(null, addr,
NetworkInterface.getByName(nnri.interfaceName));
}
} catch (SocketException | UnknownHostException e) {
Log.e(TAG, "onDataPathConfirm: error obtaining scoped IPv6 address -- " + e);
nnri.peerIpv6 = null;
}
if (nnri.peerIpv6 != null) {
final WifiAwareNetworkInfo ni = new WifiAwareNetworkInfo(
nnri.peerIpv6, nnri.peerPort, nnri.peerTransportProtocol);
ncBuilder.setTransportInfo(ni);
if (VDBG) {
Log.v(TAG, "onDataPathConfirm: AwareNetworkInfo=" + ni);
}
}
if (!mNiWrapper.configureAgentProperties(nnri, nnri.equivalentRequests, ndpId,
ncBuilder, linkProperties)) {
declareUnfullfillableAndEndDp(nnri, ndpId);
return networkSpecifier;
}
final NetworkAgentConfig naConfig = new NetworkAgentConfig.Builder()
.setLegacyType(ConnectivityManager.TYPE_NONE)
.setLegacyTypeName(NETWORK_TAG)
.build();
nnri.networkAgent = new WifiAwareNetworkAgent(mLooper, mContext,
AGENT_TAG_PREFIX + nnri.ndpId, ncBuilder.build(),
linkProperties, NETWORK_FACTORY_SCORE_AVAIL,
naConfig, mNetworkFactory.getProvider(), nnri);
nnri.startValidationTimestamp = mClock.getElapsedSinceBootMillis();
handleAddressValidation(nnri, linkProperties, ndpId, networkSpecifier.isOutOfBand());
} else {
if (VDBG) {
Log.v(TAG, "onDataPathConfirm: data-path for networkSpecifier=" + networkSpecifier
+ " rejected - reason=" + reason);
}
mNetworkRequestsCache.remove(networkSpecifier);
mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri);
mAwareMetrics.recordNdpStatus(reason, networkSpecifier.isOutOfBand(),
nnri.startTimestamp);
}
return networkSpecifier;
}
private void handleAddressValidation(AwareNetworkRequestInformation nnri,
LinkProperties linkProperties, int ndpId, boolean isOutOfBand) {
if (mNiWrapper.isAddressUsable(linkProperties)) {
mNiWrapper.setConnected(nnri.networkAgent);
mAwareMetrics.recordNdpStatus(NanStatusType.SUCCESS, isOutOfBand, nnri.startTimestamp);
nnri.startTimestamp = mClock.getElapsedSinceBootMillis(); // update time-stamp
mAwareMetrics.recordNdpCreation(nnri.uid, nnri.packageName, mNetworkRequestsCache);
} else {
if (mClock.getElapsedSinceBootMillis() - nnri.startValidationTimestamp
> ADDRESS_VALIDATION_TIMEOUT_MS) {
Log.e(TAG, "Timed-out while waiting for IPv6 address to be usable");
declareUnfullfillableAndEndDp(nnri, ndpId);
return;
}
if (mDbg) {
Log.d(TAG, "Failed address validation");
}
mHandler.postDelayed(() -> {
handleAddressValidation(nnri, linkProperties, ndpId, isOutOfBand);
}, ADDRESS_VALIDATION_RETRY_INTERVAL_MS);
}
}
private void declareUnfullfillableAndEndDp(AwareNetworkRequestInformation nnri, int ndpId) {
mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri);
mMgr.endDataPath(ndpId);
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 (VDBG) Log.v(TAG, "onDataPathEnd: ndpId=" + ndpId);
Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> nnriE =
getNetworkRequestByNdpId(ndpId);
if (nnriE == null) {
if (VDBG) {
Log.v(TAG, "onDataPathEnd: network request not found for ndpId=" + ndpId);
}
return;
}
tearDownInterfaceIfPossible(nnriE.getValue());
if (nnriE.getValue().state == AwareNetworkRequestInformation.STATE_CONFIRMED
|| nnriE.getValue().state == AwareNetworkRequestInformation.STATE_TERMINATING) {
mAwareMetrics.recordNdpSessionDuration(nnriE.getValue().startTimestamp);
}
mNetworkRequestsCache.remove(nnriE.getKey());
mNetworkFactory.tickleConnectivityIfWaiting();
}
/**
* Notification (unsolicited/asynchronous) from the firmware that the channel for the specified
* NDP ids has been updated.
*/
public void onDataPathSchedUpdate(byte[] peerMac, List<Integer> ndpIds,
List<NanDataPathChannelInfo> channelInfo) {
if (VDBG) {
Log.v(TAG, "onDataPathSchedUpdate: peerMac=" + MacAddress.fromBytes(peerMac).toString()
+ ", ndpIds=" + ndpIds + ", channelInfo=" + channelInfo);
}
for (int ndpId : ndpIds) {
Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> nnriE =
getNetworkRequestByNdpId(ndpId);
if (nnriE == null) {
Log.e(TAG, "onDataPathSchedUpdate: ndpId=" + ndpId + " - not found");
continue;
}
if (!Arrays.equals(peerMac, nnriE.getValue().peerDiscoveryMac)) {
Log.e(TAG, "onDataPathSchedUpdate: ndpId=" + ndpId + ", report NMI="
+ MacAddress.fromBytes(peerMac).toString() + " doesn't match NDP NMI="
+ MacAddress.fromBytes(nnriE.getValue().peerDiscoveryMac).toString());
continue;
}
nnriE.getValue().channelInfo = channelInfo;
}
}
/**
* Called whenever Aware comes down. Clean up all pending and up network requests and agents.
*/
public void onAwareDownCleanupDataPaths() {
if (VDBG) Log.v(TAG, "onAwareDownCleanupDataPaths");
Iterator<Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation>> it =
mNetworkRequestsCache.entrySet().iterator();
while (it.hasNext()) {
tearDownInterfaceIfPossible(it.next().getValue());
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(NetworkSpecifier networkSpecifier) {
if (mDbg) Log.v(TAG, "handleDataPathTimeout: networkSpecifier=" + networkSpecifier);
AwareNetworkRequestInformation nnri = mNetworkRequestsCache.remove(networkSpecifier);
if (nnri == null) {
if (mDbg) {
Log.v(TAG,
"handleDataPathTimeout: network request not found for networkSpecifier="
+ networkSpecifier);
}
return;
}
mAwareMetrics.recordNdpStatus(NanStatusType.INTERNAL_FAILURE,
nnri.networkSpecifier.isOutOfBand(), nnri.startTimestamp);
mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri);
mMgr.endDataPath(nnri.ndpId);
nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING;
}
private class WifiAwareNetworkFactory extends NetworkFactory {
// Request received while waiting for confirmation that a canonically identical data-path
// (NDP) is in the process of being terminated
private boolean mWaitingForTermination = false;
WifiAwareNetworkFactory(Looper looper, Context context, NetworkCapabilities filter) {
super(looper, context, NETWORK_TAG, filter);
}
public void tickleConnectivityIfWaiting() {
if (mWaitingForTermination) {
if (VDBG) Log.v(TAG, "tickleConnectivityIfWaiting: was waiting!");
mWaitingForTermination = false;
reevaluateAllRequests();
}
}
@Override
public boolean acceptRequest(NetworkRequest request, int score) {
if (VDBG) {
Log.v(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request + ", score="
+ score);
}
if (!mMgr.isUsageEnabled()) {
if (VDBG) {
Log.v(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request
+ " -- Aware disabled");
}
return false;
}
if (mInterfaces.isEmpty()) {
Log.w(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request
+ " -- No Aware interfaces are up");
return false;
}
NetworkSpecifier networkSpecifierBase = request.getNetworkSpecifier();
if (!(networkSpecifierBase instanceof WifiAwareNetworkSpecifier)) {
Log.w(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request
+ " - not a WifiAwareNetworkSpecifier");
return false;
}
WifiAwareNetworkSpecifier networkSpecifier =
(WifiAwareNetworkSpecifier) networkSpecifierBase;
// look up specifier - are we being called again?
AwareNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier);
if (nnri != null) {
if (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<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> 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);
return true;
}
@Override
protected void needNetworkFor(NetworkRequest networkRequest, int score) {
if (VDBG) {
Log.v(TAG, "WifiAwareNetworkFactory.needNetworkFor: networkRequest="
+ networkRequest + ", score=" + score);
}
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.peerInstanceId,
NanDataPathChannelCfg.CHANNEL_NOT_REQUESTED, selectChannelForRequest(nnri),
nnri.peerDiscoveryMac, nnri.interfaceName, nnri.networkSpecifier.pmk,
nnri.networkSpecifier.passphrase, nnri.networkSpecifier.isOutOfBand(),
null);
nnri.state =
AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE;
nnri.startTimestamp = mClock.getElapsedSinceBootMillis();
} else {
nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST;
}
}
@Override
protected void releaseNetworkFor(NetworkRequest networkRequest) {
if (VDBG) {
Log.v(TAG, "WifiAwareNetworkFactory.releaseNetworkFor: networkRequest="
+ networkRequest);
}
NetworkSpecifier networkSpecifierObj = networkRequest.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 (VDBG) {
Log.v(TAG, "releaseNetworkFor: there are no further requests, networkRequest="
+ networkRequest);
}
if (nnri.ndpId != 0) { // 0 is never a valid ID!
if (VDBG) Log.v(TAG, "releaseNetworkFor: in progress NDP being terminated");
mMgr.endDataPath(nnri.ndpId);
nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING;
} else {
mNetworkRequestsCache.remove(networkSpecifier);
if (nnri.networkAgent != null) {
letAppKnowThatRequestsAreUnavailable(nnri);
}
}
} 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 AwareNetworkRequestInformation mAwareNetworkRequestInfo;
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;
register();
}
@Override
public void onNetworkUnwanted() {
if (VDBG) {
Log.v(TAG, "WifiAwareNetworkAgent.unwanted: request=" + mAwareNetworkRequestInfo);
}
mMgr.endDataPath(mAwareNetworkRequestInfo.ndpId);
mAwareNetworkRequestInfo.state = AwareNetworkRequestInformation.STATE_TERMINATING;
// Will get a callback (on both initiator and responder) when data-path actually
// terminated. At that point will inform the agent and will clear the cache.
}
void reconfigureAgentAsDisconnected() {
if (VDBG) {
Log.v(TAG, "WifiAwareNetworkAgent.reconfigureAgentAsDisconnected: request="
+ mAwareNetworkRequestInfo);
}
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 (VDBG) {
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).
*
* Construct a list of all interfaces currently used to communicate to the peer. The remaining
* interfaces are available for use for this request - if none are left then the request should
* fail (signaled to the caller by returning a null).
*/
private String selectInterfaceForRequest(AwareNetworkRequestInformation req) {
SortedSet<String> potential = new TreeSet<>(mInterfaces);
Set<String> used = new HashSet<>();
if (VDBG) {
Log.v(TAG, "selectInterfaceForRequest: req=" + req + ", mNetworkRequestsCache="
+ mNetworkRequestsCache);
}
for (AwareNetworkRequestInformation nnri : mNetworkRequestsCache.values()) {
if (nnri == req) {
continue;
}
if (Arrays.equals(req.peerDiscoveryMac, nnri.peerDiscoveryMac)) {
used.add(nnri.interfaceName);
}
}
if (VDBG) {
Log.v(TAG, "selectInterfaceForRequest: potential=" + potential + ", used=" + used);
}
for (String ifName: potential) {
if (!used.contains(ifName)) {
return ifName;
}
}
Log.e(TAG, "selectInterfaceForRequest: req=" + req + " - no interfaces available!");
return null;
}
/**
* Select a channel for the network request.
*
* TODO (b/38209409): The value from this function isn't currently used - the channel selection
* is delegated to the HAL.
*/
private int selectChannelForRequest(AwareNetworkRequestInformation req) {
return 2437;
}
/**
* Aware network request. State object: contains network request information/state through its
* lifetime.
*/
@VisibleForTesting
public static class AwareNetworkRequestInformation {
static final int STATE_IDLE = 100;
static final int STATE_WAIT_FOR_CONFIRM = 101;
static final int STATE_CONFIRMED = 102;
static final int STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE = 103;
static final int STATE_RESPONDER_WAIT_FOR_REQUEST = 104;
static final int STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE = 105;
static final int STATE_TERMINATING = 106;
public int state;
public int uid;
public String packageName;
public String interfaceName;
public int pubSubId = 0;
public int peerInstanceId = 0;
public byte[] peerDiscoveryMac = null;
public int ndpId = 0; // 0 is never a valid ID!
public byte[] peerDataMac;
public Inet6Address peerIpv6;
public int peerPort = 0; // uninitialized (invalid) value
public int peerTransportProtocol = -1; // uninitialized (invalid) value
public byte[] peerIpv6Override = null;
public WifiAwareNetworkSpecifier networkSpecifier;
public List<NanDataPathChannelInfo> channelInfo;
public long startTimestamp = 0; // request is made (initiator) / get request (responder)
public long startValidationTimestamp = 0; // NDP created and starting to validate IPv6 addr
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<NetworkRequest> 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 (peerIpv6 != null) {
builder.setTransportInfo(
new WifiAwareNetworkInfo(peerIpv6, peerPort, peerTransportProtocol));
}
return builder.build();
}
/**
* Returns a canonical descriptor for the network request.
*/
CanonicalConnectionInfo getCanonicalDescriptor() {
return new CanonicalConnectionInfo(peerDiscoveryMac, 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 27: no longer allow "ANY"-style responders (initiators were never
// permitted).
// Note: checks are done on the manager. This is a backup for apps which bypass the
// check.
if (!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.peerInstanceId = peerInstanceId;
nnri.peerDiscoveryMac = 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(", peerInstanceId=").append(
peerInstanceId).append(", peerDiscoveryMac=").append(
peerDiscoveryMac == null ? ""
: String.valueOf(HexEncoding.encode(peerDiscoveryMac))).append(
", ndpId=").append(ndpId).append(", peerDataMac=").append(
peerDataMac == null ? ""
: String.valueOf(HexEncoding.encode(peerDataMac)))
.append(", peerIpv6=").append(peerIpv6).append(", peerPort=").append(
peerPort).append(", peerTransportProtocol=").append(
peerTransportProtocol).append(", startTimestamp=").append(
startTimestamp).append(", channelInfo=").append(
channelInfo).append(", equivalentSpecifiers=[");
for (NetworkRequest nr : equivalentRequests) {
sb.append(nr.toString()).append(", ");
}
return sb.append("]").toString();
}
}
/**
* A canonical (unique) descriptor of the peer connection.
*/
static class CanonicalConnectionInfo {
CanonicalConnectionInfo(byte[] peerDiscoveryMac, byte[] pmk, int sessionId,
String passphrase) {
this.peerDiscoveryMac = peerDiscoveryMac;
this.pmk = pmk;
this.sessionId = sessionId;
this.passphrase = passphrase;
}
public final byte[] peerDiscoveryMac;
/*
* Security configuration matching:
* - open: pmk/passphrase = null
* - pmk: pmk != null, passphrase = null
* - passphrase: passphrase != null, sessionId used (==0 for OOB), pmk=null
*/
public final byte[] pmk;
public final int sessionId;
public final String passphrase;
public boolean matches(CanonicalConnectionInfo other) {
return (other.peerDiscoveryMac == null || 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,
Set<NetworkRequest> networkRequests, int ndpId,
NetworkCapabilities.Builder ncBuilder, LinkProperties linkProperties) {
// find link-local address
InetAddress linkLocal = null;
NetworkInterface ni;
try {
ni = NetworkInterface.getByName(nnri.interfaceName);
} catch (SocketException e) {
Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri
+ ": can't get network interface - " + e);
mMgr.endDataPath(ndpId);
nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING;
return false;
}
if (ni == null) {
Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri
+ ": can't get network interface (null)");
mMgr.endDataPath(ndpId);
nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING;
return false;
}
Enumeration<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);
nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING;
return false;
}
ncBuilder.setRequestorUid(nnri.uid);
ncBuilder.setRequestorPackageName(nnri.packageName);
ncBuilder.setNetworkSpecifier(new WifiAwareAgentNetworkSpecifier(
networkRequests.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.d(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
* - <port | 0, transport-protocol | -1, ipv6-override | null> 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<Integer, Integer> 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 <port | 0, transport-protocol | -1> otherwise
*/
private static Pair<Integer, Integer> 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);
}
}