From 26c9132b8b5993f8edbb999696e18fa6469d6759 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Mon, 11 Jul 2016 11:36:25 +0900 Subject: [PATCH] Parse IPv6 tethering counters. Bug: 9580643 Change-Id: Icbfd8c6480a4e14433004e90b71a104ae4da9c5d --- server/BandwidthController.cpp | 66 ++++++++++++++++++++---------- server/BandwidthControllerTest.cpp | 83 ++++++++++++++++++++++++++++++++------ 2 files changed, 114 insertions(+), 35 deletions(-) diff --git a/server/BandwidthController.cpp b/server/BandwidthController.cpp index 5596784..09dc483 100644 --- a/server/BandwidthController.cpp +++ b/server/BandwidthController.cpp @@ -1100,6 +1100,12 @@ void BandwidthController::addStats(TetherStatsList& statsList, const TetherStats * 27 2002 RETURN all -- rmnet0 wlan0 0.0.0.0/0 0.0.0.0/0 * 1040 107471 RETURN all -- bt-pan rmnet0 0.0.0.0/0 0.0.0.0/0 * 1450 1708806 RETURN all -- rmnet0 bt-pan 0.0.0.0/0 0.0.0.0/0 + * or: + * Chain natctrl_tether_counters (0 references) + * pkts bytes target prot opt in out source destination + * 0 0 RETURN all wlan0 rmnet_data0 ::/0 ::/0 + * 0 0 RETURN all rmnet_data0 wlan0 ::/0 ::/0 + * * It results in an error if invoked and no tethering counter rules exist. The constraint * helps detect complete parsing failure. */ @@ -1128,8 +1134,17 @@ int BandwidthController::addForwardChainStats(const TetherStats& filter, while (NULL != (buffPtr = fgets(lineBuffer, MAX_IPT_OUTPUT_LINE_LEN, fp))) { /* Clean up, so a failed parse can still print info */ iface0[0] = iface1[0] = rest[0] = packets = bytes = 0; - res = sscanf(buffPtr, "%" SCNd64" %" SCNd64" RETURN all -- %s %s 0.%s", - &packets, &bytes, iface0, iface1, rest); + if (strstr(buffPtr, "0.0.0.0")) { + // IPv4 has -- indicating what to do with fragments... + // 26 2373 RETURN all -- wlan0 rmnet0 0.0.0.0/0 0.0.0.0/0 + res = sscanf(buffPtr, "%" SCNd64" %" SCNd64" RETURN all -- %s %s 0.%s", + &packets, &bytes, iface0, iface1, rest); + } else { + // ... but IPv6 does not. + // 26 2373 RETURN all wlan0 rmnet0 ::/0 ::/0 + res = sscanf(buffPtr, "%" SCNd64" %" SCNd64" RETURN all %s %s ::/%s", + &packets, &bytes, iface0, iface1, rest); + } ALOGV("parse res=%d iface0=<%s> iface1=<%s> pkts=%" PRId64" bytes=%" PRId64" rest=<%s> orig line=<%s>", res, iface0, iface1, packets, bytes, rest, buffPtr); extraProcessingInfo += buffPtr; @@ -1207,14 +1222,7 @@ char *BandwidthController::TetherStats::getStatsLine(void) const { return msg; } -int BandwidthController::getTetherStats(SocketClient *cli, TetherStats& filter, - std::string &extraProcessingInfo) { - int res; - std::string fullCmd; - FILE *iptOutput; - - TetherStatsList statsList; - +std::string getTetherStatsCommand(const char *binary) { /* * Why not use some kind of lib to talk to iptables? * Because the only libs are libiptc and libip6tc in iptables, and they are @@ -1222,18 +1230,33 @@ int BandwidthController::getTetherStats(SocketClient *cli, TetherStats& filter, * preloaded/linked, and require apparently a lot of wrapper code to get * the wanted info. */ - fullCmd = IPTABLES_PATH; - fullCmd += " -nvx -w -L "; - fullCmd += NatController::LOCAL_TETHER_COUNTERS_CHAIN; - iptOutput = popenFunction(fullCmd.c_str(), "r"); - if (!iptOutput) { - ALOGE("Failed to run %s err=%s", fullCmd.c_str(), strerror(errno)); - extraProcessingInfo += "Failed to run iptables."; - return -1; - } + return android::base::StringPrintf("%s -nvx -w -L %s", binary, + NatController::LOCAL_TETHER_COUNTERS_CHAIN); +} - res = addForwardChainStats(filter, statsList, iptOutput, extraProcessingInfo); - pclose(iptOutput); +int BandwidthController::getTetherStats(SocketClient *cli, TetherStats& filter, + std::string &extraProcessingInfo) { + int res = 0; + std::string fullCmd; + FILE *iptOutput; + + TetherStatsList statsList; + + for (const auto binary : {IPTABLES_PATH, IP6TABLES_PATH}) { + fullCmd = getTetherStatsCommand(binary); + iptOutput = popenFunction(fullCmd.c_str(), "r"); + if (!iptOutput) { + ALOGE("Failed to run %s err=%s", fullCmd.c_str(), strerror(errno)); + extraProcessingInfo += "Failed to run iptables."; + return -1; + } + + res = addForwardChainStats(filter, statsList, iptOutput, extraProcessingInfo); + pclose(iptOutput); + if (res != 0) { + return res; + } + } if (filter.intIface[0] && filter.extIface[0] && statsList.size() == 1) { cli->sendMsg(ResponseCode::TetheringStatsResult, statsList[0].getStatsLine(), false); @@ -1246,7 +1269,6 @@ int BandwidthController::getTetherStats(SocketClient *cli, TetherStats& filter, } } - /* Currently NatController doesn't do ipv6 tethering, so we are done. */ return res; } diff --git a/server/BandwidthControllerTest.cpp b/server/BandwidthControllerTest.cpp index 9a60402..3e11596 100644 --- a/server/BandwidthControllerTest.cpp +++ b/server/BandwidthControllerTest.cpp @@ -43,6 +43,15 @@ public: void addPopenContents(std::string contents) { sPopenContents.push_back(contents); } + + void addPopenContents(std::string contents1, std::string contents2) { + sPopenContents.push_back(contents1); + sPopenContents.push_back(contents2); + } + + void clearPopenContents() { + sPopenContents.clear(); + } }; TEST_F(BandwidthControllerTest, TestSetupIptablesHooks) { @@ -149,6 +158,13 @@ std::string kIPv4TetherCounters = android::base::Join(std::vector { " 1450 1708806 RETURN all -- rmnet0 bt-pan 0.0.0.0/0 0.0.0.0/0", }, '\n'); +std::string kIPv6TetherCounters = android::base::Join(std::vector { + "Chain natctrl_tether_counters (2 references)", + " pkts bytes target prot opt in out source destination", + " 10000 10000000 RETURN all wlan0 rmnet0 ::/0 ::/0", + " 20000 20000000 RETURN all rmnet0 wlan0 ::/0 ::/0", +}, '\n'); + std::string readSocketClientResponse(int fd) { char buf[32768]; ssize_t bytesRead = read(fd, buf, sizeof(buf)); @@ -175,63 +191,104 @@ TEST_F(BandwidthControllerTest, TestGetTetherStats) { std::string err; BandwidthController::TetherStats filter; - addPopenContents(kIPv4TetherCounters); + + // If no filter is specified, both IPv4 and IPv6 counters must have at least one interface pair. + addPopenContents(kIPv4TetherCounters, ""); + ASSERT_EQ(-1, mBw.getTetherStats(&cli, filter, err)); + expectNoSocketClientResponse(socketPair[1]); + clearPopenContents(); + + addPopenContents("", kIPv6TetherCounters); + ASSERT_EQ(-1, mBw.getTetherStats(&cli, filter, err)); + clearPopenContents(); + + // IPv4 and IPv6 counters are properly added together. + addPopenContents(kIPv4TetherCounters, kIPv6TetherCounters); + filter = BandwidthController::TetherStats(); std::string expected = - "114 wlan0 rmnet0 2373 26 2002 27\n" + "114 wlan0 rmnet0 10002373 10026 20002002 20027\n" "114 bt-pan rmnet0 107471 1040 1708806 1450\n" "200 Tethering stats list completed\n"; ASSERT_EQ(0, mBw.getTetherStats(&cli, filter, err)); ASSERT_EQ(expected, readSocketClientResponse(socketPair[1])); + expectNoSocketClientResponse(socketPair[1]); + clearPopenContents(); - addPopenContents(kIPv4TetherCounters); + // Test filtering. + addPopenContents(kIPv4TetherCounters, kIPv6TetherCounters); filter = BandwidthController::TetherStats("bt-pan", "rmnet0", -1, -1, -1, -1); expected = "221 bt-pan rmnet0 107471 1040 1708806 1450\n"; ASSERT_EQ(0, mBw.getTetherStats(&cli, filter, err)); ASSERT_EQ(expected, readSocketClientResponse(socketPair[1])); + expectNoSocketClientResponse(socketPair[1]); + clearPopenContents(); - addPopenContents(kIPv4TetherCounters); - filter = BandwidthController::TetherStats("rmnet0", "wlan0", -1, -1, -1, -1); - expected = "221 rmnet0 wlan0 2002 27 2373 26\n"; + addPopenContents(kIPv4TetherCounters, kIPv6TetherCounters); + filter = BandwidthController::TetherStats("wlan0", "rmnet0", -1, -1, -1, -1); + expected = "221 wlan0 rmnet0 10002373 10026 20002002 20027\n"; ASSERT_EQ(0, mBw.getTetherStats(&cli, filter, err)); ASSERT_EQ(expected, readSocketClientResponse(socketPair[1])); + clearPopenContents(); - addPopenContents(kIPv4TetherCounters); + // Select nonexistent interfaces. + addPopenContents(kIPv4TetherCounters, kIPv6TetherCounters); filter = BandwidthController::TetherStats("rmnet0", "foo0", -1, -1, -1, -1); expected = "200 Tethering stats list completed\n"; ASSERT_EQ(0, mBw.getTetherStats(&cli, filter, err)); ASSERT_EQ(expected, readSocketClientResponse(socketPair[1])); + clearPopenContents(); // No stats with a filter: no error. - addPopenContents(""); + addPopenContents("", ""); ASSERT_EQ(0, mBw.getTetherStats(&cli, filter, err)); ASSERT_EQ("200 Tethering stats list completed\n", readSocketClientResponse(socketPair[1])); - addPopenContents("foo"); + clearPopenContents(); + + addPopenContents("foo", "foo"); ASSERT_EQ(0, mBw.getTetherStats(&cli, filter, err)); ASSERT_EQ("200 Tethering stats list completed\n", readSocketClientResponse(socketPair[1])); + clearPopenContents(); // No stats and empty filter: error. filter = BandwidthController::TetherStats(); - addPopenContents(""); + addPopenContents("", kIPv6TetherCounters); ASSERT_EQ(-1, mBw.getTetherStats(&cli, filter, err)); - addPopenContents("foo"); + expectNoSocketClientResponse(socketPair[1]); + clearPopenContents(); + + addPopenContents(kIPv4TetherCounters, ""); ASSERT_EQ(-1, mBw.getTetherStats(&cli, filter, err)); expectNoSocketClientResponse(socketPair[1]); + clearPopenContents(); // Include only one pair of interfaces and things are fine. std::vector counterLines = android::base::Split(kIPv4TetherCounters, "\n"); std::vector brokenCounterLines = counterLines; counterLines.resize(4); std::string counters = android::base::Join(counterLines, "\n") + "\n"; - addPopenContents(counters); + addPopenContents(counters, counters); expected = - "114 wlan0 rmnet0 2373 26 2002 27\n" + "114 wlan0 rmnet0 4746 52 4004 54\n" "200 Tethering stats list completed\n"; ASSERT_EQ(0, mBw.getTetherStats(&cli, filter, err)); ASSERT_EQ(expected, readSocketClientResponse(socketPair[1])); + clearPopenContents(); // But if interfaces aren't paired, it's always an error. counterLines.resize(3); counters = android::base::Join(counterLines, "\n") + "\n"; + addPopenContents(counters, counters); + ASSERT_EQ(-1, mBw.getTetherStats(&cli, filter, err)); + expectNoSocketClientResponse(socketPair[1]); + clearPopenContents(); + + // popen() failing is always an error. + addPopenContents(kIPv4TetherCounters); + ASSERT_EQ(-1, mBw.getTetherStats(&cli, filter, err)); + expectNoSocketClientResponse(socketPair[1]); + clearPopenContents(); + addPopenContents(kIPv6TetherCounters); ASSERT_EQ(-1, mBw.getTetherStats(&cli, filter, err)); expectNoSocketClientResponse(socketPair[1]); + clearPopenContents(); } -- 2.11.0