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