Merge "CaptivePortal: implement setDelegateUid API." into main
This commit is contained in:
commit
69aa3c5469
6 changed files with 212 additions and 1 deletions
|
|
@ -75,6 +75,7 @@ java_defaults {
|
|||
// the module builds against API (the parcelable declarations exist in framework.aidl)
|
||||
"frameworks/base/core/java", // For framework parcelables
|
||||
"frameworks/native/aidl/binder", // For PersistableBundle.aidl
|
||||
"packages/modules/Connectivity/Tethering/common/TetheringLib/src",
|
||||
],
|
||||
},
|
||||
stub_only_libs: [
|
||||
|
|
@ -335,6 +336,7 @@ droidstubs {
|
|||
aidl: {
|
||||
include_dirs: [
|
||||
"packages/modules/Connectivity/framework/aidl-export",
|
||||
"packages/modules/Connectivity/Tethering/common/TetheringLib/src",
|
||||
"frameworks/native/aidl/binder", // For PersistableBundle.aidl
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -18,10 +18,19 @@ package android.net;
|
|||
import android.annotation.NonNull;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.OutcomeReceiver;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceSpecificException;
|
||||
import android.system.OsConstants;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* A class allowing apps handling the {@link ConnectivityManager#ACTION_CAPTIVE_PORTAL_SIGN_IN}
|
||||
|
|
@ -69,6 +78,15 @@ public class CaptivePortal implements Parcelable {
|
|||
@SystemApi
|
||||
public static final int APP_REQUEST_REEVALUATION_REQUIRED = APP_REQUEST_BASE + 0;
|
||||
|
||||
/**
|
||||
* Binder object used for tracking the lifetime of the process, so CS can perform cleanup if
|
||||
* the CaptivePortal app dies. This binder is not parcelled as part of this object. It is
|
||||
* created in the client process and sent to the server by setDelegateUid so that the server
|
||||
* can use it to register a death recipient.
|
||||
*
|
||||
*/
|
||||
private final Binder mLifetimeBinder = new Binder();
|
||||
|
||||
private final IBinder mBinder;
|
||||
|
||||
/** @hide */
|
||||
|
|
@ -167,4 +185,56 @@ public class CaptivePortal implements Parcelable {
|
|||
@SystemApi
|
||||
public void logEvent(int eventId, @NonNull String packageName) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the UID of the app that is allowed to perform network traffic for captive
|
||||
* portal login.
|
||||
*
|
||||
* This app will be allowed to communicate directly on the captive
|
||||
* portal by binding to the {@link android.net.Network} extra passed in the
|
||||
* ACTION_CAPTIVE_PORTAL_SIGN_IN broadcast that contained this object.
|
||||
*
|
||||
* Communication will bypass network access restrictions such as VPNs and
|
||||
* Private DNS settings, so the delegated UID must be trusted to ensure that only
|
||||
* traffic intended for captive portal login binds to that network.
|
||||
*
|
||||
* By default, no UID is delegated. The delegation can be cleared by calling
|
||||
* this method again with {@link android.os.Process.INVALID_UID}. Only one UID can
|
||||
* be delegated at any given time.
|
||||
*
|
||||
* The operation is asynchronous. The uid is only guaranteed to have access when
|
||||
* the provided OutcomeReceiver is called.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
|
||||
// OutcomeReceiver is not available on R, but the mainline version of this
|
||||
// class is only available on S+.
|
||||
@TargetApi(Build.VERSION_CODES.S)
|
||||
public void setDelegateUid(int uid, @NonNull Executor executor,
|
||||
@NonNull final OutcomeReceiver<Void, ServiceSpecificException> receiver) {
|
||||
Objects.requireNonNull(executor);
|
||||
Objects.requireNonNull(receiver);
|
||||
try {
|
||||
ICaptivePortal.Stub.asInterface(mBinder).setDelegateUid(
|
||||
uid,
|
||||
mLifetimeBinder,
|
||||
new IIntResultListener.Stub() {
|
||||
@Override
|
||||
public void onResult(int resultCode) {
|
||||
if (resultCode != 0) {
|
||||
final String msg = "Fail to set the delegate UID " + uid
|
||||
+ ", error: " + OsConstants.errnoName(resultCode);
|
||||
executor.execute(() -> {
|
||||
receiver.onError(new ServiceSpecificException(resultCode, msg));
|
||||
});
|
||||
} else {
|
||||
executor.execute(() -> receiver.onResult(null));
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@
|
|||
|
||||
package android.net;
|
||||
|
||||
import android.net.IIntResultListener;
|
||||
import android.os.IBinder;
|
||||
|
||||
/**
|
||||
* Interface to inform NetworkMonitor of decisions of app handling captive portal.
|
||||
* @hide
|
||||
|
|
@ -23,4 +26,5 @@ package android.net;
|
|||
oneway interface ICaptivePortal {
|
||||
void appRequest(int request);
|
||||
void appResponse(int response);
|
||||
void setDelegateUid(int uid, IBinder binder, IIntResultListener listener);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,6 +121,9 @@ import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_SELF_CER
|
|||
import static android.net.connectivity.ConnectivityCompatChanges.NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION;
|
||||
import static android.os.Process.INVALID_UID;
|
||||
import static android.os.Process.VPN_UID;
|
||||
import static android.system.OsConstants.ENOENT;
|
||||
import static android.system.OsConstants.ENOTCONN;
|
||||
import static android.system.OsConstants.EOPNOTSUPP;
|
||||
import static android.system.OsConstants.ETH_P_ALL;
|
||||
import static android.system.OsConstants.IPPROTO_TCP;
|
||||
import static android.system.OsConstants.IPPROTO_UDP;
|
||||
|
|
@ -195,6 +198,7 @@ import android.net.ICaptivePortal;
|
|||
import android.net.IConnectivityDiagnosticsCallback;
|
||||
import android.net.IConnectivityManager;
|
||||
import android.net.IDnsResolver;
|
||||
import android.net.IIntResultListener;
|
||||
import android.net.INetd;
|
||||
import android.net.INetworkActivityListener;
|
||||
import android.net.INetworkAgent;
|
||||
|
|
@ -6340,8 +6344,20 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
}
|
||||
}
|
||||
|
||||
private class CaptivePortalImpl extends ICaptivePortal.Stub {
|
||||
public class CaptivePortalImpl extends ICaptivePortal.Stub implements IBinder.DeathRecipient {
|
||||
private final Network mNetwork;
|
||||
// Binder object to track the lifetime of the setDelegateUid caller for cleanup purposes.
|
||||
//
|
||||
// Note that in theory it can happen that there are multiple callers for a given
|
||||
// object. For example, the app that receives the CaptivePortal object from the Intent
|
||||
// fired by startCaptivePortalAppInternal could send the object to another process, or
|
||||
// clone it. Only the first of these objects that calls setDelegateUid will properly
|
||||
// register a death recipient. Calls from the other objects will work, but only the
|
||||
// first object's death will cause the death recipient to fire.
|
||||
// TODO: track all callers by callerBinder instead of CaptivePortalImpl, store callerBinder
|
||||
// in a Set. When the death recipient fires, we can remove the callingBinder from the set,
|
||||
// and when the set is empty, we can clear the delegated UID.
|
||||
private IBinder mDelegateUidCaller;
|
||||
|
||||
private CaptivePortalImpl(Network network) {
|
||||
mNetwork = network;
|
||||
|
|
@ -6381,6 +6397,55 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
}
|
||||
}
|
||||
|
||||
private int handleSetDelegateUid(int uid, @NonNull final IBinder callerBinder) {
|
||||
if (mDelegateUidCaller == null) {
|
||||
mDelegateUidCaller = callerBinder;
|
||||
try {
|
||||
// While technically unnecessary, it is safe to register a DeathRecipient for
|
||||
// a cleanup operation (where uid = INVALID_UID).
|
||||
mDelegateUidCaller.linkToDeath(this, 0);
|
||||
} catch (RemoteException e) {
|
||||
// remote has died, return early.
|
||||
return ENOTCONN;
|
||||
}
|
||||
}
|
||||
|
||||
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(mNetwork);
|
||||
if (nai == null) return ENOENT; // network does not exist anymore.
|
||||
if (nai.isDestroyed()) return ENOENT; // network has already been destroyed.
|
||||
|
||||
// TODO: consider allowing the uid to bypass VPN on all networks before V.
|
||||
if (!mDeps.isAtLeastV()) return EOPNOTSUPP;
|
||||
|
||||
// Check whether there has already been a delegate UID configured, if so, perform
|
||||
// cleanup and disallow bypassing VPN for that UID if no other caller is delegating
|
||||
// this UID.
|
||||
// TODO: consider using exceptions instead of errnos.
|
||||
final int errno = nai.removeCaptivePortalDelegateUid(this);
|
||||
if (errno != 0) return errno;
|
||||
|
||||
// If uid == INVALID_UID, we are done.
|
||||
if (uid == INVALID_UID) return 0;
|
||||
return nai.setCaptivePortalDelegateUid(this, uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDelegateUid(int uid, @NonNull final IBinder callerBinder,
|
||||
@NonNull final IIntResultListener listener) {
|
||||
Objects.requireNonNull(callerBinder);
|
||||
Objects.requireNonNull(listener);
|
||||
enforceAnyPermissionOf(mContext, NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
|
||||
|
||||
mHandler.post(() -> {
|
||||
final int errno = handleSetDelegateUid(uid, callerBinder);
|
||||
try {
|
||||
listener.onResult(errno);
|
||||
} catch (RemoteException e) {
|
||||
// remote has died, nothing to do.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private NetworkMonitorManager getNetworkMonitorManager(final Network network) {
|
||||
// getNetworkAgentInfoForNetwork is thread-safe
|
||||
|
|
@ -6390,6 +6455,13 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||
// nai.networkMonitor() is thread-safe
|
||||
return nai.networkMonitor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
// Cleanup invalid UID and restore the VPN bypass rule. Because mDelegateUidCaller is
|
||||
// never reset, it cannot be null in this context.
|
||||
mHandler.post(() -> handleSetDelegateUid(INVALID_UID, mDelegateUidCaller));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean avoidBadWifi() {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
|
|||
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
|
||||
import static android.net.NetworkCapabilities.transportNamesOf;
|
||||
import static android.system.OsConstants.EIO;
|
||||
import static android.system.OsConstants.EEXIST;
|
||||
import static android.system.OsConstants.ENOENT;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
|
@ -57,9 +60,11 @@ import android.net.TcpKeepalivePacketData;
|
|||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceSpecificException;
|
||||
import android.os.SystemClock;
|
||||
import android.telephony.data.EpsBearerQosSessionAttributes;
|
||||
import android.telephony.data.NrQosSessionAttributes;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
|
@ -70,6 +75,7 @@ import com.android.internal.util.IndentingPrintWriter;
|
|||
import com.android.internal.util.WakeupMessage;
|
||||
import com.android.net.module.util.HandlerUtils;
|
||||
import com.android.server.ConnectivityService;
|
||||
import com.android.server.ConnectivityService.CaptivePortalImpl;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.net.Inet4Address;
|
||||
|
|
@ -574,6 +580,10 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
|
|||
// For fast lookups. Indexes into mInactivityTimers by request ID.
|
||||
private final SparseArray<InactivityTimer> mInactivityTimerForRequest = new SparseArray<>();
|
||||
|
||||
// Map of delegated UIDs used to bypass VPN and its captive portal app caller.
|
||||
private final ArrayMap<CaptivePortalImpl, Integer> mCaptivePortalDelegateUids =
|
||||
new ArrayMap<>();
|
||||
|
||||
// Inactivity expiry timer. Armed whenever mInactivityTimers is non-empty, regardless of
|
||||
// whether the network is inactive or not. Always set to the expiry of the mInactivityTimers
|
||||
// that expires last. When the timer fires, all inactivity state is cleared, and if the network
|
||||
|
|
@ -626,6 +636,7 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
|
|||
private final Context mContext;
|
||||
private final Handler mHandler;
|
||||
private final QosCallbackTracker mQosCallbackTracker;
|
||||
private final INetd mNetd;
|
||||
|
||||
private final long mCreationTime;
|
||||
|
||||
|
|
@ -655,6 +666,7 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
|
|||
mConnServiceDeps = deps;
|
||||
setScore(score); // uses members connService, networkCapabilities and networkAgentConfig
|
||||
clatd = new Nat464Xlat(this, netd, dnsResolver, deps);
|
||||
mNetd = netd;
|
||||
mContext = context;
|
||||
mHandler = handler;
|
||||
this.factorySerialNumber = factorySerialNumber;
|
||||
|
|
@ -1549,6 +1561,52 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable {
|
|||
}
|
||||
}
|
||||
|
||||
private int allowBypassVpnOnNetwork(boolean allow, int uid, int netId) {
|
||||
try {
|
||||
mNetd.networkAllowBypassVpnOnNetwork(allow, uid, netId);
|
||||
return 0;
|
||||
} catch (RemoteException e) {
|
||||
// Netd has crashed, and this process is about to crash as well.
|
||||
return EIO;
|
||||
} catch (ServiceSpecificException e) {
|
||||
return e.errorCode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the delegate UID of the app that is allowed to perform network traffic for captive
|
||||
* portal login, and configure the netd bypass rule with this delegated UID.
|
||||
*
|
||||
* @param caller the captive portal app to that delegated UID
|
||||
* @param uid the delegated UID of the captive portal app.
|
||||
* @return Return 0 if set the UID and VPN bypass rule successfully or bypass rule corresponding
|
||||
* to this UID already exists otherwise return errno.
|
||||
*/
|
||||
public int setCaptivePortalDelegateUid(@NonNull final CaptivePortalImpl caller, int uid) {
|
||||
final int errorCode = allowBypassVpnOnNetwork(true /* allow */, uid, network.netId);
|
||||
if (errorCode == 0 || errorCode == EEXIST) {
|
||||
mCaptivePortalDelegateUids.put(caller, uid);
|
||||
}
|
||||
return errorCode == EEXIST ? 0 : errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the delegate UID of the app that is allowed to perform network traffic for captive
|
||||
* portal login, and remove the netd bypass rule if no other caller is delegating this UID.
|
||||
*
|
||||
* @param caller the captive portal app to that delegated UID.
|
||||
* @return Return 0 if remove the UID and VPN bypass rule successfully or bypass rule
|
||||
* corresponding to this UID doesn't exist otherwise return errno.
|
||||
*/
|
||||
public int removeCaptivePortalDelegateUid(@NonNull final CaptivePortalImpl caller) {
|
||||
final Integer maybeDelegateUid = mCaptivePortalDelegateUids.remove(caller);
|
||||
if (maybeDelegateUid == null) return 0;
|
||||
if (mCaptivePortalDelegateUids.values().contains(maybeDelegateUid)) return 0;
|
||||
final int errorCode =
|
||||
allowBypassVpnOnNetwork(false /* allow */, maybeDelegateUid, network.netId);
|
||||
return errorCode == ENOENT ? 0 : errorCode;
|
||||
}
|
||||
|
||||
private static boolean areAllowedUidsAcceptableFromNetworkAgent(
|
||||
@NonNull final NetworkCapabilities nc, final boolean hasAutomotiveFeature,
|
||||
@NonNull final ConnectivityService.Dependencies deps,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package android.net;
|
|||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
|
@ -55,6 +56,10 @@ public class CaptivePortalTest {
|
|||
mCode = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDelegateUid(int uid, IBinder binder, IIntResultListener listener) {
|
||||
}
|
||||
|
||||
// This is only @Override on R-
|
||||
public void logEvent(int eventId, String packageName) throws RemoteException {
|
||||
mCode = eventId;
|
||||
|
|
|
|||
Loading…
Reference in a new issue