Add support to attach / replace / detach clsact qdisc to libtcutils

Bug: 202086915
Bug: 157552970
Test: atest LibTcUtilsTest
Change-Id: I2f2e2f66acb7590e8951ac6e178c5893ba635597
diff --git a/staticlibs/native/tcutils/include/tcutils/tcutils.h b/staticlibs/native/tcutils/include/tcutils/tcutils.h
index d1e1bb7..0dbde34 100644
--- a/staticlibs/native/tcutils/include/tcutils/tcutils.h
+++ b/staticlibs/native/tcutils/include/tcutils/tcutils.h
@@ -17,10 +17,26 @@
 #pragma once
 
 #include <cstdint>
+#include <linux/rtnetlink.h>
 
 namespace android {
 
 int isEthernet(const char *iface, bool &isEthernet);
+
+int doTcQdiscClsact(int ifIndex, uint16_t nlMsgType, uint16_t nlMsgFlags);
+
+static inline int tcAddQdiscClsact(int ifIndex) {
+  return doTcQdiscClsact(ifIndex, RTM_NEWQDISC, NLM_F_EXCL | NLM_F_CREATE);
+}
+
+static inline int tcReplaceQdiscClsact(int ifIndex) {
+  return doTcQdiscClsact(ifIndex, RTM_NEWQDISC, NLM_F_CREATE | NLM_F_REPLACE);
+}
+
+static inline int tcDeleteQdiscClsact(int ifIndex) {
+  return doTcQdiscClsact(ifIndex, RTM_DELQDISC, 0);
+}
+
 int tcAddBpfFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto,
                    const char *bpfProgPath);
 int tcDeleteFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto);
diff --git a/staticlibs/native/tcutils/tcutils.cpp b/staticlibs/native/tcutils/tcutils.cpp
index 3f24178..9b8c843 100644
--- a/staticlibs/native/tcutils/tcutils.cpp
+++ b/staticlibs/native/tcutils/tcutils.cpp
@@ -212,6 +212,54 @@
   }
 }
 
+// ADD:     nlMsgType=RTM_NEWQDISC nlMsgFlags=NLM_F_EXCL|NLM_F_CREATE
+// REPLACE: nlMsgType=RTM_NEWQDISC nlMsgFlags=NLM_F_CREATE|NLM_F_REPLACE
+// DEL:     nlMsgType=RTM_DELQDISC nlMsgFlags=0
+int doTcQdiscClsact(int ifIndex, uint16_t nlMsgType, uint16_t nlMsgFlags) {
+  // This is the name of the qdisc we are attaching.
+  // Some hoop jumping to make this compile time constant with known size,
+  // so that the structure declaration is well defined at compile time.
+#define CLSACT "clsact"
+  // sizeof() includes the terminating NULL
+  static constexpr size_t ASCIIZ_LEN_CLSACT = sizeof(CLSACT);
+
+  const struct {
+    nlmsghdr n;
+    tcmsg t;
+    struct {
+      nlattr attr;
+      char str[NLMSG_ALIGN(ASCIIZ_LEN_CLSACT)];
+    } kind;
+  } req = {
+      .n =
+          {
+              .nlmsg_len = sizeof(req),
+              .nlmsg_type = nlMsgType,
+              .nlmsg_flags =
+                  static_cast<__u16>(NETLINK_REQUEST_FLAGS | nlMsgFlags),
+          },
+      .t =
+          {
+              .tcm_family = AF_UNSPEC,
+              .tcm_ifindex = ifIndex,
+              .tcm_handle = TC_H_MAKE(TC_H_CLSACT, 0),
+              .tcm_parent = TC_H_CLSACT,
+          },
+      .kind =
+          {
+              .attr =
+                  {
+                      .nla_len = NLA_HDRLEN + ASCIIZ_LEN_CLSACT,
+                      .nla_type = TCA_KIND,
+                  },
+              .str = CLSACT,
+          },
+  };
+#undef CLSACT
+
+  return sendAndProcessNetlinkResponse(&req, sizeof(req));
+}
+
 // tc filter add dev .. in/egress prio 1 protocol ipv6/ip bpf object-pinned
 // /sys/fs/bpf/... direct-action
 int tcAddBpfFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto,
diff --git a/staticlibs/native/tcutils/tests/tcutils_test.cpp b/staticlibs/native/tcutils/tests/tcutils_test.cpp
index bab2d3e..6f28fbe 100644
--- a/staticlibs/native/tcutils/tests/tcutils_test.cpp
+++ b/staticlibs/native/tcutils/tests/tcutils_test.cpp
@@ -63,4 +63,16 @@
   ASSERT_FALSE(result);
 }
 
+// See Linux kernel source in include/net/flow.h
+static constexpr int LOOPBACK_IFINDEX = 1;
+
+TEST(LibTcUtilsTest, AttachReplaceDetachClsactLo) {
+  // This attaches and detaches a configuration-less and thus no-op clsact
+  // qdisc to loopback interface (and it takes fractions of a second)
+  EXPECT_EQ(0, tcAddQdiscClsact(LOOPBACK_IFINDEX));
+  EXPECT_EQ(0, tcReplaceQdiscClsact(LOOPBACK_IFINDEX));
+  EXPECT_EQ(0, tcDeleteQdiscClsact(LOOPBACK_IFINDEX));
+  EXPECT_EQ(-EINVAL, tcDeleteQdiscClsact(LOOPBACK_IFINDEX));
+}
+
 } // namespace android