summaryrefslogtreecommitdiff
path: root/server/TetherControllerTest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'server/TetherControllerTest.cpp')
-rw-r--r--server/TetherControllerTest.cpp351
1 files changed, 351 insertions, 0 deletions
diff --git a/server/TetherControllerTest.cpp b/server/TetherControllerTest.cpp
new file mode 100644
index 00000000..887c6488
--- /dev/null
+++ b/server/TetherControllerTest.cpp
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2016 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.
+ *
+ * TetherControllerTest.cpp - unit tests for TetherController.cpp
+ */
+
+#include <string>
+#include <vector>
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <gtest/gtest.h>
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include "TetherController.h"
+#include "IptablesBaseTest.h"
+
+using android::base::Join;
+using android::base::StringPrintf;
+
+namespace android {
+namespace net {
+
+class TetherControllerTest : public IptablesBaseTest {
+public:
+ TetherControllerTest() {
+ TetherController::iptablesRestoreFunction = fakeExecIptablesRestoreWithOutput;
+ }
+
+protected:
+ TetherController mTetherCtrl;
+
+ int setDefaults() {
+ return mTetherCtrl.setDefaults();
+ }
+
+ const ExpectedIptablesCommands FLUSH_COMMANDS = {
+ { V4, "*filter\n"
+ ":natctrl_FORWARD -\n"
+ "-A natctrl_FORWARD -j DROP\n"
+ "COMMIT\n"
+ "*nat\n"
+ ":natctrl_nat_POSTROUTING -\n"
+ "COMMIT\n" },
+ { V6, "*filter\n"
+ ":natctrl_FORWARD -\n"
+ "COMMIT\n"
+ "*raw\n"
+ ":natctrl_raw_PREROUTING -\n"
+ "COMMIT\n" },
+ };
+
+ const ExpectedIptablesCommands SETUP_COMMANDS = {
+ { V4, "*filter\n"
+ ":natctrl_FORWARD -\n"
+ "-A natctrl_FORWARD -j DROP\n"
+ "COMMIT\n"
+ "*nat\n"
+ ":natctrl_nat_POSTROUTING -\n"
+ "COMMIT\n" },
+ { V6, "*filter\n"
+ ":natctrl_FORWARD -\n"
+ "COMMIT\n"
+ "*raw\n"
+ ":natctrl_raw_PREROUTING -\n"
+ "COMMIT\n" },
+ { V4, "*mangle\n"
+ "-A natctrl_mangle_FORWARD -p tcp --tcp-flags SYN SYN "
+ "-j TCPMSS --clamp-mss-to-pmtu\n"
+ "COMMIT\n" },
+ { V4V6, "*filter\n"
+ ":natctrl_tether_counters -\n"
+ "COMMIT\n" },
+ };
+
+ ExpectedIptablesCommands firstNatCommands(const char *extIf) {
+ std::string v4Cmd = StringPrintf(
+ "*nat\n"
+ "-A natctrl_nat_POSTROUTING -o %s -j MASQUERADE\n"
+ "COMMIT\n", extIf);
+ std::string v6Cmd =
+ "*filter\n"
+ "-A natctrl_FORWARD -g natctrl_tether_counters\n"
+ "COMMIT\n";
+ return {
+ { V4, v4Cmd },
+ { V6, v6Cmd },
+ };
+ }
+
+ ExpectedIptablesCommands startNatCommands(const char *intIf, const char *extIf) {
+ std::string rpfilterCmd = StringPrintf(
+ "*raw\n"
+ "-A natctrl_raw_PREROUTING -i %s -m rpfilter --invert ! -s fe80::/64 -j DROP\n"
+ "COMMIT\n", intIf);
+
+ std::vector<std::string> v4Cmds = {
+ "*filter",
+ StringPrintf("-A natctrl_FORWARD -i %s -o %s -m state --state"
+ " ESTABLISHED,RELATED -g natctrl_tether_counters", extIf, intIf),
+ StringPrintf("-A natctrl_FORWARD -i %s -o %s -m state --state INVALID -j DROP",
+ intIf, extIf),
+ StringPrintf("-A natctrl_FORWARD -i %s -o %s -g natctrl_tether_counters",
+ intIf, extIf),
+ StringPrintf("-A natctrl_tether_counters -i %s -o %s -j RETURN", intIf, extIf),
+ StringPrintf("-A natctrl_tether_counters -i %s -o %s -j RETURN", extIf, intIf),
+ "-D natctrl_FORWARD -j DROP",
+ "-A natctrl_FORWARD -j DROP",
+ "COMMIT\n",
+ };
+
+ std::vector<std::string> v6Cmds = {
+ "*filter",
+ StringPrintf("-A natctrl_tether_counters -i %s -o %s -j RETURN", intIf, extIf),
+ StringPrintf("-A natctrl_tether_counters -i %s -o %s -j RETURN", extIf, intIf),
+ "COMMIT\n",
+ };
+
+ return {
+ { V6, rpfilterCmd },
+ { V4, Join(v4Cmds, '\n') },
+ { V6, Join(v6Cmds, '\n') },
+ };
+ }
+
+ ExpectedIptablesCommands stopNatCommands(const char *intIf, const char *extIf) {
+ std::string rpfilterCmd = StringPrintf(
+ "*raw\n"
+ "-D natctrl_raw_PREROUTING -i %s -m rpfilter --invert ! -s fe80::/64 -j DROP\n"
+ "COMMIT\n", intIf);
+
+ std::vector<std::string> v4Cmds = {
+ "*filter",
+ StringPrintf("-D natctrl_FORWARD -i %s -o %s -m state --state"
+ " ESTABLISHED,RELATED -g natctrl_tether_counters", extIf, intIf),
+ StringPrintf("-D natctrl_FORWARD -i %s -o %s -m state --state INVALID -j DROP",
+ intIf, extIf),
+ StringPrintf("-D natctrl_FORWARD -i %s -o %s -g natctrl_tether_counters",
+ intIf, extIf),
+ "COMMIT\n",
+ };
+
+ return {
+ { V6, rpfilterCmd },
+ { V4, Join(v4Cmds, '\n') },
+ };
+
+ }
+};
+
+TEST_F(TetherControllerTest, TestSetupIptablesHooks) {
+ mTetherCtrl.setupIptablesHooks();
+ expectIptablesRestoreCommands(SETUP_COMMANDS);
+}
+
+TEST_F(TetherControllerTest, TestSetDefaults) {
+ setDefaults();
+ expectIptablesRestoreCommands(FLUSH_COMMANDS);
+}
+
+TEST_F(TetherControllerTest, TestAddAndRemoveNat) {
+ ExpectedIptablesCommands expected;
+ ExpectedIptablesCommands setupFirstNatCommands = firstNatCommands("rmnet0");
+ ExpectedIptablesCommands startFirstNatCommands = startNatCommands("wlan0", "rmnet0");
+ expected.insert(expected.end(), setupFirstNatCommands.begin(), setupFirstNatCommands.end());
+ expected.insert(expected.end(), startFirstNatCommands.begin(), startFirstNatCommands.end());
+ mTetherCtrl.enableNat("wlan0", "rmnet0");
+ expectIptablesRestoreCommands(expected);
+
+ ExpectedIptablesCommands startOtherNat = startNatCommands("usb0", "rmnet0");
+ mTetherCtrl.enableNat("usb0", "rmnet0");
+ expectIptablesRestoreCommands(startOtherNat);
+
+ ExpectedIptablesCommands stopOtherNat = stopNatCommands("wlan0", "rmnet0");
+ mTetherCtrl.disableNat("wlan0", "rmnet0");
+ expectIptablesRestoreCommands(stopOtherNat);
+
+ expected = stopNatCommands("usb0", "rmnet0");
+ expected.insert(expected.end(), FLUSH_COMMANDS.begin(), FLUSH_COMMANDS.end());
+ mTetherCtrl.disableNat("usb0", "rmnet0");
+ expectIptablesRestoreCommands(expected);
+}
+
+std::string kIPv4TetherCounters = Join(std::vector<std::string> {
+ "Chain natctrl_tether_counters (4 references)",
+ " pkts bytes target prot opt in out source destination",
+ " 26 2373 RETURN all -- wlan0 rmnet0 0.0.0.0/0 0.0.0.0/0",
+ " 27 2002 RETURN all -- rmnet0 wlan0 0.0.0.0/0 0.0.0.0/0",
+ " 1040 107471 RETURN all -- bt-pan rmnet0 0.0.0.0/0 0.0.0.0/0",
+ " 1450 1708806 RETURN all -- rmnet0 bt-pan 0.0.0.0/0 0.0.0.0/0",
+}, '\n');
+
+std::string kIPv6TetherCounters = Join(std::vector<std::string> {
+ "Chain natctrl_tether_counters (2 references)",
+ " pkts bytes target prot opt in out source destination",
+ " 10000 10000000 RETURN all wlan0 rmnet0 ::/0 ::/0",
+ " 20000 20000000 RETURN all rmnet0 wlan0 ::/0 ::/0",
+}, '\n');
+
+std::string readSocketClientResponse(int fd) {
+ char buf[32768];
+ ssize_t bytesRead = read(fd, buf, sizeof(buf));
+ if (bytesRead < 0) {
+ return "";
+ }
+ for (int i = 0; i < bytesRead; i++) {
+ if (buf[i] == '\0') buf[i] = '\n';
+ }
+ return std::string(buf, bytesRead);
+}
+
+void expectNoSocketClientResponse(int fd) {
+ char buf[64];
+ EXPECT_EQ(-1, read(fd, buf, sizeof(buf)));
+}
+
+TEST_F(TetherControllerTest, TestGetTetherStats) {
+ int socketPair[2];
+ ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, socketPair));
+ ASSERT_EQ(0, fcntl(socketPair[0], F_SETFL, O_NONBLOCK | fcntl(socketPair[0], F_GETFL)));
+ ASSERT_EQ(0, fcntl(socketPair[1], F_SETFL, O_NONBLOCK | fcntl(socketPair[1], F_GETFL)));
+ SocketClient cli(socketPair[0], false);
+
+ std::string err;
+ TetherController::TetherStats filter;
+
+ // If no filter is specified, both IPv4 and IPv6 counters must have at least one interface pair.
+ addIptablesRestoreOutput(kIPv4TetherCounters);
+ ASSERT_EQ(-1, mTetherCtrl.getTetherStats(&cli, filter, err));
+ expectNoSocketClientResponse(socketPair[1]);
+ clearIptablesRestoreOutput();
+
+ addIptablesRestoreOutput(kIPv6TetherCounters);
+ ASSERT_EQ(-1, mTetherCtrl.getTetherStats(&cli, filter, err));
+ clearIptablesRestoreOutput();
+
+ // IPv4 and IPv6 counters are properly added together.
+ addIptablesRestoreOutput(kIPv4TetherCounters, kIPv6TetherCounters);
+ filter = TetherController::TetherStats();
+ std::string expected =
+ "114 wlan0 rmnet0 10002373 10026 20002002 20027\n"
+ "114 bt-pan rmnet0 107471 1040 1708806 1450\n"
+ "200 Tethering stats list completed\n";
+ ASSERT_EQ(0, mTetherCtrl.getTetherStats(&cli, filter, err));
+ ASSERT_EQ(expected, readSocketClientResponse(socketPair[1]));
+ expectNoSocketClientResponse(socketPair[1]);
+ clearIptablesRestoreOutput();
+
+ // Test filtering.
+ addIptablesRestoreOutput(kIPv4TetherCounters, kIPv6TetherCounters);
+ filter = TetherController::TetherStats("bt-pan", "rmnet0", -1, -1, -1, -1);
+ expected = "221 bt-pan rmnet0 107471 1040 1708806 1450\n";
+ ASSERT_EQ(0, mTetherCtrl.getTetherStats(&cli, filter, err));
+ ASSERT_EQ(expected, readSocketClientResponse(socketPair[1]));
+ expectNoSocketClientResponse(socketPair[1]);
+ clearIptablesRestoreOutput();
+
+ addIptablesRestoreOutput(kIPv4TetherCounters, kIPv6TetherCounters);
+ filter = TetherController::TetherStats("wlan0", "rmnet0", -1, -1, -1, -1);
+ expected = "221 wlan0 rmnet0 10002373 10026 20002002 20027\n";
+ ASSERT_EQ(0, mTetherCtrl.getTetherStats(&cli, filter, err));
+ ASSERT_EQ(expected, readSocketClientResponse(socketPair[1]));
+ clearIptablesRestoreOutput();
+
+ // Select nonexistent interfaces.
+ addIptablesRestoreOutput(kIPv4TetherCounters, kIPv6TetherCounters);
+ filter = TetherController::TetherStats("rmnet0", "foo0", -1, -1, -1, -1);
+ expected = "200 Tethering stats list completed\n";
+ ASSERT_EQ(0, mTetherCtrl.getTetherStats(&cli, filter, err));
+ ASSERT_EQ(expected, readSocketClientResponse(socketPair[1]));
+ clearIptablesRestoreOutput();
+
+ // No stats with a filter: no error.
+ addIptablesRestoreOutput("", "");
+ ASSERT_EQ(0, mTetherCtrl.getTetherStats(&cli, filter, err));
+ ASSERT_EQ("200 Tethering stats list completed\n", readSocketClientResponse(socketPair[1]));
+ clearIptablesRestoreOutput();
+
+ addIptablesRestoreOutput("foo", "foo");
+ ASSERT_EQ(0, mTetherCtrl.getTetherStats(&cli, filter, err));
+ ASSERT_EQ("200 Tethering stats list completed\n", readSocketClientResponse(socketPair[1]));
+ clearIptablesRestoreOutput();
+
+ // No stats and empty filter: error.
+ filter = TetherController::TetherStats();
+ addIptablesRestoreOutput("", kIPv6TetherCounters);
+ ASSERT_EQ(-1, mTetherCtrl.getTetherStats(&cli, filter, err));
+ expectNoSocketClientResponse(socketPair[1]);
+ clearIptablesRestoreOutput();
+
+ addIptablesRestoreOutput(kIPv4TetherCounters, "");
+ ASSERT_EQ(-1, mTetherCtrl.getTetherStats(&cli, filter, err));
+ expectNoSocketClientResponse(socketPair[1]);
+ clearIptablesRestoreOutput();
+
+ // Include only one pair of interfaces and things are fine.
+ std::vector<std::string> counterLines = android::base::Split(kIPv4TetherCounters, "\n");
+ std::vector<std::string> brokenCounterLines = counterLines;
+ counterLines.resize(4);
+ std::string counters = Join(counterLines, "\n") + "\n";
+ addIptablesRestoreOutput(counters, counters);
+ expected =
+ "114 wlan0 rmnet0 4746 52 4004 54\n"
+ "200 Tethering stats list completed\n";
+ ASSERT_EQ(0, mTetherCtrl.getTetherStats(&cli, filter, err));
+ ASSERT_EQ(expected, readSocketClientResponse(socketPair[1]));
+ clearIptablesRestoreOutput();
+
+ // But if interfaces aren't paired, it's always an error.
+ err = "";
+ counterLines.resize(3);
+ counters = Join(counterLines, "\n") + "\n";
+ addIptablesRestoreOutput(counters, counters);
+ ASSERT_EQ(-1, mTetherCtrl.getTetherStats(&cli, filter, err));
+ expectNoSocketClientResponse(socketPair[1]);
+ clearIptablesRestoreOutput();
+
+ // Token unit test of the fact that we return the stats in the error message which the caller
+ // ignores.
+ std::string expectedError = counters;
+ EXPECT_EQ(expectedError, err);
+
+ addIptablesRestoreOutput(kIPv4TetherCounters);
+ ASSERT_EQ(-1, mTetherCtrl.getTetherStats(&cli, filter, err));
+ expectNoSocketClientResponse(socketPair[1]);
+ clearIptablesRestoreOutput();
+ addIptablesRestoreOutput(kIPv6TetherCounters);
+ ASSERT_EQ(-1, mTetherCtrl.getTetherStats(&cli, filter, err));
+ expectNoSocketClientResponse(socketPair[1]);
+ clearIptablesRestoreOutput();
+}
+
+} // namespace net
+} // namespace android