iptables.cc revision 6c733cf77b78062afd7d70eb68f8832d77362086
1// Copyright 2014 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "iptables.h"
16
17#include <linux/capability.h>
18
19#include <string>
20#include <vector>
21
22#include <base/logging.h>
23#include <base/strings/string_number_conversions.h>
24#include <base/strings/string_util.h>
25#include <base/strings/stringprintf.h>
26#include <brillo/minijail/minijail.h>
27#include <brillo/process.h>
28
29namespace {
30#if defined(__ANDROID__)
31const char kIpTablesPath[] = "/system/bin/iptables";
32const char kIp6TablesPath[] = "/system/bin/ip6tables";
33const char kIpPath[] = "/system/bin/ip";
34#else
35const char kIpTablesPath[] = "/sbin/iptables";
36const char kIp6TablesPath[] = "/sbin/ip6tables";
37const char kIpPath[] = "/bin/ip";
38const char kUnprivilegedUser[] = "nobody";
39#endif  // __ANDROID__
40
41const uint64_t kIpTablesCapMask =
42    CAP_TO_MASK(CAP_NET_ADMIN) | CAP_TO_MASK(CAP_NET_RAW);
43
44// Interface names must be shorter than 'IFNAMSIZ' chars.
45// See http://man7.org/linux/man-pages/man7/netdevice.7.html
46// 'IFNAMSIZ' is 16 in recent kernels.
47// See http://lxr.free-electrons.com/source/include/uapi/linux/if.h#L26
48const size_t kInterfaceNameSize = 16;
49
50const char kMarkForUserTraffic[] = "1";
51
52const char kTableIdForUserTraffic[] = "1";
53
54bool IsValidInterfaceName(const std::string& iface) {
55  // |iface| should be shorter than |kInterfaceNameSize| chars and have only
56  // alphanumeric characters (embedded hypens and periods are also permitted).
57  if (iface.length() >= kInterfaceNameSize) {
58    return false;
59  }
60  if (base::StartsWith(iface, "-", base::CompareCase::SENSITIVE) ||
61      base::EndsWith(iface, "-", base::CompareCase::SENSITIVE) ||
62      base::StartsWith(iface, ".", base::CompareCase::SENSITIVE) ||
63      base::EndsWith(iface, ".", base::CompareCase::SENSITIVE)) {
64    return false;
65  }
66  for (auto c : iface) {
67    if (!std::isalnum(c) && (c != '-') && (c != '.')) {
68      return false;
69    }
70  }
71  return true;
72}
73}  // namespace
74
75namespace firewalld {
76
77IpTables::IpTables() {
78#if defined(__ANDROID__)
79  ip6_enabled_ = false;
80#endif  // __ANDROID__
81}
82
83IpTables::~IpTables() {
84  // Plug all holes when destructed.
85  PlugAllHoles();
86}
87
88bool IpTables::PunchTcpHole(uint16_t in_port, const std::string& in_interface) {
89  return PunchHole(in_port, in_interface, &tcp_holes_, kProtocolTcp);
90}
91
92bool IpTables::PunchUdpHole(uint16_t in_port, const std::string& in_interface) {
93  return PunchHole(in_port, in_interface, &udp_holes_, kProtocolUdp);
94}
95
96bool IpTables::PlugTcpHole(uint16_t in_port, const std::string& in_interface) {
97  return PlugHole(in_port, in_interface, &tcp_holes_, kProtocolTcp);
98}
99
100bool IpTables::PlugUdpHole(uint16_t in_port, const std::string& in_interface) {
101  return PlugHole(in_port, in_interface, &udp_holes_, kProtocolUdp);
102}
103
104bool IpTables::RequestVpnSetup(const std::vector<std::string>& usernames,
105                               const std::string& interface) {
106  return ApplyVpnSetup(usernames, interface, true /* add */);
107}
108
109bool IpTables::RemoveVpnSetup(const std::vector<std::string>& usernames,
110                              const std::string& interface) {
111  return ApplyVpnSetup(usernames, interface, false /* delete */);
112}
113
114bool IpTables::PunchHole(uint16_t port,
115                         const std::string& interface,
116                         std::set<Hole>* holes,
117                         ProtocolEnum protocol) {
118  if (port == 0) {
119    // Port 0 is not a valid TCP/UDP port.
120    return false;
121  }
122
123  if (!IsValidInterfaceName(interface)) {
124    LOG(ERROR) << "Invalid interface name '" << interface << "'";
125    return false;
126  }
127
128  Hole hole = std::make_pair(port, interface);
129  if (holes->find(hole) != holes->end()) {
130    // We have already punched a hole for |port| on |interface|.
131    // Be idempotent: do nothing and succeed.
132    return true;
133  }
134
135  std::string sprotocol = protocol == kProtocolTcp ? "TCP" : "UDP";
136  LOG(INFO) << "Punching hole for " << sprotocol << " port " << port
137            << " on interface '" << interface << "'";
138  if (!AddAcceptRules(protocol, port, interface)) {
139    // If the 'iptables' command fails, this method fails.
140    LOG(ERROR) << "Adding ACCEPT rules failed";
141    return false;
142  }
143
144  // Track the hole we just punched.
145  holes->insert(hole);
146
147  return true;
148}
149
150bool IpTables::PlugHole(uint16_t port,
151                        const std::string& interface,
152                        std::set<Hole>* holes,
153                        ProtocolEnum protocol) {
154  if (port == 0) {
155    // Port 0 is not a valid TCP/UDP port.
156    return false;
157  }
158
159  Hole hole = std::make_pair(port, interface);
160
161  if (holes->find(hole) == holes->end()) {
162    // There is no firewall hole for |port| on |interface|.
163    // Even though this makes |PlugHole| not idempotent,
164    // and Punch/Plug not entirely symmetrical, fail. It might help catch bugs.
165    return false;
166  }
167
168  std::string sprotocol = protocol == kProtocolTcp ? "TCP" : "UDP";
169  LOG(INFO) << "Plugging hole for " << sprotocol << " port " << port
170            << " on interface '" << interface << "'";
171  if (!DeleteAcceptRules(protocol, port, interface)) {
172    // If the 'iptables' command fails, this method fails.
173    LOG(ERROR) << "Deleting ACCEPT rules failed";
174    return false;
175  }
176
177  // Stop tracking the hole we just plugged.
178  holes->erase(hole);
179
180  return true;
181}
182
183void IpTables::PlugAllHoles() {
184  // Copy the container so that we can remove elements from the original.
185  // TCP
186  std::set<Hole> holes = tcp_holes_;
187  for (auto hole : holes) {
188    PlugHole(hole.first /* port */, hole.second /* interface */, &tcp_holes_,
189             kProtocolTcp);
190  }
191
192  // UDP
193  holes = udp_holes_;
194  for (auto hole : holes) {
195    PlugHole(hole.first /* port */, hole.second /* interface */, &udp_holes_,
196             kProtocolUdp);
197  }
198
199  CHECK(tcp_holes_.size() == 0) << "Failed to plug all TCP holes.";
200  CHECK(udp_holes_.size() == 0) << "Failed to plug all UDP holes.";
201}
202
203bool IpTables::AddAcceptRules(ProtocolEnum protocol,
204                              uint16_t port,
205                              const std::string& interface) {
206  if (!AddAcceptRule(kIpTablesPath, protocol, port, interface)) {
207    LOG(ERROR) << "Could not add ACCEPT rule using '" << kIpTablesPath << "'";
208    return false;
209  }
210
211  if (AddAcceptRule(kIp6TablesPath, protocol, port, interface)) {
212    // This worked, record this fact and insist that it works thereafter.
213    ip6_enabled_ = true;
214  } else if (ip6_enabled_) {
215    // It's supposed to work, fail.
216    LOG(ERROR) << "Could not add ACCEPT rule using '" << kIp6TablesPath
217               << "', aborting operation";
218    DeleteAcceptRule(kIpTablesPath, protocol, port, interface);
219    return false;
220  } else {
221    // It never worked, just ignore it.
222    LOG(WARNING) << "Could not add ACCEPT rule using '" << kIp6TablesPath
223                 << "', ignoring";
224  }
225
226  return true;
227}
228
229bool IpTables::DeleteAcceptRules(ProtocolEnum protocol,
230                                 uint16_t port,
231                                 const std::string& interface) {
232  bool ip4_success = DeleteAcceptRule(kIpTablesPath, protocol, port,
233                                      interface);
234  bool ip6_success = !ip6_enabled_ || DeleteAcceptRule(kIp6TablesPath, protocol,
235                                                       port, interface);
236  return ip4_success && ip6_success;
237}
238
239bool IpTables::AddAcceptRule(const std::string& executable_path,
240                             ProtocolEnum protocol,
241                             uint16_t port,
242                             const std::string& interface) {
243  std::vector<std::string> argv;
244  argv.push_back(executable_path);
245  argv.push_back("-I");  // insert
246  argv.push_back("INPUT");
247  argv.push_back("-p");  // protocol
248  argv.push_back(protocol == kProtocolTcp ? "tcp" : "udp");
249  argv.push_back("--dport");  // destination port
250  argv.push_back(std::to_string(port));
251  if (!interface.empty()) {
252    argv.push_back("-i");  // interface
253    argv.push_back(interface);
254  }
255  argv.push_back("-j");
256  argv.push_back("ACCEPT");
257  argv.push_back("-w");  // Wait for xtables lock.
258
259  // Use CAP_NET_ADMIN|CAP_NET_RAW.
260  return ExecvNonRoot(argv, kIpTablesCapMask) == 0;
261}
262
263bool IpTables::DeleteAcceptRule(const std::string& executable_path,
264                                ProtocolEnum protocol,
265                                uint16_t port,
266                                const std::string& interface) {
267  std::vector<std::string> argv;
268  argv.push_back(executable_path);
269  argv.push_back("-D");  // delete
270  argv.push_back("INPUT");
271  argv.push_back("-p");  // protocol
272  argv.push_back(protocol == kProtocolTcp ? "tcp" : "udp");
273  argv.push_back("--dport");  // destination port
274  argv.push_back(std::to_string(port));
275  if (interface != "") {
276    argv.push_back("-i");  // interface
277    argv.push_back(interface);
278  }
279  argv.push_back("-j");
280  argv.push_back("ACCEPT");
281  argv.push_back("-w");  // Wait for xtables lock.
282
283  // Use CAP_NET_ADMIN|CAP_NET_RAW.
284  return ExecvNonRoot(argv, kIpTablesCapMask) == 0;
285}
286
287bool IpTables::ApplyMasquerade46(const std::string& interface, bool add) {
288  bool return_value = true;
289
290  if (!ApplyMasquerade(kIpTablesPath, interface, add)) {
291    LOG(ERROR) << (add ? "Adding" : "Removing")
292               << " masquerade failed for interface " << interface
293               << " using '" << kIpTablesPath << "'";
294    return_value = false;
295    if (add)
296      return false;
297  }
298  if (!ApplyMasquerade(kIp6TablesPath, interface, add)) {
299    LOG(ERROR) << (add ? "Adding" : "Removing")
300               << " masquerade failed for interface " << interface
301               << " using '" << kIp6TablesPath << "'";
302    return_value = false;
303  }
304  return return_value;
305}
306
307bool IpTables::ApplyMarkForUserTraffic46(const std::string& username,
308                                         bool add) {
309  bool return_value = true;
310
311  if (!ApplyMarkForUserTraffic(kIpTablesPath, username, add)) {
312    LOG(ERROR) << (add ? "Adding" : "Removing")
313               << " mark failed for user " << username
314               << " using '" << kIpTablesPath << "'";
315    return_value = false;
316    if (add)
317      return false;
318  }
319  if (!ApplyMarkForUserTraffic(kIp6TablesPath, username, add)) {
320    LOG(ERROR) << (add ? "Adding" : "Removing")
321               << " mark failed for user " << username
322               << " using '" << kIp6TablesPath << "'";
323    return_value = false;
324  }
325  return return_value;
326}
327
328bool IpTables::ApplyVpnSetup(const std::vector<std::string>& usernames,
329                             const std::string& interface,
330                             bool add) {
331  bool return_value = true;
332  std::vector<std::string> added_usernames;
333
334  if (!ApplyRuleForUserTraffic(kIPv4, add)) {
335    LOG(ERROR) << (add ? "Adding" : "Removing")
336               << " rule for IPv4 user traffic failed";
337    if (add)
338      return false;
339    return_value = false;
340  }
341
342  if (!ApplyRuleForUserTraffic(kIPv6, add)) {
343    LOG(ERROR) << (add ? "Adding" : "Removing")
344               << " rule for IPv6 user traffic failed";
345    if (add) {
346      ApplyVpnSetup(added_usernames, interface, false);
347      return false;
348    }
349    return_value = false;
350  }
351
352  if (!ApplyMasquerade46(interface, add)) {
353    if (add) {
354      ApplyVpnSetup(added_usernames, interface, false);
355      return false;
356    }
357    return_value = false;
358  }
359
360  for (const auto& username : usernames) {
361    if (!ApplyMarkForUserTraffic46(username, add)) {
362      if (add) {
363        ApplyVpnSetup(added_usernames, interface, false);
364        return false;
365      }
366      return_value = false;
367    }
368    if (add) {
369      added_usernames.push_back(username);
370    }
371  }
372
373  return return_value;
374}
375
376bool IpTables::ApplyMasquerade(const std::string& executable_path,
377                               const std::string& interface,
378                               bool add) {
379  std::vector<std::string> argv;
380  argv.push_back(executable_path);
381  argv.push_back("-t");  // table
382  argv.push_back("nat");
383  argv.push_back(add ? "-A" : "-D");  // rule
384  argv.push_back("POSTROUTING");
385  argv.push_back("-o");  // output interface
386  argv.push_back(interface);
387  argv.push_back("-j");
388  argv.push_back("MASQUERADE");
389
390  // Use CAP_NET_ADMIN|CAP_NET_RAW.
391  return ExecvNonRoot(argv, kIpTablesCapMask) == 0;
392}
393
394bool IpTables::ApplyMarkForUserTraffic(const std::string& executable_path,
395                                       const std::string& user_name,
396                                       bool add) {
397  std::vector<std::string> argv;
398  argv.push_back(executable_path);
399  argv.push_back("-t");  // table
400  argv.push_back("mangle");
401  argv.push_back(add ? "-A" : "-D");  // rule
402  argv.push_back("OUTPUT");
403  argv.push_back("-m");
404  argv.push_back("owner");
405  argv.push_back("--uid-owner");
406  argv.push_back(user_name);
407  argv.push_back("-j");
408  argv.push_back("MARK");
409  argv.push_back("--set-mark");
410  argv.push_back(kMarkForUserTraffic);
411
412  // Use CAP_NET_ADMIN|CAP_NET_RAW.
413  return ExecvNonRoot(argv, kIpTablesCapMask) == 0;
414}
415
416bool IpTables::ApplyRuleForUserTraffic(IPVersionEnum ip_version, bool add) {
417  brillo::ProcessImpl ip;
418  ip.AddArg(kIpPath);
419  if (ip_version == kIPv6)
420    ip.AddArg("-6");
421  ip.AddArg("rule");
422  ip.AddArg(add ? "add" : "delete");
423  ip.AddArg("fwmark");
424  ip.AddArg(kMarkForUserTraffic);
425  ip.AddArg("table");
426  ip.AddArg(kTableIdForUserTraffic);
427
428  return ip.Run() == 0;
429}
430
431int IpTables::ExecvNonRoot(const std::vector<std::string>& argv,
432                           uint64_t capmask) {
433  brillo::Minijail* m = brillo::Minijail::GetInstance();
434  minijail* jail = m->New();
435#if !defined(__ANDROID__)
436  // TODO(garnold) This needs to be re-enabled once we figure out which
437  // unprivileged user we want to use.
438  m->DropRoot(jail, kUnprivilegedUser, kUnprivilegedUser);
439#endif  // __ANDROID__
440  m->UseCapabilities(jail, capmask);
441
442  std::vector<char*> args;
443  for (const auto& arg : argv) {
444    args.push_back(const_cast<char*>(arg.c_str()));
445  }
446  args.push_back(nullptr);
447
448  int status;
449  bool ran = m->RunSyncAndDestroy(jail, args, &status);
450  return ran ? status : -1;
451}
452
453}  // namespace firewalld
454