1/*
2 * Copyright 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <string>
18#include <fcntl.h>
19#include <sys/file.h>
20#include <sys/socket.h>
21#include <sys/un.h>
22
23#include <gtest/gtest.h>
24
25#define LOG_TAG "IptablesRestoreControllerTest"
26#include <cutils/log.h>
27#include <android-base/stringprintf.h>
28#include <android-base/strings.h>
29
30#include "IptablesRestoreController.h"
31#include "NetdConstants.h"
32#include "Stopwatch.h"
33
34#define XT_LOCK_NAME "/system/etc/xtables.lock"
35#define XT_LOCK_ATTEMPTS 10
36#define XT_LOCK_POLL_INTERVAL_MS 100
37
38using android::base::Join;
39using android::base::StringPrintf;
40
41class IptablesRestoreControllerTest : public ::testing::Test {
42public:
43  IptablesRestoreController con;
44  int mDefaultMaxRetries = con.MAX_RETRIES;
45  int mDefaultPollTimeoutMs = con.POLL_TIMEOUT_MS;
46  int mIptablesLock = -1;
47  std::string mChainName;
48
49  static void SetUpTestCase() {
50      blockSigpipe();
51  }
52
53  void SetUp() {
54    ASSERT_EQ(0, createTestChain());
55  }
56
57  void TearDown() {
58    con.MAX_RETRIES = mDefaultMaxRetries;
59    con.POLL_TIMEOUT_MS = mDefaultPollTimeoutMs;
60    deleteTestChain();
61  }
62
63  pid_t getIpRestorePid(const IptablesRestoreController::IptablesProcessType type) {
64      return con.getIpRestorePid(type);
65  };
66
67  void expectNoIptablesRestoreProcess(pid_t pid) {
68    // We can't readlink /proc/PID/exe, because zombie processes don't have it.
69    // Parse /proc/PID/stat instead.
70    std::string statPath = StringPrintf("/proc/%d/stat", pid);
71    int fd = open(statPath.c_str(), O_RDONLY);
72    if (fd == -1) {
73      // ENOENT means the process is gone (expected).
74      ASSERT_EQ(errno, ENOENT)
75        << "Unexpected error opening " << statPath << ": " << strerror(errno);
76      return;
77    }
78
79    // If the PID exists, it's possible (though very unlikely) that the PID was reused. Check the
80    // binary name as well, to ensure the test isn't flaky.
81    char statBuf[1024];
82    ASSERT_NE(-1, read(fd, statBuf, sizeof(statBuf)))
83        << "Could not read from " << statPath << ": " << strerror(errno);
84    close(fd);
85
86    std::string statString(statBuf);
87    EXPECT_FALSE(statString.find("iptables-restor") || statString.find("ip6tables-resto"))
88      << "Previous iptables-restore pid " << pid << " still alive: " << statString;
89  }
90
91  int createTestChain() {
92    mChainName = StringPrintf("netd_unit_test_%u", arc4random_uniform(10000)).c_str();
93
94    // Create a chain to list.
95    std::vector<std::string> createCommands = {
96        "*filter",
97        StringPrintf(":%s -", mChainName.c_str()),
98        StringPrintf("-A %s -j RETURN", mChainName.c_str()),
99        "COMMIT",
100        ""
101    };
102
103    int ret = con.execute(V4V6, Join(createCommands, "\n"), nullptr);
104    if (ret) mChainName = "";
105    return ret;
106  }
107
108  void deleteTestChain() {
109    std::vector<std::string> deleteCommands = {
110        "*filter",
111        StringPrintf(":%s -", mChainName.c_str()),  // Flush chain (otherwise we can't delete it).
112        StringPrintf("-X %s", mChainName.c_str()),  // Delete it.
113        "COMMIT",
114        ""
115    };
116    con.execute(V4V6, Join(deleteCommands, "\n"), nullptr);
117    mChainName = "";
118  }
119
120  int acquireIptablesLock() {
121    mIptablesLock = open(XT_LOCK_NAME, O_CREAT, 0600);
122    if (mIptablesLock == -1) return mIptablesLock;
123    int attempts;
124    for (attempts = 0; attempts < XT_LOCK_ATTEMPTS; attempts++) {
125      if (flock(mIptablesLock, LOCK_EX | LOCK_NB) == 0) {
126        return 0;
127      }
128      usleep(XT_LOCK_POLL_INTERVAL_MS * 1000);
129    }
130    EXPECT_LT(attempts, XT_LOCK_ATTEMPTS) <<
131        "Could not acquire iptables lock after " << XT_LOCK_ATTEMPTS << " attempts " <<
132        XT_LOCK_POLL_INTERVAL_MS << "ms apart";
133    return -1;
134  }
135
136  void releaseIptablesLock() {
137    if (mIptablesLock != -1) {
138      close(mIptablesLock);
139    }
140  }
141
142  void setRetryParameters(int maxRetries, int pollTimeoutMs) {
143    con.MAX_RETRIES = maxRetries;
144    con.POLL_TIMEOUT_MS = pollTimeoutMs;
145  }
146};
147
148TEST_F(IptablesRestoreControllerTest, TestBasicCommand) {
149  std::string output;
150
151  EXPECT_EQ(0, con.execute(IptablesTarget::V4V6, "#Test\n", nullptr));
152
153  pid_t pid4 = getIpRestorePid(IptablesRestoreController::IPTABLES_PROCESS);
154  pid_t pid6 = getIpRestorePid(IptablesRestoreController::IP6TABLES_PROCESS);
155
156  EXPECT_EQ(0, con.execute(IptablesTarget::V6, "#Test\n", nullptr));
157  EXPECT_EQ(0, con.execute(IptablesTarget::V4, "#Test\n", nullptr));
158
159  EXPECT_EQ(0, con.execute(IptablesTarget::V4V6, "#Test\n", &output));
160  EXPECT_EQ("#Test\n#Test\n", output);  // One for IPv4 and one for IPv6.
161
162  // Check the PIDs are the same as they were before. If they're not, the child processes were
163  // restarted, which causes a 30-60ms delay.
164  EXPECT_EQ(pid4, getIpRestorePid(IptablesRestoreController::IPTABLES_PROCESS));
165  EXPECT_EQ(pid6, getIpRestorePid(IptablesRestoreController::IP6TABLES_PROCESS));
166}
167
168TEST_F(IptablesRestoreControllerTest, TestRestartOnMalformedCommand) {
169  std::string buffer;
170  for (int i = 0; i < 50; i++) {
171      IptablesTarget target = (IptablesTarget) (i % 3);
172      std::string *output = (i % 2) ? &buffer : nullptr;
173      ASSERT_EQ(-1, con.execute(target, "malformed command\n", output)) <<
174          "Malformed command did not fail at iteration " << i;
175      ASSERT_EQ(0, con.execute(target, "#Test\n", output)) <<
176          "No-op command did not succeed at iteration " << i;
177  }
178}
179
180TEST_F(IptablesRestoreControllerTest, TestRestartOnProcessDeath) {
181  std::string output;
182
183  // Run a command to ensure that the processes are running.
184  EXPECT_EQ(0, con.execute(IptablesTarget::V4V6, "#Test\n", &output));
185
186  pid_t pid4 = getIpRestorePid(IptablesRestoreController::IPTABLES_PROCESS);
187  pid_t pid6 = getIpRestorePid(IptablesRestoreController::IP6TABLES_PROCESS);
188
189  ASSERT_EQ(0, kill(pid4, 0)) << "iptables-restore pid " << pid4 << " does not exist";
190  ASSERT_EQ(0, kill(pid6, 0)) << "ip6tables-restore pid " << pid6 << " does not exist";
191  ASSERT_EQ(0, kill(pid4, SIGTERM)) << "Failed to send SIGTERM to iptables-restore pid " << pid4;
192  ASSERT_EQ(0, kill(pid6, SIGTERM)) << "Failed to send SIGTERM to ip6tables-restore pid " << pid6;
193
194  // Wait 100ms for processes to terminate.
195  TEMP_FAILURE_RETRY(usleep(100 * 1000));
196
197  // Ensure that running a new command properly restarts the processes.
198  EXPECT_EQ(0, con.execute(IptablesTarget::V4V6, "#Test\n", nullptr));
199  EXPECT_NE(pid4, getIpRestorePid(IptablesRestoreController::IPTABLES_PROCESS));
200  EXPECT_NE(pid6, getIpRestorePid(IptablesRestoreController::IP6TABLES_PROCESS));
201
202  // Check there are no zombies.
203  expectNoIptablesRestoreProcess(pid4);
204  expectNoIptablesRestoreProcess(pid6);
205}
206
207TEST_F(IptablesRestoreControllerTest, TestCommandTimeout) {
208  // Don't wait 10 seconds for this test to fail.
209  setRetryParameters(3, 50);
210
211  // Expected contents of the chain.
212  std::vector<std::string> expectedLines = {
213      StringPrintf("Chain %s (0 references)", mChainName.c_str()),
214      "target     prot opt source               destination         ",
215      "RETURN     all  --  0.0.0.0/0            0.0.0.0/0           ",
216      StringPrintf("Chain %s (0 references)", mChainName.c_str()),
217      "target     prot opt source               destination         ",
218      "RETURN     all      ::/0                 ::/0                ",
219      ""
220  };
221  std::string expected = Join(expectedLines, "\n");
222
223  std::vector<std::string> listCommands = {
224      "*filter",
225      StringPrintf("-n -L %s", mChainName.c_str()),  // List chain.
226      "COMMIT",
227      ""
228  };
229  std::string commandString = Join(listCommands, "\n");
230  std::string output;
231
232  EXPECT_EQ(0, con.execute(IptablesTarget::V4V6, commandString, &output));
233  EXPECT_EQ(expected, output);
234
235  ASSERT_EQ(0, acquireIptablesLock());
236  EXPECT_EQ(-1, con.execute(IptablesTarget::V4V6, commandString, &output));
237  EXPECT_EQ(-1, con.execute(IptablesTarget::V4V6, commandString, &output));
238  releaseIptablesLock();
239
240  EXPECT_EQ(0, con.execute(IptablesTarget::V4V6, commandString, &output));
241  EXPECT_EQ(expected, output);
242}
243
244TEST_F(IptablesRestoreControllerTest, TestUidRuleBenchmark) {
245    const std::vector<int> ITERATIONS = { 1, 5, 10 };
246
247    const std::string IPTABLES_RESTORE_ADD =
248            "*filter\n-I fw_powersave -m owner --uid-owner 2000000000 -j RETURN\nCOMMIT\n";
249    const std::string IPTABLES_RESTORE_DEL =
250            "*filter\n-D fw_powersave -m owner --uid-owner 2000000000 -j RETURN\nCOMMIT\n";
251
252    for (const int iterations : ITERATIONS) {
253        Stopwatch s;
254        for (int i = 0; i < iterations; i++) {
255            EXPECT_EQ(0, con.execute(V4V6, IPTABLES_RESTORE_ADD, nullptr));
256            EXPECT_EQ(0, con.execute(V4V6, IPTABLES_RESTORE_DEL, nullptr));
257        }
258        float timeTaken = s.getTimeAndReset();
259        fprintf(stderr, "    Add/del %d UID rules via restore: %.1fms (%.2fms per operation)\n",
260                iterations, timeTaken, timeTaken / 2 / iterations);
261
262        for (int i = 0; i < iterations; i++) {
263            EXPECT_EQ(0, execIptables(V4V6, "-I", "fw_powersave", "-m", "owner",
264                                      "--uid-owner", "2000000000", "-j", "RETURN", nullptr));
265            EXPECT_EQ(0, execIptables(V4V6, "-D", "fw_powersave", "-m", "owner",
266                                      "--uid-owner", "2000000000", "-j", "RETURN", nullptr));
267        }
268        timeTaken = s.getTimeAndReset();
269        fprintf(stderr, "    Add/del %d UID rules via iptables: %.1fms (%.2fms per operation)\n",
270                iterations, timeTaken, timeTaken / 2 / iterations);
271    }
272}
273