iptables.cc revision 6c733cf77b78062afd7d70eb68f8832d77362086
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 <brillo/minijail/minijail.h> 27#include <brillo/process.h> 28 29namespace { 30#if defined(__ANDROID__) 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 // __ANDROID__ 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 and periods are also permitted). 57 if (iface.length() >= kInterfaceNameSize) { 58 return false; 59 } 60 if (base::StartsWith(iface, "-", base::CompareCase::SENSITIVE) || 61 base::EndsWith(iface, "-", base::CompareCase::SENSITIVE) || 62 base::StartsWith(iface, ".", base::CompareCase::SENSITIVE) || 63 base::EndsWith(iface, ".", base::CompareCase::SENSITIVE)) { 64 return false; 65 } 66 for (auto c : iface) { 67 if (!std::isalnum(c) && (c != '-') && (c != '.')) { 68 return false; 69 } 70 } 71 return true; 72} 73} // namespace 74 75namespace firewalld { 76 77IpTables::IpTables() { 78#if defined(__ANDROID__) 79 ip6_enabled_ = false; 80#endif // __ANDROID__ 81} 82 83IpTables::~IpTables() { 84 // Plug all holes when destructed. 85 PlugAllHoles(); 86} 87 88bool IpTables::PunchTcpHole(uint16_t in_port, const std::string& in_interface) { 89 return PunchHole(in_port, in_interface, &tcp_holes_, kProtocolTcp); 90} 91 92bool IpTables::PunchUdpHole(uint16_t in_port, const std::string& in_interface) { 93 return PunchHole(in_port, in_interface, &udp_holes_, kProtocolUdp); 94} 95 96bool IpTables::PlugTcpHole(uint16_t in_port, const std::string& in_interface) { 97 return PlugHole(in_port, in_interface, &tcp_holes_, kProtocolTcp); 98} 99 100bool IpTables::PlugUdpHole(uint16_t in_port, const std::string& in_interface) { 101 return PlugHole(in_port, in_interface, &udp_holes_, kProtocolUdp); 102} 103 104bool IpTables::RequestVpnSetup(const std::vector<std::string>& usernames, 105 const std::string& interface) { 106 return ApplyVpnSetup(usernames, interface, true /* add */); 107} 108 109bool IpTables::RemoveVpnSetup(const std::vector<std::string>& usernames, 110 const std::string& interface) { 111 return ApplyVpnSetup(usernames, interface, false /* delete */); 112} 113 114bool IpTables::PunchHole(uint16_t port, 115 const std::string& interface, 116 std::set<Hole>* holes, 117 ProtocolEnum protocol) { 118 if (port == 0) { 119 // Port 0 is not a valid TCP/UDP port. 120 return false; 121 } 122 123 if (!IsValidInterfaceName(interface)) { 124 LOG(ERROR) << "Invalid interface name '" << interface << "'"; 125 return false; 126 } 127 128 Hole hole = std::make_pair(port, interface); 129 if (holes->find(hole) != holes->end()) { 130 // We have already punched a hole for |port| on |interface|. 131 // Be idempotent: do nothing and succeed. 132 return true; 133 } 134 135 std::string sprotocol = protocol == kProtocolTcp ? "TCP" : "UDP"; 136 LOG(INFO) << "Punching hole for " << sprotocol << " port " << port 137 << " on interface '" << interface << "'"; 138 if (!AddAcceptRules(protocol, port, interface)) { 139 // If the 'iptables' command fails, this method fails. 140 LOG(ERROR) << "Adding ACCEPT rules failed"; 141 return false; 142 } 143 144 // Track the hole we just punched. 145 holes->insert(hole); 146 147 return true; 148} 149 150bool IpTables::PlugHole(uint16_t port, 151 const std::string& interface, 152 std::set<Hole>* holes, 153 ProtocolEnum protocol) { 154 if (port == 0) { 155 // Port 0 is not a valid TCP/UDP port. 156 return false; 157 } 158 159 Hole hole = std::make_pair(port, interface); 160 161 if (holes->find(hole) == holes->end()) { 162 // There is no firewall hole for |port| on |interface|. 163 // Even though this makes |PlugHole| not idempotent, 164 // and Punch/Plug not entirely symmetrical, fail. It might help catch bugs. 165 return false; 166 } 167 168 std::string sprotocol = protocol == kProtocolTcp ? "TCP" : "UDP"; 169 LOG(INFO) << "Plugging hole for " << sprotocol << " port " << port 170 << " on interface '" << interface << "'"; 171 if (!DeleteAcceptRules(protocol, port, interface)) { 172 // If the 'iptables' command fails, this method fails. 173 LOG(ERROR) << "Deleting ACCEPT rules failed"; 174 return false; 175 } 176 177 // Stop tracking the hole we just plugged. 178 holes->erase(hole); 179 180 return true; 181} 182 183void IpTables::PlugAllHoles() { 184 // Copy the container so that we can remove elements from the original. 185 // TCP 186 std::set<Hole> holes = tcp_holes_; 187 for (auto hole : holes) { 188 PlugHole(hole.first /* port */, hole.second /* interface */, &tcp_holes_, 189 kProtocolTcp); 190 } 191 192 // UDP 193 holes = udp_holes_; 194 for (auto hole : holes) { 195 PlugHole(hole.first /* port */, hole.second /* interface */, &udp_holes_, 196 kProtocolUdp); 197 } 198 199 CHECK(tcp_holes_.size() == 0) << "Failed to plug all TCP holes."; 200 CHECK(udp_holes_.size() == 0) << "Failed to plug all UDP holes."; 201} 202 203bool IpTables::AddAcceptRules(ProtocolEnum protocol, 204 uint16_t port, 205 const std::string& interface) { 206 if (!AddAcceptRule(kIpTablesPath, protocol, port, interface)) { 207 LOG(ERROR) << "Could not add ACCEPT rule using '" << kIpTablesPath << "'"; 208 return false; 209 } 210 211 if (AddAcceptRule(kIp6TablesPath, protocol, port, interface)) { 212 // This worked, record this fact and insist that it works thereafter. 213 ip6_enabled_ = true; 214 } else if (ip6_enabled_) { 215 // It's supposed to work, fail. 216 LOG(ERROR) << "Could not add ACCEPT rule using '" << kIp6TablesPath 217 << "', aborting operation"; 218 DeleteAcceptRule(kIpTablesPath, protocol, port, interface); 219 return false; 220 } else { 221 // It never worked, just ignore it. 222 LOG(WARNING) << "Could not add ACCEPT rule using '" << kIp6TablesPath 223 << "', ignoring"; 224 } 225 226 return true; 227} 228 229bool IpTables::DeleteAcceptRules(ProtocolEnum protocol, 230 uint16_t port, 231 const std::string& interface) { 232 bool ip4_success = DeleteAcceptRule(kIpTablesPath, protocol, port, 233 interface); 234 bool ip6_success = !ip6_enabled_ || DeleteAcceptRule(kIp6TablesPath, protocol, 235 port, interface); 236 return ip4_success && ip6_success; 237} 238 239bool IpTables::AddAcceptRule(const std::string& executable_path, 240 ProtocolEnum protocol, 241 uint16_t port, 242 const std::string& interface) { 243 std::vector<std::string> argv; 244 argv.push_back(executable_path); 245 argv.push_back("-I"); // insert 246 argv.push_back("INPUT"); 247 argv.push_back("-p"); // protocol 248 argv.push_back(protocol == kProtocolTcp ? "tcp" : "udp"); 249 argv.push_back("--dport"); // destination port 250 argv.push_back(std::to_string(port)); 251 if (!interface.empty()) { 252 argv.push_back("-i"); // interface 253 argv.push_back(interface); 254 } 255 argv.push_back("-j"); 256 argv.push_back("ACCEPT"); 257 argv.push_back("-w"); // Wait for xtables lock. 258 259 // Use CAP_NET_ADMIN|CAP_NET_RAW. 260 return ExecvNonRoot(argv, kIpTablesCapMask) == 0; 261} 262 263bool IpTables::DeleteAcceptRule(const std::string& executable_path, 264 ProtocolEnum protocol, 265 uint16_t port, 266 const std::string& interface) { 267 std::vector<std::string> argv; 268 argv.push_back(executable_path); 269 argv.push_back("-D"); // delete 270 argv.push_back("INPUT"); 271 argv.push_back("-p"); // protocol 272 argv.push_back(protocol == kProtocolTcp ? "tcp" : "udp"); 273 argv.push_back("--dport"); // destination port 274 argv.push_back(std::to_string(port)); 275 if (interface != "") { 276 argv.push_back("-i"); // interface 277 argv.push_back(interface); 278 } 279 argv.push_back("-j"); 280 argv.push_back("ACCEPT"); 281 argv.push_back("-w"); // Wait for xtables lock. 282 283 // Use CAP_NET_ADMIN|CAP_NET_RAW. 284 return ExecvNonRoot(argv, kIpTablesCapMask) == 0; 285} 286 287bool IpTables::ApplyMasquerade46(const std::string& interface, bool add) { 288 bool return_value = true; 289 290 if (!ApplyMasquerade(kIpTablesPath, interface, add)) { 291 LOG(ERROR) << (add ? "Adding" : "Removing") 292 << " masquerade failed for interface " << interface 293 << " using '" << kIpTablesPath << "'"; 294 return_value = false; 295 if (add) 296 return false; 297 } 298 if (!ApplyMasquerade(kIp6TablesPath, interface, add)) { 299 LOG(ERROR) << (add ? "Adding" : "Removing") 300 << " masquerade failed for interface " << interface 301 << " using '" << kIp6TablesPath << "'"; 302 return_value = false; 303 } 304 return return_value; 305} 306 307bool IpTables::ApplyMarkForUserTraffic46(const std::string& username, 308 bool add) { 309 bool return_value = true; 310 311 if (!ApplyMarkForUserTraffic(kIpTablesPath, username, add)) { 312 LOG(ERROR) << (add ? "Adding" : "Removing") 313 << " mark failed for user " << username 314 << " using '" << kIpTablesPath << "'"; 315 return_value = false; 316 if (add) 317 return false; 318 } 319 if (!ApplyMarkForUserTraffic(kIp6TablesPath, username, add)) { 320 LOG(ERROR) << (add ? "Adding" : "Removing") 321 << " mark failed for user " << username 322 << " using '" << kIp6TablesPath << "'"; 323 return_value = false; 324 } 325 return return_value; 326} 327 328bool IpTables::ApplyVpnSetup(const std::vector<std::string>& usernames, 329 const std::string& interface, 330 bool add) { 331 bool return_value = true; 332 std::vector<std::string> added_usernames; 333 334 if (!ApplyRuleForUserTraffic(kIPv4, add)) { 335 LOG(ERROR) << (add ? "Adding" : "Removing") 336 << " rule for IPv4 user traffic failed"; 337 if (add) 338 return false; 339 return_value = false; 340 } 341 342 if (!ApplyRuleForUserTraffic(kIPv6, add)) { 343 LOG(ERROR) << (add ? "Adding" : "Removing") 344 << " rule for IPv6 user traffic failed"; 345 if (add) { 346 ApplyVpnSetup(added_usernames, interface, false); 347 return false; 348 } 349 return_value = false; 350 } 351 352 if (!ApplyMasquerade46(interface, add)) { 353 if (add) { 354 ApplyVpnSetup(added_usernames, interface, false); 355 return false; 356 } 357 return_value = false; 358 } 359 360 for (const auto& username : usernames) { 361 if (!ApplyMarkForUserTraffic46(username, add)) { 362 if (add) { 363 ApplyVpnSetup(added_usernames, interface, false); 364 return false; 365 } 366 return_value = false; 367 } 368 if (add) { 369 added_usernames.push_back(username); 370 } 371 } 372 373 return return_value; 374} 375 376bool IpTables::ApplyMasquerade(const std::string& executable_path, 377 const std::string& interface, 378 bool add) { 379 std::vector<std::string> argv; 380 argv.push_back(executable_path); 381 argv.push_back("-t"); // table 382 argv.push_back("nat"); 383 argv.push_back(add ? "-A" : "-D"); // rule 384 argv.push_back("POSTROUTING"); 385 argv.push_back("-o"); // output interface 386 argv.push_back(interface); 387 argv.push_back("-j"); 388 argv.push_back("MASQUERADE"); 389 390 // Use CAP_NET_ADMIN|CAP_NET_RAW. 391 return ExecvNonRoot(argv, kIpTablesCapMask) == 0; 392} 393 394bool IpTables::ApplyMarkForUserTraffic(const std::string& executable_path, 395 const std::string& user_name, 396 bool add) { 397 std::vector<std::string> argv; 398 argv.push_back(executable_path); 399 argv.push_back("-t"); // table 400 argv.push_back("mangle"); 401 argv.push_back(add ? "-A" : "-D"); // rule 402 argv.push_back("OUTPUT"); 403 argv.push_back("-m"); 404 argv.push_back("owner"); 405 argv.push_back("--uid-owner"); 406 argv.push_back(user_name); 407 argv.push_back("-j"); 408 argv.push_back("MARK"); 409 argv.push_back("--set-mark"); 410 argv.push_back(kMarkForUserTraffic); 411 412 // Use CAP_NET_ADMIN|CAP_NET_RAW. 413 return ExecvNonRoot(argv, kIpTablesCapMask) == 0; 414} 415 416bool IpTables::ApplyRuleForUserTraffic(IPVersionEnum ip_version, bool add) { 417 brillo::ProcessImpl ip; 418 ip.AddArg(kIpPath); 419 if (ip_version == kIPv6) 420 ip.AddArg("-6"); 421 ip.AddArg("rule"); 422 ip.AddArg(add ? "add" : "delete"); 423 ip.AddArg("fwmark"); 424 ip.AddArg(kMarkForUserTraffic); 425 ip.AddArg("table"); 426 ip.AddArg(kTableIdForUserTraffic); 427 428 return ip.Run() == 0; 429} 430 431int IpTables::ExecvNonRoot(const std::vector<std::string>& argv, 432 uint64_t capmask) { 433 brillo::Minijail* m = brillo::Minijail::GetInstance(); 434 minijail* jail = m->New(); 435#if !defined(__ANDROID__) 436 // TODO(garnold) This needs to be re-enabled once we figure out which 437 // unprivileged user we want to use. 438 m->DropRoot(jail, kUnprivilegedUser, kUnprivilegedUser); 439#endif // __ANDROID__ 440 m->UseCapabilities(jail, capmask); 441 442 std::vector<char*> args; 443 for (const auto& arg : argv) { 444 args.push_back(const_cast<char*>(arg.c_str())); 445 } 446 args.push_back(nullptr); 447 448 int status; 449 bool ran = m->RunSyncAndDestroy(jail, args, &status); 450 return ran ? status : -1; 451} 452 453} // namespace firewalld 454