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