iptables.cc revision 398b5cf3626b312ed68d6fffc484daa6c6647415
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 argv.push_back("-w"); // Wait for xtables lock. 224 225 // Use CAP_NET_ADMIN|CAP_NET_RAW. 226 return ExecvNonRoot(argv, kIpTablesCapMask) == 0; 227} 228 229bool IpTables::DeleteAcceptRule(const std::string& executable_path, 230 ProtocolEnum protocol, 231 uint16_t port, 232 const std::string& interface) { 233 std::vector<std::string> argv; 234 argv.push_back(executable_path); 235 argv.push_back("-D"); // delete 236 argv.push_back("INPUT"); 237 argv.push_back("-p"); // protocol 238 argv.push_back(protocol == kProtocolTcp ? "tcp" : "udp"); 239 argv.push_back("--dport"); // destination port 240 argv.push_back(std::to_string(port)); 241 if (interface != "") { 242 argv.push_back("-i"); // interface 243 argv.push_back(interface); 244 } 245 argv.push_back("-j"); 246 argv.push_back("ACCEPT"); 247 argv.push_back("-w"); // Wait for xtables lock. 248 249 // Use CAP_NET_ADMIN|CAP_NET_RAW. 250 return ExecvNonRoot(argv, kIpTablesCapMask) == 0; 251} 252 253bool IpTables::ApplyVpnSetup(const std::vector<std::string>& usernames, 254 const std::string& interface, 255 bool add) { 256 bool return_value = true; 257 258 if (!ApplyRuleForUserTraffic(add)) { 259 LOG(ERROR) << (add ? "Adding" : "Removing") 260 << " rule for user traffic failed"; 261 if (add) 262 return false; 263 return_value = false; 264 } 265 266 if (!ApplyMasquerade(interface, add)) { 267 LOG(ERROR) << (add ? "Adding" : "Removing") 268 << " masquerade failed for interface " 269 << interface; 270 if (add) { 271 ApplyRuleForUserTraffic(false); 272 return false; 273 } 274 return_value = false; 275 } 276 277 std::vector<std::string> added_usernames; 278 for (const auto& username : usernames) { 279 if (!ApplyMarkForUserTraffic(username, add)) { 280 LOG(ERROR) << (add ? "Adding" : "Removing") 281 << " mark failed for user " 282 << username; 283 if (add) { 284 ApplyVpnSetup(added_usernames, interface, false); 285 return false; 286 } 287 return_value = false; 288 } 289 if (add) { 290 added_usernames.push_back(username); 291 } 292 } 293 294 return return_value; 295} 296 297bool IpTables::ApplyMasquerade(const std::string& interface, bool add) { 298 std::vector<std::string> argv; 299 argv.push_back(kIpTablesPath); 300 argv.push_back("-t"); // table 301 argv.push_back("nat"); 302 argv.push_back(add ? "-A" : "-D"); // rule 303 argv.push_back("POSTROUTING"); 304 argv.push_back("-o"); // output interface 305 argv.push_back(interface); 306 argv.push_back("-j"); 307 argv.push_back("MASQUERADE"); 308 309 // Use CAP_NET_ADMIN|CAP_NET_RAW. 310 return ExecvNonRoot(argv, kIpTablesCapMask) == 0; 311} 312 313bool IpTables::ApplyMarkForUserTraffic(const std::string& user_name, 314 bool add) { 315 std::vector<std::string> argv; 316 argv.push_back(kIpTablesPath); 317 argv.push_back("-t"); // table 318 argv.push_back("mangle"); 319 argv.push_back(add ? "-A" : "-D"); // rule 320 argv.push_back("OUTPUT"); 321 argv.push_back("-m"); 322 argv.push_back("owner"); 323 argv.push_back("--uid-owner"); 324 argv.push_back(user_name); 325 argv.push_back("-j"); 326 argv.push_back("MARK"); 327 argv.push_back("--set-mark"); 328 argv.push_back(kMarkForUserTraffic); 329 330 // Use CAP_NET_ADMIN|CAP_NET_RAW. 331 return ExecvNonRoot(argv, kIpTablesCapMask) == 0; 332} 333 334bool IpTables::ApplyRuleForUserTraffic(bool add) { 335 chromeos::ProcessImpl ip; 336 ip.AddArg(kIpPath); 337 ip.AddArg("rule"); 338 ip.AddArg(add ? "add" : "delete"); 339 ip.AddArg("fwmark"); 340 ip.AddArg(kMarkForUserTraffic); 341 ip.AddArg("table"); 342 ip.AddArg(kTableIdForUserTraffic); 343 344 return ip.Run() == 0; 345} 346 347int IpTables::ExecvNonRoot(const std::vector<std::string>& argv, 348 uint64_t capmask) { 349 chromeos::Minijail* m = chromeos::Minijail::GetInstance(); 350 minijail* jail = m->New(); 351 m->DropRoot(jail, kUnprivilegedUser, kUnprivilegedUser); 352 m->UseCapabilities(jail, capmask); 353 354 std::vector<char*> args; 355 for (const auto& arg : argv) { 356 args.push_back(const_cast<char*>(arg.c_str())); 357 } 358 args.push_back(nullptr); 359 360 int status; 361 bool ran = m->RunSyncAndDestroy(jail, args, &status); 362 return ran ? status : -1; 363} 364 365} // namespace firewalld 366