Add binder call tetherOffloadSetInterfaceQuota
Provide binder calls for setting the limit for the given upstream
interface.
Bug: 150736748
Test: atest
Change-Id: I5def133022ee0ae232972c9ccffccd041b4b47a6
Merged-In: I5def133022ee0ae232972c9ccffccd041b4b47a6
diff --git a/server/NetdNativeService.cpp b/server/NetdNativeService.cpp
index 732821b..c580670 100644
--- a/server/NetdNativeService.cpp
+++ b/server/NetdNativeService.cpp
@@ -1295,5 +1295,11 @@
return binder::Status::ok();
}
+binder::Status NetdNativeService::tetherOffloadSetInterfaceQuota(int ifIndex, int64_t quotaBytes) {
+ NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
+ int res = gCtls->tetherCtrl.setTetherOffloadInterfaceQuota(ifIndex, quotaBytes);
+ return statusFromErrcode(res);
+}
+
} // namespace net
} // namespace android
diff --git a/server/NetdNativeService.h b/server/NetdNativeService.h
index f9979d3..f019b1f 100644
--- a/server/NetdNativeService.h
+++ b/server/NetdNativeService.h
@@ -141,6 +141,7 @@
binder::Status tetherOffloadRuleAdd(const android::net::TetherOffloadRuleParcel& rule) override;
binder::Status tetherOffloadRuleRemove(
const android::net::TetherOffloadRuleParcel& rule) override;
+ binder::Status tetherOffloadSetInterfaceQuota(int ifIndex, int64_t quotaBytes) override;
// Interface-related commands.
binder::Status interfaceAddAddress(const std::string &ifName,
diff --git a/server/TetherController.cpp b/server/TetherController.cpp
index 04af0ae..06740ab 100644
--- a/server/TetherController.cpp
+++ b/server/TetherController.cpp
@@ -84,6 +84,10 @@
// Chosen to match AID_DNS_TETHER, as made "friendly" by fs_config_generator.py.
constexpr const char kDnsmasqUsername[] = "dns_tether";
+// A value used by interface quota indicates there is no limit.
+// Sync from frameworks/base/core/java/android/net/netstats/provider/NetworkStatsProvider.java
+constexpr int64_t QUOTA_UNLIMITED = -1;
+
bool writeToFile(const char* filename, const char* value) {
int fd = open(filename, O_WRONLY | O_CLOEXEC);
if (fd < 0) {
@@ -1137,6 +1141,27 @@
}
}
+int TetherController::setTetherOffloadInterfaceQuota(int ifIndex, int64_t maxBytes) {
+ if (!mBpfStatsMap.isValid() || !mBpfLimitMap.isValid()) return -ENOTSUP;
+
+ if (ifIndex <= 0) return -ENODEV;
+
+ if (maxBytes < QUOTA_UNLIMITED) {
+ ALOGE("Invalid bytes value. Must be -1 (unlimited) or 0..max_int64.");
+ return -ERANGE;
+ }
+
+ // Note that a value of unlimited quota (-1) indicates simply max_uint64.
+ const auto res = setBpfLimit(static_cast<uint32_t>(ifIndex), static_cast<uint64_t>(maxBytes));
+ if (!res.ok()) {
+ ALOGE("Fail to set quota %" PRId64 " for interface index %d: %s", maxBytes, ifIndex,
+ strerror(res.error().code()));
+ return -res.error().code();
+ }
+
+ return 0;
+}
+
void TetherController::dumpIfaces(DumpWriter& dw) {
dw.println("Interface pairs:");
diff --git a/server/TetherController.h b/server/TetherController.h
index 513a659..f8d6941 100644
--- a/server/TetherController.h
+++ b/server/TetherController.h
@@ -108,6 +108,8 @@
base::Result<void> addOffloadRule(const TetherOffloadRuleParcel& rule);
base::Result<void> removeOffloadRule(const TetherOffloadRuleParcel& rule);
+ int setTetherOffloadInterfaceQuota(int ifIndex, int64_t maxBytes);
+
class TetherStats {
public:
TetherStats() = default;
diff --git a/server/TetherControllerTest.cpp b/server/TetherControllerTest.cpp
index d2ff4a7..db9892f 100644
--- a/server/TetherControllerTest.cpp
+++ b/server/TetherControllerTest.cpp
@@ -502,5 +502,47 @@
clearIptablesRestoreOutput();
}
+TEST_F(TetherControllerTest, TestTetherOffloadSetQuota) {
+ SKIP_IF_BPF_NOT_SUPPORTED;
+
+ const uint32_t ifindex = 100;
+ const uint64_t minQuota = 0;
+ const uint64_t maxQuota = std::numeric_limits<int64_t>::max();
+ const uint64_t infinityQuota = std::numeric_limits<uint64_t>::max();
+
+ // Create a stats entry with zeroes in the first time set limit.
+ ASSERT_EQ(0, mTetherCtrl.setTetherOffloadInterfaceQuota(ifindex, minQuota));
+ const StatusOr<TetherOffloadStatsList> result = mTetherCtrl.getTetherOffloadStats();
+ ASSERT_OK(result);
+ const TetherOffloadStatsList& actual = result.value();
+ ASSERT_EQ(1U, actual.size());
+ EXPECT_THAT(actual, Contains(TetherOffloadStats{ifindex, 0, 0, 0, 0})) << toString(actual);
+
+ // Verify the quota with the boundary {min, max, infinity}.
+ const uint64_t rxBytes = 1000;
+ const uint64_t txBytes = 2000;
+ updateMaps(ifindex, rxBytes, 0 /*unused*/, txBytes, 0 /*unused*/);
+
+ for (const uint64_t quota : {minQuota, maxQuota, infinityQuota}) {
+ ASSERT_EQ(0, mTetherCtrl.setTetherOffloadInterfaceQuota(ifindex, quota));
+ base::Result<uint64_t> result = mFakeTetherLimitMap.readValue(ifindex);
+ ASSERT_RESULT_OK(result);
+
+ const uint64_t expectedQuota =
+ (quota == infinityQuota) ? infinityQuota : quota + rxBytes + txBytes;
+ EXPECT_EQ(expectedQuota, result.value());
+ }
+
+ // The valid range of interface index is 1..max_int64.
+ const uint32_t invalidIfindex = 0;
+ int ret = mTetherCtrl.setTetherOffloadInterfaceQuota(invalidIfindex /*bad*/, infinityQuota);
+ ASSERT_EQ(-ENODEV, ret);
+
+ // The valid range of quota is 0..max_int64 or -1 (unlimited).
+ const uint64_t invalidQuota = std::numeric_limits<int64_t>::min();
+ ret = mTetherCtrl.setTetherOffloadInterfaceQuota(ifindex, invalidQuota /*bad*/);
+ ASSERT_EQ(-ERANGE, ret);
+}
+
} // namespace net
} // namespace android
diff --git a/server/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl b/server/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl
index 5bb3bcf..bacad9d 100644
--- a/server/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl
+++ b/server/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl
@@ -120,6 +120,7 @@
void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
android.net.TetherStatsParcel[] tetherOffloadGetStats();
+ void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
const int IPV4 = 4;
const int IPV6 = 6;
const int CONF = 1;
diff --git a/server/binder/android/net/INetd.aidl b/server/binder/android/net/INetd.aidl
index 2377a28..e211f55 100644
--- a/server/binder/android/net/INetd.aidl
+++ b/server/binder/android/net/INetd.aidl
@@ -1280,4 +1280,15 @@
* cause of the failure.
*/
TetherStatsParcel[] tetherOffloadGetStats();
+
+ /**
+ * Set a per-interface quota for tethering offload.
+ *
+ * @param ifIndex Index of upstream interface
+ * @param quotaBytes The quota defined as the number of bytes, starting from zero and counting
+ * from *now*. A value of QUOTA_UNLIMITED (-1) indicates there is no limit.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
}