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