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);
 }