iptables.cc revision 40653d0e058ff0f7908b28874224bbb085e99905
1// Copyright 2014 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "firewalld/iptables.h"
6
7#include <string>
8#include <vector>
9
10#include <base/logging.h>
11#include <base/strings/string_number_conversions.h>
12#include <base/strings/stringprintf.h>
13#include <chromeos/process.h>
14
15namespace {
16const char kIpTablesPath[] = "/sbin/iptables";
17const char kIp6TablesPath[] = "/sbin/ip6tables";
18
19const char kIpPath[] = "/bin/ip";
20
21// Interface names must be shorter than 'IFNAMSIZ' chars.
22// See http://man7.org/linux/man-pages/man7/netdevice.7.html
23// 'IFNAMSIZ' is 16 in recent kernels.
24// See http://lxr.free-electrons.com/source/include/uapi/linux/if.h#L26
25const size_t kInterfaceNameSize = 16;
26
27const char kMarkForUserTraffic[] = "1";
28
29const char kTableIdForUserTraffic[] = "1";
30
31bool IsValidInterfaceName(const std::string& iface) {
32  // |iface| should be shorter than |kInterfaceNameSize| chars,
33  // and have only alphanumeric characters.
34  if (iface.length() >= kInterfaceNameSize) {
35    return false;
36  }
37  for (auto c : iface) {
38    if (!std::isalnum(c)) {
39      return false;
40    }
41  }
42  return true;
43}
44}  // namespace
45
46namespace firewalld {
47
48IpTables::IpTables() : IpTables{kIpTablesPath, kIp6TablesPath} {}
49
50IpTables::IpTables(const std::string& ip4_path, const std::string& ip6_path)
51    : ip4_exec_path_{ip4_path}, ip6_exec_path_{ip6_path} {}
52
53IpTables::~IpTables() {
54  // Plug all holes when destructed.
55  PlugAllHoles();
56}
57
58bool IpTables::PunchTcpHole(uint16_t in_port, const std::string& in_interface) {
59  return PunchHole(in_port, in_interface, &tcp_holes_, kProtocolTcp);
60}
61
62bool IpTables::PunchUdpHole(uint16_t in_port, const std::string& in_interface) {
63  return PunchHole(in_port, in_interface, &udp_holes_, kProtocolUdp);
64}
65
66bool IpTables::PlugTcpHole(uint16_t in_port, const std::string& in_interface) {
67  return PlugHole(in_port, in_interface, &tcp_holes_, kProtocolTcp);
68}
69
70bool IpTables::PlugUdpHole(uint16_t in_port, const std::string& in_interface) {
71  return PlugHole(in_port, in_interface, &udp_holes_, kProtocolUdp);
72}
73
74bool IpTables::RequestVpnSetup(const std::vector<std::string>& usernames,
75                               const std::string& interface) {
76  return ApplyVpnSetup(usernames, interface, true /* add */);
77}
78
79bool IpTables::RemoveVpnSetup(const std::vector<std::string>& usernames,
80                              const std::string& interface) {
81  return ApplyVpnSetup(usernames, interface, false /* delete */);
82}
83
84bool IpTables::PunchHole(uint16_t port,
85                         const std::string& interface,
86                         std::set<Hole>* holes,
87                         ProtocolEnum protocol) {
88  if (port == 0) {
89    // Port 0 is not a valid TCP/UDP port.
90    return false;
91  }
92
93  if (!IsValidInterfaceName(interface)) {
94    LOG(ERROR) << "Invalid interface name '" << interface << "'";
95    return false;
96  }
97
98  Hole hole = std::make_pair(port, interface);
99  if (holes->find(hole) != holes->end()) {
100    // We have already punched a hole for |port| on |interface|.
101    // Be idempotent: do nothing and succeed.
102    return true;
103  }
104
105  std::string sprotocol = protocol == kProtocolTcp ? "TCP" : "UDP";
106  LOG(INFO) << "Punching hole for " << sprotocol << " port " << port
107            << " on interface '" << interface << "'";
108  if (!AddAcceptRules(protocol, port, interface)) {
109    // If the 'iptables' command fails, this method fails.
110    LOG(ERROR) << "Adding ACCEPT rules failed";
111    return false;
112  }
113
114  // Track the hole we just punched.
115  holes->insert(hole);
116
117  return true;
118}
119
120bool IpTables::PlugHole(uint16_t port,
121                        const std::string& interface,
122                        std::set<Hole>* holes,
123                        ProtocolEnum protocol) {
124  if (port == 0) {
125    // Port 0 is not a valid TCP/UDP port.
126    return false;
127  }
128
129  Hole hole = std::make_pair(port, interface);
130
131  if (holes->find(hole) == holes->end()) {
132    // There is no firewall hole for |port| on |interface|.
133    // Even though this makes |PlugHole| not idempotent,
134    // and Punch/Plug not entirely symmetrical, fail. It might help catch bugs.
135    return false;
136  }
137
138  std::string sprotocol = protocol == kProtocolTcp ? "TCP" : "UDP";
139  LOG(INFO) << "Plugging hole for " << sprotocol << " port " << port
140            << " on interface '" << interface << "'";
141  if (!DeleteAcceptRules(protocol, port, interface)) {
142    // If the 'iptables' command fails, this method fails.
143    LOG(ERROR) << "Deleting ACCEPT rules failed";
144    return false;
145  }
146
147  // Stop tracking the hole we just plugged.
148  holes->erase(hole);
149
150  return true;
151}
152
153void IpTables::PlugAllHoles() {
154  // Copy the container so that we can remove elements from the original.
155  // TCP
156  std::set<Hole> holes = tcp_holes_;
157  for (auto hole : holes) {
158    PlugHole(hole.first /* port */, hole.second /* interface */, &tcp_holes_,
159             kProtocolTcp);
160  }
161
162  // UDP
163  holes = udp_holes_;
164  for (auto hole : holes) {
165    PlugHole(hole.first /* port */, hole.second /* interface */, &udp_holes_,
166             kProtocolUdp);
167  }
168
169  CHECK(tcp_holes_.size() == 0) << "Failed to plug all TCP holes.";
170  CHECK(udp_holes_.size() == 0) << "Failed to plug all UDP holes.";
171}
172
173bool IpTables::AddAcceptRules(ProtocolEnum protocol,
174                              uint16_t port,
175                              const std::string& interface) {
176  if (!AddAcceptRule(ip4_exec_path_, protocol, port, interface)) {
177    LOG(ERROR) << "Could not add ACCEPT rule using '" << ip4_exec_path_ << "'";
178    return false;
179  }
180  if (!AddAcceptRule(ip6_exec_path_, protocol, port, interface)) {
181    LOG(ERROR) << "Could not add ACCEPT rule using '" << ip6_exec_path_ << "'";
182    DeleteAcceptRule(ip4_exec_path_, protocol, port, interface);
183    return false;
184  }
185  return true;
186}
187
188bool IpTables::DeleteAcceptRules(ProtocolEnum protocol,
189                                 uint16_t port,
190                                 const std::string& interface) {
191  bool ip4_success = DeleteAcceptRule(ip4_exec_path_, protocol, port,
192                                      interface);
193  bool ip6_success = DeleteAcceptRule(ip6_exec_path_, protocol, port,
194                                      interface);
195  return ip4_success && ip6_success;
196}
197
198bool IpTables::AddAcceptRule(const std::string& executable_path,
199                             ProtocolEnum protocol,
200                             uint16_t port,
201                             const std::string& interface) {
202  chromeos::ProcessImpl iptables;
203  iptables.AddArg(executable_path);
204  iptables.AddArg("-I");  // insert
205  iptables.AddArg("INPUT");
206  iptables.AddArg("-p");  // protocol
207  iptables.AddArg(protocol == kProtocolTcp ? "tcp" : "udp");
208  iptables.AddArg("--dport");  // destination port
209  iptables.AddArg(std::to_string(port));
210  if (!interface.empty()) {
211    iptables.AddArg("-i");  // interface
212    iptables.AddArg(interface);
213  }
214  iptables.AddArg("-j");
215  iptables.AddArg("ACCEPT");
216
217  return iptables.Run() == 0;
218}
219
220bool IpTables::DeleteAcceptRule(const std::string& executable_path,
221                                ProtocolEnum protocol,
222                                uint16_t port,
223                                const std::string& interface) {
224  chromeos::ProcessImpl iptables;
225  iptables.AddArg(executable_path);
226  iptables.AddArg("-D");  // delete
227  iptables.AddArg("INPUT");
228  iptables.AddArg("-p");  // protocol
229  iptables.AddArg(protocol == kProtocolTcp ? "tcp" : "udp");
230  iptables.AddArg("--dport");  // destination port
231  std::string port_number = base::StringPrintf("%d", port);
232  iptables.AddArg(port_number.c_str());
233  if (interface != "") {
234    iptables.AddArg("-i");  // interface
235    iptables.AddArg(interface);
236  }
237  iptables.AddArg("-j");
238  iptables.AddArg("ACCEPT");
239
240  return iptables.Run() == 0;
241}
242
243bool IpTables::ApplyVpnSetup(const std::vector<std::string>& usernames,
244                             const std::string& interface,
245                             bool add) {
246  bool return_value = true;
247
248  if (!ApplyRuleForUserTraffic(add)) {
249    LOG(ERROR) << (add ? "Adding" : "Removing")
250               << " rule for user traffic failed";
251    if (add)
252      return false;
253    return_value = false;
254  }
255
256  if (!ApplyMasquerade(interface, add)) {
257    LOG(ERROR) << (add ? "Adding" : "Removing")
258               << " masquerade failed for interface "
259               << interface;
260    if (add) {
261      ApplyRuleForUserTraffic(false);
262      return false;
263    }
264    return_value = false;
265  }
266
267  std::vector<std::string> added_usernames;
268  for (const auto& username : usernames) {
269    if (!ApplyMarkForUserTraffic(username, add)) {
270      LOG(ERROR) << (add ? "Adding" : "Removing")
271                 << " mark failed for user "
272                 << username;
273      if (add) {
274        ApplyVpnSetup(added_usernames, interface, false);
275        return false;
276      }
277      return_value = false;
278    }
279    if (add) {
280      added_usernames.push_back(username);
281    }
282  }
283
284  return return_value;
285}
286
287bool IpTables::ApplyMasquerade(const std::string& interface, bool add) {
288  chromeos::ProcessImpl iptables;
289  iptables.AddArg(ip4_exec_path_);
290  iptables.AddArg("-t");  // table
291  iptables.AddArg("nat");
292  iptables.AddArg(add ? "-A" : "-D");  // rule
293  iptables.AddArg("POSTROUTING");
294  iptables.AddArg("-o");  // output interface
295  iptables.AddArg(interface);
296  iptables.AddArg("-j");
297  iptables.AddArg("MASQUERADE");
298
299  return iptables.Run() == 0;
300}
301
302bool IpTables::ApplyMarkForUserTraffic(const std::string& user_name,
303                                       bool add) {
304  chromeos::ProcessImpl iptables;
305  iptables.AddArg(ip4_exec_path_);
306  iptables.AddArg("-t");  // table
307  iptables.AddArg("mangle");
308  iptables.AddArg(add ? "-A" : "-D");  // rule
309  iptables.AddArg("OUTPUT");
310  iptables.AddArg("-m");
311  iptables.AddArg("owner");
312  iptables.AddArg("--uid-owner");
313  iptables.AddArg(user_name);
314  iptables.AddArg("-j");
315  iptables.AddArg("MARK");
316  iptables.AddArg("--set-mark");
317  iptables.AddArg(kMarkForUserTraffic);
318
319  return iptables.Run() == 0;
320}
321
322bool IpTables::ApplyRuleForUserTraffic(bool add) {
323  chromeos::ProcessImpl ip;
324  ip.AddArg(kIpPath);
325  ip.AddArg("rule");
326  ip.AddArg(add ? "add" : "delete");
327  ip.AddArg("fwmark");
328  ip.AddArg(kMarkForUserTraffic);
329  ip.AddArg("table");
330  ip.AddArg(kTableIdForUserTraffic);
331
332  return ip.Run() == 0;
333}
334
335}  // namespace firewalld
336