You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
347 lines
11 KiB
347 lines
11 KiB
/*
|
|
* 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.
|
|
*/
|
|
|
|
#define LOG_TAG "TcpSocketMonitor"
|
|
|
|
#include <chrono>
|
|
#include <cinttypes>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include <arpa/inet.h>
|
|
#include <netinet/tcp.h>
|
|
#include <linux/tcp.h>
|
|
|
|
#include "Controllers.h"
|
|
#include "SockDiag.h"
|
|
#include "TcpSocketMonitor.h"
|
|
#include "netdutils/DumpWriter.h"
|
|
|
|
using android::netdutils::DumpWriter;
|
|
using android::netdutils::ScopedIndent;
|
|
|
|
namespace android {
|
|
namespace net {
|
|
|
|
using std::chrono::duration_cast;
|
|
using std::chrono::steady_clock;
|
|
|
|
constexpr const char* getTcpStateName(int t) {
|
|
switch (t) {
|
|
case TCP_ESTABLISHED:
|
|
return "ESTABLISHED";
|
|
case TCP_SYN_SENT:
|
|
return "SYN-SENT";
|
|
case TCP_SYN_RECV:
|
|
return "SYN-RECV";
|
|
case TCP_FIN_WAIT1:
|
|
return "FIN-WAIT-1";
|
|
case TCP_FIN_WAIT2:
|
|
return "FIN-WAIT-2";
|
|
case TCP_TIME_WAIT:
|
|
return "TIME-WAIT";
|
|
case TCP_CLOSE:
|
|
return "CLOSE";
|
|
case TCP_CLOSE_WAIT:
|
|
return "CLOSE-WAIT";
|
|
case TCP_LAST_ACK:
|
|
return "LAST-ACK";
|
|
case TCP_LISTEN:
|
|
return "LISTEN";
|
|
case TCP_CLOSING:
|
|
return "CLOSING";
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
// Helper macro for reading fields into struct tcp_info and handling different struct tcp_info
|
|
// versions in the kernel.
|
|
#define TCPINFO_GET(ptr, fld, len, zero) \
|
|
(((ptr) != nullptr && (offsetof(struct tcp_info, fld) + sizeof((ptr)->fld)) < (len)) \
|
|
? (ptr)->fld \
|
|
: (zero))
|
|
|
|
static void tcpInfoPrint(DumpWriter &dw, Fwmark mark, const struct inet_diag_msg *sockinfo,
|
|
const struct tcp_info *tcpinfo, uint32_t tcpinfoLen) {
|
|
char saddr[INET6_ADDRSTRLEN] = {};
|
|
char daddr[INET6_ADDRSTRLEN] = {};
|
|
inet_ntop(sockinfo->idiag_family, &(sockinfo->id.idiag_src), saddr, sizeof(saddr));
|
|
inet_ntop(sockinfo->idiag_family, &(sockinfo->id.idiag_dst), daddr, sizeof(daddr));
|
|
|
|
dw.println(
|
|
"netId=%d uid=%u mark=0x%x saddr=%s daddr=%s sport=%u dport=%u tcp_state=%s(%u) "
|
|
"rtt=%gms sent=%u lost=%u",
|
|
mark.netId,
|
|
sockinfo->idiag_uid,
|
|
mark.intValue,
|
|
saddr,
|
|
daddr,
|
|
ntohs(sockinfo->id.idiag_sport),
|
|
ntohs(sockinfo->id.idiag_dport),
|
|
getTcpStateName(sockinfo->idiag_state), sockinfo->idiag_state,
|
|
TCPINFO_GET(tcpinfo, tcpi_rtt, tcpinfoLen, 0) / 1000.0,
|
|
TCPINFO_GET(tcpinfo, tcpi_segs_out, tcpinfoLen, 0),
|
|
TCPINFO_GET(tcpinfo, tcpi_lost, tcpinfoLen, 0));
|
|
}
|
|
|
|
const String16 TcpSocketMonitor::DUMP_KEYWORD = String16("tcp_socket_info");
|
|
const milliseconds TcpSocketMonitor::kDefaultPollingInterval = milliseconds(30000);
|
|
|
|
void TcpSocketMonitor::dump(DumpWriter& dw) {
|
|
std::lock_guard guard(mLock);
|
|
|
|
dw.println("TcpSocketMonitor");
|
|
ScopedIndent tcpSocketMonitorDetails(dw);
|
|
|
|
const auto now = steady_clock::now();
|
|
const auto d = duration_cast<milliseconds>(now - mLastPoll);
|
|
dw.println("running=%d, suspended=%d, last poll %lld ms ago",
|
|
mIsRunning, mIsSuspended, d.count());
|
|
|
|
if (!mNetworkStats.empty()) {
|
|
dw.blankline();
|
|
dw.println("Network stats:");
|
|
for (const std::pair<const uint32_t, TcpStats>& stats : mNetworkStats) {
|
|
if (stats.second.nSockets == 0) {
|
|
continue;
|
|
}
|
|
dw.println("netId=%d sent=%d lost=%d rttMs=%gms sentAckDiff=%dms",
|
|
stats.first,
|
|
stats.second.sent,
|
|
stats.second.lost,
|
|
stats.second.rttUs / 1000.0 / stats.second.nSockets,
|
|
stats.second.sentAckDiffMs / stats.second.nSockets);
|
|
}
|
|
}
|
|
|
|
if (!mSocketEntries.empty()) {
|
|
dw.blankline();
|
|
dw.println("Socket entries:");
|
|
for (const std::pair<const uint64_t, SocketEntry>& stats : mSocketEntries) {
|
|
dw.println("netId=%u uid=%u cookie=%" PRIu64, stats.second.mark.netId, stats.second.uid,
|
|
stats.first);
|
|
}
|
|
}
|
|
|
|
SockDiag sd;
|
|
if (sd.open()) {
|
|
dw.blankline();
|
|
dw.println("Current socket dump:");
|
|
const auto tcpInfoReader = [&dw](Fwmark mark, const struct inet_diag_msg *sockinfo,
|
|
const struct tcp_info *tcpinfo, uint32_t tcpinfoLen) {
|
|
tcpInfoPrint(dw, mark, sockinfo, tcpinfo, tcpinfoLen);
|
|
};
|
|
|
|
if (int ret = sd.getLiveTcpInfos(tcpInfoReader)) {
|
|
ALOGE("Failed to dump TCP socket info: %s", strerror(-ret));
|
|
}
|
|
} else {
|
|
ALOGE("Error opening sock diag for dumping TCP socket info");
|
|
}
|
|
}
|
|
|
|
void TcpSocketMonitor::setPollingInterval(milliseconds nextSleepDurationMs) {
|
|
std::lock_guard guard(mLock);
|
|
|
|
mNextSleepDurationMs = nextSleepDurationMs;
|
|
|
|
ALOGD("tcpinfo polling interval set to %lld ms", mNextSleepDurationMs.count());
|
|
}
|
|
|
|
void TcpSocketMonitor::resumePolling() {
|
|
bool wasSuspended;
|
|
{
|
|
std::lock_guard guard(mLock);
|
|
|
|
wasSuspended = mIsSuspended;
|
|
mIsSuspended = false;
|
|
ALOGD("resuming tcpinfo polling (interval=%lldms)", mNextSleepDurationMs.count());
|
|
}
|
|
|
|
if (wasSuspended) {
|
|
mCv.notify_all();
|
|
}
|
|
}
|
|
|
|
void TcpSocketMonitor::suspendPolling() {
|
|
std::lock_guard guard(mLock);
|
|
|
|
bool wasSuspended = mIsSuspended;
|
|
mIsSuspended = true;
|
|
ALOGD("suspending tcpinfo polling");
|
|
|
|
if (!wasSuspended) {
|
|
mSocketEntries.clear();
|
|
}
|
|
}
|
|
|
|
void TcpSocketMonitor::poll() {
|
|
std::lock_guard guard(mLock);
|
|
|
|
if (mIsSuspended) {
|
|
return;
|
|
}
|
|
|
|
SockDiag sd;
|
|
if (!sd.open()) {
|
|
ALOGE("Error opening sock diag for polling TCP socket info");
|
|
return;
|
|
}
|
|
|
|
const auto now = steady_clock::now();
|
|
const auto tcpInfoReader = [this, now](Fwmark mark, const struct inet_diag_msg *sockinfo,
|
|
const struct tcp_info *tcpinfo,
|
|
uint32_t tcpinfoLen) NO_THREAD_SAFETY_ANALYSIS {
|
|
if (sockinfo == nullptr || tcpinfo == nullptr || tcpinfoLen == 0 || mark.intValue == 0) {
|
|
return;
|
|
}
|
|
updateSocketStats(now, mark, sockinfo, tcpinfo, tcpinfoLen);
|
|
};
|
|
|
|
// Reset mNetworkStats
|
|
mNetworkStats.clear();
|
|
|
|
if (int ret = sd.getLiveTcpInfos(tcpInfoReader)) {
|
|
ALOGE("Failed to poll TCP socket info: %s", strerror(-ret));
|
|
return;
|
|
}
|
|
|
|
// Remove any SocketEntry not updated
|
|
for (auto it = mSocketEntries.cbegin(); it != mSocketEntries.cend();) {
|
|
if (it->second.lastUpdate < now) {
|
|
it = mSocketEntries.erase(it);
|
|
} else {
|
|
it++;
|
|
}
|
|
}
|
|
|
|
const auto listener = gCtls->eventReporter.getNetdEventListener();
|
|
if (listener != nullptr) {
|
|
std::vector<int> netIds;
|
|
std::vector<int> sentPackets;
|
|
std::vector<int> lostPackets;
|
|
std::vector<int> rtts;
|
|
std::vector<int> sentAckDiffs;
|
|
for (auto const& stats : mNetworkStats) {
|
|
int32_t nSockets = stats.second.nSockets;
|
|
if (nSockets == 0) {
|
|
continue;
|
|
}
|
|
netIds.push_back(stats.first);
|
|
sentPackets.push_back(stats.second.sent);
|
|
lostPackets.push_back(stats.second.lost);
|
|
rtts.push_back(stats.second.rttUs / nSockets);
|
|
sentAckDiffs.push_back(stats.second.sentAckDiffMs / nSockets);
|
|
}
|
|
listener->onTcpSocketStatsEvent(netIds, sentPackets, lostPackets, rtts, sentAckDiffs);
|
|
}
|
|
|
|
mLastPoll = now;
|
|
}
|
|
|
|
void TcpSocketMonitor::waitForNextPoll() {
|
|
bool isSuspended;
|
|
milliseconds nextSleepDurationMs;
|
|
{
|
|
std::lock_guard guard(mLock);
|
|
isSuspended = mIsSuspended;
|
|
nextSleepDurationMs= mNextSleepDurationMs;
|
|
}
|
|
|
|
std::unique_lock<std::mutex> ul(mLock);
|
|
if (isSuspended) {
|
|
mCv.wait(ul);
|
|
} else {
|
|
mCv.wait_for(ul, nextSleepDurationMs);
|
|
}
|
|
}
|
|
|
|
bool TcpSocketMonitor::isRunning() {
|
|
std::lock_guard guard(mLock);
|
|
return mIsRunning;
|
|
}
|
|
|
|
void TcpSocketMonitor::updateSocketStats(time_point now, Fwmark mark,
|
|
const struct inet_diag_msg *sockinfo,
|
|
const struct tcp_info *tcpinfo,
|
|
uint32_t tcpinfoLen) NO_THREAD_SAFETY_ANALYSIS {
|
|
int32_t lastAck = TCPINFO_GET(tcpinfo, tcpi_last_ack_recv, tcpinfoLen, 0);
|
|
int32_t lastSent = TCPINFO_GET(tcpinfo, tcpi_last_data_sent, tcpinfoLen, 0);
|
|
TcpStats diff = {
|
|
.sent = TCPINFO_GET(tcpinfo, tcpi_segs_out, tcpinfoLen, 0),
|
|
.lost = TCPINFO_GET(tcpinfo, tcpi_lost, tcpinfoLen, 0),
|
|
.rttUs = TCPINFO_GET(tcpinfo, tcpi_rtt, tcpinfoLen, 0),
|
|
.sentAckDiffMs = lastAck - lastSent,
|
|
.nSockets = 1,
|
|
};
|
|
|
|
{
|
|
// Update socket stats with the newest entry, computing the diff w.r.t the previous entry.
|
|
const uint64_t cookie = (static_cast<uint64_t>(sockinfo->id.idiag_cookie[0]) << 32)
|
|
| static_cast<uint64_t>(sockinfo->id.idiag_cookie[1]);
|
|
const SocketEntry previous = mSocketEntries[cookie];
|
|
mSocketEntries[cookie] = {
|
|
.sent = diff.sent,
|
|
.lost = diff.lost,
|
|
.lastUpdate = now,
|
|
.mark = mark,
|
|
.uid = sockinfo->idiag_uid,
|
|
};
|
|
|
|
diff.sent -= previous.sent;
|
|
diff.lost -= previous.lost;
|
|
}
|
|
|
|
{
|
|
// Aggregate the diff per network id.
|
|
auto& stats = mNetworkStats[mark.netId];
|
|
stats.sent += diff.sent;
|
|
stats.lost += diff.lost;
|
|
stats.rttUs += diff.rttUs;
|
|
stats.sentAckDiffMs += diff.sentAckDiffMs;
|
|
stats.nSockets += diff.nSockets;
|
|
}
|
|
}
|
|
|
|
TcpSocketMonitor::TcpSocketMonitor() {
|
|
std::lock_guard guard(mLock);
|
|
|
|
mNextSleepDurationMs = kDefaultPollingInterval;
|
|
mIsRunning = true;
|
|
mIsSuspended = true;
|
|
mPollingThread = std::thread([this] {
|
|
(void) this;
|
|
while (isRunning()) {
|
|
poll();
|
|
waitForNextPoll();
|
|
}
|
|
});
|
|
}
|
|
|
|
TcpSocketMonitor::~TcpSocketMonitor() {
|
|
{
|
|
std::lock_guard guard(mLock);
|
|
mIsRunning = false;
|
|
mIsSuspended = true;
|
|
}
|
|
mCv.notify_all();
|
|
mPollingThread.join();
|
|
}
|
|
|
|
} // namespace net
|
|
} // namespace android
|