diff options
| -rwxr-xr-x | api/current.txt | 2 | ||||
| -rw-r--r-- | core/java/android/net/ConnectionInfo.aidl | 20 | ||||
| -rw-r--r-- | core/java/android/net/ConnectionInfo.java | 83 | ||||
| -rw-r--r-- | core/java/android/net/ConnectivityManager.java | 23 | ||||
| -rw-r--r-- | core/java/android/net/IConnectivityManager.aidl | 3 | ||||
| -rw-r--r-- | core/java/android/os/Process.java | 5 | ||||
| -rw-r--r-- | services/core/java/com/android/server/ConnectivityService.java | 57 | ||||
| -rw-r--r-- | services/net/java/android/net/netlink/InetDiagMessage.java | 187 | ||||
| -rw-r--r-- | services/net/java/android/net/netlink/NetlinkConstants.java | 9 | ||||
| -rw-r--r-- | services/net/java/android/net/netlink/NetlinkMessage.java | 2 | ||||
| -rw-r--r-- | services/net/java/android/net/netlink/StructInetDiagMsg.java | 67 | ||||
| -rw-r--r-- | services/net/java/android/net/netlink/StructInetDiagReqV2.java | 80 | ||||
| -rw-r--r-- | services/net/java/android/net/netlink/StructInetDiagSockId.java | 86 | ||||
| -rw-r--r-- | tests/net/AndroidManifest.xml | 1 | ||||
| -rw-r--r-- | tests/net/java/android/net/netlink/InetDiagSocketTest.java | 337 |
15 files changed, 960 insertions, 2 deletions
diff --git a/api/current.txt b/api/current.txt index 2a1295c1ed02..5da10e41a9f8 100755 --- a/api/current.txt +++ b/api/current.txt @@ -26927,6 +26927,7 @@ package android.net { method public android.net.Network[] getAllNetworks(); method public deprecated boolean getBackgroundDataSetting(); method public android.net.Network getBoundNetworkForProcess(); + method public int getConnectionOwnerUid(int, java.net.InetSocketAddress, java.net.InetSocketAddress); method public android.net.ProxyInfo getDefaultProxy(); method public android.net.LinkProperties getLinkProperties(android.net.Network); method public int getMultipathPreference(android.net.Network); @@ -33053,6 +33054,7 @@ package android.os { method public static final void setThreadPriority(int) throws java.lang.IllegalArgumentException, java.lang.SecurityException; method public static final deprecated boolean supportsProcesses(); field public static final int FIRST_APPLICATION_UID = 10000; // 0x2710 + field public static final int INVALID_UID = -1; // 0xffffffff field public static final int LAST_APPLICATION_UID = 19999; // 0x4e1f field public static final int PHONE_UID = 1001; // 0x3e9 field public static final int SIGNAL_KILL = 9; // 0x9 diff --git a/core/java/android/net/ConnectionInfo.aidl b/core/java/android/net/ConnectionInfo.aidl new file mode 100644 index 000000000000..07faf8bbbed8 --- /dev/null +++ b/core/java/android/net/ConnectionInfo.aidl @@ -0,0 +1,20 @@ +/* +** +** 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; + +parcelable ConnectionInfo; diff --git a/core/java/android/net/ConnectionInfo.java b/core/java/android/net/ConnectionInfo.java new file mode 100644 index 000000000000..58d0e05be6fd --- /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<ConnectionInfo> CREATOR = new Creator<ConnectionInfo>() { + 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 ce1879620ce3..f2e907833612 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 ce95b60dd2db..e7d441df82a6 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/core/java/android/os/Process.java b/core/java/android/os/Process.java index 7ce7c9237815..d0cdf6e75224 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -40,6 +40,11 @@ public class Process { public static final String SECONDARY_ZYGOTE_SOCKET = "zygote_secondary"; /** + * An invalid UID value. + */ + public static final int INVALID_UID = -1; + + /** * Defines the root UID. * @hide */ diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 760209024c57..e41a09ef672e 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; + } } diff --git a/services/net/java/android/net/netlink/InetDiagMessage.java b/services/net/java/android/net/netlink/InetDiagMessage.java new file mode 100644 index 000000000000..af9e601da9ec --- /dev/null +++ b/services/net/java/android/net/netlink/InetDiagMessage.java @@ -0,0 +1,187 @@ +/* + * 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.netlink; + +import static android.os.Process.INVALID_UID; +import static android.net.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY; +import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE; +import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP; +import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.NETLINK_INET_DIAG; + +import android.os.Build; +import android.os.Process; +import android.system.ErrnoException; +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.InterruptedIOException; +import java.net.DatagramSocket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * A NetlinkMessage subclass for netlink inet_diag messages. + * + * see also: <linux_src>/include/uapi/linux/inet_diag.h + * + * @hide + */ +public class InetDiagMessage extends NetlinkMessage { + public static final String TAG = "InetDiagMessage"; + private static final int TIMEOUT_MS = 500; + + public static byte[] InetDiagReqV2(int protocol, InetSocketAddress local, + InetSocketAddress remote, int family, short flags) { + final byte[] bytes = new byte[StructNlMsgHdr.STRUCT_SIZE + StructInetDiagReqV2.STRUCT_SIZE]; + final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.nativeOrder()); + + final StructNlMsgHdr nlMsgHdr = new StructNlMsgHdr(); + nlMsgHdr.nlmsg_len = bytes.length; + nlMsgHdr.nlmsg_type = SOCK_DIAG_BY_FAMILY; + nlMsgHdr.nlmsg_flags = flags; + nlMsgHdr.pack(byteBuffer); + + final StructInetDiagReqV2 inetDiagReqV2 = new StructInetDiagReqV2(protocol, local, remote, + family); + inetDiagReqV2.pack(byteBuffer); + return bytes; + } + + public StructInetDiagMsg mStructInetDiagMsg; + + private InetDiagMessage(StructNlMsgHdr header) { + super(header); + mStructInetDiagMsg = new StructInetDiagMsg(); + } + + public static InetDiagMessage parse(StructNlMsgHdr header, ByteBuffer byteBuffer) { + final InetDiagMessage msg = new InetDiagMessage(header); + msg.mStructInetDiagMsg = StructInetDiagMsg.parse(byteBuffer); + return msg; + } + + private static int lookupUidByFamily(int protocol, InetSocketAddress local, + InetSocketAddress remote, int family, short flags, + FileDescriptor fd) + throws ErrnoException, InterruptedIOException { + byte[] msg = InetDiagReqV2(protocol, local, remote, family, flags); + NetlinkSocket.sendMessage(fd, msg, 0, msg.length, TIMEOUT_MS); + ByteBuffer response = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT_MS); + + final NetlinkMessage nlMsg = NetlinkMessage.parse(response); + final StructNlMsgHdr hdr = nlMsg.getHeader(); + if (hdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) { + return INVALID_UID; + } + if (nlMsg instanceof InetDiagMessage) { + return ((InetDiagMessage) nlMsg).mStructInetDiagMsg.idiag_uid; + } + return INVALID_UID; + } + + private static final int FAMILY[] = {AF_INET6, AF_INET}; + + private static int lookupUid(int protocol, InetSocketAddress local, + InetSocketAddress remote, FileDescriptor fd) + throws ErrnoException, InterruptedIOException { + int uid; + + for (int family : FAMILY) { + /** + * For exact match lookup, swap local and remote for UDP lookups due to kernel + * bug which will not be fixed. See aosp/755889 and + * https://www.mail-archive.com/netdev@vger.kernel.org/msg248638.html + */ + if (protocol == IPPROTO_UDP) { + uid = lookupUidByFamily(protocol, remote, local, family, NLM_F_REQUEST, fd); + } else { + uid = lookupUidByFamily(protocol, local, remote, family, NLM_F_REQUEST, fd); + } + if (uid != INVALID_UID) { + return uid; + } + } + + /** + * For UDP it's possible for a socket to send packets to arbitrary destinations, even if the + * socket is not connected (and even if the socket is connected to a different destination). + * If we want this API to work for such packets, then on miss we need to do a second lookup + * with only the local address and port filled in. + * Always use flags == NLM_F_REQUEST | NLM_F_DUMP for wildcard. + */ + if (protocol == IPPROTO_UDP) { + try { + InetSocketAddress wildcard = new InetSocketAddress( + Inet6Address.getByName("::"), 0); + uid = lookupUidByFamily(protocol, local, wildcard, AF_INET6, + (short) (NLM_F_REQUEST | NLM_F_DUMP), fd); + if (uid != INVALID_UID) { + return uid; + } + wildcard = new InetSocketAddress(Inet4Address.getByName("0.0.0.0"), 0); + uid = lookupUidByFamily(protocol, local, wildcard, AF_INET, + (short) (NLM_F_REQUEST | NLM_F_DUMP), fd); + if (uid != INVALID_UID) { + return uid; + } + } catch (UnknownHostException e) { + Log.e(TAG, e.toString()); + } + } + return INVALID_UID; + } + + /** + * Use an inet_diag socket to look up the UID associated with the input local and remote + * address/port and protocol of a connection. + */ + public static int getConnectionOwnerUid(int protocol, InetSocketAddress local, + InetSocketAddress remote) { + try { + final FileDescriptor fd = NetlinkSocket.forProto(NETLINK_INET_DIAG); + NetlinkSocket.connectToKernel(fd); + + return lookupUid(protocol, local, remote, fd); + + } catch (ErrnoException | SocketException | IllegalArgumentException + | InterruptedIOException e) { + Log.e(TAG, e.toString()); + } + return INVALID_UID; + } + + @Override + public String toString() { + return "InetDiagMessage{ " + + "nlmsghdr{" + (mHeader == null ? "" : mHeader.toString()) + "}, " + + "inet_diag_msg{" + + (mStructInetDiagMsg == null ? "" : mStructInetDiagMsg.toString()) + "} " + + "}"; + } +} diff --git a/services/net/java/android/net/netlink/NetlinkConstants.java b/services/net/java/android/net/netlink/NetlinkConstants.java index e331701efbce..fc1551c87b6b 100644 --- a/services/net/java/android/net/netlink/NetlinkConstants.java +++ b/services/net/java/android/net/netlink/NetlinkConstants.java @@ -54,6 +54,12 @@ public class NetlinkConstants { return String.valueOf(family); } + public static String stringForProtocol(int protocol) { + if (protocol == OsConstants.IPPROTO_TCP) { return "IPPROTO_TCP"; } + if (protocol == OsConstants.IPPROTO_UDP) { return "IPPROTO_UDP"; } + return String.valueOf(protocol); + } + public static String hexify(byte[] bytes) { if (bytes == null) { return "(null)"; } return HexDump.toHexString(bytes); @@ -90,6 +96,9 @@ public class NetlinkConstants { public static final short RTM_GETRULE = 34; public static final short RTM_NEWNDUSEROPT = 68; + /* see <linux_src>/include/uapi/linux/sock_diag.h */ + public static final short SOCK_DIAG_BY_FAMILY = 20; + public static String stringForNlMsgType(short nlm_type) { switch (nlm_type) { case NLMSG_NOOP: return "NLMSG_NOOP"; diff --git a/services/net/java/android/net/netlink/NetlinkMessage.java b/services/net/java/android/net/netlink/NetlinkMessage.java index 3bf75cabea17..a325db800813 100644 --- a/services/net/java/android/net/netlink/NetlinkMessage.java +++ b/services/net/java/android/net/netlink/NetlinkMessage.java @@ -69,6 +69,8 @@ public class NetlinkMessage { case NetlinkConstants.RTM_DELNEIGH: case NetlinkConstants.RTM_GETNEIGH: return (NetlinkMessage) RtNetlinkNeighborMessage.parse(nlmsghdr, byteBuffer); + case NetlinkConstants.SOCK_DIAG_BY_FAMILY: + return (NetlinkMessage) InetDiagMessage.parse(nlmsghdr, byteBuffer); default: if (nlmsghdr.nlmsg_type <= NetlinkConstants.NLMSG_MAX_RESERVED) { // Netlink control message. Just parse the header for now, diff --git a/services/net/java/android/net/netlink/StructInetDiagMsg.java b/services/net/java/android/net/netlink/StructInetDiagMsg.java new file mode 100644 index 000000000000..da824ad01efa --- /dev/null +++ b/services/net/java/android/net/netlink/StructInetDiagMsg.java @@ -0,0 +1,67 @@ +/* + * 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.netlink; + +import static java.nio.ByteOrder.BIG_ENDIAN; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; + +import java.net.Inet4Address; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import android.util.Log; + +/** + * struct inet_diag_msg + * + * see <linux_src>/include/uapi/linux/inet_diag.h + * + * struct inet_diag_msg { + * __u8 idiag_family; + * __u8 idiag_state; + * __u8 idiag_timer; + * __u8 idiag_retrans; + * struct inet_diag_sockid id; + * __u32 idiag_expires; + * __u32 idiag_rqueue; + * __u32 idiag_wqueue; + * __u32 idiag_uid; + * __u32 idiag_inode; + * }; + * + * @hide + */ +public class StructInetDiagMsg { + public static final int STRUCT_SIZE = 4 + StructInetDiagSockId.STRUCT_SIZE + 20; + private static final int IDIAG_UID_OFFSET = StructNlMsgHdr.STRUCT_SIZE + 4 + + StructInetDiagSockId.STRUCT_SIZE + 12; + public int idiag_uid; + + public static StructInetDiagMsg parse(ByteBuffer byteBuffer) { + StructInetDiagMsg struct = new StructInetDiagMsg(); + struct.idiag_uid = byteBuffer.getInt(IDIAG_UID_OFFSET); + return struct; + } + + @Override + public String toString() { + return "StructInetDiagMsg{ " + + "idiag_uid{" + idiag_uid + "}, " + + "}"; + } +} diff --git a/services/net/java/android/net/netlink/StructInetDiagReqV2.java b/services/net/java/android/net/netlink/StructInetDiagReqV2.java new file mode 100644 index 000000000000..49a93258e714 --- /dev/null +++ b/services/net/java/android/net/netlink/StructInetDiagReqV2.java @@ -0,0 +1,80 @@ +/* + * 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.netlink; + +import static java.nio.ByteOrder.BIG_ENDIAN; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * struct inet_diag_req_v2 + * + * see <linux_src>/include/uapi/linux/inet_diag.h + * + * struct inet_diag_req_v2 { + * __u8 sdiag_family; + * __u8 sdiag_protocol; + * __u8 idiag_ext; + * __u8 pad; + * __u32 idiag_states; + * struct inet_diag_sockid id; + * }; + * + * @hide + */ +public class StructInetDiagReqV2 { + public static final int STRUCT_SIZE = 8 + StructInetDiagSockId.STRUCT_SIZE; + + private final byte sdiag_family; + private final byte sdiag_protocol; + private final StructInetDiagSockId id; + private final int INET_DIAG_REQ_V2_ALL_STATES = (int) 0xffffffff; + + + public StructInetDiagReqV2(int protocol, InetSocketAddress local, InetSocketAddress remote, + int family) { + sdiag_family = (byte) family; + sdiag_protocol = (byte) protocol; + id = new StructInetDiagSockId(local, remote); + } + + public void pack(ByteBuffer byteBuffer) { + // The ByteOrder must have already been set by the caller. + byteBuffer.put((byte) sdiag_family); + byteBuffer.put((byte) sdiag_protocol); + byteBuffer.put((byte) 0); + byteBuffer.put((byte) 0); + byteBuffer.putInt(INET_DIAG_REQ_V2_ALL_STATES); + id.pack(byteBuffer); + } + + @Override + public String toString() { + final String familyStr = NetlinkConstants.stringForAddressFamily(sdiag_family); + final String protocolStr = NetlinkConstants.stringForAddressFamily(sdiag_protocol); + + return "StructInetDiagReqV2{ " + + "sdiag_family{" + familyStr + "}, " + + "sdiag_protocol{" + protocolStr + "}, " + + "idiag_ext{" + 0 + ")}, " + + "pad{" + 0 + "}, " + + "idiag_states{" + Integer.toHexString(INET_DIAG_REQ_V2_ALL_STATES) + "}, " + + id.toString() + + "}"; + } +} diff --git a/services/net/java/android/net/netlink/StructInetDiagSockId.java b/services/net/java/android/net/netlink/StructInetDiagSockId.java new file mode 100644 index 000000000000..2e9fa253463d --- /dev/null +++ b/services/net/java/android/net/netlink/StructInetDiagSockId.java @@ -0,0 +1,86 @@ +/* + * 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.netlink; + +import static java.nio.ByteOrder.BIG_ENDIAN; + +import java.net.Inet4Address; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * struct inet_diag_req_v2 + * + * see <linux_src>/include/uapi/linux/inet_diag.h + * + * struct inet_diag_sockid { + * __be16 idiag_sport; + * __be16 idiag_dport; + * __be32 idiag_src[4]; + * __be32 idiag_dst[4]; + * __u32 idiag_if; + * __u32 idiag_cookie[2]; + * #define INET_DIAG_NOCOOKIE (~0U) + * }; + * + * @hide + */ +public class StructInetDiagSockId { + public static final int STRUCT_SIZE = 48; + + private final InetSocketAddress mLocSocketAddress; + private final InetSocketAddress mRemSocketAddress; + private final byte[] INET_DIAG_NOCOOKIE = new byte[]{ + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}; + private final byte[] IPV4_PADDING = new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + public StructInetDiagSockId(InetSocketAddress loc, InetSocketAddress rem) { + mLocSocketAddress = loc; + mRemSocketAddress = rem; + } + + public void pack(ByteBuffer byteBuffer) { + byteBuffer.order(BIG_ENDIAN); + byteBuffer.putShort((short) mLocSocketAddress.getPort()); + byteBuffer.putShort((short) mRemSocketAddress.getPort()); + byteBuffer.put(mLocSocketAddress.getAddress().getAddress()); + if (mLocSocketAddress.getAddress() instanceof Inet4Address) { + byteBuffer.put(IPV4_PADDING); + } + byteBuffer.put(mRemSocketAddress.getAddress().getAddress()); + if (mRemSocketAddress.getAddress() instanceof Inet4Address) { + byteBuffer.put(IPV4_PADDING); + } + byteBuffer.order(ByteOrder.nativeOrder()); + byteBuffer.putInt(0); + byteBuffer.put(INET_DIAG_NOCOOKIE); + } + + @Override + public String toString() { + return "StructInetDiagSockId{ " + + "idiag_sport{" + mLocSocketAddress.getPort() + "}, " + + "idiag_dport{" + mRemSocketAddress.getPort() + "}, " + + "idiag_src{" + mLocSocketAddress.getAddress().getHostAddress() + "}, " + + "idiag_dst{" + mRemSocketAddress.getAddress().getHostAddress() + "}, " + + "idiag_if{" + 0 + "} " + + "idiag_cookie{INET_DIAG_NOCOOKIE}" + + "}"; + } +} diff --git a/tests/net/AndroidManifest.xml b/tests/net/AndroidManifest.xml index ba1a2ead8beb..6dae3f11b371 100644 --- a/tests/net/AndroidManifest.xml +++ b/tests/net/AndroidManifest.xml @@ -44,6 +44,7 @@ <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" /> <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" /> <uses-permission android:name="android.permission.INSTALL_PACKAGES" /> + <uses-permission android:name="android.permission.NETWORK_STACK" /> <application> <uses-library android:name="android.test.runner" /> diff --git a/tests/net/java/android/net/netlink/InetDiagSocketTest.java b/tests/net/java/android/net/netlink/InetDiagSocketTest.java new file mode 100644 index 000000000000..39ecb7e5a45e --- /dev/null +++ b/tests/net/java/android/net/netlink/InetDiagSocketTest.java @@ -0,0 +1,337 @@ +/* + * 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.netlink; + +import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP; +import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST; +import static android.os.Process.INVALID_UID; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.IPPROTO_TCP; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.SOCK_DGRAM; +import static android.system.OsConstants.SOCK_STREAM; +import static android.system.OsConstants.SOL_SOCKET; +import static android.system.OsConstants.SO_RCVTIMEO; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.app.Instrumentation; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.netlink.StructNlMsgHdr; +import android.os.Process; +import android.support.test.runner.AndroidJUnit4; +import android.support.test.filters.SmallTest; +import android.support.test.InstrumentationRegistry; +import android.system.Os; +import android.system.StructTimeval; +import android.util.Log; +import java.io.FileDescriptor; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import libcore.util.HexEncoding; + +import org.junit.Before; +import org.junit.runner.RunWith; +import org.junit.Test; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class InetDiagSocketTest { + private final String TAG = "InetDiagSocketTest"; + private ConnectivityManager mCm; + private Context mContext; + private final static int SOCKET_TIMEOUT_MS = 100; + private boolean mInetDiagUdpEnabled; + + @Before + public void setUp() throws Exception { + Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + mContext = instrumentation.getTargetContext(); + mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + int expectedUid = Process.myUid(); + UdpConnection udp = new UdpConnection("127.0.0.1", "127.0.0.2"); + int uid = mCm.getConnectionOwnerUid(udp.protocol, udp.local, udp.remote); + mInetDiagUdpEnabled = (uid == expectedUid); + } + + private class Connection { + public int socketDomain; + public int socketType; + public InetAddress localAddress; + public InetAddress remoteAddress; + public InetAddress localhostAddress; + public InetSocketAddress local; + public InetSocketAddress remote; + public int protocol; + public FileDescriptor localFd; + public FileDescriptor remoteFd; + + public FileDescriptor createSocket() throws Exception { + return Os.socket(socketDomain, socketType, protocol); + } + + public Connection(String to, String from) throws Exception { + remoteAddress = InetAddress.getByName(to); + if (from != null) { + localAddress = InetAddress.getByName(from); + } else { + localAddress = (remoteAddress instanceof Inet4Address) ? + Inet4Address.getByName("localhost") : Inet6Address.getByName("::"); + } + if ((localAddress instanceof Inet4Address) && (remoteAddress instanceof Inet4Address)) { + socketDomain = AF_INET; + localhostAddress = Inet4Address.getByName("localhost"); + } else { + socketDomain = AF_INET6; + localhostAddress = Inet6Address.getByName("::"); + } + } + + public void close() throws Exception { + Os.close(localFd); + } + } + + private class TcpConnection extends Connection { + public TcpConnection(String to, String from) throws Exception { + super(to, from); + protocol = IPPROTO_TCP; + socketType = SOCK_STREAM; + + remoteFd = createSocket(); + Os.bind(remoteFd, remoteAddress, 0); + Os.listen(remoteFd, 10); + int remotePort = ((InetSocketAddress) Os.getsockname(remoteFd)).getPort(); + + localFd = createSocket(); + Os.bind(localFd, localAddress, 0); + Os.connect(localFd, remoteAddress, remotePort); + + local = (InetSocketAddress) Os.getsockname(localFd); + remote = (InetSocketAddress) Os.getpeername(localFd); + } + + public void close() throws Exception { + super.close(); + Os.close(remoteFd); + } + } + private class UdpConnection extends Connection { + public UdpConnection(String to, String from) throws Exception { + super(to, from); + protocol = IPPROTO_UDP; + socketType = SOCK_DGRAM; + + remoteFd = null; + localFd = createSocket(); + Os.bind(localFd, localAddress, 0); + + Os.connect(localFd, remoteAddress, 7); + local = (InetSocketAddress) Os.getsockname(localFd); + remote = new InetSocketAddress(remoteAddress, 7); + } + } + + private void checkConnectionOwnerUid(int protocol, InetSocketAddress local, + InetSocketAddress remote, boolean expectSuccess) { + final int expectedUid = expectSuccess ? Process.myUid() : INVALID_UID; + final int uid = mCm.getConnectionOwnerUid(protocol, local, remote); + assertEquals(expectedUid, uid); + } + + private int findLikelyFreeUdpPort(UdpConnection conn) throws Exception { + UdpConnection udp = new UdpConnection(conn.remoteAddress.getHostAddress(), + conn.localAddress.getHostAddress()); + final int localPort = udp.local.getPort(); + udp.close(); + return localPort; + } + + public void checkGetConnectionOwnerUid(String to, String from) throws Exception { + /** + * For TCP connections, create a test connection and verify that this + * {protocol, local, remote} socket result in receiving a valid UID. + */ + TcpConnection tcp = new TcpConnection(to, from); + checkConnectionOwnerUid(tcp.protocol, tcp.local, tcp.remote, true); + checkConnectionOwnerUid(IPPROTO_UDP, tcp.local, tcp.remote, false); + checkConnectionOwnerUid(tcp.protocol, new InetSocketAddress(0), tcp.remote, false); + checkConnectionOwnerUid(tcp.protocol, tcp.local, new InetSocketAddress(0), false); + tcp.close(); + + /** + * TODO: STOPSHIP: Always test for UDP, do not allow opt-out. + */ + if (!mInetDiagUdpEnabled) return; + + /** + * For UDP connections, either a complete match {protocol, local, remote} or a + * partial match {protocol, local} should return a valid UID. + */ + UdpConnection udp = new UdpConnection(to,from); + checkConnectionOwnerUid(udp.protocol, udp.local, udp.remote, true); + checkConnectionOwnerUid(udp.protocol, udp.local, new InetSocketAddress(0), true); + checkConnectionOwnerUid(IPPROTO_TCP, udp.local, udp.remote, false); + checkConnectionOwnerUid(udp.protocol, new InetSocketAddress(findLikelyFreeUdpPort(udp)), + udp.remote, false); + udp.close(); + } + + @Test + public void testGetConnectionOwnerUid() throws Exception { + checkGetConnectionOwnerUid("::", null); + checkGetConnectionOwnerUid("::", "::"); + checkGetConnectionOwnerUid("0.0.0.0", null); + checkGetConnectionOwnerUid("0.0.0.0", "0.0.0.0"); + checkGetConnectionOwnerUid("127.0.0.1", null); + checkGetConnectionOwnerUid("127.0.0.1", "127.0.0.2"); + checkGetConnectionOwnerUid("::1", null); + checkGetConnectionOwnerUid("::1", "::1"); + } + + // Hexadecimal representation of InetDiagReqV2 request. + private static final String INET_DIAG_REQ_V2_UDP_INET4_HEX = + // struct nlmsghdr + "48000000" + // length = 72 + "1400" + // type = SOCK_DIAG_BY_FAMILY + "0103" + // flags = NLM_F_REQUEST | NLM_F_DUMP + "00000000" + // seqno + "00000000" + // pid (0 == kernel) + // struct inet_diag_req_v2 + "02" + // family = AF_INET + "11" + // protcol = IPPROTO_UDP + "00" + // idiag_ext + "00" + // pad + "ffffffff" + // idiag_states + // inet_diag_sockid + "a5de" + // idiag_sport = 42462 + "b971" + // idiag_dport = 47473 + "0a006402000000000000000000000000" + // idiag_src = 10.0.100.2 + "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8 + "00000000" + // idiag_if + "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE + private static final byte[] INET_DIAG_REQ_V2_UDP_INET4_BYTES = + HexEncoding.decode(INET_DIAG_REQ_V2_UDP_INET4_HEX.toCharArray(), false); + + @Test + public void testInetDiagReqV2UdpInet4() throws Exception { + InetSocketAddress local = new InetSocketAddress(InetAddress.getByName("10.0.100.2"), + 42462); + InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"), + 47473); + final byte[] msg = InetDiagMessage.InetDiagReqV2(IPPROTO_UDP, local, remote, AF_INET, + (short) (NLM_F_REQUEST | NLM_F_DUMP)); + assertArrayEquals(INET_DIAG_REQ_V2_UDP_INET4_BYTES, msg); + } + + // Hexadecimal representation of InetDiagReqV2 request. + private static final String INET_DIAG_REQ_V2_TCP_INET6_HEX = + // struct nlmsghdr + "48000000" + // length = 72 + "1400" + // type = SOCK_DIAG_BY_FAMILY + "0100" + // flags = NLM_F_REQUEST + "00000000" + // seqno + "00000000" + // pid (0 == kernel) + // struct inet_diag_req_v2 + "0a" + // family = AF_INET6 + "06" + // protcol = IPPROTO_TCP + "00" + // idiag_ext + "00" + // pad + "ffffffff" + // idiag_states + // inet_diag_sockid + "a5de" + // idiag_sport = 42462 + "b971" + // idiag_dport = 47473 + "fe8000000000000086c9b2fffe6aed4b" + // idiag_src = fe80::86c9:b2ff:fe6a:ed4b + "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8 + "00000000" + // idiag_if + "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE + private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_BYTES = + HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_HEX.toCharArray(), false); + + @Test + public void testInetDiagReqV2TcpInet6() throws Exception { + InetSocketAddress local = new InetSocketAddress( + InetAddress.getByName("fe80::86c9:b2ff:fe6a:ed4b"), 42462); + InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"), + 47473); + byte[] msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET6, + NLM_F_REQUEST); + + assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_BYTES, msg); + } + + // Hexadecimal representation of InetDiagReqV2 request. + private static final String INET_DIAG_MSG_HEX = + // struct nlmsghdr + "58000000" + // length = 88 + "1400" + // type = SOCK_DIAG_BY_FAMILY + "0200" + // flags = NLM_F_MULTI + "00000000" + // seqno + "f5220000" + // pid (0 == kernel) + // struct inet_diag_msg + "0a" + // family = AF_INET6 + "01" + // idiag_state + "00" + // idiag_timer + "00" + // idiag_retrans + // inet_diag_sockid + "a817" + // idiag_sport = 43031 + "960f" + // idiag_dport = 38415 + "fe8000000000000086c9b2fffe6aed4b" + // idiag_src = fe80::86c9:b2ff:fe6a:ed4b + "00000000000000000000ffff08080808" + // idiag_dst = 8.8.8.8 + "00000000" + // idiag_if + "ffffffffffffffff" + // idiag_cookie = INET_DIAG_NOCOOKIE + "00000000" + // idiag_expires + "00000000" + // idiag_rqueue + "00000000" + // idiag_wqueue + "a3270000" + // idiag_uid + "A57E1900"; // idiag_inode + private static final byte[] INET_DIAG_MSG_BYTES = + HexEncoding.decode(INET_DIAG_MSG_HEX.toCharArray(), false); + + @Test + public void testParseInetDiagResponse() throws Exception { + final ByteBuffer byteBuffer = ByteBuffer.wrap(INET_DIAG_MSG_BYTES); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer); + assertNotNull(msg); + + assertTrue(msg instanceof InetDiagMessage); + final InetDiagMessage inetDiagMsg = (InetDiagMessage) msg; + assertEquals(10147, inetDiagMsg.mStructInetDiagMsg.idiag_uid); + + final StructNlMsgHdr hdr = inetDiagMsg.getHeader(); + assertNotNull(hdr); + assertEquals(NetlinkConstants.SOCK_DIAG_BY_FAMILY, hdr.nlmsg_type); + assertEquals(StructNlMsgHdr.NLM_F_MULTI, hdr.nlmsg_flags); + assertEquals(0, hdr.nlmsg_seq); + assertEquals(8949, hdr.nlmsg_pid); + } +} |