diff --git a/core/java/android/net/ConnectionInfo.java b/core/java/android/net/ConnectionInfo.java new file mode 100644 index 0000000000..58d0e05be6 --- /dev/null +++ b/core/java/android/net/ConnectionInfo.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2018 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.os.Parcel; +import android.os.Parcelable; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; + +/** + * Describe a network connection including local and remote address/port of a connection and the + * transport protocol. + * + * @hide + */ +public final class ConnectionInfo implements Parcelable { + public final int protocol; + public final InetSocketAddress local; + public final InetSocketAddress remote; + + @Override + public int describeContents() { + return 0; + } + + public ConnectionInfo(int protocol, InetSocketAddress local, InetSocketAddress remote) { + this.protocol = protocol; + this.local = local; + this.remote = remote; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(protocol); + out.writeByteArray(local.getAddress().getAddress()); + out.writeInt(local.getPort()); + out.writeByteArray(remote.getAddress().getAddress()); + out.writeInt(remote.getPort()); + } + + public static final Creator CREATOR = new Creator() { + public ConnectionInfo createFromParcel(Parcel in) { + int protocol = in.readInt(); + InetAddress localAddress; + try { + localAddress = InetAddress.getByAddress(in.createByteArray()); + } catch (UnknownHostException e) { + throw new IllegalArgumentException("Invalid InetAddress"); + } + int localPort = in.readInt(); + InetAddress remoteAddress; + try { + remoteAddress = InetAddress.getByAddress(in.createByteArray()); + } catch (UnknownHostException e) { + throw new IllegalArgumentException("Invalid InetAddress"); + } + int remotePort = in.readInt(); + InetSocketAddress local = new InetSocketAddress(localAddress, localPort); + InetSocketAddress remote = new InetSocketAddress(remoteAddress, remotePort); + return new ConnectionInfo(protocol, local, remote); + } + + public ConnectionInfo[] newArray(int size) { + return new ConnectionInfo[size]; + } + }; +} diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index ce1879620c..f2e9078336 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -59,6 +59,7 @@ import libcore.net.event.NetworkEventDispatcher; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -3930,4 +3931,26 @@ public class ConnectivityManager { throw e.rethrowFromSystemServer(); } } + + /** + * Returns the {@code uid} of the owner of a network connection. + * + * @param protocol The protocol of the connection. Only {@code IPPROTO_TCP} and + * {@code IPPROTO_UDP} currently supported. + * @param local The local {@link InetSocketAddress} of a connection. + * @param remote The remote {@link InetSocketAddress} of a connection. + * + * @return {@code uid} if the connection is found and the app has permission to observe it + * (e.g., if it is associated with the calling VPN app's tunnel) or + * {@link android.os.Process#INVALID_UID} if the connection is not found. + */ + public int getConnectionOwnerUid(int protocol, InetSocketAddress local, + InetSocketAddress remote) { + ConnectionInfo connectionInfo = new ConnectionInfo(protocol, local, remote); + try { + return mService.getConnectionOwnerUid(connectionInfo); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index ce95b60dd2..e7d441df82 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -17,6 +17,7 @@ package android.net; import android.app.PendingIntent; +import android.net.ConnectionInfo; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; @@ -182,4 +183,6 @@ interface IConnectivityManager String getCaptivePortalServerUrl(); byte[] getNetworkWatchlistConfigHash(); + + int getConnectionOwnerUid(in ConnectionInfo connectionInfo); } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 760209024c..e41a09ef67 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -35,6 +35,9 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_VPN; +import static android.os.Process.INVALID_UID; +import static android.system.OsConstants.IPPROTO_TCP; +import static android.system.OsConstants.IPPROTO_UDP; import static com.android.internal.util.Preconditions.checkNotNull; @@ -49,6 +52,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.database.ContentObserver; +import android.net.ConnectionInfo; import android.net.ConnectivityManager; import android.net.ConnectivityManager.PacketKeepalive; import android.net.IConnectivityManager; @@ -75,7 +79,6 @@ import android.net.NetworkSpecifier; import android.net.NetworkState; import android.net.NetworkUtils; import android.net.NetworkWatchlistManager; -import android.net.Proxy; import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.UidRange; @@ -83,6 +86,7 @@ import android.net.Uri; import android.net.VpnService; import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; +import android.net.netlink.InetDiagMessage; import android.net.util.MultinetworkPolicyTracker; import android.os.Binder; import android.os.Build; @@ -153,7 +157,6 @@ import com.android.server.connectivity.NetworkDiagnostics; import com.android.server.connectivity.NetworkMonitor; import com.android.server.connectivity.NetworkNotificationManager; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; -import com.android.server.connectivity.PacManager; import com.android.server.connectivity.PermissionMonitor; import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.Tethering; @@ -1680,6 +1683,11 @@ public class ConnectivityService extends IConnectivityManager.Stub "ConnectivityService"); } + private boolean checkNetworkStackPermission() { + return PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( + android.Manifest.permission.NETWORK_STACK); + } + private void enforceConnectivityRestrictedNetworksPermission() { try { mContext.enforceCallingOrSelfPermission( @@ -5922,4 +5930,49 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println(" Get airplane mode."); } } + + /** + * Caller either needs to be an active VPN, or hold the NETWORK_STACK permission + * for testing. + */ + private Vpn enforceActiveVpnOrNetworkStackPermission() { + if (checkNetworkStackPermission()) { + return null; + } + final int uid = Binder.getCallingUid(); + final int user = UserHandle.getUserId(uid); + synchronized (mVpns) { + Vpn vpn = mVpns.get(user); + try { + if (vpn.getVpnInfo().ownerUid == uid) return vpn; + } catch (NullPointerException e) { + /* vpn is null, or VPN is not connected and getVpnInfo() is null. */ + } + } + throw new SecurityException("App must either be an active VPN or have the NETWORK_STACK " + + "permission"); + } + + /** + * @param connectionInfo the connection to resolve. + * @return {@code uid} if the connection is found and the app has permission to observe it + * (e.g., if it is associated with the calling VPN app's tunnel) or {@code INVALID_UID} if the + * connection is not found. + */ + public int getConnectionOwnerUid(ConnectionInfo connectionInfo) { + final Vpn vpn = enforceActiveVpnOrNetworkStackPermission(); + if (connectionInfo.protocol != IPPROTO_TCP && connectionInfo.protocol != IPPROTO_UDP) { + throw new IllegalArgumentException("Unsupported protocol " + connectionInfo.protocol); + } + + final int uid = InetDiagMessage.getConnectionOwnerUid(connectionInfo.protocol, + connectionInfo.local, connectionInfo.remote); + + /* Filter out Uids not associated with the VPN. */ + if (vpn != null && !vpn.appliesToUid(uid)) { + return INVALID_UID; + } + + return uid; + } }