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