ClatUtils - implement tcQdisc(Add|Replace|Del)DevClsact()

Test: atest netd_unit_test
Bug: 65674744
Signed-off-by: Maciej Żenczykowski <maze@google.com>
Change-Id: Ie71edf4b5ae60d161546073a7f2c28f7af7bcc39
diff --git a/server/ClatUtils.cpp b/server/ClatUtils.cpp
index 7936c7d..f282ab0 100644
--- a/server/ClatUtils.cpp
+++ b/server/ClatUtils.cpp
@@ -19,6 +19,8 @@
 #include <errno.h>
 #include <linux/if.h>
 #include <linux/netlink.h>
+#include <linux/pkt_sched.h>
+#include <linux/rtnetlink.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
 #include <sys/types.h>
@@ -137,5 +139,70 @@
     return resp.e.error;  // returns 0 on success
 }
 
+// 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 fd, int ifIndex, __u16 nlMsgType, __u16 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"
+    static const char clsact[] = CLSACT;
+    // sizeof() includes the terminating NULL
+#define 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 ASCIIZ_LEN_CLSACT
+#undef CLSACT
+
+    const int rv = send(fd, &req, sizeof(req), 0);
+    if (rv == -1) return -errno;
+    if (rv != sizeof(req)) return -EMSGSIZE;
+
+    return processNetlinkResponse(fd);
+}
+
+int tcQdiscAddDevClsact(int fd, int ifIndex) {
+    return doTcQdiscClsact(fd, ifIndex, RTM_NEWQDISC, NLM_F_EXCL | NLM_F_CREATE);
+}
+
+int tcQdiscReplaceDevClsact(int fd, int ifIndex) {
+    return doTcQdiscClsact(fd, ifIndex, RTM_NEWQDISC, NLM_F_CREATE | NLM_F_REPLACE);
+}
+
+int tcQdiscDelDevClsact(int fd, int ifIndex) {
+    return doTcQdiscClsact(fd, ifIndex, RTM_DELQDISC, 0);
+}
+
 }  // namespace net
 }  // namespace android
diff --git a/server/ClatUtils.h b/server/ClatUtils.h
index 2788cb5..6c3681f 100644
--- a/server/ClatUtils.h
+++ b/server/ClatUtils.h
@@ -32,6 +32,10 @@
 
 int processNetlinkResponse(int fd);
 
+int tcQdiscAddDevClsact(int fd, int ifIndex);
+int tcQdiscReplaceDevClsact(int fd, int ifIndex);
+int tcQdiscDelDevClsact(int fd, int ifIndex);
+
 }  // namespace net
 }  // namespace android
 
diff --git a/server/ClatUtilsTest.cpp b/server/ClatUtilsTest.cpp
index 7fb0d5d..8c21528 100644
--- a/server/ClatUtilsTest.cpp
+++ b/server/ClatUtilsTest.cpp
@@ -93,5 +93,24 @@
     close(fd);
 }
 
+// See Linux kernel source in include/net/flow.h
+#define LOOPBACK_IFINDEX 1
+
+TEST_F(ClatUtilsTest, AttachReplaceDetachClsactLo) {
+    // Technically does not depend on ebpf, but does depend on clsact,
+    // and we do not really care if it works on pre-4.9-Q anyway.
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    int fd = openNetlinkSocket();
+    ASSERT_LE(3, fd);
+
+    // 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, tcQdiscAddDevClsact(fd, LOOPBACK_IFINDEX));
+    EXPECT_EQ(0, tcQdiscReplaceDevClsact(fd, LOOPBACK_IFINDEX));
+    EXPECT_EQ(0, tcQdiscDelDevClsact(fd, LOOPBACK_IFINDEX));
+    close(fd);
+}
+
 }  // namespace net
 }  // namespace android