Add Qos Callback support

* Provide App Developers Qos related info associated to
  a bound socket through ConnectivityManager
* Qos sessions are generated and filtered by Network Agents
  and sent back through the Connectivity Service to the
  API consumer.
* The structure of the code within com.android.server
  is designed to support different types of filters in the
  the future.
* The first type of Qos Attributes are related to EPS
  Bearers in order support RCS.

Bug: 155176305
Test: Added to cts/NetworkAgentTest
Test: Added to ConnectivityServiceTest
Change-Id: I145dd065d9deeee449eb9695ab3f6c8556ee7c09
This commit is contained in:
Daniel Bright
2020-06-15 16:10:01 -07:00
parent f2a4eeb55d
commit f9e945b074
18 changed files with 2020 additions and 49 deletions

View File

@@ -19,6 +19,7 @@ import static android.net.IpSecManager.INVALID_RESOURCE_ID;
import static android.net.NetworkRequest.Type.LISTEN;
import static android.net.NetworkRequest.Type.REQUEST;
import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
import static android.net.QosCallback.QosCallbackRegistrationException;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
@@ -4848,4 +4849,118 @@ public class ConnectivityManager {
Log.d(TAG, "setOemNetworkPreference called with preference: "
+ preference.toString());
}
@NonNull
private final List<QosCallbackConnection> mQosCallbackConnections = new ArrayList<>();
/**
* Registers a {@link QosSocketInfo} with an associated {@link QosCallback}. The callback will
* receive available QoS events related to the {@link Network} and local ip + port
* specified within socketInfo.
* <p/>
* The same {@link QosCallback} must be unregistered before being registered a second time,
* otherwise {@link QosCallbackRegistrationException} is thrown.
* <p/>
* This API does not, in itself, require any permission if called with a network that is not
* restricted. However, the underlying implementation currently only supports the IMS network,
* which is always restricted. That means non-preinstalled callers can't possibly find this API
* useful, because they'd never be called back on networks that they would have access to.
*
* @throws SecurityException if {@link QosSocketInfo#getNetwork()} is restricted and the app is
* missing CONNECTIVITY_USE_RESTRICTED_NETWORKS permission.
* @throws QosCallback.QosCallbackRegistrationException if qosCallback is already registered.
* @throws RuntimeException if the app already has too many callbacks registered.
*
* Exceptions after the time of registration is passed through
* {@link QosCallback#onError(QosCallbackException)}. see: {@link QosCallbackException}.
*
* @param socketInfo the socket information used to match QoS events
* @param callback receives qos events that satisfy socketInfo
* @param executor The executor on which the callback will be invoked. The provided
* {@link Executor} must run callback sequentially, otherwise the order of
* callbacks cannot be guaranteed.
*
* @hide
*/
@SystemApi
public void registerQosCallback(@NonNull final QosSocketInfo socketInfo,
@NonNull final QosCallback callback,
@CallbackExecutor @NonNull final Executor executor) {
Objects.requireNonNull(socketInfo, "socketInfo must be non-null");
Objects.requireNonNull(callback, "callback must be non-null");
Objects.requireNonNull(executor, "executor must be non-null");
try {
synchronized (mQosCallbackConnections) {
if (getQosCallbackConnection(callback) == null) {
final QosCallbackConnection connection =
new QosCallbackConnection(this, callback, executor);
mQosCallbackConnections.add(connection);
mService.registerQosSocketCallback(socketInfo, connection);
} else {
Log.e(TAG, "registerQosCallback: Callback already registered");
throw new QosCallbackRegistrationException();
}
}
} catch (final RemoteException e) {
Log.e(TAG, "registerQosCallback: Error while registering ", e);
// The same unregister method method is called for consistency even though nothing
// will be sent to the ConnectivityService since the callback was never successfully
// registered.
unregisterQosCallback(callback);
e.rethrowFromSystemServer();
} catch (final ServiceSpecificException e) {
Log.e(TAG, "registerQosCallback: Error while registering ", e);
unregisterQosCallback(callback);
throw convertServiceException(e);
}
}
/**
* Unregisters the given {@link QosCallback}. The {@link QosCallback} will no longer receive
* events once unregistered and can be registered a second time.
* <p/>
* If the {@link QosCallback} does not have an active registration, it is a no-op.
*
* @param callback the callback being unregistered
*
* @hide
*/
@SystemApi
public void unregisterQosCallback(@NonNull final QosCallback callback) {
Objects.requireNonNull(callback, "The callback must be non-null");
try {
synchronized (mQosCallbackConnections) {
final QosCallbackConnection connection = getQosCallbackConnection(callback);
if (connection != null) {
connection.stopReceivingMessages();
mService.unregisterQosCallback(connection);
mQosCallbackConnections.remove(connection);
} else {
Log.d(TAG, "unregisterQosCallback: Callback not registered");
}
}
} catch (final RemoteException e) {
Log.e(TAG, "unregisterQosCallback: Error while unregistering ", e);
e.rethrowFromSystemServer();
}
}
/**
* Gets the connection related to the callback.
*
* @param callback the callback to look up
* @return the related connection
*/
@Nullable
private QosCallbackConnection getQosCallbackConnection(final QosCallback callback) {
for (final QosCallbackConnection connection : mQosCallbackConnections) {
// Checking by reference here is intentional
if (connection.getCallback() == callback) {
return connection;
}
}
return null;
}
}

View File

@@ -20,6 +20,8 @@ import android.app.PendingIntent;
import android.net.ConnectionInfo;
import android.net.ConnectivityDiagnosticsManager;
import android.net.IConnectivityDiagnosticsCallback;
import android.net.IQosCallback;
import android.net.ISocketKeepaliveCallback;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkAgentConfig;
@@ -27,9 +29,9 @@ import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.NetworkState;
import android.net.ISocketKeepaliveCallback;
import android.net.ProxyInfo;
import android.net.UidRange;
import android.net.QosSocketInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.os.INetworkActivityListener;
@@ -239,4 +241,7 @@ interface IConnectivityManager
void unregisterNetworkActivityListener(in INetworkActivityListener l);
boolean isDefaultNetworkActive();
void registerQosSocketCallback(in QosSocketInfo socketInfo, in IQosCallback callback);
void unregisterQosCallback(in IQosCallback callback);
}

View File

@@ -30,6 +30,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.telephony.data.EpsBearerQosSessionAttributes;
import android.util.Log;
import com.android.connectivity.aidl.INetworkAgent;
@@ -341,6 +342,24 @@ public abstract class NetworkAgent {
*/
private static final int EVENT_AGENT_DISCONNECTED = BASE + 19;
/**
* Sent by QosCallbackTracker to {@link NetworkAgent} to register a new filter with
* callback.
*
* arg1 = QoS agent callback ID
* obj = {@link QosFilter}
* @hide
*/
public static final int CMD_REGISTER_QOS_CALLBACK = BASE + 20;
/**
* Sent by QosCallbackTracker to {@link NetworkAgent} to unregister a callback.
*
* arg1 = QoS agent callback ID
* @hide
*/
public static final int CMD_UNREGISTER_QOS_CALLBACK = BASE + 21;
private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
// The subtype can be changed with (TODO) setLegacySubtype, but it starts
// with 0 (TelephonyManager.NETWORK_TYPE_UNKNOWN) and an empty description.
@@ -520,6 +539,17 @@ public abstract class NetworkAgent {
onRemoveKeepalivePacketFilter(msg.arg1 /* slot */);
break;
}
case CMD_REGISTER_QOS_CALLBACK: {
onQosCallbackRegistered(
msg.arg1 /* QoS callback id */,
(QosFilter) msg.obj /* QoS filter */);
break;
}
case CMD_UNREGISTER_QOS_CALLBACK: {
onQosCallbackUnregistered(
msg.arg1 /* QoS callback id */);
break;
}
}
}
}
@@ -553,6 +583,8 @@ public abstract class NetworkAgent {
}
private static class NetworkAgentBinder extends INetworkAgent.Stub {
private static final String LOG_TAG = NetworkAgentBinder.class.getSimpleName();
private final Handler mHandler;
private NetworkAgentBinder(Handler handler) {
@@ -639,6 +671,25 @@ public abstract class NetworkAgent {
mHandler.sendMessage(mHandler.obtainMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER,
slot, 0));
}
@Override
public void onQosFilterCallbackRegistered(final int qosCallbackId,
final QosFilterParcelable qosFilterParcelable) {
if (qosFilterParcelable.getQosFilter() != null) {
mHandler.sendMessage(
mHandler.obtainMessage(CMD_REGISTER_QOS_CALLBACK, qosCallbackId, 0,
qosFilterParcelable.getQosFilter()));
return;
}
Log.wtf(LOG_TAG, "onQosFilterCallbackRegistered: qos filter is null.");
}
@Override
public void onQosCallbackUnregistered(final int qosCallbackId) {
mHandler.sendMessage(mHandler.obtainMessage(
CMD_UNREGISTER_QOS_CALLBACK, qosCallbackId, 0, null));
}
}
/**
@@ -1067,8 +1118,68 @@ public abstract class NetworkAgent {
protected void preventAutomaticReconnect() {
}
/**
* Called when a qos callback is registered with a filter.
* @param qosCallbackId the id for the callback registered
* @param filter the filter being registered
*/
public void onQosCallbackRegistered(final int qosCallbackId, final @NonNull QosFilter filter) {
}
/**
* Called when a qos callback is registered with a filter.
* <p/>
* Any QoS events that are sent with the same callback id after this method is called
* are a no-op.
*
* @param qosCallbackId the id for the callback being unregistered
*/
public void onQosCallbackUnregistered(final int qosCallbackId) {
}
/**
* Sends the attributes of Eps Bearer Qos Session back to the Application
*
* @param qosCallbackId the callback id that the session belongs to
* @param sessionId the unique session id across all Eps Bearer Qos Sessions
* @param attributes the attributes of the Eps Qos Session
*/
public final void sendQosSessionAvailable(final int qosCallbackId, final int sessionId,
@NonNull final EpsBearerQosSessionAttributes attributes) {
Objects.requireNonNull(attributes, "The attributes must be non-null");
queueOrSendMessage(ra -> ra.sendEpsQosSessionAvailable(qosCallbackId,
new QosSession(sessionId, QosSession.TYPE_EPS_BEARER),
attributes));
}
/**
* Sends event that the Eps Qos Session was lost.
*
* @param qosCallbackId the callback id that the session belongs to
* @param sessionId the unique session id across all Eps Bearer Qos Sessions
*/
public final void sendQosSessionLost(final int qosCallbackId, final int sessionId) {
queueOrSendMessage(ra -> ra.sendQosSessionLost(qosCallbackId,
new QosSession(sessionId, QosSession.TYPE_EPS_BEARER)));
}
/**
* Sends the exception type back to the application.
*
* The NetworkAgent should not send anymore messages with this id.
*
* @param qosCallbackId the callback id this exception belongs to
* @param exceptionType the type of exception
*/
public final void sendQosCallbackError(final int qosCallbackId,
@QosCallbackException.ExceptionType final int exceptionType) {
queueOrSendMessage(ra -> ra.sendQosCallbackError(qosCallbackId, exceptionType));
}
/** @hide */
protected void log(String s) {
protected void log(final String s) {
Log.d(LOG_TAG, "NetworkAgent: " + s);
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (C) 2020 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 android.net;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import java.util.concurrent.Executor;
/**
* Receives Qos information given a {@link Network}. The callback is registered with
* {@link ConnectivityManager#registerQosCallback}.
*
* <p>
* <br/>
* The callback will no longer receive calls if any of the following takes place:
* <ol>
* <li>{@link ConnectivityManager#unregisterQosCallback(QosCallback)} is called with the same
* callback instance.</li>
* <li>{@link QosCallback#onError(QosCallbackException)} is called.</li>
* <li>A network specific issue occurs. eg. Congestion on a carrier network.</li>
* <li>The network registered with the callback has no associated QoS providers</li>
* </ul>
* {@hide}
*/
@SystemApi
public abstract class QosCallback {
/**
* Invoked after an error occurs on a registered callback. Once called, the callback is
* automatically unregistered and the callback will no longer receive calls.
*
* <p>The underlying exception can either be a runtime exception or a custom exception made for
* {@link QosCallback}. see: {@link QosCallbackException}.
*
* @param exception wraps the underlying cause
*/
public void onError(@NonNull final QosCallbackException exception) {
}
/**
* Called when a Qos Session first becomes available to the callback or if its attributes have
* changed.
* <p>
* Note: The callback may be called multiple times with the same attributes.
*
* @param session the available session
* @param sessionAttributes the attributes of the session
*/
public void onQosSessionAvailable(@NonNull final QosSession session,
@NonNull final QosSessionAttributes sessionAttributes) {
}
/**
* Called after a Qos Session is lost.
* <p>
* At least one call to
* {@link QosCallback#onQosSessionAvailable(QosSession, QosSessionAttributes)}
* with the same {@link QosSession} will precede a call to lost.
*
* @param session the lost session
*/
public void onQosSessionLost(@NonNull final QosSession session) {
}
/**
* Thrown when there is a problem registering {@link QosCallback} with
* {@link ConnectivityManager#registerQosCallback(QosSocketInfo, QosCallback, Executor)}.
*/
public static class QosCallbackRegistrationException extends RuntimeException {
/**
* @hide
*/
public QosCallbackRegistrationException() {
super();
}
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright (C) 2020 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 android.net;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.telephony.data.EpsBearerQosSessionAttributes;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
* Sends messages from {@link com.android.server.ConnectivityService} to the registered
* {@link QosCallback}.
* <p/>
* This is a satellite class of {@link ConnectivityManager} and not meant
* to be used in other contexts.
*
* @hide
*/
class QosCallbackConnection extends android.net.IQosCallback.Stub {
@NonNull private final ConnectivityManager mConnectivityManager;
@Nullable private volatile QosCallback mCallback;
@NonNull private final Executor mExecutor;
@VisibleForTesting
@Nullable
public QosCallback getCallback() {
return mCallback;
}
/**
* The constructor for the connection
*
* @param connectivityManager the mgr that created this connection
* @param callback the callback to send messages back to
* @param executor The executor on which the callback will be invoked. The provided
* {@link Executor} must run callback sequentially, otherwise the order of
* callbacks cannot be guaranteed.
*/
QosCallbackConnection(@NonNull final ConnectivityManager connectivityManager,
@NonNull final QosCallback callback,
@NonNull final Executor executor) {
mConnectivityManager = Objects.requireNonNull(connectivityManager,
"connectivityManager must be non-null");
mCallback = Objects.requireNonNull(callback, "callback must be non-null");
mExecutor = Objects.requireNonNull(executor, "executor must be non-null");
}
/**
* Called when either the {@link EpsBearerQosSessionAttributes} has changed or on the first time
* the attributes have become available.
*
* @param session the session that is now available
* @param attributes the corresponding attributes of session
*/
@Override
public void onQosEpsBearerSessionAvailable(@NonNull final QosSession session,
@NonNull final EpsBearerQosSessionAttributes attributes) {
mExecutor.execute(() -> {
final QosCallback callback = mCallback;
if (callback != null) {
callback.onQosSessionAvailable(session, attributes);
}
});
}
/**
* Called when the session is lost.
*
* @param session the session that was lost
*/
@Override
public void onQosSessionLost(@NonNull final QosSession session) {
mExecutor.execute(() -> {
final QosCallback callback = mCallback;
if (callback != null) {
callback.onQosSessionLost(session);
}
});
}
/**
* Called when there is an error on the registered callback.
*
* @param errorType the type of error
*/
@Override
public void onError(@QosCallbackException.ExceptionType final int errorType) {
mExecutor.execute(() -> {
final QosCallback callback = mCallback;
if (callback != null) {
// Messages no longer need to be received since there was an error.
stopReceivingMessages();
mConnectivityManager.unregisterQosCallback(callback);
callback.onError(QosCallbackException.createException(errorType));
}
});
}
/**
* The callback will stop receiving messages.
* <p/>
* There are no synchronization guarantees on exactly when the callback will stop receiving
* messages.
*/
void stopReceivingMessages() {
mCallback = null;
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright (C) 2020 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 android.net;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* This is the exception type passed back through the onError method on {@link QosCallback}.
* {@link QosCallbackException#getCause()} contains the actual error that caused this exception.
*
* The possible exception types as causes are:
* 1. {@link NetworkReleasedException}
* 2. {@link SocketNotBoundException}
* 3. {@link UnsupportedOperationException}
* 4. {@link SocketLocalAddressChangedException}
*
* @hide
*/
@SystemApi
public final class QosCallbackException extends Exception {
/** @hide */
@IntDef(prefix = {"EX_TYPE_"}, value = {
EX_TYPE_FILTER_NONE,
EX_TYPE_FILTER_NETWORK_RELEASED,
EX_TYPE_FILTER_SOCKET_NOT_BOUND,
EX_TYPE_FILTER_NOT_SUPPORTED,
EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ExceptionType {}
private static final String TAG = "QosCallbackException";
// Types of exceptions supported //
/** {@hide} */
public static final int EX_TYPE_FILTER_NONE = 0;
/** {@hide} */
public static final int EX_TYPE_FILTER_NETWORK_RELEASED = 1;
/** {@hide} */
public static final int EX_TYPE_FILTER_SOCKET_NOT_BOUND = 2;
/** {@hide} */
public static final int EX_TYPE_FILTER_NOT_SUPPORTED = 3;
/** {@hide} */
public static final int EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED = 4;
/**
* Creates exception based off of a type and message. Not all types of exceptions accept a
* custom message.
*
* {@hide}
*/
@NonNull
static QosCallbackException createException(@ExceptionType final int type) {
switch (type) {
case EX_TYPE_FILTER_NETWORK_RELEASED:
return new QosCallbackException(new NetworkReleasedException());
case EX_TYPE_FILTER_SOCKET_NOT_BOUND:
return new QosCallbackException(new SocketNotBoundException());
case EX_TYPE_FILTER_NOT_SUPPORTED:
return new QosCallbackException(new UnsupportedOperationException(
"This device does not support the specified filter"));
case EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED:
return new QosCallbackException(
new SocketLocalAddressChangedException());
default:
Log.wtf(TAG, "create: No case setup for exception type: '" + type + "'");
return new QosCallbackException(
new RuntimeException("Unknown exception code: " + type));
}
}
/**
* @hide
*/
public QosCallbackException(@NonNull final String message) {
super(message);
}
/**
* @hide
*/
public QosCallbackException(@NonNull final Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2020 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 android.net;
import android.annotation.NonNull;
import android.annotation.SystemApi;
/**
* Provides the related filtering logic to the {@link NetworkAgent} to match {@link QosSession}s
* to their related {@link QosCallback}.
*
* Used by the {@link com.android.server.ConnectivityService} to validate a {@link QosCallback}
* is still able to receive a {@link QosSession}.
*
* @hide
*/
@SystemApi
public abstract class QosFilter {
/**
* The constructor is kept hidden from outside this package to ensure that all derived types
* are known and properly handled when being passed to and from {@link NetworkAgent}.
*
* @hide
*/
QosFilter() {
}
/**
* The network used with this filter.
*
* @return the registered {@link Network}
*/
@NonNull
public abstract Network getNetwork();
/**
* Validates that conditions have not changed such that no further {@link QosSession}s should
* be passed back to the {@link QosCallback} associated to this filter.
*
* @return the error code when present, otherwise the filter is valid
*
* @hide
*/
@QosCallbackException.ExceptionType
public abstract int validate();
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright (C) 2021 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 android.net;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import java.util.Objects;
/**
* Aware of how to parcel different types of {@link QosFilter}s. Any new type of qos filter must
* have a specialized case written here.
* <p/>
* Specifically leveraged when transferring {@link QosFilter} from
* {@link com.android.server.ConnectivityService} to {@link NetworkAgent} when the filter is first
* registered.
* <p/>
* This is not meant to be used in other contexts.
*
* @hide
*/
public final class QosFilterParcelable implements Parcelable {
private static final String LOG_TAG = QosFilterParcelable.class.getSimpleName();
// Indicates that the filter was not successfully written to the parcel.
private static final int NO_FILTER_PRESENT = 0;
// The parcel is of type qos socket filter.
private static final int QOS_SOCKET_FILTER = 1;
private final QosFilter mQosFilter;
/**
* The underlying qos filter.
* <p/>
* Null only in the case parceling failed.
*/
@Nullable
public QosFilter getQosFilter() {
return mQosFilter;
}
public QosFilterParcelable(@NonNull final QosFilter qosFilter) {
Objects.requireNonNull(qosFilter, "qosFilter must be non-null");
// NOTE: Normally a type check would belong here, but doing so breaks unit tests that rely
// on mocking qos filter.
mQosFilter = qosFilter;
}
private QosFilterParcelable(final Parcel in) {
final int filterParcelType = in.readInt();
switch (filterParcelType) {
case QOS_SOCKET_FILTER: {
mQosFilter = new QosSocketFilter(QosSocketInfo.CREATOR.createFromParcel(in));
break;
}
case NO_FILTER_PRESENT:
default: {
mQosFilter = null;
}
}
}
public static final Creator<QosFilterParcelable> CREATOR = new Creator<QosFilterParcelable>() {
@Override
public QosFilterParcelable createFromParcel(final Parcel in) {
return new QosFilterParcelable(in);
}
@Override
public QosFilterParcelable[] newArray(final int size) {
return new QosFilterParcelable[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
if (mQosFilter instanceof QosSocketFilter) {
dest.writeInt(QOS_SOCKET_FILTER);
final QosSocketFilter qosSocketFilter = (QosSocketFilter) mQosFilter;
qosSocketFilter.getQosSocketInfo().writeToParcel(dest, 0);
return;
}
dest.writeInt(NO_FILTER_PRESENT);
Log.e(LOG_TAG, "Parceling failed, unknown type of filter present: " + mQosFilter);
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (C) 2020 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 android.net;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Provides identifying information of a QoS session. Sent to an application through
* {@link QosCallback}.
*
* @hide
*/
@SystemApi
public final class QosSession implements Parcelable {
/**
* The {@link QosSession} is a LTE EPS Session.
*/
public static final int TYPE_EPS_BEARER = 1;
private final int mSessionId;
private final int mSessionType;
/**
* Gets the unique id of the session that is used to differentiate sessions across different
* types.
* <p/>
* Note: Different qos sessions can be provided by different actors.
*
* @return the unique id
*/
public long getUniqueId() {
return (long) mSessionType << 32 | mSessionId;
}
/**
* Gets the session id that is unique within that type.
* <p/>
* Note: The session id is set by the actor providing the qos. It can be either manufactured by
* the actor, but also may have a particular meaning within that type. For example, using the
* bearer id as the session id for {@link android.telephony.data.EpsBearerQosSessionAttributes}
* is a straight forward way to keep the sessions unique from one another within that type.
*
* @return the id of the session
*/
public int getSessionId() {
return mSessionId;
}
/**
* Gets the type of session.
*/
@QosSessionType
public int getSessionType() {
return mSessionType;
}
/**
* Creates a {@link QosSession}.
*
* @param sessionId uniquely identifies the session across all sessions of the same type
* @param sessionType the type of session
*/
public QosSession(final int sessionId, @QosSessionType final int sessionType) {
//Ensures the session id is unique across types of sessions
mSessionId = sessionId;
mSessionType = sessionType;
}
@Override
public String toString() {
return "QosSession{"
+ "mSessionId=" + mSessionId
+ ", mSessionType=" + mSessionType
+ '}';
}
/**
* Annotations for types of qos sessions.
*/
@IntDef(value = {
TYPE_EPS_BEARER,
})
@interface QosSessionType {}
private QosSession(final Parcel in) {
mSessionId = in.readInt();
mSessionType = in.readInt();
}
@NonNull
public static final Creator<QosSession> CREATOR = new Creator<QosSession>() {
@NonNull
@Override
public QosSession createFromParcel(@NonNull final Parcel in) {
return new QosSession(in);
}
@NonNull
@Override
public QosSession[] newArray(final int size) {
return new QosSession[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull final Parcel dest, final int flags) {
dest.writeInt(mSessionId);
dest.writeInt(mSessionType);
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright (C) 2020 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 android.net;
import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE;
import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
import java.io.FileDescriptor;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Objects;
/**
* Filters a {@link QosSession} according to the binding on the provided {@link Socket}.
*
* @hide
*/
public class QosSocketFilter extends QosFilter {
private static final String TAG = QosSocketFilter.class.getSimpleName();
@NonNull
private final QosSocketInfo mQosSocketInfo;
/**
* Creates a {@link QosSocketFilter} based off of {@link QosSocketInfo}.
*
* @param qosSocketInfo the information required to filter and validate
*/
public QosSocketFilter(@NonNull final QosSocketInfo qosSocketInfo) {
Objects.requireNonNull(qosSocketInfo, "qosSocketInfo must be non-null");
mQosSocketInfo = qosSocketInfo;
}
/**
* Gets the parcelable qos socket info that was used to create the filter.
*/
@NonNull
public QosSocketInfo getQosSocketInfo() {
return mQosSocketInfo;
}
/**
* Performs two validations:
* 1. If the socket is not bound, then return
* {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND}. This is detected
* by checking the local address on the filter which becomes null when the socket is no
* longer bound.
* 2. In the scenario that the socket is now bound to a different local address, which can
* happen in the case of UDP, then
* {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED} is returned.
* @return validation error code
*/
@Override
public int validate() {
final InetSocketAddress sa = getAddressFromFileDescriptor();
if (sa == null) {
return QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND;
}
if (!sa.equals(mQosSocketInfo.getLocalSocketAddress())) {
return EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
}
return EX_TYPE_FILTER_NONE;
}
/**
* The local address of the socket's binding.
*
* Note: If the socket is no longer bound, null is returned.
*
* @return the local address
*/
@Nullable
private InetSocketAddress getAddressFromFileDescriptor() {
final ParcelFileDescriptor parcelFileDescriptor = mQosSocketInfo.getParcelFileDescriptor();
if (parcelFileDescriptor == null) return null;
final FileDescriptor fd = parcelFileDescriptor.getFileDescriptor();
if (fd == null) return null;
final SocketAddress address;
try {
address = Os.getsockname(fd);
} catch (final ErrnoException e) {
Log.e(TAG, "getAddressFromFileDescriptor: getLocalAddress exception", e);
return null;
}
if (address instanceof InetSocketAddress) {
return (InetSocketAddress) address;
}
return null;
}
/**
* The network used with this filter.
*
* @return the registered {@link Network}
*/
@NonNull
@Override
public Network getNetwork() {
return mQosSocketInfo.getNetwork();
}
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright (C) 2020 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 android.net;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Objects;
/**
* Used in conjunction with
* {@link ConnectivityManager#registerQosCallback}
* in order to receive Qos Sessions related to the local address and port of a bound {@link Socket}.
*
* @hide
*/
@SystemApi
public final class QosSocketInfo implements Parcelable {
@NonNull
private final Network mNetwork;
@NonNull
private final ParcelFileDescriptor mParcelFileDescriptor;
@NonNull
private final InetSocketAddress mLocalSocketAddress;
/**
* The {@link Network} the socket is on.
*
* @return the registered {@link Network}
*/
@NonNull
public Network getNetwork() {
return mNetwork;
}
/**
* The parcel file descriptor wrapped around the socket's file descriptor.
*
* @return the parcel file descriptor of the socket
*/
@NonNull
ParcelFileDescriptor getParcelFileDescriptor() {
return mParcelFileDescriptor;
}
/**
* The local address of the socket passed into {@link QosSocketInfo(Network, Socket)}.
* The value does not reflect any changes that occur to the socket after it is first set
* in the constructor.
*
* @return the local address of the socket
*/
@NonNull
public InetSocketAddress getLocalSocketAddress() {
return mLocalSocketAddress;
}
/**
* Creates a {@link QosSocketInfo} given a {@link Network} and bound {@link Socket}. The
* {@link Socket} must remain bound in order to receive {@link QosSession}s.
*
* @param network the network
* @param socket the bound {@link Socket}
*/
public QosSocketInfo(@NonNull final Network network, @NonNull final Socket socket)
throws IOException {
Objects.requireNonNull(socket, "socket cannot be null");
mNetwork = Objects.requireNonNull(network, "network cannot be null");
mParcelFileDescriptor = ParcelFileDescriptor.dup(socket.getFileDescriptor$());
mLocalSocketAddress =
new InetSocketAddress(socket.getLocalAddress(), socket.getLocalPort());
}
/* Parcelable methods */
private QosSocketInfo(final Parcel in) {
mNetwork = Objects.requireNonNull(Network.CREATOR.createFromParcel(in));
mParcelFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in);
final int addressLength = in.readInt();
mLocalSocketAddress = readSocketAddress(in, addressLength);
}
private InetSocketAddress readSocketAddress(final Parcel in, final int addressLength) {
final byte[] address = new byte[addressLength];
in.readByteArray(address);
final int port = in.readInt();
try {
return new InetSocketAddress(InetAddress.getByAddress(address), port);
} catch (final UnknownHostException e) {
/* The catch block was purposely left empty. UnknownHostException will never be thrown
since the address provided is numeric and non-null. */
}
return new InetSocketAddress();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull final Parcel dest, final int flags) {
mNetwork.writeToParcel(dest, 0);
mParcelFileDescriptor.writeToParcel(dest, 0);
final byte[] address = mLocalSocketAddress.getAddress().getAddress();
dest.writeInt(address.length);
dest.writeByteArray(address);
dest.writeInt(mLocalSocketAddress.getPort());
}
@NonNull
public static final Parcelable.Creator<QosSocketInfo> CREATOR =
new Parcelable.Creator<QosSocketInfo>() {
@NonNull
@Override
public QosSocketInfo createFromParcel(final Parcel in) {
return new QosSocketInfo(in);
}
@NonNull
@Override
public QosSocketInfo[] newArray(final int size) {
return new QosSocketInfo[size];
}
};
}

View File

@@ -94,6 +94,7 @@ import android.net.INetworkMonitor;
import android.net.INetworkMonitorCallbacks;
import android.net.INetworkPolicyListener;
import android.net.INetworkStatsService;
import android.net.IQosCallback;
import android.net.ISocketKeepaliveCallback;
import android.net.InetAddresses;
import android.net.IpMemoryStore;
@@ -121,6 +122,10 @@ import android.net.NetworkUtils;
import android.net.NetworkWatchlistManager;
import android.net.PrivateDnsConfigParcel;
import android.net.ProxyInfo;
import android.net.QosCallbackException;
import android.net.QosFilter;
import android.net.QosSocketFilter;
import android.net.QosSocketInfo;
import android.net.RouteInfo;
import android.net.RouteInfoParcel;
import android.net.SocketKeepalive;
@@ -204,6 +209,7 @@ import com.android.server.connectivity.NetworkNotificationManager.NotificationTy
import com.android.server.connectivity.NetworkRanker;
import com.android.server.connectivity.PermissionMonitor;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.QosCallbackTracker;
import com.android.server.connectivity.Vpn;
import com.android.server.net.BaseNetworkObserver;
import com.android.server.net.LockdownVpnTracker;
@@ -279,6 +285,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
// Default to 30s linger time-out. Modifiable only for testing.
private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
private static final int DEFAULT_LINGER_DELAY_MS = 30_000;
// The maximum number of network request allowed per uid before an exception is thrown.
private static final int MAX_NETWORK_REQUESTS_PER_UID = 100;
@VisibleForTesting
protected int mLingerDelayMs; // Can't be final, or test subclass constructors can't change it.
@@ -291,6 +301,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
@VisibleForTesting
protected final PermissionMonitor mPermissionMonitor;
private final PerUidCounter mNetworkRequestCounter;
private KeyStore mKeyStore;
@VisibleForTesting
@@ -614,6 +626,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
private final LocationPermissionChecker mLocationPermissionChecker;
private KeepaliveTracker mKeepaliveTracker;
private QosCallbackTracker mQosCallbackTracker;
private NetworkNotificationManager mNotifier;
private LingerMonitor mLingerMonitor;
@@ -857,6 +870,66 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
};
/**
* Keeps track of the number of requests made under different uids.
*/
public static class PerUidCounter {
private final int mMaxCountPerUid;
// Map from UID to number of NetworkRequests that UID has filed.
@GuardedBy("mUidToNetworkRequestCount")
private final SparseIntArray mUidToNetworkRequestCount = new SparseIntArray();
/**
* Constructor
*
* @param maxCountPerUid the maximum count per uid allowed
*/
public PerUidCounter(final int maxCountPerUid) {
mMaxCountPerUid = maxCountPerUid;
}
/**
* Increments the request count of the given uid. Throws an exception if the number
* of open requests for the uid exceeds the value of maxCounterPerUid which is the value
* passed into the constructor. see: {@link #PerUidCounter(int)}.
*
* @throws ServiceSpecificException with
* {@link ConnectivityManager.Errors.TOO_MANY_REQUESTS} if the number of requests for
* the uid exceed the allowed number.
*
* @param uid the uid that the request was made under
*/
public void incrementCountOrThrow(final int uid) {
synchronized (mUidToNetworkRequestCount) {
final int networkRequests = mUidToNetworkRequestCount.get(uid, 0) + 1;
if (networkRequests >= mMaxCountPerUid) {
throw new ServiceSpecificException(
ConnectivityManager.Errors.TOO_MANY_REQUESTS);
}
mUidToNetworkRequestCount.put(uid, networkRequests);
}
}
/**
* Decrements the request count of the given uid.
*
* @param uid the uid that the request was made under
*/
public void decrementCount(final int uid) {
synchronized (mUidToNetworkRequestCount) {
final int requests = mUidToNetworkRequestCount.get(uid, 0);
if (requests < 1) {
logwtf("BUG: too small request count " + requests + " for UID " + uid);
} else if (requests == 1) {
mUidToNetworkRequestCount.delete(uid);
} else {
mUidToNetworkRequestCount.put(uid, requests - 1);
}
}
}
}
/**
* Dependencies of ConnectivityService, for injection in tests.
*/
@@ -945,6 +1018,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
mSystemProperties = mDeps.getSystemProperties();
mNetIdManager = mDeps.makeNetIdManager();
mContext = Objects.requireNonNull(context, "missing Context");
mNetworkRequestCounter = new PerUidCounter(MAX_NETWORK_REQUESTS_PER_UID);
mMetricsLog = logger;
mDefaultRequest = createDefaultInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST);
@@ -1125,6 +1199,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
mKeepaliveTracker = new KeepaliveTracker(mContext, mHandler);
mNotifier = new NetworkNotificationManager(mContext, mTelephonyManager);
mQosCallbackTracker = new QosCallbackTracker(mHandler, mNetworkRequestCounter);
final int dailyLimit = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT,
@@ -2777,6 +2852,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
updateCapabilitiesForNetwork(nai);
notifyIfacesChangedForNetworkStats();
}
break;
}
}
}
@@ -3338,6 +3414,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
// of rematchAllNetworksAndRequests
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
mKeepaliveTracker.handleStopAllKeepalives(nai, SocketKeepalive.ERROR_INVALID_NETWORK);
mQosCallbackTracker.handleNetworkReleased(nai.network);
for (String iface : nai.linkProperties.getAllInterfaceNames()) {
// Disable wakeup packet monitoring for each interface.
wakeupModifyInterface(iface, nai.networkCapabilities, false);
@@ -3607,7 +3685,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
nri.unlinkDeathRecipient();
mNetworkRequests.remove(nri.request);
decrementNetworkRequestPerUidCount(nri);
mNetworkRequestCounter.decrementCount(nri.mUid);
mNetworkRequestInfoLogs.log("RELEASE " + nri);
if (nri.request.isRequest()) {
@@ -3680,19 +3758,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
}
private void decrementNetworkRequestPerUidCount(final NetworkRequestInfo nri) {
synchronized (mUidToNetworkRequestCount) {
final int requests = mUidToNetworkRequestCount.get(nri.mUid, 0);
if (requests < 1) {
Log.wtf(TAG, "BUG: too small request count " + requests + " for UID " + nri.mUid);
} else if (requests == 1) {
mUidToNetworkRequestCount.removeAt(mUidToNetworkRequestCount.indexOfKey(nri.mUid));
} else {
mUidToNetworkRequestCount.put(nri.mUid, requests - 1);
}
}
}
@Override
public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
enforceNetworkStackSettingsOrSetup();
@@ -4519,6 +4584,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
Log.w(TAG, s);
}
private static void logwtf(String s) {
Log.wtf(TAG, s);
}
private static void loge(String s) {
Log.e(TAG, s);
}
@@ -5261,11 +5330,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>();
private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>();
private static final int MAX_NETWORK_REQUESTS_PER_UID = 100;
// Map from UID to number of NetworkRequests that UID has filed.
@GuardedBy("mUidToNetworkRequestCount")
private final SparseIntArray mUidToNetworkRequestCount = new SparseIntArray();
private static class NetworkProviderInfo {
public final String name;
public final Messenger messenger;
@@ -5379,7 +5443,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
mBinder = null;
mPid = getCallingPid();
mUid = mDeps.getCallingUid();
enforceRequestCountLimit();
mNetworkRequestCounter.incrementCountOrThrow(mUid);
}
NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder) {
@@ -5392,7 +5456,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
mPid = getCallingPid();
mUid = mDeps.getCallingUid();
mPendingIntent = null;
enforceRequestCountLimit();
mNetworkRequestCounter.incrementCountOrThrow(mUid);
try {
mBinder.linkToDeath(this, 0);
@@ -5429,17 +5493,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
return null;
}
private void enforceRequestCountLimit() {
synchronized (mUidToNetworkRequestCount) {
int networkRequests = mUidToNetworkRequestCount.get(mUid, 0) + 1;
if (networkRequests >= MAX_NETWORK_REQUESTS_PER_UID) {
throw new ServiceSpecificException(
ConnectivityManager.Errors.TOO_MANY_REQUESTS);
}
mUidToNetworkRequestCount.put(mUid, networkRequests);
}
}
void unlinkDeathRecipient() {
if (mBinder != null) {
mBinder.unlinkToDeath(this, 0);
@@ -5998,7 +6051,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
final NetworkAgentInfo nai = new NetworkAgentInfo(na,
new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc,
currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig),
this, mNetd, mDnsResolver, mNMS, providerId, uid);
this, mNetd, mDnsResolver, mNMS, providerId, uid, mQosCallbackTracker);
// Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says.
processCapabilitiesFromAgent(nai, nc);
@@ -8278,7 +8331,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
// Decrement the reference count for this NetworkRequestInfo. The reference count is
// incremented when the NetworkRequestInfo is created as part of
// enforceRequestCountLimit().
decrementNetworkRequestPerUidCount(nri);
mNetworkRequestCounter.decrementCount(nri.mUid);
return;
}
@@ -8344,7 +8397,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
// Decrement the reference count for this NetworkRequestInfo. The reference count is
// incremented when the NetworkRequestInfo is created as part of
// enforceRequestCountLimit().
decrementNetworkRequestPerUidCount(nri);
mNetworkRequestCounter.decrementCount(nri.mUid);
iCb.unlinkToDeath(cbInfo, 0);
}
@@ -8694,4 +8747,53 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
}
}
/**
* Registers {@link QosSocketFilter} with {@link IQosCallback}.
*
* @param socketInfo the socket information
* @param callback the callback to register
*/
@Override
public void registerQosSocketCallback(@NonNull final QosSocketInfo socketInfo,
@NonNull final IQosCallback callback) {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(socketInfo.getNetwork());
if (nai == null || nai.networkCapabilities == null) {
try {
callback.onError(QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED);
} catch (final RemoteException ex) {
loge("registerQosCallbackInternal: RemoteException", ex);
}
return;
}
registerQosCallbackInternal(new QosSocketFilter(socketInfo), callback, nai);
}
/**
* Register a {@link IQosCallback} with base {@link QosFilter}.
*
* @param filter the filter to register
* @param callback the callback to register
* @param nai the agent information related to the filter's network
*/
@VisibleForTesting
public void registerQosCallbackInternal(@NonNull final QosFilter filter,
@NonNull final IQosCallback callback, @NonNull final NetworkAgentInfo nai) {
if (filter == null) throw new IllegalArgumentException("filter must be non-null");
if (callback == null) throw new IllegalArgumentException("callback must be non-null");
if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
enforceConnectivityRestrictedNetworksPermission();
}
mQosCallbackTracker.registerCallback(callback, filter, nai);
}
/**
* Unregisters the given callback.
*
* @param callback the callback to unregister
*/
@Override
public void unregisterQosCallback(@NonNull final IQosCallback callback) {
mQosCallbackTracker.unregisterCallback(callback);
}
}

View File

@@ -36,12 +36,17 @@ import android.net.NetworkInfo;
import android.net.NetworkMonitorManager;
import android.net.NetworkRequest;
import android.net.NetworkState;
import android.net.QosCallbackException;
import android.net.QosFilter;
import android.net.QosFilterParcelable;
import android.net.QosSession;
import android.net.TcpKeepalivePacketData;
import android.os.Handler;
import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.RemoteException;
import android.os.SystemClock;
import android.telephony.data.EpsBearerQosSessionAttributes;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -321,18 +326,20 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
private final ConnectivityService mConnService;
private final Context mContext;
private final Handler mHandler;
private final QosCallbackTracker mQosCallbackTracker;
public NetworkAgentInfo(INetworkAgent na, Network net, NetworkInfo info,
LinkProperties lp, NetworkCapabilities nc, int score, Context context,
Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd,
IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber,
int creatorUid) {
int creatorUid, QosCallbackTracker qosCallbackTracker) {
Objects.requireNonNull(net);
Objects.requireNonNull(info);
Objects.requireNonNull(lp);
Objects.requireNonNull(nc);
Objects.requireNonNull(context);
Objects.requireNonNull(config);
Objects.requireNonNull(qosCallbackTracker);
networkAgent = na;
network = net;
networkInfo = info;
@@ -346,6 +353,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
networkAgentConfig = config;
this.factorySerialNumber = factorySerialNumber;
this.creatorUid = creatorUid;
mQosCallbackTracker = qosCallbackTracker;
}
private class AgentDeathMonitor implements IBinder.DeathRecipient {
@@ -531,6 +539,31 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
}
}
/**
* Notify the NetworkAgent that the qos filter should be registered against the given qos
* callback id.
*/
public void onQosFilterCallbackRegistered(final int qosCallbackId,
final QosFilter qosFilter) {
try {
networkAgent.onQosFilterCallbackRegistered(qosCallbackId,
new QosFilterParcelable(qosFilter));
} catch (final RemoteException e) {
Log.e(TAG, "Error registering a qos callback id against a qos filter", e);
}
}
/**
* Notify the NetworkAgent that the given qos callback id should be unregistered.
*/
public void onQosCallbackUnregistered(final int qosCallbackId) {
try {
networkAgent.onQosCallbackUnregistered(qosCallbackId);
} catch (RemoteException e) {
Log.e(TAG, "Error unregistering a qos callback id", e);
}
}
// TODO: consider moving out of NetworkAgentInfo into its own class
private class NetworkAgentMessageHandler extends INetworkAgentRegistry.Stub {
private final Handler mHandler;
@@ -584,6 +617,23 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
mHandler.obtainMessage(NetworkAgent.EVENT_UNDERLYING_NETWORKS_CHANGED,
new Pair<>(NetworkAgentInfo.this, networks)).sendToTarget();
}
@Override
public void sendEpsQosSessionAvailable(final int qosCallbackId, final QosSession session,
final EpsBearerQosSessionAttributes attributes) {
mQosCallbackTracker.sendEventQosSessionAvailable(qosCallbackId, session, attributes);
}
@Override
public void sendQosSessionLost(final int qosCallbackId, final QosSession session) {
mQosCallbackTracker.sendEventQosSessionLost(qosCallbackId, session);
}
@Override
public void sendQosCallbackError(final int qosCallbackId,
@QosCallbackException.ExceptionType final int exceptionType) {
mQosCallbackTracker.sendEventQosCallbackError(qosCallbackId, exceptionType);
}
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 The Android Open Source Project
* Copyright (C) 2020 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.
@@ -16,5 +16,177 @@
package com.android.server.connectivity;
class QosCallbackAgentConnection {
import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE;
import android.annotation.NonNull;
import android.net.IQosCallback;
import android.net.Network;
import android.net.QosCallbackException;
import android.net.QosFilter;
import android.net.QosSession;
import android.os.IBinder;
import android.os.RemoteException;
import android.telephony.data.EpsBearerQosSessionAttributes;
import android.util.Slog;
import java.util.Objects;
/**
* Wraps callback related information and sends messages between network agent and the application.
* <p/>
* This is a satellite class of {@link com.android.server.ConnectivityService} and not meant
* to be used in other contexts.
*
* @hide
*/
class QosCallbackAgentConnection implements IBinder.DeathRecipient {
private static final String TAG = QosCallbackAgentConnection.class.getSimpleName();
private static final boolean DBG = false;
private final int mAgentCallbackId;
@NonNull private final QosCallbackTracker mQosCallbackTracker;
@NonNull private final IQosCallback mCallback;
@NonNull private final IBinder mBinder;
@NonNull private final QosFilter mFilter;
@NonNull private final NetworkAgentInfo mNetworkAgentInfo;
private final int mUid;
/**
* Gets the uid
* @return uid
*/
int getUid() {
return mUid;
}
/**
* Gets the binder
* @return binder
*/
@NonNull
IBinder getBinder() {
return mBinder;
}
/**
* Gets the callback id
*
* @return callback id
*/
int getAgentCallbackId() {
return mAgentCallbackId;
}
/**
* Gets the network tied to the callback of this connection
*
* @return network
*/
@NonNull
Network getNetwork() {
return mFilter.getNetwork();
}
QosCallbackAgentConnection(@NonNull final QosCallbackTracker qosCallbackTracker,
final int agentCallbackId,
@NonNull final IQosCallback callback,
@NonNull final QosFilter filter,
final int uid,
@NonNull final NetworkAgentInfo networkAgentInfo) {
Objects.requireNonNull(qosCallbackTracker, "qosCallbackTracker must be non-null");
Objects.requireNonNull(callback, "callback must be non-null");
Objects.requireNonNull(filter, "filter must be non-null");
Objects.requireNonNull(networkAgentInfo, "networkAgentInfo must be non-null");
mQosCallbackTracker = qosCallbackTracker;
mAgentCallbackId = agentCallbackId;
mCallback = callback;
mFilter = filter;
mUid = uid;
mBinder = mCallback.asBinder();
mNetworkAgentInfo = networkAgentInfo;
}
@Override
public void binderDied() {
logw("binderDied: binder died with callback id: " + mAgentCallbackId);
mQosCallbackTracker.unregisterCallback(mCallback);
}
void unlinkToDeathRecipient() {
mBinder.unlinkToDeath(this, 0);
}
// Returns false if the NetworkAgent was never notified.
boolean sendCmdRegisterCallback() {
final int exceptionType = mFilter.validate();
if (exceptionType != EX_TYPE_FILTER_NONE) {
try {
if (DBG) log("sendCmdRegisterCallback: filter validation failed");
mCallback.onError(exceptionType);
} catch (final RemoteException e) {
loge("sendCmdRegisterCallback:", e);
}
return false;
}
try {
mBinder.linkToDeath(this, 0);
} catch (final RemoteException e) {
loge("failed linking to death recipient", e);
return false;
}
mNetworkAgentInfo.onQosFilterCallbackRegistered(mAgentCallbackId, mFilter);
return true;
}
void sendCmdUnregisterCallback() {
if (DBG) log("sendCmdUnregisterCallback: unregistering");
mNetworkAgentInfo.onQosCallbackUnregistered(mAgentCallbackId);
}
void sendEventQosSessionAvailable(final QosSession session,
final EpsBearerQosSessionAttributes attributes) {
try {
if (DBG) log("sendEventQosSessionAvailable: sending...");
mCallback.onQosEpsBearerSessionAvailable(session, attributes);
} catch (final RemoteException e) {
loge("sendEventQosSessionAvailable: remote exception", e);
}
}
void sendEventQosSessionLost(@NonNull final QosSession session) {
try {
if (DBG) log("sendEventQosSessionLost: sending...");
mCallback.onQosSessionLost(session);
} catch (final RemoteException e) {
loge("sendEventQosSessionLost: remote exception", e);
}
}
void sendEventQosCallbackError(@QosCallbackException.ExceptionType final int exceptionType) {
try {
if (DBG) log("sendEventQosCallbackError: sending...");
mCallback.onError(exceptionType);
} catch (final RemoteException e) {
loge("sendEventQosCallbackError: remote exception", e);
}
}
private static void log(@NonNull final String msg) {
Slog.d(TAG, msg);
}
private static void logw(@NonNull final String msg) {
Slog.w(TAG, msg);
}
private static void loge(@NonNull final String msg, final Throwable t) {
Slog.e(TAG, msg, t);
}
private static void logwtf(@NonNull final String msg) {
Slog.wtf(TAG, msg);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 The Android Open Source Project
* Copyright (C) 2020 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.
@@ -16,5 +16,262 @@
package com.android.server.connectivity;
class QosCallbackTracker {
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.IQosCallback;
import android.net.Network;
import android.net.QosCallbackException;
import android.net.QosFilter;
import android.net.QosSession;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.telephony.data.EpsBearerQosSessionAttributes;
import android.util.Slog;
import com.android.internal.util.CollectionUtils;
import com.android.server.ConnectivityService;
import java.util.ArrayList;
import java.util.List;
/**
* Tracks qos callbacks and handles the communication between the network agent and application.
* <p/>
* Any method prefixed by handle must be called from the
* {@link com.android.server.ConnectivityService} handler thread.
*
* @hide
*/
public class QosCallbackTracker {
private static final String TAG = QosCallbackTracker.class.getSimpleName();
private static final boolean DBG = true;
@NonNull
private final Handler mConnectivityServiceHandler;
@NonNull
private final ConnectivityService.PerUidCounter mNetworkRequestCounter;
/**
* Each agent gets a unique callback id that is used to proxy messages back to the original
* callback.
* <p/>
* Note: The fact that this is initialized to 0 is to ensure that the thread running
* {@link #handleRegisterCallback(IQosCallback, QosFilter, int, NetworkAgentInfo)} sees the
* initialized value. This would not necessarily be the case if the value was initialized to
* the non-default value.
* <p/>
* Note: The term previous does not apply to the first callback id that is assigned.
*/
private int mPreviousAgentCallbackId = 0;
@NonNull
private final List<QosCallbackAgentConnection> mConnections = new ArrayList<>();
/**
*
* @param connectivityServiceHandler must be the same handler used with
* {@link com.android.server.ConnectivityService}
* @param networkRequestCounter keeps track of the number of open requests under a given
* uid
*/
public QosCallbackTracker(@NonNull final Handler connectivityServiceHandler,
final ConnectivityService.PerUidCounter networkRequestCounter) {
mConnectivityServiceHandler = connectivityServiceHandler;
mNetworkRequestCounter = networkRequestCounter;
}
/**
* Registers the callback with the tracker
*
* @param callback the callback to register
* @param filter the filter being registered alongside the callback
*/
public void registerCallback(@NonNull final IQosCallback callback,
@NonNull final QosFilter filter, @NonNull final NetworkAgentInfo networkAgentInfo) {
final int uid = Binder.getCallingUid();
// Enforce that the number of requests under this uid has exceeded the allowed number
mNetworkRequestCounter.incrementCountOrThrow(uid);
mConnectivityServiceHandler.post(
() -> handleRegisterCallback(callback, filter, uid, networkAgentInfo));
}
private void handleRegisterCallback(@NonNull final IQosCallback callback,
@NonNull final QosFilter filter, final int uid,
@NonNull final NetworkAgentInfo networkAgentInfo) {
final QosCallbackAgentConnection ac =
handleRegisterCallbackInternal(callback, filter, uid, networkAgentInfo);
if (ac != null) {
if (DBG) log("handleRegisterCallback: added callback " + ac.getAgentCallbackId());
mConnections.add(ac);
} else {
mNetworkRequestCounter.decrementCount(uid);
}
}
private QosCallbackAgentConnection handleRegisterCallbackInternal(
@NonNull final IQosCallback callback,
@NonNull final QosFilter filter, final int uid,
@NonNull final NetworkAgentInfo networkAgentInfo) {
final IBinder binder = callback.asBinder();
if (CollectionUtils.any(mConnections, c -> c.getBinder().equals(binder))) {
// A duplicate registration would have only made this far due to a programming error.
logwtf("handleRegisterCallback: Callbacks can only be register once.");
return null;
}
mPreviousAgentCallbackId = mPreviousAgentCallbackId + 1;
final int newCallbackId = mPreviousAgentCallbackId;
final QosCallbackAgentConnection ac =
new QosCallbackAgentConnection(this, newCallbackId, callback,
filter, uid, networkAgentInfo);
final int exceptionType = filter.validate();
if (exceptionType != QosCallbackException.EX_TYPE_FILTER_NONE) {
ac.sendEventQosCallbackError(exceptionType);
return null;
}
// Only add to the callback maps if the NetworkAgent successfully registered it
if (!ac.sendCmdRegisterCallback()) {
// There was an issue when registering the agent
if (DBG) log("handleRegisterCallback: error sending register callback");
mNetworkRequestCounter.decrementCount(uid);
return null;
}
return ac;
}
/**
* Unregisters callback
* @param callback callback to unregister
*/
public void unregisterCallback(@NonNull final IQosCallback callback) {
mConnectivityServiceHandler.post(() -> handleUnregisterCallback(callback.asBinder(), true));
}
private void handleUnregisterCallback(@NonNull final IBinder binder,
final boolean sendToNetworkAgent) {
final QosCallbackAgentConnection agentConnection =
CollectionUtils.find(mConnections, c -> c.getBinder().equals(binder));
if (agentConnection == null) {
logw("handleUnregisterCallback: agentConnection is null");
return;
}
if (DBG) {
log("handleUnregisterCallback: unregister "
+ agentConnection.getAgentCallbackId());
}
mNetworkRequestCounter.decrementCount(agentConnection.getUid());
mConnections.remove(agentConnection);
if (sendToNetworkAgent) {
agentConnection.sendCmdUnregisterCallback();
}
agentConnection.unlinkToDeathRecipient();
}
/**
* Called when the NetworkAgent sends the qos session available event
*
* @param qosCallbackId the callback id that the qos session is now available to
* @param session the qos session that is now available
* @param attributes the qos attributes that are now available on the qos session
*/
public void sendEventQosSessionAvailable(final int qosCallbackId,
final QosSession session,
final EpsBearerQosSessionAttributes attributes) {
runOnAgentConnection(qosCallbackId, "sendEventQosSessionAvailable: ",
ac -> ac.sendEventQosSessionAvailable(session, attributes));
}
/**
* Called when the NetworkAgent sends the qos session lost event
*
* @param qosCallbackId the callback id that lost the qos session
* @param session the corresponding qos session
*/
public void sendEventQosSessionLost(final int qosCallbackId,
final QosSession session) {
runOnAgentConnection(qosCallbackId, "sendEventQosSessionLost: ",
ac -> ac.sendEventQosSessionLost(session));
}
/**
* Called when the NetworkAgent sends the qos session on error event
*
* @param qosCallbackId the callback id that should receive the exception
* @param exceptionType the type of exception that caused the callback to error
*/
public void sendEventQosCallbackError(final int qosCallbackId,
@QosCallbackException.ExceptionType final int exceptionType) {
runOnAgentConnection(qosCallbackId, "sendEventQosCallbackError: ",
ac -> {
ac.sendEventQosCallbackError(exceptionType);
handleUnregisterCallback(ac.getBinder(), false);
});
}
/**
* Unregisters all callbacks associated to this network agent
*
* Note: Must be called on the connectivity service handler thread
*
* @param network the network that was released
*/
public void handleNetworkReleased(@Nullable final Network network) {
final List<QosCallbackAgentConnection> connections =
CollectionUtils.filter(mConnections, ac -> ac.getNetwork().equals(network));
for (final QosCallbackAgentConnection agentConnection : connections) {
agentConnection.sendEventQosCallbackError(
QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED);
// Call unregister workflow w\o sending anything to agent since it is disconnected.
handleUnregisterCallback(agentConnection.getBinder(), false);
}
}
private interface AgentConnectionAction {
void execute(@NonNull QosCallbackAgentConnection agentConnection);
}
@Nullable
private void runOnAgentConnection(final int qosCallbackId,
@NonNull final String logPrefix,
@NonNull final AgentConnectionAction action) {
mConnectivityServiceHandler.post(() -> {
final QosCallbackAgentConnection ac =
CollectionUtils.find(mConnections,
c -> c.getAgentCallbackId() == qosCallbackId);
if (ac == null) {
loge(logPrefix + ": " + qosCallbackId + " missing callback id");
return;
}
action.execute(ac);
});
}
private static void log(final String msg) {
Slog.d(TAG, msg);
}
private static void logw(final String msg) {
Slog.w(TAG, msg);
}
private static void loge(final String msg) {
Slog.e(TAG, msg);
}
private static void logwtf(final String msg) {
Slog.wtf(TAG, msg);
}
}

View File

@@ -31,6 +31,7 @@ import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import android.annotation.NonNull;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
@@ -40,6 +41,7 @@ import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkProvider;
import android.net.NetworkSpecifier;
import android.net.QosFilter;
import android.net.SocketKeepalive;
import android.net.UidRange;
import android.os.ConditionVariable;
@@ -47,10 +49,12 @@ import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;
import com.android.net.module.util.ArrayTrackRecord;
import com.android.server.connectivity.ConnectivityConstants;
import com.android.testutils.HandlerUtils;
import com.android.testutils.TestableNetworkCallback;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -71,6 +75,8 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
// start/stop. Useful when simulate KeepaliveTracker is waiting for response from modem.
private long mKeepaliveResponseDelay = 0L;
private Integer mExpectedKeepaliveSlot = null;
private final ArrayTrackRecord<CallbackType>.ReadHead mCallbackHistory =
new ArrayTrackRecord<CallbackType>().newReadHead();
public NetworkAgentWrapper(int transport, LinkProperties linkProperties,
NetworkCapabilities ncTemplate, Context context) throws Exception {
@@ -156,6 +162,20 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
mWrapper.mKeepaliveResponseDelay);
}
@Override
public void onQosCallbackRegistered(final int qosCallbackId,
final @NonNull QosFilter filter) {
Log.i(mWrapper.mLogTag, "onQosCallbackRegistered");
mWrapper.mCallbackHistory.add(
new CallbackType.OnQosCallbackRegister(qosCallbackId, filter));
}
@Override
public void onQosCallbackUnregistered(final int qosCallbackId) {
Log.i(mWrapper.mLogTag, "onQosCallbackUnregistered");
mWrapper.mCallbackHistory.add(new CallbackType.OnQosCallbackUnregister(qosCallbackId));
}
@Override
protected void preventAutomaticReconnect() {
mWrapper.mPreventReconnectReceived.open();
@@ -279,7 +299,60 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
return mNetworkCapabilities;
}
public @NonNull ArrayTrackRecord<CallbackType>.ReadHead getCallbackHistory() {
return mCallbackHistory;
}
public void waitForIdle(long timeoutMs) {
HandlerUtils.waitForIdle(mHandlerThread, timeoutMs);
}
abstract static class CallbackType {
final int mQosCallbackId;
protected CallbackType(final int qosCallbackId) {
mQosCallbackId = qosCallbackId;
}
static class OnQosCallbackRegister extends CallbackType {
final QosFilter mFilter;
OnQosCallbackRegister(final int qosCallbackId, final QosFilter filter) {
super(qosCallbackId);
mFilter = filter;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final OnQosCallbackRegister that = (OnQosCallbackRegister) o;
return mQosCallbackId == that.mQosCallbackId
&& Objects.equals(mFilter, that.mFilter);
}
@Override
public int hashCode() {
return Objects.hash(mQosCallbackId, mFilter);
}
}
static class OnQosCallbackUnregister extends CallbackType {
OnQosCallbackUnregister(final int qosCallbackId) {
super(qosCallbackId);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final OnQosCallbackUnregister that = (OnQosCallbackUnregister) o;
return mQosCallbackId == that.mQosCallbackId;
}
@Override
public int hashCode() {
return Objects.hash(mQosCallbackId);
}
}
}
}

View File

@@ -167,6 +167,7 @@ import android.net.INetworkMonitor;
import android.net.INetworkMonitorCallbacks;
import android.net.INetworkPolicyListener;
import android.net.INetworkStatsService;
import android.net.IQosCallback;
import android.net.InetAddresses;
import android.net.InterfaceConfigurationParcel;
import android.net.IpPrefix;
@@ -190,6 +191,9 @@ import android.net.NetworkStackClient;
import android.net.NetworkState;
import android.net.NetworkTestResultParcelable;
import android.net.ProxyInfo;
import android.net.QosCallbackException;
import android.net.QosFilter;
import android.net.QosSession;
import android.net.ResolverParamsParcel;
import android.net.RouteInfo;
import android.net.RouteInfoParcel;
@@ -218,6 +222,7 @@ import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
@@ -226,10 +231,12 @@ import android.security.Credentials;
import android.security.KeyStore;
import android.system.Os;
import android.telephony.TelephonyManager;
import android.telephony.data.EpsBearerQosSessionAttributes;
import android.test.mock.MockContentResolver;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -251,6 +258,7 @@ import com.android.server.connectivity.Nat464Xlat;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.QosCallbackTracker;
import com.android.server.connectivity.Vpn;
import com.android.server.net.NetworkPinner;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -368,6 +376,8 @@ public class ConnectivityServiceTest {
private WrappedMultinetworkPolicyTracker mPolicyTracker;
private HandlerThread mAlarmManagerThread;
private TestNetIdManager mNetIdManager;
private QosCallbackMockHelper mQosCallbackMockHelper;
private QosCallbackTracker mQosCallbackTracker;
@Mock DeviceIdleInternal mDeviceIdleInternal;
@Mock INetworkManagementService mNetworkManagementService;
@@ -1395,6 +1405,7 @@ public class ConnectivityServiceTest {
mService.systemReadyInternal();
mockVpn(Process.myUid());
mCm.bindProcessToNetwork(null);
mQosCallbackTracker = mock(QosCallbackTracker.class);
// Ensure that the default setting for Captive Portals is used for most tests
setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
@@ -1470,6 +1481,11 @@ public class ConnectivityServiceTest {
mEthernetNetworkAgent.disconnect();
mEthernetNetworkAgent = null;
}
if (mQosCallbackMockHelper != null) {
mQosCallbackMockHelper.tearDown();
mQosCallbackMockHelper = null;
}
mMockVpn.disconnect();
waitForIdle();
@@ -4379,7 +4395,7 @@ public class ConnectivityServiceTest {
}
private Network connectKeepaliveNetwork(LinkProperties lp) throws Exception {
// Ensure the network is disconnected before we do anything.
// Ensure the network is disconnected before anything else occurs
if (mWiFiNetworkAgent != null) {
assertNull(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()));
}
@@ -8512,7 +8528,7 @@ public class ConnectivityServiceTest {
TelephonyManager.getNetworkTypeName(TelephonyManager.NETWORK_TYPE_LTE));
return new NetworkAgentInfo(null, new Network(NET_ID), info, new LinkProperties(),
nc, 0, mServiceContext, null, new NetworkAgentConfig(), mService, null, null, null,
0, INVALID_UID);
0, INVALID_UID, mQosCallbackTracker);
}
@Test
@@ -8890,7 +8906,7 @@ public class ConnectivityServiceTest {
@Test
public void testInvalidRequestTypes() {
final int[] invalidReqTypeInts = new int[] {-1, NetworkRequest.Type.NONE.ordinal(),
final int[] invalidReqTypeInts = new int[]{-1, NetworkRequest.Type.NONE.ordinal(),
NetworkRequest.Type.LISTEN.ordinal(), NetworkRequest.Type.values().length};
final NetworkCapabilities nc = new NetworkCapabilities().addTransportType(TRANSPORT_WIFI);
@@ -8903,4 +8919,151 @@ public class ConnectivityServiceTest {
);
}
}
private class QosCallbackMockHelper {
@NonNull public final QosFilter mFilter;
@NonNull public final IQosCallback mCallback;
@NonNull public final TestNetworkAgentWrapper mAgentWrapper;
@NonNull private final List<IQosCallback> mCallbacks = new ArrayList();
QosCallbackMockHelper() throws Exception {
Log.d(TAG, "QosCallbackMockHelper: ");
mFilter = mock(QosFilter.class);
// Ensure the network is disconnected before anything else occurs
assertNull(mCellNetworkAgent);
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
verifyActiveNetwork(TRANSPORT_CELLULAR);
waitForIdle();
final Network network = mCellNetworkAgent.getNetwork();
final Pair<IQosCallback, IBinder> pair = createQosCallback();
mCallback = pair.first;
when(mFilter.getNetwork()).thenReturn(network);
when(mFilter.validate()).thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE);
mAgentWrapper = mCellNetworkAgent;
}
void registerQosCallback(@NonNull final QosFilter filter,
@NonNull final IQosCallback callback) {
mCallbacks.add(callback);
final NetworkAgentInfo nai =
mService.getNetworkAgentInfoForNetwork(filter.getNetwork());
mService.registerQosCallbackInternal(filter, callback, nai);
}
void tearDown() {
for (int i = 0; i < mCallbacks.size(); i++) {
mService.unregisterQosCallback(mCallbacks.get(i));
}
}
}
private Pair<IQosCallback, IBinder> createQosCallback() {
final IQosCallback callback = mock(IQosCallback.class);
final IBinder binder = mock(Binder.class);
when(callback.asBinder()).thenReturn(binder);
when(binder.isBinderAlive()).thenReturn(true);
return new Pair<>(callback, binder);
}
@Test
public void testQosCallbackRegistration() throws Exception {
mQosCallbackMockHelper = new QosCallbackMockHelper();
final NetworkAgentWrapper wrapper = mQosCallbackMockHelper.mAgentWrapper;
when(mQosCallbackMockHelper.mFilter.validate())
.thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE);
mQosCallbackMockHelper.registerQosCallback(
mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
final NetworkAgentWrapper.CallbackType.OnQosCallbackRegister cbRegister1 =
(NetworkAgentWrapper.CallbackType.OnQosCallbackRegister)
wrapper.getCallbackHistory().poll(1000, x -> true);
assertNotNull(cbRegister1);
final int registerCallbackId = cbRegister1.mQosCallbackId;
mService.unregisterQosCallback(mQosCallbackMockHelper.mCallback);
final NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister cbUnregister;
cbUnregister = (NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister)
wrapper.getCallbackHistory().poll(1000, x -> true);
assertNotNull(cbUnregister);
assertEquals(registerCallbackId, cbUnregister.mQosCallbackId);
assertNull(wrapper.getCallbackHistory().poll(200, x -> true));
}
@Test
public void testQosCallbackNoRegistrationOnValidationError() throws Exception {
mQosCallbackMockHelper = new QosCallbackMockHelper();
when(mQosCallbackMockHelper.mFilter.validate())
.thenReturn(QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED);
mQosCallbackMockHelper.registerQosCallback(
mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
waitForIdle();
verify(mQosCallbackMockHelper.mCallback)
.onError(eq(QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED));
}
@Test
public void testQosCallbackAvailableAndLost() throws Exception {
mQosCallbackMockHelper = new QosCallbackMockHelper();
final int sessionId = 10;
final int qosCallbackId = 1;
when(mQosCallbackMockHelper.mFilter.validate())
.thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE);
mQosCallbackMockHelper.registerQosCallback(
mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
waitForIdle();
final EpsBearerQosSessionAttributes attributes = new EpsBearerQosSessionAttributes(
1, 2, 3, 4, 5, new ArrayList<>());
mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
.sendQosSessionAvailable(qosCallbackId, sessionId, attributes);
waitForIdle();
verify(mQosCallbackMockHelper.mCallback).onQosEpsBearerSessionAvailable(argThat(session ->
session.getSessionId() == sessionId
&& session.getSessionType() == QosSession.TYPE_EPS_BEARER), eq(attributes));
mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
.sendQosSessionLost(qosCallbackId, sessionId);
waitForIdle();
verify(mQosCallbackMockHelper.mCallback).onQosSessionLost(argThat(session ->
session.getSessionId() == sessionId
&& session.getSessionType() == QosSession.TYPE_EPS_BEARER));
}
@Test
public void testQosCallbackTooManyRequests() throws Exception {
mQosCallbackMockHelper = new QosCallbackMockHelper();
when(mQosCallbackMockHelper.mFilter.validate())
.thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE);
for (int i = 0; i < 100; i++) {
final Pair<IQosCallback, IBinder> pair = createQosCallback();
try {
mQosCallbackMockHelper.registerQosCallback(
mQosCallbackMockHelper.mFilter, pair.first);
} catch (ServiceSpecificException e) {
assertEquals(e.errorCode, ConnectivityManager.Errors.TOO_MANY_REQUESTS);
if (i < 50) {
fail("TOO_MANY_REQUESTS thrown too early, the count is " + i);
}
// As long as there is at least 50 requests, it is safe to assume it works.
// Note: The count isn't being tested precisely against 100 because the counter
// is shared with request network.
return;
}
}
fail("TOO_MANY_REQUESTS never thrown");
}
}

View File

@@ -78,6 +78,7 @@ public class LingerMonitorTest {
@Mock Context mCtx;
@Mock NetworkNotificationManager mNotifier;
@Mock Resources mResources;
@Mock QosCallbackTracker mQosCallbackTracker;
@Before
public void setUp() {
@@ -358,7 +359,7 @@ public class LingerMonitorTest {
NetworkAgentInfo nai = new NetworkAgentInfo(null, new Network(netId), info,
new LinkProperties(), caps, 50, mCtx, null, new NetworkAgentConfig() /* config */,
mConnService, mNetd, mDnsResolver, mNMS, NetworkProvider.ID_NONE,
Binder.getCallingUid());
Binder.getCallingUid(), mQosCallbackTracker);
nai.everValidated = true;
return nai;
}