Merge "Update case to correctly support usage for T"
diff --git a/staticlibs/device/com/android/net/module/util/TcUtils.java b/staticlibs/device/com/android/net/module/util/TcUtils.java
index 14724d4..5b78bc1 100644
--- a/staticlibs/device/com/android/net/module/util/TcUtils.java
+++ b/staticlibs/device/com/android/net/module/util/TcUtils.java
@@ -49,6 +49,30 @@
short proto, String bpfProgPath) throws IOException;
/**
+ * Attach a tc police action.
+ *
+ * Attaches a matchall filter to the clsact qdisc with a tc police and tc bpf action attached.
+ * This causes the ingress rate to be limited and exceeding packets to be forwarded to a bpf
+ * program (specified in bpfProgPah) that accounts for the packets before dropping them.
+ *
+ * Equivalent to the following 'tc' command:
+ * tc filter add dev .. ingress prio .. protocol .. matchall \
+ * action police rate .. burst .. conform-exceed pipe/continue \
+ * action bpf object-pinned .. \
+ * drop
+ *
+ * @param ifIndex the network interface index.
+ * @param prio the filter preference.
+ * @param proto protocol.
+ * @param rateInBytesPerSec rate limit in bytes/s.
+ * @param bpfProgPath bpg program that accounts for rate exceeding packets before they are
+ * dropped.
+ * @throws IOException
+ */
+ public static native void tcFilterAddDevIngressPolice(int ifIndex, short prio, short proto,
+ int rateInBytesPerSec, String bpfProgPath) throws IOException;
+
+ /**
* Delete a tc filter.
*
* Equivalent to the following 'tc' command:
diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
index e5a3668..2307a6b 100644
--- a/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
@@ -54,6 +54,24 @@
}
}
+// tc filter add dev .. ingress prio .. protocol .. matchall \
+// action police rate .. burst .. conform-exceed pipe/continue \
+// action bpf object-pinned .. \
+// drop
+static void com_android_net_module_util_TcUtils_tcFilterAddDevIngressPolice(
+ JNIEnv *env, jobject clazz, jint ifIndex, jshort prio, jshort proto,
+ jint rateInBytesPerSec, jstring bpfProgPath) {
+ ScopedUtfChars pathname(env, bpfProgPath);
+ int error = tcAddIngressPoliceFilter(ifIndex, prio, proto, rateInBytesPerSec,
+ pathname.c_str());
+ if (error) {
+ throwIOException(env,
+ "com_android_net_module_util_TcUtils_"
+ "tcFilterAddDevIngressPolice error: ",
+ error);
+ }
+}
+
// tc filter del dev .. in/egress prio .. protocol ..
static void com_android_net_module_util_TcUtils_tcFilterDelDev(
JNIEnv *env, jobject clazz, jint ifIndex, jboolean ingress, jshort prio,
@@ -75,6 +93,8 @@
(void *)com_android_net_module_util_TcUtils_isEthernet},
{"tcFilterAddDevBpf", "(IZSSLjava/lang/String;)V",
(void *)com_android_net_module_util_TcUtils_tcFilterAddDevBpf},
+ {"tcFilterAddDevIngressPolice", "(ISSILjava/lang/String;)V",
+ (void *)com_android_net_module_util_TcUtils_tcFilterAddDevIngressPolice},
{"tcFilterDelDev", "(IZSS)V",
(void *)com_android_net_module_util_TcUtils_tcFilterDelDev},
};
diff --git a/staticlibs/native/nettestutils/Android.bp b/staticlibs/native/nettestutils/Android.bp
new file mode 100644
index 0000000..4fa367d
--- /dev/null
+++ b/staticlibs/native/nettestutils/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library_static {
+ name: "libnettestutils",
+ export_include_dirs: ["include"],
+ srcs: ["DumpService.cpp"],
+
+ shared_libs: [
+ "libbinder",
+ "libutils",
+ ],
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+}
diff --git a/staticlibs/native/nettestutils/DumpService.cpp b/staticlibs/native/nettestutils/DumpService.cpp
new file mode 100644
index 0000000..ba3d77e
--- /dev/null
+++ b/staticlibs/native/nettestutils/DumpService.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "nettestutils/DumpService.h"
+
+#include <android-base/file.h>
+
+#include <sstream>
+#include <thread>
+
+android::status_t dumpService(const android::sp<android::IBinder>& binder,
+ const std::vector<std::string>& args,
+ std::vector<std::string>& outputLines) {
+ if (!outputLines.empty()) return -EUCLEAN;
+
+ android::base::unique_fd localFd, remoteFd;
+ if (!Pipe(&localFd, &remoteFd)) return -errno;
+
+ android::Vector<android::String16> str16Args;
+ for (const auto& arg : args) {
+ str16Args.push(android::String16(arg.c_str()));
+ }
+ android::status_t ret;
+ // dump() blocks until another thread has consumed all its output.
+ std::thread dumpThread =
+ std::thread([&ret, binder, remoteFd{std::move(remoteFd)}, str16Args]() {
+ ret = binder->dump(remoteFd, str16Args);
+ });
+
+ std::string dumpContent;
+ if (!android::base::ReadFdToString(localFd.get(), &dumpContent)) {
+ return -errno;
+ }
+ dumpThread.join();
+ if (ret != android::OK) return ret;
+
+ std::stringstream dumpStream(std::move(dumpContent));
+ std::string line;
+ while (std::getline(dumpStream, line)) {
+ outputLines.push_back(line);
+ }
+
+ return android::OK;
+}
diff --git a/staticlibs/native/nettestutils/include/nettestutils/DumpService.h b/staticlibs/native/nettestutils/include/nettestutils/DumpService.h
new file mode 100644
index 0000000..2a72181
--- /dev/null
+++ b/staticlibs/native/nettestutils/include/nettestutils/DumpService.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <binder/Binder.h>
+
+#include <vector>
+
+android::status_t dumpService(const android::sp<android::IBinder>& binder,
+ const std::vector<std::string>& args,
+ std::vector<std::string>& outputLines);
diff --git a/staticlibs/native/tcutils/Android.bp b/staticlibs/native/tcutils/Android.bp
index c26fca6..e819e4c 100644
--- a/staticlibs/native/tcutils/Android.bp
+++ b/staticlibs/native/tcutils/Android.bp
@@ -42,3 +42,27 @@
"//system/netd/server",
],
}
+
+cc_test {
+ name: "libtcutils_test",
+ srcs: [
+ "tests/tcutils_test.cpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-error=unused-variable",
+ ],
+ header_libs: ["bpf_syscall_wrappers"],
+ static_libs: [
+ "libgmock",
+ "libtcutils",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ ],
+ min_sdk_version: "30",
+ require_root: true,
+ test_suites: ["general-tests"],
+}
diff --git a/staticlibs/native/tcutils/include/tcutils/tcutils.h b/staticlibs/native/tcutils/include/tcutils/tcutils.h
index d1e1bb7..a8ec2e8 100644
--- a/staticlibs/native/tcutils/include/tcutils/tcutils.h
+++ b/staticlibs/native/tcutils/include/tcutils/tcutils.h
@@ -17,12 +17,31 @@
#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 tcAddIngressPoliceFilter(int ifIndex, uint16_t prio, uint16_t proto,
+ unsigned rateInBytesPerSec,
+ const char *bpfProgPath);
int tcDeleteFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto);
} // namespace android
diff --git a/staticlibs/native/tcutils/kernelversion.h b/staticlibs/native/tcutils/kernelversion.h
index 59b9e05..3be1ad2 100644
--- a/staticlibs/native/tcutils/kernelversion.h
+++ b/staticlibs/native/tcutils/kernelversion.h
@@ -23,6 +23,8 @@
// In the mean time copying verbatim from:
// frameworks/libs/net/common/native/bpf_headers
+#pragma once
+
#include <stdio.h>
#include <sys/utsname.h>
@@ -30,7 +32,7 @@
namespace android {
-unsigned kernelVersion() {
+static inline unsigned kernelVersion() {
struct utsname buf;
int ret = uname(&buf);
if (ret)
@@ -49,8 +51,9 @@
return KVER(kver_major, kver_minor, kver_sub);
}
-bool isAtLeastKernelVersion(unsigned major, unsigned minor, unsigned sub) {
+static inline bool isAtLeastKernelVersion(unsigned major, unsigned minor,
+ unsigned sub) {
return kernelVersion() >= KVER(major, minor, sub);
}
-}
+} // namespace android
diff --git a/staticlibs/native/tcutils/logging.h b/staticlibs/native/tcutils/logging.h
new file mode 100644
index 0000000..70604b3
--- /dev/null
+++ b/staticlibs/native/tcutils/logging.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/log.h>
+#include <stdarg.h>
+
+#ifndef LOG_TAG
+#define LOG_TAG "TcUtils_Undef"
+#endif
+
+namespace android {
+
+static inline void ALOGE(const char *fmt...) {
+ va_list args;
+ va_start(args, fmt);
+ __android_log_vprint(ANDROID_LOG_ERROR, LOG_TAG, fmt, args);
+ va_end(args);
+}
+
+}
diff --git a/staticlibs/native/tcutils/tcutils.cpp b/staticlibs/native/tcutils/tcutils.cpp
index e30a500..0e17f67 100644
--- a/staticlibs/native/tcutils/tcutils.cpp
+++ b/staticlibs/native/tcutils/tcutils.cpp
@@ -18,10 +18,10 @@
#include "tcutils/tcutils.h"
+#include "logging.h"
#include "kernelversion.h"
#include "scopeguard.h"
-#include <android/log.h>
#include <arpa/inet.h>
#include <cerrno>
#include <cstring>
@@ -32,8 +32,9 @@
#include <linux/pkt_cls.h>
#include <linux/pkt_sched.h>
#include <linux/rtnetlink.h>
+#include <linux/tc_act/tc_bpf.h>
#include <net/if.h>
-#include <stdarg.h>
+#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <utility>
@@ -51,12 +52,318 @@
namespace android {
namespace {
-void logError(const char *fmt...) {
- va_list args;
- va_start(args, fmt);
- __android_log_vprint(ANDROID_LOG_ERROR, LOG_TAG, fmt, args);
- va_end(args);
-}
+/**
+ * IngressPoliceFilterBuilder builds a nlmsg request equivalent to the following
+ * tc command:
+ *
+ * tc filter add dev .. ingress prio .. protocol .. matchall \
+ * action police rate .. burst .. conform-exceed pipe/continue \
+ * action bpf object-pinned .. \
+ * drop
+ */
+class IngressPoliceFilterBuilder final {
+ // default mtu is 2047, so the cell logarithm factor (cell_log) is 3.
+ // 0x7FF >> 0x3FF x 2^1 >> 0x1FF x 2^2 >> 0xFF x 2^3
+ static constexpr int RTAB_CELL_LOGARITHM = 3;
+ static constexpr size_t RTAB_SIZE = 256;
+ static constexpr unsigned TIME_UNITS_PER_SEC = 1000000;
+
+ struct Request {
+ nlmsghdr n;
+ tcmsg t;
+ struct {
+ nlattr attr;
+ char str[NLMSG_ALIGN(sizeof("matchall"))];
+ } kind;
+ struct {
+ nlattr attr;
+ struct {
+ nlattr attr;
+ struct {
+ nlattr attr;
+ struct {
+ nlattr attr;
+ char str[NLMSG_ALIGN(sizeof("police"))];
+ } kind;
+ struct {
+ nlattr attr;
+ struct {
+ nlattr attr;
+ struct tc_police obj;
+ } police;
+ struct {
+ nlattr attr;
+ uint32_t u32[RTAB_SIZE];
+ } rtab;
+ struct {
+ nlattr attr;
+ int32_t s32;
+ } notexceedact;
+ } opt;
+ } act1;
+ struct {
+ nlattr attr;
+ struct {
+ nlattr attr;
+ char str[NLMSG_ALIGN(sizeof("bpf"))];
+ } kind;
+ struct {
+ nlattr attr;
+ struct {
+ nlattr attr;
+ uint32_t u32;
+ } fd;
+ struct {
+ nlattr attr;
+ char str[NLMSG_ALIGN(CLS_BPF_NAME_LEN)];
+ } name;
+ struct {
+ nlattr attr;
+ struct tc_act_bpf obj;
+ } parms;
+ } opt;
+ } act2;
+ } acts;
+ } opt;
+ };
+
+ // class members
+ const unsigned mBurstInBytes;
+ const char *mBpfProgPath;
+ int mBpfFd;
+ Request mRequest;
+
+ static double getTickInUsec() {
+ FILE *fp = fopen("/proc/net/psched", "re");
+ if (!fp) {
+ ALOGE("fopen(\"/proc/net/psched\"): %s", strerror(errno));
+ return 0.0;
+ }
+ auto scopeGuard = base::make_scope_guard([fp] { fclose(fp); });
+
+ uint32_t t2us;
+ uint32_t us2t;
+ uint32_t clockRes;
+ const bool isError =
+ fscanf(fp, "%08x%08x%08x", &t2us, &us2t, &clockRes) != 3;
+
+ if (isError) {
+ ALOGE("fscanf(/proc/net/psched, \"%%08x%%08x%%08x\"): %s",
+ strerror(errno));
+ return 0.0;
+ }
+
+ const double clockFactor =
+ static_cast<double>(clockRes) / TIME_UNITS_PER_SEC;
+ return static_cast<double>(t2us) / static_cast<double>(us2t) * clockFactor;
+ }
+
+ static inline const double kTickInUsec = getTickInUsec();
+
+public:
+ // clang-format off
+ IngressPoliceFilterBuilder(int ifIndex, uint16_t prio, uint16_t proto, unsigned rateInBytesPerSec,
+ unsigned burstInBytes, const char* bpfProgPath)
+ : mBurstInBytes(burstInBytes),
+ mBpfProgPath(bpfProgPath),
+ mBpfFd(-1),
+ mRequest{
+ .n = {
+ .nlmsg_len = sizeof(mRequest),
+ .nlmsg_type = RTM_NEWTFILTER,
+ .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE,
+ },
+ .t = {
+ .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 = (static_cast<uint32_t>(prio) << 16)
+ | static_cast<uint32_t>(htons(proto)),
+ },
+ .kind = {
+ .attr = {
+ .nla_len = sizeof(mRequest.kind),
+ .nla_type = TCA_KIND,
+ },
+ .str = "matchall",
+ },
+ .opt = {
+ .attr = {
+ .nla_len = sizeof(mRequest.opt),
+ .nla_type = TCA_OPTIONS,
+ },
+ .acts = {
+ .attr = {
+ .nla_len = sizeof(mRequest.opt.acts),
+ .nla_type = TCA_U32_ACT,
+ },
+ .act1 = {
+ .attr = {
+ .nla_len = sizeof(mRequest.opt.acts.act1),
+ .nla_type = 1, // action priority
+ },
+ .kind = {
+ .attr = {
+ .nla_len = sizeof(mRequest.opt.acts.act1.kind),
+ .nla_type = TCA_ACT_KIND,
+ },
+ .str = "police",
+ },
+ .opt = {
+ .attr = {
+ .nla_len = sizeof(mRequest.opt.acts.act1.opt),
+ .nla_type = TCA_ACT_OPTIONS | NLA_F_NESTED,
+ },
+ .police = {
+ .attr = {
+ .nla_len = sizeof(mRequest.opt.acts.act1.opt.police),
+ .nla_type = TCA_POLICE_TBF,
+ },
+ .obj = {
+ .action = TC_ACT_PIPE,
+ .burst = 0,
+ .rate = {
+ .cell_log = RTAB_CELL_LOGARITHM,
+ .linklayer = TC_LINKLAYER_ETHERNET,
+ .cell_align = -1,
+ .rate = rateInBytesPerSec,
+ },
+ },
+ },
+ .rtab = {
+ .attr = {
+ .nla_len = sizeof(mRequest.opt.acts.act1.opt.rtab),
+ .nla_type = TCA_POLICE_RATE,
+ },
+ .u32 = {},
+ },
+ .notexceedact = {
+ .attr = {
+ .nla_len = sizeof(mRequest.opt.acts.act1.opt.notexceedact),
+ .nla_type = TCA_POLICE_RESULT,
+ },
+ .s32 = TC_ACT_UNSPEC,
+ },
+ },
+ },
+ .act2 = {
+ .attr = {
+ .nla_len = sizeof(mRequest.opt.acts.act2),
+ .nla_type = 2, // action priority
+ },
+ .kind = {
+ .attr = {
+ .nla_len = sizeof(mRequest.opt.acts.act2.kind),
+ .nla_type = TCA_ACT_KIND,
+ },
+ .str = "bpf",
+ },
+ .opt = {
+ .attr = {
+ .nla_len = sizeof(mRequest.opt.acts.act2.opt),
+ .nla_type = TCA_ACT_OPTIONS | NLA_F_NESTED,
+ },
+ .fd = {
+ .attr = {
+ .nla_len = sizeof(mRequest.opt.acts.act2.opt.fd),
+ .nla_type = TCA_ACT_BPF_FD,
+ },
+ .u32 = 0, // set during build()
+ },
+ .name = {
+ .attr = {
+ .nla_len = sizeof(mRequest.opt.acts.act2.opt.name),
+ .nla_type = TCA_ACT_BPF_NAME,
+ },
+ .str = "placeholder",
+ },
+ .parms = {
+ .attr = {
+ .nla_len = sizeof(mRequest.opt.acts.act2.opt.parms),
+ .nla_type = TCA_ACT_BPF_PARMS,
+ },
+ .obj = {
+ // default action to be executed when bpf prog
+ // returns TC_ACT_UNSPEC.
+ .action = TC_ACT_SHOT,
+ },
+ },
+ },
+ },
+ },
+ },
+ } {
+ // constructor body
+ }
+ // clang-format on
+
+ ~IngressPoliceFilterBuilder() {
+ // TODO: use unique_fd
+ if (mBpfFd != -1) {
+ close(mBpfFd);
+ }
+ }
+
+ constexpr unsigned getRequestSize() const { return sizeof(Request); }
+
+private:
+ unsigned calculateXmitTime(unsigned size) {
+ const uint32_t rate = mRequest.opt.acts.act1.opt.police.obj.rate.rate;
+ return (static_cast<double>(size) / static_cast<double>(rate)) *
+ TIME_UNITS_PER_SEC * kTickInUsec;
+ }
+
+ void initBurstRate() {
+ mRequest.opt.acts.act1.opt.police.obj.burst =
+ calculateXmitTime(mBurstInBytes);
+ }
+
+ // Calculates a table with 256 transmission times for different packet sizes
+ // (all the way up to MTU). RTAB_CELL_LOGARITHM is used as a scaling factor.
+ // In this case, MTU size is always 2048, so RTAB_CELL_LOGARITHM is always
+ // 3. Therefore, this function generates the transmission times for packets
+ // of size 1..256 x 2^3.
+ void initRateTable() {
+ for (unsigned i = 0; i < RTAB_SIZE; ++i) {
+ unsigned adjustedSize = (i + 1) << RTAB_CELL_LOGARITHM;
+ mRequest.opt.acts.act1.opt.rtab.u32[i] = calculateXmitTime(adjustedSize);
+ }
+ }
+
+ int initBpfFd() {
+ mBpfFd = bpf::retrieveProgram(mBpfProgPath);
+ if (mBpfFd == -1) {
+ int error = errno;
+ ALOGE("retrieveProgram failed: %d", error);
+ return -error;
+ }
+
+ mRequest.opt.acts.act2.opt.fd.u32 = static_cast<uint32_t>(mBpfFd);
+ snprintf(mRequest.opt.acts.act2.opt.name.str,
+ sizeof(mRequest.opt.acts.act2.opt.name.str), "%s:[*fsobj]",
+ basename(mBpfProgPath));
+
+ return 0;
+ }
+
+public:
+ int build() {
+ if (kTickInUsec == 0.0) {
+ return -EINVAL;
+ }
+
+ initBurstRate();
+ initRateTable();
+ return initBpfFd();
+ }
+
+ const Request *getRequest() const {
+ // Make sure to call build() before calling this function. Otherwise, the
+ // request will be invalid.
+ return &mRequest;
+ }
+};
const sockaddr_nl KERNEL_NLADDR = {AF_NETLINK, 0, 0, 0};
const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
@@ -66,7 +373,7 @@
int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
if (fd == -1) {
int error = errno;
- logError("socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE): %d",
+ ALOGE("socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE): %d",
error);
return -error;
}
@@ -75,7 +382,7 @@
static constexpr int on = 1;
if (setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on))) {
int error = errno;
- logError("setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, 1): %d", error);
+ ALOGE("setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, 1): %d", error);
return -error;
}
@@ -83,7 +390,7 @@
if (bind(fd, (const struct sockaddr *)&KERNEL_NLADDR,
sizeof(KERNEL_NLADDR))) {
int error = errno;
- logError("bind(fd, {AF_NETLINK, 0, 0}: %d)", error);
+ ALOGE("bind(fd, {AF_NETLINK, 0, 0}: %d)", error);
return -error;
}
@@ -91,7 +398,7 @@
if (connect(fd, (const struct sockaddr *)&KERNEL_NLADDR,
sizeof(KERNEL_NLADDR))) {
int error = errno;
- logError("connect(fd, {AF_NETLINK, 0, 0}): %d", error);
+ ALOGE("connect(fd, {AF_NETLINK, 0, 0}): %d", error);
return -error;
}
@@ -99,12 +406,12 @@
if (rv == -1) {
int error = errno;
- logError("send(fd, req, len, 0) failed: %d", error);
+ ALOGE("send(fd, req, len, 0) failed: %d", error);
return -error;
}
if (rv != len) {
- logError("send(fd, req, len = %d, 0) returned invalid message size %d", len,
+ ALOGE("send(fd, req, len = %d, 0) returned invalid message size %d", len,
rv);
return -EMSGSIZE;
}
@@ -119,29 +426,29 @@
if (rv == -1) {
int error = errno;
- logError("recv() failed: %d", error);
+ ALOGE("recv() failed: %d", error);
return -error;
}
if (rv < (int)NLMSG_SPACE(sizeof(struct nlmsgerr))) {
- logError("recv() returned short packet: %d", rv);
+ ALOGE("recv() returned short packet: %d", rv);
return -EBADMSG;
}
if (resp.h.nlmsg_len != (unsigned)rv) {
- logError("recv() returned invalid header length: %d != %d",
+ ALOGE("recv() returned invalid header length: %d != %d",
resp.h.nlmsg_len, rv);
return -EBADMSG;
}
if (resp.h.nlmsg_type != NLMSG_ERROR) {
- logError("recv() did not return NLMSG_ERROR message: %d",
+ ALOGE("recv() did not return NLMSG_ERROR message: %d",
resp.h.nlmsg_type);
return -ENOMSG;
}
if (resp.e.error) {
- logError("NLMSG_ERROR message return error: %d", resp.e.error);
+ ALOGE("NLMSG_ERROR message return error: %d", resp.e.error);
}
return resp.e.error; // returns 0 on success
}
@@ -172,9 +479,9 @@
int isEthernet(const char *iface, bool &isEthernet) {
int rv = hardwareAddressType(iface);
if (rv < 0) {
- logError("Get hardware address type of interface %s failed: %s", iface,
+ ALOGE("Get hardware address type of interface %s failed: %s", iface,
strerror(-rv));
- return -rv;
+ return rv;
}
// Backwards compatibility with pre-GKI kernels that use various custom
@@ -206,18 +513,66 @@
isEthernet = false;
return 0;
default:
- logError("Unknown hardware address type %d on interface %s", rv, iface);
- return -ENOENT;
+ ALOGE("Unknown hardware address type %d on interface %s", rv, iface);
+ return -EAFNOSUPPORT;
}
}
+// 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,
const char *bpfProgPath) {
const int bpfFd = bpf::retrieveProgram(bpfProgPath);
if (bpfFd == -1) {
- logError("retrieveProgram failed: %d", errno);
+ ALOGE("retrieveProgram failed: %d", errno);
return -errno;
}
auto scopeGuard = base::make_scope_guard([bpfFd] { close(bpfFd); });
@@ -319,6 +674,37 @@
return error;
}
+// tc filter add dev .. ingress prio .. protocol .. matchall \
+// action police rate .. burst .. conform-exceed pipe/continue \
+// action bpf object-pinned .. \
+// drop
+//
+// TODO: tc-police does not do ECN marking, so in the future, we should consider
+// adding a second tc-police filter at a lower priority that rate limits traffic
+// at something like 0.8 times the global rate limit and ecn marks exceeding
+// packets inside a bpf program (but does not drop them).
+int tcAddIngressPoliceFilter(int ifIndex, uint16_t prio, uint16_t proto,
+ unsigned rateInBytesPerSec,
+ const char *bpfProgPath) {
+ // TODO: this value needs to be validated.
+ // TCP IW10 (initial congestion window) means servers will send 10 mtus worth
+ // of data on initial connect.
+ // If nic is LRO capable it could aggregate up to 64KiB, so again probably a
+ // bad idea to set burst below that, because ingress packets could get
+ // aggregated to 64KiB at the nic.
+ // I don't know, but I wonder whether we shouldn't just do 128KiB and not do
+ // any math.
+ static constexpr unsigned BURST_SIZE_IN_BYTES = 128 * 1024; // 128KiB
+ IngressPoliceFilterBuilder filter(ifIndex, prio, proto, rateInBytesPerSec,
+ BURST_SIZE_IN_BYTES, bpfProgPath);
+ const int error = filter.build();
+ if (error) {
+ return error;
+ }
+ return sendAndProcessNetlinkResponse(filter.getRequest(),
+ filter.getRequestSize());
+}
+
// tc filter del dev .. in/egress prio .. protocol ..
int tcDeleteFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto) {
const struct {
diff --git a/staticlibs/native/tcutils/tests/tcutils_test.cpp b/staticlibs/native/tcutils/tests/tcutils_test.cpp
new file mode 100644
index 0000000..32736d6
--- /dev/null
+++ b/staticlibs/native/tcutils/tests/tcutils_test.cpp
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * TcUtilsTest.cpp - unit tests for TcUtils.cpp
+ */
+
+#include <gtest/gtest.h>
+
+#include "kernelversion.h"
+#include <tcutils/tcutils.h>
+
+#include <BpfSyscallWrappers.h>
+#include <errno.h>
+#include <linux/if_ether.h>
+
+namespace android {
+
+TEST(LibTcUtilsTest, IsEthernetOfNonExistingIf) {
+ bool result = false;
+ int error = isEthernet("not_existing_if", result);
+ ASSERT_FALSE(result);
+ ASSERT_EQ(-ENODEV, error);
+}
+
+TEST(LibTcUtilsTest, IsEthernetOfLoopback) {
+ bool result = false;
+ int error = isEthernet("lo", result);
+ ASSERT_FALSE(result);
+ ASSERT_EQ(-EAFNOSUPPORT, error);
+}
+
+// If wireless 'wlan0' interface exists it should be Ethernet.
+// See also HardwareAddressTypeOfWireless.
+TEST(LibTcUtilsTest, IsEthernetOfWireless) {
+ bool result = false;
+ int error = isEthernet("wlan0", result);
+ if (!result && error == -ENODEV)
+ return;
+
+ ASSERT_EQ(0, error);
+ ASSERT_TRUE(result);
+}
+
+// If cellular 'rmnet_data0' interface exists it should
+// *probably* not be Ethernet and instead be RawIp.
+// See also HardwareAddressTypeOfCellular.
+TEST(LibTcUtilsTest, IsEthernetOfCellular) {
+ bool result = false;
+ int error = isEthernet("rmnet_data0", result);
+ if (!result && error == -ENODEV)
+ return;
+
+ ASSERT_EQ(0, error);
+ 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));
+}
+
+TEST(LibTcUtilsTest, AddAndDeleteBpfFilter) {
+ // TODO: this should use bpf_shared.h rather than hardcoding the path
+ static constexpr char bpfProgPath[] =
+ "/sys/fs/bpf/tethering/prog_offload_schedcls_tether_downstream6_ether";
+ const int errNOENT = isAtLeastKernelVersion(4, 19, 0) ? ENOENT : EINVAL;
+
+ // static test values
+ static constexpr bool ingress = true;
+ static constexpr uint16_t prio = 17;
+ static constexpr uint16_t proto = ETH_P_ALL;
+
+ // try to delete missing filter from missing qdisc
+ EXPECT_EQ(-EINVAL, tcDeleteFilter(LOOPBACK_IFINDEX, ingress, prio, proto));
+ // try to attach bpf filter to missing qdisc
+ EXPECT_EQ(-EINVAL, tcAddBpfFilter(LOOPBACK_IFINDEX, ingress, prio, proto,
+ bpfProgPath));
+ // add the clsact qdisc
+ EXPECT_EQ(0, tcAddQdiscClsact(LOOPBACK_IFINDEX));
+ // try to delete missing filter when there is a qdisc attached
+ EXPECT_EQ(-errNOENT, tcDeleteFilter(LOOPBACK_IFINDEX, ingress, prio, proto));
+ // add and delete a bpf filter
+ EXPECT_EQ(
+ 0, tcAddBpfFilter(LOOPBACK_IFINDEX, ingress, prio, proto, bpfProgPath));
+ EXPECT_EQ(0, tcDeleteFilter(LOOPBACK_IFINDEX, ingress, prio, proto));
+ // try to remove the same filter a second time
+ EXPECT_EQ(-errNOENT, tcDeleteFilter(LOOPBACK_IFINDEX, ingress, prio, proto));
+ // remove the clsact qdisc
+ EXPECT_EQ(0, tcDeleteQdiscClsact(LOOPBACK_IFINDEX));
+ // once again, try to delete missing filter from missing qdisc
+ EXPECT_EQ(-EINVAL, tcDeleteFilter(LOOPBACK_IFINDEX, ingress, prio, proto));
+}
+
+TEST(LibTcUtilsTest, AddAndDeleteIngressPoliceFilter) {
+ // TODO: this should use bpf_shared.h rather than hardcoding the path
+ static constexpr char bpfProgPath[] =
+ "/sys/fs/bpf/prog_netd_schedact_ingress_account";
+ int fd = bpf::retrieveProgram(bpfProgPath);
+ if (fd == -1) {
+ // ingress policing is not supported.
+ return;
+ }
+ close(fd);
+
+ const int errNOENT = isAtLeastKernelVersion(4, 19, 0) ? ENOENT : EINVAL;
+
+ // static test values
+ static constexpr unsigned rateInBytesPerSec =
+ 1024 * 1024; // 8mbit/s => 1mbyte/s => 1024*1024 bytes/s.
+ static constexpr uint16_t prio = 17;
+ static constexpr uint16_t proto = ETH_P_ALL;
+
+ // try to delete missing filter from missing qdisc
+ EXPECT_EQ(-EINVAL,
+ tcDeleteFilter(LOOPBACK_IFINDEX, true /*ingress*/, prio, proto));
+ // try to attach bpf filter to missing qdisc
+ EXPECT_EQ(-EINVAL, tcAddIngressPoliceFilter(LOOPBACK_IFINDEX, prio, proto,
+ rateInBytesPerSec, bpfProgPath));
+ // add the clsact qdisc
+ EXPECT_EQ(0, tcAddQdiscClsact(LOOPBACK_IFINDEX));
+ // try to delete missing filter when there is a qdisc attached
+ EXPECT_EQ(-errNOENT,
+ tcDeleteFilter(LOOPBACK_IFINDEX, true /*ingress*/, prio, proto));
+ // add and delete a bpf filter
+ EXPECT_EQ(0, tcAddIngressPoliceFilter(LOOPBACK_IFINDEX, prio, proto,
+ rateInBytesPerSec, bpfProgPath));
+ EXPECT_EQ(0, tcDeleteFilter(LOOPBACK_IFINDEX, true /*ingress*/, prio, proto));
+ // try to remove the same filter a second time
+ EXPECT_EQ(-errNOENT,
+ tcDeleteFilter(LOOPBACK_IFINDEX, true /*ingress*/, prio, proto));
+ // remove the clsact qdisc
+ EXPECT_EQ(0, tcDeleteQdiscClsact(LOOPBACK_IFINDEX));
+ // once again, try to delete missing filter from missing qdisc
+ EXPECT_EQ(-EINVAL,
+ tcDeleteFilter(LOOPBACK_IFINDEX, true /*ingress*/, prio, proto));
+}
+
+} // namespace android
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
index 1be64c1..133c983 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -79,6 +79,6 @@
"host/**/*.kt",
],
libs: ["tradefed"],
- test_suites: ["device-tests", "general-tests", "cts", "mts"],
+ test_suites: ["device-tests", "general-tests", "cts", "mts-networking"],
data: [":ConnectivityChecker"],
}