From baeccc455b293c2c83dbe6463f56b741177bd612 Mon Sep 17 00:00:00 2001 From: JP Abgrall Date: Tue, 25 Jun 2013 09:44:10 -0700 Subject: [PATCH] netd: tethering stats: persistent + list-all support * Persistent stats Previously we would parse the iptables counters out of the FORWARD rules used for tethering. Those rules could come an go before they were parsed, which would cause us to incorrectly count traffic. Now we have separate counting rules (and quota2 counters) which persist beyond tethering. * Rename the iface0/iface1 Match NatControllers notions for tethering ifaces during enable. Detect weird call from userspace (until b/9565268 gets fixed), or else it leaves an ugly iptables state. * The commands affected: - ndc bandwidth gettetheringstats intIface extIface . no change from before: return a single stats line - ndc bandwidth gettetheringstats . return a list of results showing all tethered stats - ndc bandwidth gettetheringstats "" extIface - ndc bandwidth gettetheringstats intIface . return a list of results matching the tethering on the given interface. Bug: 9565268 Bug: 5868832 Change-Id: I8559d9a184abcffaf65998fb3cc8c9c50d46bf06 --- BandwidthController.cpp | 128 +++++++++++++++++++++++++---------- BandwidthController.h | 37 +++++++---- CommandListener.cpp | 17 ++--- NatController.cpp | 173 +++++++++++++++++++++++++++++++++++++++++++----- NatController.h | 2 + ResponseCode.h | 2 +- 6 files changed, 282 insertions(+), 77 deletions(-) diff --git a/BandwidthController.cpp b/BandwidthController.cpp index d51ea25..f720e0c 100644 --- a/BandwidthController.cpp +++ b/BandwidthController.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -// #define LOG_NDEBUG 0 +#define LOG_NDEBUG 0 /* * The CommandListener, FrameworkListener don't allow for @@ -44,6 +44,8 @@ #include "NetdConstants.h" #include "BandwidthController.h" +#include "NatController.h" /* For LOCAL_TETHER_COUNTERS_CHAIN */ +#include "ResponseCode.h" /* Alphabetical */ #define ALERT_IPT_TEMPLATE "%s %s -m quota2 ! --quota %lld --name %s" @@ -632,11 +634,11 @@ int BandwidthController::setInterfaceQuota(const char *iface, int64_t maxBytes) if (it == quotaIfaces.end()) { /* Preparing the iface adds a penalty_box check */ res |= prepCostlyIface(ifn, QuotaUnique); - /* - * The rejecting quota limit should go after the penalty box checks - * or else a naughty app could just eat up the quota. - * So we append here. - */ + /* + * The rejecting quota limit should go after the penalty box checks + * or else a naughty app could just eat up the quota. + * So we append here. + */ quotaCmd = makeIptablesQuotaCmd(IptOpAppend, costName, maxBytes); res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); if (res) { @@ -983,23 +985,33 @@ int BandwidthController::removeCostlyAlert(const char *costName, int64_t *alertB /* * Parse the ptks and bytes out of: - * Chain FORWARD (policy RETURN 0 packets, 0 bytes) - * pkts bytes target prot opt in out source destination - * 0 0 RETURN all -- rmnet0 wlan0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED - * 0 0 DROP all -- wlan0 rmnet0 0.0.0.0/0 0.0.0.0/0 state INVALID - * 0 0 RETURN all -- wlan0 rmnet0 0.0.0.0/0 0.0.0.0/0 - * + * Chain natctrl_tether_counters (4 references) + * pkts bytes target prot opt in out source destination + * 26 2373 RETURN all -- wlan0 rmnet0 0.0.0.0/0 0.0.0.0/0 counter wlan0_rmnet0: 0 bytes + * 27 2002 RETURN all -- rmnet0 wlan0 0.0.0.0/0 0.0.0.0/0 counter rmnet0_wlan0: 0 bytes + * 1040 107471 RETURN all -- bt-pan rmnet0 0.0.0.0/0 0.0.0.0/0 counter bt-pan_rmnet0: 0 bytes + * 1450 1708806 RETURN all -- rmnet0 bt-pan 0.0.0.0/0 0.0.0.0/0 counter rmnet0_bt-pan: 0 bytes */ -int BandwidthController::parseForwardChainStats(TetherStats &stats, FILE *fp, - std::string &extraProcessingInfo) { +int BandwidthController::parseForwardChainStats(SocketClient *cli, const TetherStats filter, + FILE *fp, std::string &extraProcessingInfo) { int res; char lineBuffer[MAX_IPT_OUTPUT_LINE_LEN]; char iface0[MAX_IPT_OUTPUT_LINE_LEN]; char iface1[MAX_IPT_OUTPUT_LINE_LEN]; char rest[MAX_IPT_OUTPUT_LINE_LEN]; + TetherStats stats; char *buffPtr; int64_t packets, bytes; + int statsFound = 0; + + bool filterPair = filter.intIface[0] && filter.extIface[0]; + + char *filterMsg = filter.getStatsLine(); + ALOGV("filter: %s", filterMsg); + free(filterMsg); + + stats = filter; while (NULL != (buffPtr = fgets(lineBuffer, MAX_IPT_OUTPUT_LINE_LEN, fp))) { /* Clean up, so a failed parse can still print info */ @@ -1013,39 +1025,84 @@ int BandwidthController::parseForwardChainStats(TetherStats &stats, FILE *fp, if (res != 5) { continue; } - if ((stats.ifaceIn == iface0) && (stats.ifaceOut == iface1)) { - ALOGV("iface_in=%s iface_out=%s rx_bytes=%lld rx_packets=%lld ", iface0, iface1, bytes, packets); - stats.rxPackets = packets; - stats.rxBytes = bytes; - } else if ((stats.ifaceOut == iface0) && (stats.ifaceIn == iface1)) { - ALOGV("iface_in=%s iface_out=%s tx_bytes=%lld tx_packets=%lld ", iface1, iface0, bytes, packets); - stats.txPackets = packets; - stats.txBytes = bytes; + /* + * The following assumes that the 1st rule has in:extIface out:intIface, + * which is what NatController sets up. + * If not filtering, the 1st match rx, and sets up the pair for the tx side. + */ + if (filter.intIface[0] && filter.extIface[0]) { + if (filter.intIface == iface0 && filter.extIface == iface1) { + ALOGV("2Filter RX iface_in=%s iface_out=%s rx_bytes=%lld rx_packets=%lld ", iface0, iface1, bytes, packets); + stats.rxPackets = packets; + stats.rxBytes = bytes; + } else if (filter.intIface == iface1 && filter.extIface == iface0) { + ALOGV("2Filter TX iface_in=%s iface_out=%s rx_bytes=%lld rx_packets=%lld ", iface0, iface1, bytes, packets); + stats.txPackets = packets; + stats.txBytes = bytes; + } + } else if (filter.intIface[0] || filter.extIface[0]) { + if (filter.intIface == iface0 || filter.extIface == iface1) { + ALOGV("1Filter RX iface_in=%s iface_out=%s rx_bytes=%lld rx_packets=%lld ", iface0, iface1, bytes, packets); + stats.intIface = iface0; + stats.extIface = iface1; + stats.rxPackets = packets; + stats.rxBytes = bytes; + } else if (filter.intIface == iface1 || filter.extIface == iface0) { + ALOGV("1Filter TX iface_in=%s iface_out=%s rx_bytes=%lld rx_packets=%lld ", iface0, iface1, bytes, packets); + stats.intIface = iface1; + stats.extIface = iface0; + stats.txPackets = packets; + stats.txBytes = bytes; + } + } else /* if (!filter.intFace[0] && !filter.extIface[0]) */ { + if (!stats.intIface[0]) { + ALOGV("0Filter RX iface_in=%s iface_out=%s rx_bytes=%lld rx_packets=%lld ", iface0, iface1, bytes, packets); + stats.intIface = iface0; + stats.extIface = iface1; + stats.rxPackets = packets; + stats.rxBytes = bytes; + } else if (stats.intIface == iface1 && stats.extIface == iface0) { + ALOGV("0Filter TX iface_in=%s iface_out=%s rx_bytes=%lld rx_packets=%lld ", iface0, iface1, bytes, packets); + stats.txPackets = packets; + stats.txBytes = bytes; + } + } + if (stats.rxBytes != -1 && stats.txBytes != -1) { + ALOGV("rx_bytes=%lld tx_bytes=%lld filterPair=%d", stats.rxBytes, stats.txBytes, filterPair); + /* Send out stats, and prep for the next if needed. */ + char *msg = stats.getStatsLine(); + if (filterPair) { + cli->sendMsg(ResponseCode::TetheringStatsResult, msg, false); + return 0; + } else { + cli->sendMsg(ResponseCode::TetheringStatsListResult, msg, false); + stats = filter; + } + free(msg); + statsFound++; } } - /* Failure if rx or tx was not found */ - return (stats.rxBytes == -1 || stats.txBytes == -1) ? -1 : 0; + /* We found some stats, and the last one isn't a partial stats. */ + if (statsFound && (stats.rxBytes == -1 || stats.txBytes == -1)) { + cli->sendMsg(ResponseCode::CommandOkay, "Tethering stats list completed", false); + return 0; + } + return -1; } - -char *BandwidthController::TetherStats::getStatsLine(void) { +char *BandwidthController::TetherStats::getStatsLine(void) const { char *msg; - asprintf(&msg, "%s %s %lld %lld %lld %lld", ifaceIn.c_str(), ifaceOut.c_str(), + asprintf(&msg, "%s %s %lld %lld %lld %lld", intIface.c_str(), extIface.c_str(), rxBytes, rxPackets, txBytes, txPackets); return msg; } -int BandwidthController::getTetherStats(TetherStats &stats, std::string &extraProcessingInfo) { +int BandwidthController::getTetherStats(SocketClient *cli, TetherStats &stats, std::string &extraProcessingInfo) { int res; std::string fullCmd; FILE *iptOutput; const char *cmd; - if (stats.rxBytes != -1 || stats.txBytes != -1) { - ALOGE("Unexpected input stats. Byte counts should be -1."); - return -1; - } - /* * Why not use some kind of lib to talk to iptables? * Because the only libs are libiptc and libip6tc in iptables, and they are @@ -1054,14 +1111,15 @@ int BandwidthController::getTetherStats(TetherStats &stats, std::string &extraPr * the wanted info. */ fullCmd = IPTABLES_PATH; - fullCmd += " -nvx -L natctrl_FORWARD"; + fullCmd += " -nvx -L "; + fullCmd += NatController::LOCAL_TETHER_COUNTERS_CHAIN; iptOutput = popen(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 = parseForwardChainStats(stats, iptOutput, extraProcessingInfo); + res = parseForwardChainStats(cli, stats, iptOutput, extraProcessingInfo); pclose(iptOutput); /* Currently NatController doesn't do ipv6 tethering, so we are done. */ diff --git a/BandwidthController.h b/BandwidthController.h index b4a2c20..13805af 100644 --- a/BandwidthController.h +++ b/BandwidthController.h @@ -20,6 +20,8 @@ #include #include // for pair +#include + class BandwidthController { public: class TetherStats { @@ -27,22 +29,24 @@ public: TetherStats(void) : rxBytes(-1), rxPackets(-1), txBytes(-1), txPackets(-1) {}; - TetherStats(std::string ifnIn, std::string ifnOut, + TetherStats(std::string intIfn, std::string extIfn, int64_t rxB, int64_t rxP, int64_t txB, int64_t txP) - : ifaceIn(ifnIn), ifaceOut(ifnOut), + : intIface(intIfn), extIface(extIfn), rxBytes(rxB), rxPackets(rxP), - txBytes(txB), txPackets(txP) {}; - std::string ifaceIn; - std::string ifaceOut; + txBytes(txB), txPackets(txP) {}; + /* Internal interface. Same as NatController's notion. */ + std::string intIface; + /* External interface. Same as NatController's notion. */ + std::string extIface; int64_t rxBytes, rxPackets; int64_t txBytes, txPackets; /* * Allocates a new string representing this: - * ifaceIn ifaceOut rx_bytes rx_packets tx_bytes tx_packets + * intIface extIface rx_bytes rx_packets tx_bytes tx_packets * The caller is responsible for free()'ing the returned ptr. */ - char *getStatsLine(void); + char *getStatsLine(void) const; }; BandwidthController(); @@ -75,10 +79,13 @@ public: int removeInterfaceAlert(const char *iface); /* - * stats should have ifaceIn and ifaceOut initialized. - * Byte counts should be left to the default (-1). + * For single pair of ifaces, stats should have ifaceIn and ifaceOut initialized. + * For all pairs, stats should have ifaceIn=ifaceOut="". + * Sends out to the cli the single stat (TetheringStatsReluts) or a list of stats + * (TetheringStatsListResult+CommandOkay). + * Error is to be handled on the outside */ - int getTetherStats(TetherStats &stats, std::string &extraProcessingInfo); + int getTetherStats(SocketClient *cli, TetherStats &stats, std::string &extraProcessingInfo); static const char* LOCAL_INPUT; static const char* LOCAL_FORWARD; @@ -136,11 +143,15 @@ protected: int removeCostlyAlert(const char *costName, int64_t *alertBytes); /* - * stats should have ifaceIn and ifaceOut initialized. - * fp should be a file to the FORWARD rules of iptables. + * stats should never have only intIface initialized. Other 3 combos are ok. + * fp should be a file to the apropriate FORWARD chain of iptables rules. * extraProcessingInfo: contains raw parsed data, and error info. + * This strongly requires that setup of the rules is in a specific order: + * in:intIface out:extIface + * in:extIface out:intIface + * and the rules are grouped in pairs when more that one tethering was setup. */ - static int parseForwardChainStats(TetherStats &stats, FILE *fp, + static int parseForwardChainStats(SocketClient *cli, const TetherStats filter, FILE *fp, std::string &extraProcessingInfo); /*------------------*/ diff --git a/CommandListener.cpp b/CommandListener.cpp index 026797f..3950f72 100644 --- a/CommandListener.cpp +++ b/CommandListener.cpp @@ -1229,23 +1229,18 @@ int CommandListener::BandwidthControlCmd::runCommand(SocketClient *cli, int argc if (!strcmp(argv[1], "gettetherstats") || !strcmp(argv[1], "gts")) { BandwidthController::TetherStats tetherStats; std::string extraProcessingInfo = ""; - if (argc != 4) { - sendGenericSyntaxError(cli, "gettetherstats "); + if (argc < 2 || argc > 4) { + sendGenericSyntaxError(cli, "gettetherstats [ ]"); return 0; } - - tetherStats.ifaceIn = argv[2]; - tetherStats.ifaceOut = argv[3]; - int rc = sBandwidthCtrl->getTetherStats(tetherStats, extraProcessingInfo); + tetherStats.intIface = argc > 2 ? argv[2] : ""; + tetherStats.extIface = argc > 3 ? argv[3] : ""; + int rc = sBandwidthCtrl->getTetherStats(cli, tetherStats, extraProcessingInfo); if (rc) { extraProcessingInfo.insert(0, "Failed to get tethering stats.\n"); sendGenericOpFailed(cli, extraProcessingInfo.c_str()); - return 0; + return 0; } - - char *msg = tetherStats.getStatsLine(); - cli->sendMsg(ResponseCode::TetheringStatsResult, msg, false); - free(msg); return 0; } diff --git a/NatController.cpp b/NatController.cpp index 140cb36..b2a0e64 100644 --- a/NatController.cpp +++ b/NatController.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -// #define LOG_NDEBUG 0 +#define LOG_NDEBUG 0 #include #include @@ -37,6 +37,7 @@ const char* NatController::LOCAL_FORWARD = "natctrl_FORWARD"; const char* NatController::LOCAL_NAT_POSTROUTING = "natctrl_nat_POSTROUTING"; +const char* NatController::LOCAL_TETHER_COUNTERS_CHAIN = "natctrl_tether_counters"; NatController::NatController(SecondaryTableController *ctrl) { secondaryTableCtrl = ctrl; @@ -55,20 +56,62 @@ int NatController::runCmd(int argc, const char **argv) { int res; res = android_fork_execvp(argc, (char **)argv, NULL, false, false); - ALOGV("runCmd() res=%d", res); + +#if !LOG_NDEBUG + std::string full_cmd = argv[0]; + argc--; argv++; + /* + * HACK: Sometimes runCmd() is called with a ridcously large value (32) + * and it works because the argv[] contains a NULL after the last + * true argv. So here we use the NULL argv[] to terminate when the argc + * is horribly wrong, and argc for the normal cases. + */ + for (; argc && argv[0]; argc--, argv++) { + full_cmd += " "; + full_cmd += argv[0]; + } + ALOGV("runCmd(%s) res=%d", full_cmd.c_str(), res); +#endif return res; } int NatController::setupIptablesHooks() { - setDefaults(); + int res; + res = setDefaults(); + if (res < 0) { + return res; + } + + struct CommandsAndArgs defaultCommands[] = { + /* + * Chain for tethering counters. + * This chain is reached via --goto, and then RETURNS. + */ + {{IPTABLES_PATH, "-F", LOCAL_TETHER_COUNTERS_CHAIN,}, 0}, + {{IPTABLES_PATH, "-X", LOCAL_TETHER_COUNTERS_CHAIN,}, 0}, + {{IPTABLES_PATH, "-N", LOCAL_TETHER_COUNTERS_CHAIN,}, 1}, + }; + for (unsigned int cmdNum = 0; cmdNum < ARRAY_SIZE(defaultCommands); cmdNum++) { + if (runCmd(ARRAY_SIZE(defaultCommands[cmdNum].cmd), defaultCommands[cmdNum].cmd) && + defaultCommands[cmdNum].checkRes) { + return -1; + } + } + return 0; } int NatController::setDefaults() { + /* + * The following only works because: + * - the defaultsCommands[].cmd array is padded with NULL, and + * - the 1st argc of runCmd() will just be the max for the CommandsAndArgs[].cmd, and + * - internally it will be memcopied to an array and terminated with a NULL. + */ struct CommandsAndArgs defaultCommands[] = { - {{IPTABLES_PATH, "-F", "natctrl_FORWARD",}, 1}, - {{IPTABLES_PATH, "-A", "natctrl_FORWARD", "-j", "DROP"}, 1}, - {{IPTABLES_PATH, "-t", "nat", "-F", "natctrl_nat_POSTROUTING"}, 1}, + {{IPTABLES_PATH, "-F", LOCAL_FORWARD,}, 1}, + {{IPTABLES_PATH, "-A", LOCAL_FORWARD, "-j", "DROP"}, 1}, + {{IPTABLES_PATH, "-t", "nat", "-F", LOCAL_NAT_POSTROUTING}, 1}, {{IP_PATH, "rule", "flush"}, 0}, {{IP_PATH, "-6", "rule", "flush"}, 0}, {{IP_PATH, "rule", "add", "from", "all", "lookup", "default", "prio", "32767"}, 0}, @@ -128,12 +171,21 @@ int NatController::enableNat(const int argc, char **argv) { const char *extIface = argv[3]; int tableNumber; + ALOGV("enableNat(intIface=<%s>, extIface=<%s>)",intIface, extIface); + if (!checkInterface(intIface) || !checkInterface(extIface)) { ALOGE("Invalid interface specified"); errno = ENODEV; return -1; } + /* Bug: b/9565268. "enableNat wlan0 wlan0". For now we fail until java-land is fixed */ + if (!strcmp(intIface, extIface)) { + ALOGE("Duplicate interface specified: %s %s", intIface, extIface); + errno = EINVAL; + return -1; + } + if (argc < 5 + addrCount) { ALOGE("Missing Argument"); errno = EINVAL; @@ -153,7 +205,7 @@ int NatController::enableNat(const int argc, char **argv) { "-t", "nat", "-A", - "natctrl_nat_POSTROUTING", + LOCAL_NAT_POSTROUTING, "-o", extIface, "-j", @@ -183,7 +235,7 @@ int NatController::enableNat(const int argc, char **argv) { const char *cmd1[] = { IPTABLES_PATH, "-D", - "natctrl_FORWARD", + LOCAL_FORWARD, "-j", "DROP" }; @@ -191,7 +243,7 @@ int NatController::enableNat(const int argc, char **argv) { const char *cmd2[] = { IPTABLES_PATH, "-A", - "natctrl_FORWARD", + LOCAL_FORWARD, "-j", "DROP" }; @@ -201,11 +253,93 @@ int NatController::enableNat(const int argc, char **argv) { return 0; } -int NatController::setForwardRules(bool add, const char *intIface, const char * extIface) { +int NatController::setTetherCountingRules(bool add, const char *intIface, const char *extIface) { + + /* We only ever add tethering quota rules so that they stick. */ + if (!add) { + return 0; + } + char *quota_name, *proc_path; + int quota_fd; + asprintf("a_name, "%s_%s", intIface, extIface); + + asprintf(&proc_path, "/proc/net/xt_quota/%s", quota_name); + quota_fd = open(proc_path, O_RDONLY); + if (quota_fd >= 0) { + /* quota for iface pair already exists */ + free(proc_path); + free(quota_name); + return 0; + } + close(quota_fd); + free(proc_path); + + const char *cmd2b[] = { + IPTABLES_PATH, + "-A", + LOCAL_TETHER_COUNTERS_CHAIN, + "-i", + intIface, + "-o", + extIface, + "-m", + "quota2", + "--name", + quota_name, + "--grow", + "-j", + "RETURN" + }; + + if (runCmd(ARRAY_SIZE(cmd2b), cmd2b) && add) { + free(quota_name); + return -1; + } + free(quota_name); + + asprintf("a_name, "%s_%s", extIface, intIface); + asprintf(&proc_path, "/proc/net/xt_quota/%s", quota_name); + quota_fd = open(proc_path, O_RDONLY); + if (quota_fd >= 0) { + /* quota for iface pair already exists */ + free(proc_path); + free(quota_name); + return 0; + } + close(quota_fd); + free(proc_path); + + const char *cmd3b[] = { + IPTABLES_PATH, + "-A", + LOCAL_TETHER_COUNTERS_CHAIN, + "-i", + extIface, + "-o", + intIface, + "-m", + "quota2", + "--name", + quota_name, + "--grow", + "-j", + "RETURN" + }; + + if (runCmd(ARRAY_SIZE(cmd3b), cmd3b) && add) { + // unwind what's been done, but don't care about success - what more could we do? + free(quota_name); + return -1; + } + free(quota_name); + return 0; +} + +int NatController::setForwardRules(bool add, const char *intIface, const char *extIface) { const char *cmd1[] = { IPTABLES_PATH, add ? "-A" : "-D", - "natctrl_FORWARD", + LOCAL_FORWARD, "-i", extIface, "-o", @@ -214,8 +348,8 @@ int NatController::setForwardRules(bool add, const char *intIface, const char * "state", "--state", "ESTABLISHED,RELATED", - "-j", - "RETURN" + "-g", + LOCAL_TETHER_COUNTERS_CHAIN }; int rc = 0; @@ -226,7 +360,7 @@ int NatController::setForwardRules(bool add, const char *intIface, const char * const char *cmd2[] = { IPTABLES_PATH, add ? "-A" : "-D", - "natctrl_FORWARD", + LOCAL_FORWARD, "-i", intIface, "-o", @@ -242,13 +376,13 @@ int NatController::setForwardRules(bool add, const char *intIface, const char * const char *cmd3[] = { IPTABLES_PATH, add ? "-A" : "-D", - "natctrl_FORWARD", + LOCAL_FORWARD, "-i", intIface, "-o", extIface, - "-j", - "RETURN" + "-g", + LOCAL_TETHER_COUNTERS_CHAIN }; if (runCmd(ARRAY_SIZE(cmd2), cmd2) && add) { @@ -263,6 +397,11 @@ int NatController::setForwardRules(bool add, const char *intIface, const char * goto err_return; } + if (setTetherCountingRules(add, intIface, extIface) && add) { + rc = -1; + goto err_return; + } + return 0; err_return: diff --git a/NatController.h b/NatController.h index ba7daaa..525ca02 100644 --- a/NatController.h +++ b/NatController.h @@ -33,6 +33,7 @@ public: static const char* LOCAL_FORWARD; static const char* LOCAL_NAT_POSTROUTING; + static const char* LOCAL_TETHER_COUNTERS_CHAIN; private: int natCount; @@ -42,6 +43,7 @@ private: int runCmd(int argc, const char **argv); bool checkInterface(const char *iface); int setForwardRules(bool set, const char *intIface, const char *extIface); + int setTetherCountingRules(bool add, const char *intIface, const char *extIface); int routesOp(bool add, const char *intIface, const char *extIface, char **argv, int addrCount); }; diff --git a/ResponseCode.h b/ResponseCode.h index 85f183a..895c807 100644 --- a/ResponseCode.h +++ b/ResponseCode.h @@ -28,7 +28,7 @@ public: static const int TetherInterfaceListResult = 111; static const int TetherDnsFwdTgtListResult = 112; static const int TtyListResult = 113; - + static const int TetheringStatsListResult = 114; // 200 series - Requested action has been successfully completed static const int CommandOkay = 200; -- 2.11.0