/*
* 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