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
38 using android::base::Join;
39 using android::base::StringPrintf;
40
41 class IptablesRestoreControllerTest : public ::testing::Test {
42 public:
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
SetUpTestCase()49 static void SetUpTestCase() {
50 blockSigpipe();
51 }
52
SetUp()53 void SetUp() {
54 ASSERT_EQ(0, createTestChain());
55 }
56
TearDown()57 void TearDown() {
58 con.MAX_RETRIES = mDefaultMaxRetries;
59 con.POLL_TIMEOUT_MS = mDefaultPollTimeoutMs;
60 deleteTestChain();
61 }
62
getIpRestorePid(const IptablesRestoreController::IptablesProcessType type)63 pid_t getIpRestorePid(const IptablesRestoreController::IptablesProcessType type) {
64 return con.getIpRestorePid(type);
65 };
66
expectNoIptablesRestoreProcess(pid_t pid)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
createTestChain()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
deleteTestChain()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
acquireIptablesLock()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
releaseIptablesLock()136 void releaseIptablesLock() {
137 if (mIptablesLock != -1) {
138 close(mIptablesLock);
139 }
140 }
141
setRetryParameters(int maxRetries,int pollTimeoutMs)142 void setRetryParameters(int maxRetries, int pollTimeoutMs) {
143 con.MAX_RETRIES = maxRetries;
144 con.POLL_TIMEOUT_MS = pollTimeoutMs;
145 }
146 };
147
TEST_F(IptablesRestoreControllerTest,TestBasicCommand)148 TEST_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
TEST_F(IptablesRestoreControllerTest,TestRestartOnMalformedCommand)168 TEST_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
TEST_F(IptablesRestoreControllerTest,TestRestartOnProcessDeath)180 TEST_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
TEST_F(IptablesRestoreControllerTest,TestCommandTimeout)207 TEST_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
TEST_F(IptablesRestoreControllerTest,TestUidRuleBenchmark)244 TEST_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