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