iptables.cc revision 7db56bd4c91a516637995b9bf75241cb0c323bf9
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 <chromeos/minijail/minijail.h>
27#include <chromeos/process.h>
28
29namespace {
30#if defined(__BRILLO__)
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  // __BRILLO__
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 are also permitted).
57  if (iface.length() >= kInterfaceNameSize) {
58    return false;
59  }
60  if (base::StartsWithASCII(iface, "-", true /* case_sensitive */) ||
61      base::EndsWith(iface, "-", true /* case_sensitive */)) {
62    return false;
63  }
64  for (auto c : iface) {
65    if (!std::isalnum(c) && (c != '-')) {
66      return false;
67    }
68  }
69  return true;
70}
71}  // namespace
72
73namespace firewalld {
74
75IpTables::IpTables() {
76#if defined(__BRILLO__)
77  ip6_enabled_ = false;
78#endif  // __BRILLO__
79}
80
81IpTables::~IpTables() {
82  // Plug all holes when destructed.
83  PlugAllHoles();
84}
85
86bool IpTables::PunchTcpHole(uint16_t in_port, const std::string& in_interface) {
87  return PunchHole(in_port, in_interface, &tcp_holes_, kProtocolTcp);
88}
89
90bool IpTables::PunchUdpHole(uint16_t in_port, const std::string& in_interface) {
91  return PunchHole(in_port, in_interface, &udp_holes_, kProtocolUdp);
92}
93
94bool IpTables::PlugTcpHole(uint16_t in_port, const std::string& in_interface) {
95  return PlugHole(in_port, in_interface, &tcp_holes_, kProtocolTcp);
96}
97
98bool IpTables::PlugUdpHole(uint16_t in_port, const std::string& in_interface) {
99  return PlugHole(in_port, in_interface, &udp_holes_, kProtocolUdp);
100}
101
102bool IpTables::RequestVpnSetup(const std::vector<std::string>& usernames,
103                               const std::string& interface) {
104  return ApplyVpnSetup(usernames, interface, true /* add */);
105}
106
107bool IpTables::RemoveVpnSetup(const std::vector<std::string>& usernames,
108                              const std::string& interface) {
109  return ApplyVpnSetup(usernames, interface, false /* delete */);
110}
111
112bool IpTables::PunchHole(uint16_t port,
113                         const std::string& interface,
114                         std::set<Hole>* holes,
115                         ProtocolEnum protocol) {
116  if (port == 0) {
117    // Port 0 is not a valid TCP/UDP port.
118    return false;
119  }
120
121  if (!IsValidInterfaceName(interface)) {
122    LOG(ERROR) << "Invalid interface name '" << interface << "'";
123    return false;
124  }
125
126  Hole hole = std::make_pair(port, interface);
127  if (holes->find(hole) != holes->end()) {
128    // We have already punched a hole for |port| on |interface|.
129    // Be idempotent: do nothing and succeed.
130    return true;
131  }
132
133  std::string sprotocol = protocol == kProtocolTcp ? "TCP" : "UDP";
134  LOG(INFO) << "Punching hole for " << sprotocol << " port " << port
135            << " on interface '" << interface << "'";
136  if (!AddAcceptRules(protocol, port, interface)) {
137    // If the 'iptables' command fails, this method fails.
138    LOG(ERROR) << "Adding ACCEPT rules failed";
139    return false;
140  }
141
142  // Track the hole we just punched.
143  holes->insert(hole);
144
145  return true;
146}
147
148bool IpTables::PlugHole(uint16_t port,
149                        const std::string& interface,
150                        std::set<Hole>* holes,
151                        ProtocolEnum protocol) {
152  if (port == 0) {
153    // Port 0 is not a valid TCP/UDP port.
154    return false;
155  }
156
157  Hole hole = std::make_pair(port, interface);
158
159  if (holes->find(hole) == holes->end()) {
160    // There is no firewall hole for |port| on |interface|.
161    // Even though this makes |PlugHole| not idempotent,
162    // and Punch/Plug not entirely symmetrical, fail. It might help catch bugs.
163    return false;
164  }
165
166  std::string sprotocol = protocol == kProtocolTcp ? "TCP" : "UDP";
167  LOG(INFO) << "Plugging hole for " << sprotocol << " port " << port
168            << " on interface '" << interface << "'";
169  if (!DeleteAcceptRules(protocol, port, interface)) {
170    // If the 'iptables' command fails, this method fails.
171    LOG(ERROR) << "Deleting ACCEPT rules failed";
172    return false;
173  }
174
175  // Stop tracking the hole we just plugged.
176  holes->erase(hole);
177
178  return true;
179}
180
181void IpTables::PlugAllHoles() {
182  // Copy the container so that we can remove elements from the original.
183  // TCP
184  std::set<Hole> holes = tcp_holes_;
185  for (auto hole : holes) {
186    PlugHole(hole.first /* port */, hole.second /* interface */, &tcp_holes_,
187             kProtocolTcp);
188  }
189
190  // UDP
191  holes = udp_holes_;
192  for (auto hole : holes) {
193    PlugHole(hole.first /* port */, hole.second /* interface */, &udp_holes_,
194             kProtocolUdp);
195  }
196
197  CHECK(tcp_holes_.size() == 0) << "Failed to plug all TCP holes.";
198  CHECK(udp_holes_.size() == 0) << "Failed to plug all UDP holes.";
199}
200
201bool IpTables::AddAcceptRules(ProtocolEnum protocol,
202                              uint16_t port,
203                              const std::string& interface) {
204  if (!AddAcceptRule(kIpTablesPath, protocol, port, interface)) {
205    LOG(ERROR) << "Could not add ACCEPT rule using '" << kIpTablesPath << "'";
206    return false;
207  }
208
209  if (AddAcceptRule(kIp6TablesPath, protocol, port, interface)) {
210    // This worked, record this fact and insist that it works thereafter.
211    ip6_enabled_ = true;
212  } else if (ip6_enabled_) {
213    // It's supposed to work, fail.
214    LOG(ERROR) << "Could not add ACCEPT rule using '" << kIp6TablesPath
215               << "', aborting operation";
216    DeleteAcceptRule(kIpTablesPath, protocol, port, interface);
217    return false;
218  } else {
219    // It never worked, just ignore it.
220    LOG(WARNING) << "Could not add ACCEPT rule using '" << kIp6TablesPath
221                 << "', ignoring";
222  }
223
224  return true;
225}
226
227bool IpTables::DeleteAcceptRules(ProtocolEnum protocol,
228                                 uint16_t port,
229                                 const std::string& interface) {
230  bool ip4_success = DeleteAcceptRule(kIpTablesPath, protocol, port,
231                                      interface);
232  bool ip6_success = !ip6_enabled_ || DeleteAcceptRule(kIp6TablesPath, protocol,
233                                                       port, interface);
234  return ip4_success && ip6_success;
235}
236
237bool IpTables::AddAcceptRule(const std::string& executable_path,
238                             ProtocolEnum protocol,
239                             uint16_t port,
240                             const std::string& interface) {
241  std::vector<std::string> argv;
242  argv.push_back(executable_path);
243  argv.push_back("-I");  // insert
244  argv.push_back("INPUT");
245  argv.push_back("-p");  // protocol
246  argv.push_back(protocol == kProtocolTcp ? "tcp" : "udp");
247  argv.push_back("--dport");  // destination port
248  argv.push_back(std::to_string(port));
249  if (!interface.empty()) {
250    argv.push_back("-i");  // interface
251    argv.push_back(interface);
252  }
253  argv.push_back("-j");
254  argv.push_back("ACCEPT");
255  argv.push_back("-w");  // Wait for xtables lock.
256
257  // Use CAP_NET_ADMIN|CAP_NET_RAW.
258  return ExecvNonRoot(argv, kIpTablesCapMask) == 0;
259}
260
261bool IpTables::DeleteAcceptRule(const std::string& executable_path,
262                                ProtocolEnum protocol,
263                                uint16_t port,
264                                const std::string& interface) {
265  std::vector<std::string> argv;
266  argv.push_back(executable_path);
267  argv.push_back("-D");  // delete
268  argv.push_back("INPUT");
269  argv.push_back("-p");  // protocol
270  argv.push_back(protocol == kProtocolTcp ? "tcp" : "udp");
271  argv.push_back("--dport");  // destination port
272  argv.push_back(std::to_string(port));
273  if (interface != "") {
274    argv.push_back("-i");  // interface
275    argv.push_back(interface);
276  }
277  argv.push_back("-j");
278  argv.push_back("ACCEPT");
279  argv.push_back("-w");  // Wait for xtables lock.
280
281  // Use CAP_NET_ADMIN|CAP_NET_RAW.
282  return ExecvNonRoot(argv, kIpTablesCapMask) == 0;
283}
284
285bool IpTables::ApplyVpnSetup(const std::vector<std::string>& usernames,
286                             const std::string& interface,
287                             bool add) {
288  bool return_value = true;
289
290  if (!ApplyRuleForUserTraffic(add)) {
291    LOG(ERROR) << (add ? "Adding" : "Removing")
292               << " rule for user traffic failed";
293    if (add)
294      return false;
295    return_value = false;
296  }
297
298  if (!ApplyMasquerade(interface, add)) {
299    LOG(ERROR) << (add ? "Adding" : "Removing")
300               << " masquerade failed for interface "
301               << interface;
302    if (add) {
303      ApplyRuleForUserTraffic(false);
304      return false;
305    }
306    return_value = false;
307  }
308
309  std::vector<std::string> added_usernames;
310  for (const auto& username : usernames) {
311    if (!ApplyMarkForUserTraffic(username, add)) {
312      LOG(ERROR) << (add ? "Adding" : "Removing")
313                 << " mark failed for user "
314                 << username;
315      if (add) {
316        ApplyVpnSetup(added_usernames, interface, false);
317        return false;
318      }
319      return_value = false;
320    }
321    if (add) {
322      added_usernames.push_back(username);
323    }
324  }
325
326  return return_value;
327}
328
329bool IpTables::ApplyMasquerade(const std::string& interface, bool add) {
330  std::vector<std::string> argv;
331  argv.push_back(kIpTablesPath);
332  argv.push_back("-t");  // table
333  argv.push_back("nat");
334  argv.push_back(add ? "-A" : "-D");  // rule
335  argv.push_back("POSTROUTING");
336  argv.push_back("-o");  // output interface
337  argv.push_back(interface);
338  argv.push_back("-j");
339  argv.push_back("MASQUERADE");
340
341  // Use CAP_NET_ADMIN|CAP_NET_RAW.
342  return ExecvNonRoot(argv, kIpTablesCapMask) == 0;
343}
344
345bool IpTables::ApplyMarkForUserTraffic(const std::string& user_name,
346                                       bool add) {
347  std::vector<std::string> argv;
348  argv.push_back(kIpTablesPath);
349  argv.push_back("-t");  // table
350  argv.push_back("mangle");
351  argv.push_back(add ? "-A" : "-D");  // rule
352  argv.push_back("OUTPUT");
353  argv.push_back("-m");
354  argv.push_back("owner");
355  argv.push_back("--uid-owner");
356  argv.push_back(user_name);
357  argv.push_back("-j");
358  argv.push_back("MARK");
359  argv.push_back("--set-mark");
360  argv.push_back(kMarkForUserTraffic);
361
362  // Use CAP_NET_ADMIN|CAP_NET_RAW.
363  return ExecvNonRoot(argv, kIpTablesCapMask) == 0;
364}
365
366bool IpTables::ApplyRuleForUserTraffic(bool add) {
367  chromeos::ProcessImpl ip;
368  ip.AddArg(kIpPath);
369  ip.AddArg("rule");
370  ip.AddArg(add ? "add" : "delete");
371  ip.AddArg("fwmark");
372  ip.AddArg(kMarkForUserTraffic);
373  ip.AddArg("table");
374  ip.AddArg(kTableIdForUserTraffic);
375
376  return ip.Run() == 0;
377}
378
379int IpTables::ExecvNonRoot(const std::vector<std::string>& argv,
380                           uint64_t capmask) {
381  chromeos::Minijail* m = chromeos::Minijail::GetInstance();
382  minijail* jail = m->New();
383#if !defined(__BRILLO__)
384  // TODO(garnold) This needs to be re-enabled once we figure out which
385  // unprivileged user we want to use.
386  m->DropRoot(jail, kUnprivilegedUser, kUnprivilegedUser);
387#endif  // __BRILLO__
388  m->UseCapabilities(jail, capmask);
389
390  std::vector<char*> args;
391  for (const auto& arg : argv) {
392    args.push_back(const_cast<char*>(arg.c_str()));
393  }
394  args.push_back(nullptr);
395
396  int status;
397  bool ran = m->RunSyncAndDestroy(jail, args, &status);
398  return ran ? status : -1;
399}
400
401}  // namespace firewalld
402