implement tcFilterAddDevEgressBpf and add tests

We also take this opportunity to fix an unpaired #undef
and to make the tests use both rawip and ether programs
(even though it's not technically correct on lo).

Test: compiles, atest
Bug: 139396664
Signed-off-by: Maciej Żenczykowski <maze@google.com>
Change-Id: I239dd314dcafcc93b4c63f7a949e963c813e81c8
diff --git a/server/ClatUtils.cpp b/server/ClatUtils.cpp
index a05e3f8..051782f 100644
--- a/server/ClatUtils.cpp
+++ b/server/ClatUtils.cpp
@@ -155,7 +155,7 @@
 // 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) {
+static 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.
@@ -217,11 +217,13 @@
     return doTcQdiscClsact(fd, ifIndex, RTM_DELQDISC, 0);
 }
 
-// tc filter add dev .. ingress prio 1 protocol ipv6 bpf object-pinned /sys/fs/bpf/... direct-action
-int tcFilterAddDevIngressBpf(int fd, int ifIndex, int bpfFd, bool ethernet) {
+// tc filter add dev .. in/egress prio 1 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
+// direct-action
+static int tcFilterAddDevBpf(int fd, int ifIndex, int bpfFd, bool ethernet, bool ingress,
+                             bool ipv6) {
     // The priority doesn't matter until we actually start attaching multiple
-    // things to the same interface's ingress point.
-    const int prio = 1;
+    // things to the same interface's in/egress point.
+    const __u32 prio = 1;
 
     // This is the name of the filter we're attaching (ie. this is the 'bpf'
     // packet classifier enabled by kernel config option CONFIG_NET_CLS_BPF.
@@ -240,27 +242,50 @@
 
     // This macro expands (from header files) to:
     //   prog_clatd_schedcls_ingress_clat_rawip:[*fsobj]
-    // and is the name of the pinned ebpf program for ARPHRD_RAWIP interfaces.
+    // and is the name of the pinned ingress ebpf program for ARPHRD_RAWIP interfaces.
     // (also compatible with anything that has 0 size L2 header)
-#define NAME_RAWIP CLAT_INGRESS_PROG_RAWIP_NAME FSOBJ_SUFFIX
-    const char name_rawip[] = NAME_RAWIP;
+#define NAME_RX_RAWIP CLAT_INGRESS_PROG_RAWIP_NAME FSOBJ_SUFFIX
+    const char name_rx_rawip[] = NAME_RX_RAWIP;
 
     // This macro expands (from header files) to:
     //   prog_clatd_schedcls_ingress_clat_ether:[*fsobj]
-    // and is the name of the pinned ebpf program for ARPHRD_ETHER interfaces.
+    // and is the name of the pinned ingress ebpf program for ARPHRD_ETHER interfaces.
     // (also compatible with anything that has standard ethernet header)
-#define NAME_ETHER CLAT_INGRESS_PROG_ETHER_NAME FSOBJ_SUFFIX
-    const char name_ether[] = NAME_ETHER;
+#define NAME_RX_ETHER CLAT_INGRESS_PROG_ETHER_NAME FSOBJ_SUFFIX
+    const char name_rx_ether[] = NAME_RX_ETHER;
 
-    // The actual name we'll use is determined at run time via 'ethernet'
-    // boolean.  We need to compile time allocate enough space in the struct
+    // This macro expands (from header files) to:
+    //   prog_clatd_schedcls_egress_clat_rawip:[*fsobj]
+    // and is the name of the pinned egress ebpf program for ARPHRD_RAWIP interfaces.
+    // (also compatible with anything that has 0 size L2 header)
+#define NAME_TX_RAWIP CLAT_EGRESS_PROG_RAWIP_NAME FSOBJ_SUFFIX
+    const char name_tx_rawip[] = NAME_TX_RAWIP;
+
+    // This macro expands (from header files) to:
+    //   prog_clatd_schedcls_egress_clat_ether:[*fsobj]
+    // and is the name of the pinned egress ebpf program for ARPHRD_ETHER interfaces.
+    // (also compatible with anything that has standard ethernet header)
+#define NAME_TX_ETHER CLAT_EGRESS_PROG_ETHER_NAME FSOBJ_SUFFIX
+    const char name_tx_ether[] = NAME_TX_ETHER;
+
+    // The actual name we'll use is determined at run time via 'ethernet' and 'ingress'
+    // booleans.  We need to compile time allocate enough space in the struct
     // hence this macro magic to make sure we have enough space for either
-    // possibility.  In practice both are actually the same size.
-#define ASCIIZ_MAXLEN_NAME \
-    ((sizeof(name_rawip) > sizeof(name_ether)) ? sizeof(name_rawip) : sizeof(name_ether))
+    // possibility.  In practice some of these are actually the same size.
+#define ASCIIZ_MAXLEN_NAME_RX                                                \
+    ((sizeof(name_rx_rawip) > sizeof(name_rx_ether)) ? sizeof(name_rx_rawip) \
+                                                     : sizeof(name_rx_ether))
+#define ASCIIZ_MAXLEN_NAME_TX                                                \
+    ((sizeof(name_tx_rawip) > sizeof(name_tx_ether)) ? sizeof(name_tx_rawip) \
+                                                     : sizeof(name_tx_ether))
+#define ASCIIZ_MAXLEN_NAME                                                   \
+    ((ASCIIZ_MAXLEN_NAME_RX > ASCIIZ_MAXLEN_NAME_TX) ? ASCIIZ_MAXLEN_NAME_RX \
+                                                     : ASCIIZ_MAXLEN_NAME_TX)
 
-    // This is not a compile time constant and is used in strcpy below
-#define NAME (ethernet ? NAME_ETHER : NAME_RAWIP)
+    // These are not compile time constants: NAME is used in strncpy below
+#define NAME_RX (ethernet ? NAME_RX_ETHER : NAME_RX_RAWIP)
+#define NAME_TX (ethernet ? NAME_TX_ETHER : NAME_TX_RAWIP)
+#define NAME (ingress ? NAME_RX : NAME_TX)
 
     struct {
         nlmsghdr n;
@@ -296,8 +321,10 @@
                             .tcm_family = AF_UNSPEC,
                             .tcm_ifindex = ifIndex,
                             .tcm_handle = TC_H_UNSPEC,
-                            .tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS),
-                            .tcm_info = (prio << 16) | htons(ETH_P_IPV6),
+                            .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
+                                                    ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
+                            .tcm_info = (prio << 16) |
+                                        (__u32)(ipv6 ? htons(ETH_P_IPV6) : htons(ETH_P_IP)),
                     },
             .kind =
                     {
@@ -332,7 +359,7 @@
                                                             .nla_type = TCA_BPF_NAME,
                                                     },
                                             // Visible via 'tc filter show', but
-                                            // is overwritten by strcpy below
+                                            // is overwritten by strncpy below
                                             .str = "placeholder",
                                     },
                             .flags =
@@ -350,10 +377,16 @@
     strncpy(req.options.name.str, NAME, sizeof(req.options.name.str));
 
 #undef NAME
+#undef NAME_TX
+#undef NAME_RX
 #undef ASCIIZ_MAXLEN_NAME
-#undef NAME_ETHER
-#undef NAME_RAWIP
-#undef NAME
+#undef ASCIIZ_MAXLEN_NAME_TX
+#undef ASCIIZ_MAXLEN_NAME_RX
+#undef NAME_TX_ETHER
+#undef NAME_TX_RAWIP
+#undef NAME_RX_ETHER
+#undef NAME_RX_RAWIP
+#undef FSOBJ_SUFFIX
 #undef ASCIIZ_LEN_BPF
 #undef BPF
 
@@ -364,5 +397,15 @@
     return processNetlinkResponse(fd);
 }
 
+// tc filter add dev .. ingress prio 1 protocol ipv6 bpf object-pinned /sys/fs/bpf/... direct-action
+int tcFilterAddDevIngressBpf(int fd, int ifIndex, int bpfFd, bool ethernet) {
+    return tcFilterAddDevBpf(fd, ifIndex, bpfFd, ethernet, /*ingress*/ true, /*ipv6*/ true);
+}
+
+// tc filter add dev .. egress prio 1 protocol ip bpf object-pinned /sys/fs/bpf/... direct-action
+int tcFilterAddDevEgressBpf(int fd, int ifIndex, int bpfFd, bool ethernet) {
+    return tcFilterAddDevBpf(fd, ifIndex, bpfFd, ethernet, /*ingress*/ false, /*ipv6*/ false);
+}
+
 }  // namespace net
 }  // namespace android