Commit ec3c1112 authored by Paul Jensen's avatar Paul Jensen Committed by Commit Bot

Add SocketTag class.

This is the first CL in a series of CLs to add socket tagging support to
URLRequests.  This CL adds the SocketTag class that will be passed
through the net/ stack and applied to sockets used to process URLRequests.
More information can be found in this Intent to Implement:
https://docs.google.com/document/d/10e0B_vdysMS-_lJNh-A80fEnEDMKAhR3W0dxTRfhVO0/edit?usp=sharing

Bug: 520198
Cq-Include-Trybots: master.tryserver.chromium.android:android_cronet_tester;master.tryserver.chromium.mac:ios-simulator-cronet
Change-Id: I1331eea1cb3727e78cb108226b3447aa47d12801
Reviewed-on: https://chromium-review.googlesource.com/772313
Commit-Queue: Paul Jensen <pauljensen@chromium.org>
Reviewed-by: default avatarMatt Menke <mmenke@chromium.org>
Cr-Commit-Position: refs/heads/master@{#519708}
parent 309e4649
......@@ -1501,6 +1501,8 @@ component("net") {
"socket/socket_options.h",
"socket/socket_posix.cc",
"socket/socket_posix.h",
"socket/socket_tag.cc",
"socket/socket_tag.h",
"socket/socks5_client_socket.cc",
"socket/socks5_client_socket.h",
"socket/socks_client_socket.cc",
......@@ -5111,6 +5113,7 @@ test("net_unittests") {
"socket/mock_client_socket_pool_manager.h",
"socket/sequenced_socket_data_unittest.cc",
"socket/socket_bio_adapter_unittest.cc",
"socket/socket_tag_unittest.cc",
"socket/socks5_client_socket_unittest.cc",
"socket/socks_client_socket_pool_unittest.cc",
"socket/socks_client_socket_unittest.cc",
......
......@@ -185,6 +185,7 @@ java_cpp_enum("net_java_test_support_enums_srcjar") {
java_cpp_enum("net_android_java_enums_srcjar") {
sources = [
"../base/network_change_notifier.h",
"../socket/socket_tag.cc",
"cellular_signal_strength.cc",
"cert_verify_result_android.h",
"keystore.h",
......
......@@ -14,9 +14,11 @@ import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.TrafficStats;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.security.KeyChain;
import android.security.NetworkSecurityPolicy;
import android.telephony.TelephonyManager;
......@@ -26,9 +28,17 @@ import org.chromium.base.ContextUtils;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.CalledByNativeUnchecked;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketImpl;
import java.net.URLConnection;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
......@@ -291,4 +301,138 @@ class AndroidNetworkLibrary {
}
return dnsServers;
}
/** Socket that exists only to provide a file descriptor when queried. */
private static class SocketFd extends Socket {
/** SocketImpl that exists only to provide a file descriptor when queried. */
private static class SocketImplFd extends SocketImpl {
private final ParcelFileDescriptor mPfd;
/**
* Create a {@link SocketImpl} that sets {@code fd} as the underlying file descriptor.
* Does not take ownership of {@code fd}, i.e. {@link #close()} is a no-op.
*/
SocketImplFd(int fd) {
mPfd = ParcelFileDescriptor.adoptFd(fd);
this.fd = mPfd.getFileDescriptor();
}
protected void accept(SocketImpl s) {
throw new RuntimeException("accept not implemented");
}
protected int available() {
throw new RuntimeException("accept not implemented");
}
protected void bind(InetAddress host, int port) {
throw new RuntimeException("accept not implemented");
}
protected void close() {
// Detach from |fd| to avoid leak detection false positives without closing |fd|.
mPfd.detachFd();
}
protected void connect(InetAddress address, int port) {
throw new RuntimeException("connect not implemented");
}
protected void connect(SocketAddress address, int timeout) {
throw new RuntimeException("connect not implemented");
}
protected void connect(String host, int port) {
throw new RuntimeException("connect not implemented");
}
protected void create(boolean stream) {
throw new RuntimeException("create not implemented");
}
protected InputStream getInputStream() {
throw new RuntimeException("getInputStream not implemented");
}
protected OutputStream getOutputStream() {
throw new RuntimeException("getOutputStream not implemented");
}
protected void listen(int backlog) {
throw new RuntimeException("listen not implemented");
}
protected void sendUrgentData(int data) {
throw new RuntimeException("sendUrgentData not implemented");
}
public Object getOption(int optID) {
throw new RuntimeException("getOption not implemented");
}
public void setOption(int optID, Object value) {
throw new RuntimeException("setOption not implemented");
}
}
/**
* Creates a {@link Socket} that sets {@code fd} as the underlying file descriptor.
* Does not take ownership of {@code fd}, i.e. {@link #close()} is a no-op.
*/
SocketFd(int fd) throws IOException {
super(new SocketImplFd(fd));
}
}
/**
* Class to wrap TrafficStats.setThreadStatsUid(int uid) and TrafficStats.clearThreadStatsUid()
* which are hidden and so must be accessed via reflection.
*/
private static class ThreadStatsUid {
// Reference to TrafficStats.setThreadStatsUid(int uid).
private static final Method sSetThreadStatsUid;
// Reference to TrafficStats.clearThreadStatsUid().
private static final Method sClearThreadStatsUid;
// Get reference to TrafficStats.setThreadStatsUid(int uid) and
// TrafficStats.clearThreadStatsUid() via reflection.
static {
try {
sSetThreadStatsUid =
TrafficStats.class.getMethod("setThreadStatsUid", Integer.TYPE);
sClearThreadStatsUid = TrafficStats.class.getMethod("clearThreadStatsUid");
} catch (NoSuchMethodException | SecurityException e) {
throw new RuntimeException("Unable to get TrafficStats methods", e);
}
}
/** Calls TrafficStats.setThreadStatsUid(uid) */
public static void set(int uid) throws IllegalAccessException, InvocationTargetException {
sSetThreadStatsUid.invoke(null, uid); // Pass null for "this" as it's a static method.
}
/** Calls TrafficStats.clearThreadStatsUid() */
public static void clear() throws IllegalAccessException, InvocationTargetException {
sClearThreadStatsUid.invoke(null); // Pass null for "this" as it's a static method.
}
}
/**
* Tag socket referenced by {@code fd} with {@code tag} for UID {@code uid}.
*
* Assumes thread UID tag isn't set upon entry, and ensures thread UID tag isn't set upon exit.
* Unfortunately there is no TrafficStatis.getThreadStatsUid().
*/
@CalledByNative
private static void tagSocket(int fd, int uid, int tag)
throws IOException, IllegalAccessException, InvocationTargetException {
// Set thread tags.
int oldTag = TrafficStats.getThreadStatsTag();
if (tag != oldTag) {
TrafficStats.setThreadStatsTag(tag);
}
if (uid != TrafficStatsUid.UNSET) {
ThreadStatsUid.set(uid);
}
// Apply thread tags to socket.
SocketFd s = new SocketFd(fd);
TrafficStats.tagSocket(s);
s.close(); // No-op, just to avoid leak detection false positives.
// Restore prior thread tags.
if (tag != oldTag) {
TrafficStats.setThreadStatsTag(oldTag);
}
if (uid != TrafficStatsUid.UNSET) {
ThreadStatsUid.clear();
}
}
}
......@@ -154,5 +154,9 @@ void GetDnsServers(std::vector<IPEndPoint>* dns_servers) {
}
}
void TagSocket(SocketDescriptor socket, uid_t uid, int32_t tag) {
Java_AndroidNetworkLibrary_tagSocket(AttachCurrentThread(), socket, uid, tag);
}
} // namespace android
} // namespace net
......@@ -8,6 +8,7 @@
#include <jni.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
#include <string>
#include <vector>
......@@ -16,6 +17,7 @@
#include "net/base/ip_endpoint.h"
#include "net/base/mime_util.h"
#include "net/base/net_export.h"
#include "net/socket/socket_descriptor.h"
namespace net {
namespace android {
......@@ -83,6 +85,12 @@ NET_EXPORT_PRIVATE std::string GetWifiSSID();
// Only callable on Marshmallow and newer releases.
NET_EXPORT_PRIVATE void GetDnsServers(std::vector<IPEndPoint>* dns_servers);
// Apply TrafficStats tag |tag| and UID |uid| to |socket|. Future network
// traffic used by |socket| will be attributed to |uid| and |tag|.
NET_EXPORT_PRIVATE void TagSocket(SocketDescriptor socket,
uid_t uid,
int32_t tag);
} // namespace android
} // namespace net
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/socket/socket_tag.h"
#include <tuple>
#include "base/logging.h"
#if defined(OS_ANDROID)
#include "net/android/network_library.h"
#endif // OS_ANDROID
namespace net {
#if defined(OS_ANDROID)
// Expose UNSET_UID to Java.
// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.net
enum TrafficStatsUid {
UNSET = -1,
};
// Java generator needs explicit integer, verify equality here.
static_assert(UNSET == SocketTag::UNSET_UID,
"TrafficStatsUid does not match SocketTag::UNSET_UID");
#endif // OS_ANDROID
bool SocketTag::operator<(const SocketTag& other) const {
#if defined(OS_ANDROID)
return std::tie(uid_, traffic_stats_tag_) <
std::tie(other.uid_, other.traffic_stats_tag_);
#else
return false;
#endif // OS_ANDROID
}
bool SocketTag::operator==(const SocketTag& other) const {
#if defined(OS_ANDROID)
return std::tie(uid_, traffic_stats_tag_) ==
std::tie(other.uid_, other.traffic_stats_tag_);
#else
return true;
#endif // OS_ANDROID
}
void SocketTag::Apply(SocketDescriptor socket) const {
#if defined(OS_ANDROID)
net::android::TagSocket(socket, uid_, traffic_stats_tag_);
#else
CHECK(false);
#endif // OS_ANDROID
}
} // namespace net
\ No newline at end of file
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef NET_SOCKET_SOCKET_TAG_H_
#define NET_SOCKET_SOCKET_TAG_H_
#include "build/build_config.h"
#include "net/base/net_export.h"
#include "net/socket/socket_descriptor.h"
#if defined(OS_ANDROID)
#include <stdint.h>
#include <sys/types.h>
#include <unistd.h>
#endif // OS_ANDROID
namespace net {
// SocketTag represents a tag that can be applied to a socket. Currently only
// implemented for Android, it facilitates assigning a Android TrafficStats tag
// and UID to a socket so that future network data usage by the socket is
// attributed to the tag and UID that the socket is tagged with.
//
// This class is small (<=64-bits) and contains only POD to facilitate default
// copy and assignment operators so that it can easily be passed by value.
class NET_EXPORT SocketTag {
public:
#if defined(OS_ANDROID)
// Default constructor doesn't set any tags.
SocketTag() : SocketTag(UNSET_UID, UNSET_TAG) {}
// Create a SocketTag with given UID |uid| and |traffic_stats_tag|.
SocketTag(uid_t uid, int32_t traffic_stats_tag)
: uid_(uid), traffic_stats_tag_(traffic_stats_tag) {}
#else
SocketTag() {}
#endif // OS_ANDROID
~SocketTag() {}
bool operator<(const SocketTag& other) const;
bool operator==(const SocketTag& other) const;
bool operator!=(const SocketTag& other) const { return !(*this == other); }
// Apply this tag to |socket|.
void Apply(SocketDescriptor socket) const;
#if defined(OS_ANDROID)
// Values to indicate no UID or tag should be set. These values match those in
// Android:
// http://androidxref.com/4.4_r1/xref/frameworks/base/core/java/android/net/TrafficStats.java#147
// http://androidxref.com/4.4_r1/xref/frameworks/base/core/java/android/net/TrafficStats.java#169
static const uid_t UNSET_UID = -1;
static const int32_t UNSET_TAG = -1;
private:
// UID to tag with.
uid_t uid_;
// TrafficStats tag to tag with.
int32_t traffic_stats_tag_;
#endif // OS_ANDROID
// Copying and assignment are allowed.
};
} // namespace net
#endif // NET_SOCKET_SOCKET_TAG_H_
\ No newline at end of file
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/socket/socket_tag.h"
#include "build/build_config.h"
#if defined(OS_ANDROID)
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#endif
#include <inttypes.h> // For SCNx64
#include <stdint.h>
#include <stdio.h>
#include <string>
#include "base/files/file_util.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
#include "net/base/sockaddr_storage.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
namespace {
#if defined(OS_ANDROID)
// Query the system to find out how many bytes were received with tag
// |expected_tag| for our UID. Return the count of recieved bytes.
uint64_t GetTaggedBytes(int32_t expected_tag) {
// To determine how many bytes the system saw with a particular tag read
// the /proc/net/xt_qtaguid/stats file which contains the kernel's
// dump of all the UIDs and their tags sent and received bytes.
uint64_t bytes = 0;
std::string contents;
EXPECT_TRUE(base::ReadFileToString(
base::FilePath::FromUTF8Unsafe("/proc/net/xt_qtaguid/stats"), &contents));
for (size_t i = contents.find('\n'); // Skip first line which is headers.
i != std::string::npos && i < contents.length();) {
uint64_t tag, rx_bytes;
uid_t uid;
int n;
// Parse out the numbers we care about. For reference here's the column
// headers:
// idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes
// tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets
// rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes
// tx_udp_packets tx_other_bytes tx_other_packets
EXPECT_EQ(sscanf(contents.c_str() + i,
"%*d %*s 0x%" SCNx64 " %d %*d %" SCNu64
" %*d %*d %*d %*d %*d %*d %*d %*d "
"%*d %*d %*d %*d %*d %*d %*d%n",
&tag, &uid, &rx_bytes, &n),
3);
// If this line matches our UID and |expected_tag| then add it to the total.
if (uid == getuid() && (int32_t)(tag >> 32) == expected_tag) {
bytes += rx_bytes;
}
// Move |i| to the next line.
i += n + 1;
}
return bytes;
}
#endif
} // namespace
// Test that SocketTag's comparison function work.
TEST(SocketTagTest, Compares) {
SocketTag unset1;
SocketTag unset2;
EXPECT_TRUE(unset1 == unset2);
EXPECT_FALSE(unset1 != unset2);
EXPECT_FALSE(unset1 < unset2);
#if defined(OS_ANDROID)
SocketTag s00(0, 0), s01(0, 1), s11(1, 1);
EXPECT_FALSE(s00 == unset1);
EXPECT_TRUE(s01 != unset2);
EXPECT_FALSE(unset1 < s00);
EXPECT_TRUE(s00 < unset2);
EXPECT_FALSE(s00 == s01);
EXPECT_FALSE(s01 == s11);
EXPECT_FALSE(s00 == s11);
EXPECT_TRUE(s00 < s01);
EXPECT_TRUE(s01 < s11);
EXPECT_TRUE(s00 < s11);
EXPECT_FALSE(s01 < s00);
EXPECT_FALSE(s11 < s01);
EXPECT_FALSE(s11 < s00);
#endif
}
// On Android, where socket tagging is supported, verify that SocketTag::Apply
// works as expected.
#if defined(OS_ANDROID)
TEST(SocketTagTest, Apply) {
// Start test server.
EmbeddedTestServer test_server;
test_server.AddDefaultHandlers(base::FilePath());
ASSERT_TRUE(test_server.Start());
// Calculate sockaddr of test server.
AddressList addr_list;
ASSERT_TRUE(test_server.GetAddressList(&addr_list));
SockaddrStorage addr;
ASSERT_TRUE(addr_list[0].ToSockAddr(addr.addr, &addr.addr_len));
// Create socket.
int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
ASSERT_NE(s, -1);
// Verify TCP connect packets are tagged and counted properly.
int32_t tag_val1 = 0x12345678;
uint64_t old_traffic = GetTaggedBytes(tag_val1);
SocketTag tag1(SocketTag::UNSET_UID, tag_val1);
tag1.Apply(s);
ASSERT_EQ(connect(s, addr.addr, addr.addr_len), 0);
EXPECT_GT(GetTaggedBytes(tag_val1), old_traffic);
// Verify socket can be retagged and with our UID.
int32_t tag_val2 = 0x87654321;
old_traffic = GetTaggedBytes(tag_val2);
SocketTag tag2(getuid(), tag_val2);
tag2.Apply(s);
const char kRequest1[] = "GET /";
ASSERT_EQ(send(s, kRequest1, strlen(kRequest1), 0), (int)strlen(kRequest1));
EXPECT_GT(GetTaggedBytes(tag_val2), old_traffic);
// Verify socket can be retagged and with our UID.
old_traffic = GetTaggedBytes(tag_val1);
tag1.Apply(s);
const char kRequest2[] = "\n\n";
ASSERT_EQ(send(s, kRequest2, strlen(kRequest2), 0), (int)strlen(kRequest2));
EXPECT_GT(GetTaggedBytes(tag_val1), old_traffic);
ASSERT_EQ(close(s), 0);
}
#endif
} // namespace net
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment