diff --git a/base/cvd/allocd/BUILD.bazel b/base/cvd/allocd/BUILD.bazel index a2812d472d3..e8c18402a47 100644 --- a/base/cvd/allocd/BUILD.bazel +++ b/base/cvd/allocd/BUILD.bazel @@ -1,5 +1,5 @@ load("@bazel_skylib//rules:common_settings.bzl", "string_flag") -load("//cuttlefish/bazel:rules.bzl", "cf_cc_binary", "cf_cc_library") +load("//cuttlefish/bazel:rules.bzl", "cf_cc_binary", "cf_cc_library", "cf_cc_test") package( default_visibility = ["//:android_cuttlefish"], @@ -33,8 +33,9 @@ cf_cc_library( depend_on_what_you_use_enabled = False, deps = select({ ":netlink": [":alloc_netlink"], - "//conditions:default": [":alloc_iproute2"], + "//conditions:default": [":alloc_netlink"], }) + [ + "//allocd/net:nftables", "//cuttlefish/common/libs/fs", "//cuttlefish/common/libs/utils:files", "//cuttlefish/common/libs/utils:network", @@ -80,3 +81,13 @@ cf_cc_library( "@abseil-cpp//absl/strings:str_format", ], ) + +cf_cc_test( + name = "alloc_utils_firewall_test", + srcs = ["alloc_utils_firewall_test.cpp"], + deps = [ + ":alloc_utils", + "//allocd/test:mock_nftables", + "//cuttlefish/result:result_matchers", + ], +) diff --git a/base/cvd/allocd/alloc_driver.h b/base/cvd/allocd/alloc_driver.h index 421ccea6653..2e42b582b4d 100644 --- a/base/cvd/allocd/alloc_driver.h +++ b/base/cvd/allocd/alloc_driver.h @@ -33,9 +33,6 @@ Result LinkTapToBridge(std::string_view tap_name, Result DeleteIface(std::string_view name); Result BridgeInUse(std::string_view name); Result BridgeExists(std::string_view name); -Result BridgeInUse(std::string_view name); Result CreateBridge(std::string_view name); -Result IptableConfig(std::string_view iptables_path, - std::string_view network, bool add); } // namespace cuttlefish diff --git a/base/cvd/allocd/alloc_iproute2.cpp b/base/cvd/allocd/alloc_iproute2.cpp index 8df0408c605..e69bb587e13 100644 --- a/base/cvd/allocd/alloc_iproute2.cpp +++ b/base/cvd/allocd/alloc_iproute2.cpp @@ -93,13 +93,4 @@ Result CreateBridge(std::string_view name) { return {}; } -Result IptableConfig(std::string_view iptables_path, - std::string_view network, bool add) { - CF_EXPECT(Execute({std::string(iptables_path), "-t", "nat", add ? "-A" : "-D", - "POSTROUTING", "-s", std::string(network), "-j", - "MASQUERADE"}) == 0, - "IptableConfig"); - return {}; -} - } // namespace cuttlefish diff --git a/base/cvd/allocd/alloc_netlink.cpp b/base/cvd/allocd/alloc_netlink.cpp index 454cb037fcd..fccffa81142 100644 --- a/base/cvd/allocd/alloc_netlink.cpp +++ b/base/cvd/allocd/alloc_netlink.cpp @@ -240,14 +240,4 @@ Result CreateBridge(std::string_view name) { return {}; } -Result IptableConfig(std::string_view iptables_path, - std::string_view network, bool add) { - // TODO: Use NETLINK_NETFILTER. - CF_EXPECT(Execute({std::string(iptables_path), "-t", "nat", add ? "-A" : "-D", - "POSTROUTING", "-s", std::string(network), "-j", - "MASQUERADE"}) == 0, - "IptableConfig"); - return {}; -} - } // namespace cuttlefish diff --git a/base/cvd/allocd/alloc_utils.cpp b/base/cvd/allocd/alloc_utils.cpp index d0a6ca4a9fa..ede166027d7 100644 --- a/base/cvd/allocd/alloc_utils.cpp +++ b/base/cvd/allocd/alloc_utils.cpp @@ -27,7 +27,6 @@ #include #include -#include "absl/base/no_destructor.h" #include "absl/log/log.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" @@ -48,16 +47,78 @@ namespace cuttlefish { namespace { -Result SearchForIptables() { - Result p = Search(Path(), "iptables"); - if (p.ok()) { - return p; +// nftables address families. +constexpr std::string_view kFamilyIp = "ip"; +constexpr std::string_view kFamilyBridge = "bridge"; + +// nftables table names. +constexpr std::string_view kNatTable = "cuttlefish_nat"; +constexpr std::string_view kBridgeTable = "cuttlefish_bridge"; + +// nftables chain names. +constexpr std::string_view kPostroutingChain = "postrouting"; +constexpr std::string_view kPreroutingChain = "prerouting"; +constexpr std::string_view kForwardChain = "forward"; + +// Base chain hook definitions, supplied when the chains are created. +constexpr std::string_view kNatPostroutingChainDef = + "{ type nat hook postrouting priority 100 ; }"; +constexpr std::string_view kBridgePreroutingChainDef = + "{ type filter hook prerouting priority -250 ; }"; +constexpr std::string_view kBridgeForwardChainDef = + "{ type filter hook forward priority 0 ; }"; + +// The /30 netmask carving each mobile interface into a point-to-point subnet. +constexpr std::string_view kMobileNetmask = "/30"; + +// Bridge subnets that receive a permanent masquerade rule during setup. +constexpr std::string_view kBridgeSubnets[] = { + "192.168.96.0/24", + "192.168.98.0/24", + "192.168.160.0/24", + "192.168.192.0/24", +}; + +// Builds the nftables expression masquerading traffic sourced from `source`, +// an address or CIDR subnet. +std::string MasqueradeRule(std::string_view source) { + return absl::StrCat("ip saddr ", source, " masquerade"); +} + +} // namespace + +Result SetupFirewall(Nftables& nft, bool setup_byob) { + CF_EXPECT(nft.EnsureTable(kFamilyIp, kNatTable)); + CF_EXPECT(nft.EnsureChain(kFamilyIp, kNatTable, kPostroutingChain, + kNatPostroutingChainDef)); + + for (std::string_view subnet : kBridgeSubnets) { + CF_EXPECT(nft.AddRule(kFamilyIp, kNatTable, kPostroutingChain, + MasqueradeRule(subnet))); + } + + if (setup_byob) { + CF_EXPECT(nft.EnsureTable(kFamilyBridge, kBridgeTable)); + CF_EXPECT(nft.EnsureChain(kFamilyBridge, kBridgeTable, kPreroutingChain, + kBridgePreroutingChainDef)); + CF_EXPECT(nft.EnsureChain(kFamilyBridge, kBridgeTable, kForwardChain, + kBridgeForwardChainDef)); } - return CF_EXPECT(Search({"/usr/sbin", "/sbin"}, "iptables")); + return {}; } -} // namespace +Result TeardownFirewall(Nftables& nft) { + auto res = nft.DeleteTable(kFamilyIp, kNatTable); + if (!res.ok()) { + LOG(WARNING) << "Failed to delete " << kNatTable + << " table: " << res.error(); + } + + (void)nft.DeleteTable(kFamilyBridge, kBridgeTable); + + return {}; +} bool CreateEthernetIface(std::string_view name, std::string_view bridge_name) { // assume bridge exists @@ -87,56 +148,43 @@ std::string MobileNetworkName(std::string_view ipaddr, std::string_view netmask, return ss.str(); } -bool CreateMobileIface(std::string_view name, uint16_t id, - std::string_view ipaddr) { - if (id > kMaxIfaceNameId) { - LOG(ERROR) << "ID exceeds maximum value to assign a netmask: " << id; - return false; - } - - auto netmask = "/30"; - Result iptables_path = IptablesPath(); - if (!iptables_path.ok()) { - return false; - } +Result CreateMobileIface(Nftables& nft, std::string_view name, + uint16_t id, std::string_view ipaddr) { + CF_EXPECTF(id <= kMaxIfaceNameId, + "ID exceeds maximum value to assign a netmask: {}", id); auto gateway = MobileGatewayName(ipaddr, id); - auto network = MobileNetworkName(ipaddr, netmask, id); + auto network = MobileNetworkName(ipaddr, kMobileNetmask, id); - if (!CreateTap(name)) { - return false; - } + CF_EXPECTF(CreateTap(name), "Failed to create tap interface: {}", name); - if (!AddGateway(name, gateway, netmask).ok()) { - DestroyIface(name); + if (!AddGateway(name, gateway, kMobileNetmask).ok()) { + (void)DestroyIface(name); + return CF_ERRF("Failed to add gateway to interface: {}", name); } - if (!IptableConfig(*iptables_path, network, true).ok()) { - (void)DestroyGateway(name, gateway, netmask); + auto rule = NftRule::Create(nft, kFamilyIp, kNatTable, kPostroutingChain, + MasqueradeRule(network)); + if (!rule.ok()) { + (void)DestroyGateway(name, gateway, kMobileNetmask); (void)DestroyIface(name); - return false; - }; + return CF_ERRF("Failed to create NftRule for interface {}: {}", name, + rule.error()); + } - return true; + return rule; } bool DestroyMobileIface(std::string_view name, uint16_t id, std::string_view ipaddr) { - if (id > 63) { + if (id > kMaxIfaceNameId) { LOG(ERROR) << "ID exceeds maximum value to assign a netmask: " << id; return false; } - auto netmask = "/30"; auto gateway = MobileGatewayName(ipaddr, id); - auto network = MobileNetworkName(ipaddr, netmask, id); - Result iptables_path = IptablesPath(); - if (!iptables_path.ok()) { - return false; - } - (void)IptableConfig(*iptables_path, network, false); - (void)DestroyGateway(name, gateway, netmask); + (void)DestroyGateway(name, gateway, kMobileNetmask); return DestroyIface(name); } @@ -226,15 +274,9 @@ bool DestroyBridge(std::string_view name) { } bool SetupBridgeGateway(std::string_view bridge_name, std::string_view ipaddr) { - Result iptables_path = IptablesPath(); - if (!iptables_path.ok()) { - return false; - } - - GatewayConfig config{false, false, false}; + GatewayConfig config{false, false}; auto gateway = absl::StrFormat("%s.1", ipaddr); auto netmask = "/24"; - auto network = absl::StrFormat("%s.0%s", ipaddr, netmask); auto dhcp_range = absl::StrFormat("%s.2,%s.255", ipaddr, ipaddr); if (!AddGateway(bridge_name, gateway, netmask).ok()) { @@ -248,30 +290,13 @@ bool SetupBridgeGateway(std::string_view bridge_name, std::string_view ipaddr) { return false; } - config.has_dnsmasq = true; - - auto ret = IptableConfig(*iptables_path, network, true).ok(); - if (!ret) { - CleanupBridgeGateway(bridge_name, ipaddr, config); - LOG(WARNING) << "Failed to setup ip tables"; - } - - return ret; + return true; } void CleanupBridgeGateway(std::string_view name, std::string_view ipaddr, const GatewayConfig& config) { auto gateway = absl::StrFormat("%s.1", ipaddr); auto netmask = "/24"; - auto network = absl::StrFormat("%s.0%s", ipaddr, netmask); - auto dhcp_range = absl::StrFormat("%s.2,%s.255", ipaddr, ipaddr); - - if (config.has_iptable) { - Result iptables_path = IptablesPath(); - if (iptables_path.ok()) { - (void)IptableConfig(*iptables_path, network, false); - } - } if (config.has_dnsmasq) { StopDnsmasq(name); @@ -349,7 +374,7 @@ bool CreateEthernetBridgeIface(std::string_view name, std::string_view ipaddr) { bool DestroyEthernetBridgeIface(std::string_view name, std::string_view ipaddr) { - GatewayConfig config{true, true, true}; + GatewayConfig config{true, true}; // Don't need to check if removing some part of the config failed, we need to // remove the entire interface, so just ignore any error until the end @@ -358,12 +383,4 @@ bool DestroyEthernetBridgeIface(std::string_view name, return DestroyBridge(name); } -Result IptablesPath() { - static const absl::NoDestructor iptables_path( - SearchForIptables().value_or("")); - - CF_EXPECT(!iptables_path->empty(), "could not find iptables"); - return *iptables_path; -} - } // namespace cuttlefish diff --git a/base/cvd/allocd/alloc_utils.h b/base/cvd/allocd/alloc_utils.h index 471833c9c38..225fbf1438c 100644 --- a/base/cvd/allocd/alloc_utils.h +++ b/base/cvd/allocd/alloc_utils.h @@ -25,6 +25,8 @@ #include #include +#include "allocd/net/nft_rule.h" +#include "allocd/net/nftables.h" #include "cuttlefish/result/result.h" namespace cuttlefish { @@ -48,11 +50,9 @@ inline constexpr uint32_t kMaxIfaceNameId = 63; struct GatewayConfig { bool has_gateway = false; bool has_dnsmasq = false; - bool has_iptable = false; }; int RunExternalCommand(const std::string& command); -Result IptablesPath(); std::optional GetUserName(uid_t uid); bool CreateTap(std::string_view name); @@ -65,8 +65,11 @@ bool DestroyIface(std::string_view name); bool DestroyBridge(std::string_view name); -bool CreateMobileIface(std::string_view name, uint16_t id, - std::string_view ipaddr); +Result SetupFirewall(Nftables& nft, bool setup_byob = false); +Result TeardownFirewall(Nftables& nft); + +Result CreateMobileIface(Nftables& nft, std::string_view name, + uint16_t id, std::string_view ipaddr); bool DestroyMobileIface(std::string_view name, uint16_t id, std::string_view ipaddr); diff --git a/base/cvd/allocd/alloc_utils_firewall_test.cpp b/base/cvd/allocd/alloc_utils_firewall_test.cpp new file mode 100644 index 00000000000..13a88a64d39 --- /dev/null +++ b/base/cvd/allocd/alloc_utils_firewall_test.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "allocd/alloc_utils.h" +#include "allocd/test/mock_nftables.h" +#include "cuttlefish/result/result_matchers.h" + +namespace cuttlefish { +namespace { + +using ::testing::_; +using ::testing::Return; + +TEST(AllocUtilsFirewallTest, SetupFirewallSuccess) { + MockNftables mock_nft; + + EXPECT_CALL(mock_nft, EnsureTable("ip", "cuttlefish_nat")) + .WillOnce(Return(Result{})); + EXPECT_CALL(mock_nft, EnsureChain("ip", "cuttlefish_nat", "postrouting", _)) + .WillOnce(Return(Result{})); + + EXPECT_CALL(mock_nft, AddRule("ip", "cuttlefish_nat", "postrouting", _)) + .Times(4) + .WillRepeatedly(Return(1)); + + EXPECT_THAT(SetupFirewall(mock_nft, /*setup_byob=*/false), IsOk()); +} + +TEST(AllocUtilsFirewallTest, SetupFirewallWithByob) { + MockNftables mock_nft; + + EXPECT_CALL(mock_nft, EnsureTable("ip", "cuttlefish_nat")) + .WillOnce(Return(Result{})); + EXPECT_CALL(mock_nft, EnsureChain("ip", "cuttlefish_nat", "postrouting", _)) + .WillOnce(Return(Result{})); + EXPECT_CALL(mock_nft, AddRule("ip", "cuttlefish_nat", "postrouting", _)) + .Times(4) + .WillRepeatedly(Return(1)); + + EXPECT_CALL(mock_nft, EnsureTable("bridge", "cuttlefish_bridge")) + .WillOnce(Return(Result{})); + EXPECT_CALL(mock_nft, EnsureChain("bridge", "cuttlefish_bridge", "prerouting", _)) + .WillOnce(Return(Result{})); + EXPECT_CALL(mock_nft, EnsureChain("bridge", "cuttlefish_bridge", "forward", _)) + .WillOnce(Return(Result{})); + + EXPECT_THAT(SetupFirewall(mock_nft, /*setup_byob=*/true), IsOk()); +} + +TEST(AllocUtilsFirewallTest, TeardownFirewallDeletesTables) { + MockNftables mock_nft; + + EXPECT_CALL(mock_nft, DeleteTable("ip", "cuttlefish_nat")) + .WillOnce(Return(Result{})); + EXPECT_CALL(mock_nft, DeleteTable("bridge", "cuttlefish_bridge")) + .WillOnce(Return(Result{})); + + EXPECT_THAT(TeardownFirewall(mock_nft), IsOk()); +} + +} // namespace +} // namespace cuttlefish diff --git a/base/cvd/allocd/net/BUILD.bazel b/base/cvd/allocd/net/BUILD.bazel index 41063f391b9..e50c5efe8ec 100644 --- a/base/cvd/allocd/net/BUILD.bazel +++ b/base/cvd/allocd/net/BUILD.bazel @@ -1,4 +1,4 @@ -load("//cuttlefish/bazel:rules.bzl", "cf_cc_binary", "cf_cc_library") +load("//cuttlefish/bazel:rules.bzl", "cf_cc_binary", "cf_cc_library", "cf_cc_test") package( default_visibility = ["//:android_cuttlefish"], @@ -19,3 +19,36 @@ cf_cc_library( "@abseil-cpp//absl/log", ], ) + +cf_cc_library( + name = "nftables", + srcs = [ + "nft_rule.cc", + "nftables_nft.cc", + ], + hdrs = [ + "nft_rule.h", + "nftables.h", + "nftables_nft.h", + ], + deps = [ + "//cuttlefish/common/libs/utils:files", + "//cuttlefish/common/libs/utils:json", + "//cuttlefish/process:command", + "//cuttlefish/process:managed_stdio", + "//cuttlefish/process:subprocess", + "//cuttlefish/result", + "@abseil-cpp//absl/base:no_destructor", + "@abseil-cpp//absl/log", + ], +) + +cf_cc_test( + name = "nft_rule_test", + srcs = ["nft_rule_test.cc"], + deps = [ + ":nftables", + "//allocd/test:mock_nftables", + "//cuttlefish/result:result_matchers", + ], +) diff --git a/base/cvd/allocd/net/nft_rule.cc b/base/cvd/allocd/net/nft_rule.cc new file mode 100644 index 00000000000..aa83e9d36e7 --- /dev/null +++ b/base/cvd/allocd/net/nft_rule.cc @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "allocd/net/nft_rule.h" + +#include + +#include +#include +#include + +#include "absl/log/log.h" +#include "allocd/net/nftables.h" +#include "cuttlefish/result/result.h" + +namespace cuttlefish { + +Result NftRule::Create(Nftables& nft, std::string_view family, + std::string_view table, std::string_view chain, + std::string_view content) { + uint32_t handle = CF_EXPECT(nft.AddRule(family, table, chain, content)); + return NftRule(&nft, family, table, chain, handle); +} + +NftRule::NftRule(Nftables* nft, std::string_view family, std::string_view table, + std::string_view chain, uint32_t handle) + : nft_(nft), + family_(family), + table_(table), + chain_(chain), + handle_(handle) {} + +NftRule::NftRule(NftRule&& r) noexcept + : nft_(std::exchange(r.nft_, nullptr)), + family_(std::move(r.family_)), + table_(std::move(r.table_)), + chain_(std::move(r.chain_)), + handle_(std::exchange(r.handle_, 0)) {} + +NftRule::~NftRule() { + if (nft_ != nullptr && handle_ != 0) { + auto res = nft_->DeleteRule(family_, table_, chain_, handle_); + if (!res.ok()) { + LOG(ERROR) << "Failed to delete nft rule in NftRule destructor: " + << res.error(); + } + } +} + +} // namespace cuttlefish diff --git a/base/cvd/allocd/net/nft_rule.h b/base/cvd/allocd/net/nft_rule.h new file mode 100644 index 00000000000..34b9ca3b5e1 --- /dev/null +++ b/base/cvd/allocd/net/nft_rule.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ALLOCD_NET_NFT_RULE_H_ +#define ALLOCD_NET_NFT_RULE_H_ + +#include + +#include +#include + +#include "allocd/net/nftables.h" +#include "cuttlefish/result/result.h" + +namespace cuttlefish { + +class NftRule { + public: + static Result Create(Nftables& nft, std::string_view family, + std::string_view table, std::string_view chain, + std::string_view content); + + NftRule() = delete; + NftRule(Nftables* nft, std::string_view family, std::string_view table, + std::string_view chain, uint32_t handle); + ~NftRule(); + + NftRule(NftRule&& r) noexcept; + NftRule& operator=(NftRule&& r) = delete; + NftRule(const NftRule& r) = delete; + NftRule& operator=(const NftRule& r) = delete; + + private: + Nftables* nft_ = nullptr; + std::string family_; + std::string table_; + std::string chain_; + uint32_t handle_ = 0; +}; + +} // namespace cuttlefish + +#endif // ALLOCD_NET_NFT_RULE_H_ diff --git a/base/cvd/allocd/net/nft_rule_test.cc b/base/cvd/allocd/net/nft_rule_test.cc new file mode 100644 index 00000000000..7c5d03fee49 --- /dev/null +++ b/base/cvd/allocd/net/nft_rule_test.cc @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "allocd/net/nft_rule.h" + +#include +#include + +#include + +#include "allocd/test/mock_nftables.h" +#include "cuttlefish/result/result_matchers.h" + +namespace cuttlefish { +namespace { + +using ::testing::Eq; +using ::testing::Return; + +TEST(NftRuleTest, CreateAndAutoDeleteOnDestruction) { + MockNftables mock_nft; + constexpr uint32_t kHandle = 42; + + EXPECT_CALL(mock_nft, AddRule("ip", "table1", "chain1", "content1")) + .WillOnce(Return(kHandle)); + EXPECT_CALL(mock_nft, DeleteRule("ip", "table1", "chain1", kHandle)) + .WillOnce(Return(Result{})); + + { + auto rule = NftRule::Create(mock_nft, "ip", "table1", "chain1", "content1"); + EXPECT_THAT(rule, IsOk()); + } +} + +TEST(NftRuleTest, MoveConstructorTransfersOwnership) { + MockNftables mock_nft; + constexpr uint32_t kHandle = 100; + + EXPECT_CALL(mock_nft, AddRule("ip", "table1", "chain1", "content1")) + .WillOnce(Return(kHandle)); + EXPECT_CALL(mock_nft, DeleteRule("ip", "table1", "chain1", kHandle)) + .WillOnce(Return(Result{})); + + { + auto rule1 = NftRule::Create(mock_nft, "ip", "table1", "chain1", "content1"); + ASSERT_THAT(rule1, IsOk()); + + NftRule rule2(std::move(*rule1)); + // When rule1 leaves scope, it should not call DeleteRule. + // Only rule2 leaving scope will call DeleteRule once. + } +} + +} // namespace +} // namespace cuttlefish diff --git a/base/cvd/allocd/net/nftables.h b/base/cvd/allocd/net/nftables.h new file mode 100644 index 00000000000..99799e500ab --- /dev/null +++ b/base/cvd/allocd/net/nftables.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ALLOCD_NET_NFTABLES_H_ +#define ALLOCD_NET_NFTABLES_H_ + +#include + +#include + +#include "cuttlefish/result/result.h" + +namespace cuttlefish { + +class Nftables { + public: + virtual ~Nftables() = default; + + virtual Result EnsureTable(std::string_view family, + std::string_view table) = 0; + virtual Result DeleteTable(std::string_view family, + std::string_view table) = 0; + virtual Result EnsureChain(std::string_view family, + std::string_view table, + std::string_view chain, + std::string_view content) = 0; + virtual Result AddRule(std::string_view family, + std::string_view table, + std::string_view chain, + std::string_view content) = 0; + virtual Result DeleteRule(std::string_view family, + std::string_view table, + std::string_view chain, + uint32_t handle) = 0; +}; + +} // namespace cuttlefish + +#endif // ALLOCD_NET_NFTABLES_H_ diff --git a/base/cvd/allocd/net/nftables_nft.cc b/base/cvd/allocd/net/nftables_nft.cc new file mode 100644 index 00000000000..887e70955a6 --- /dev/null +++ b/base/cvd/allocd/net/nftables_nft.cc @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "allocd/net/nftables_nft.h" + +#include + +#include +#include + +#include "absl/base/no_destructor.h" +#include "absl/log/log.h" +#include "cuttlefish/common/libs/utils/files.h" +#include "cuttlefish/common/libs/utils/json.h" +#include "cuttlefish/process/command.h" +#include "cuttlefish/process/managed_stdio.h" +#include "cuttlefish/process/subprocess.h" +#include "cuttlefish/result/result.h" + +namespace cuttlefish { + +namespace { + +constexpr std::string_view kNftBinary = "nft"; + +// Searches PATH, then the usual sbin locations, for the nft binary. +Result SearchForNft() { + Result p = Search(Path(), std::string(kNftBinary)); + if (p.ok()) { + return p; + } + return CF_EXPECT(Search({"/usr/sbin", "/sbin"}, std::string(kNftBinary)), + "could not find nft binary"); +} + +} // namespace + +Result NftablesNft::BinaryPath() { + static const absl::NoDestructor path( + SearchForNft().value_or("")); + CF_EXPECT(!path->empty(), "could not find nft binary"); + return *path; +} + +Result NftablesNft::EnsureTable(std::string_view family, + std::string_view table) { + Command cmd{std::string(kNftBinary)}; + cmd.AddParameter("add"); + cmd.AddParameter("table"); + cmd.AddParameter(std::string(family)); + cmd.AddParameter(std::string(table)); + + CF_EXPECTF(cmd.Start().Wait() == 0, + "Failed to ensure nft table: family={}, table={}", family, table); + return {}; +} + +Result NftablesNft::DeleteTable(std::string_view family, + std::string_view table) { + Command cmd{std::string(kNftBinary)}; + cmd.AddParameter("delete"); + cmd.AddParameter("table"); + cmd.AddParameter(std::string(family)); + cmd.AddParameter(std::string(table)); + + CF_EXPECTF(cmd.Start().Wait() == 0, + "Failed to delete nft table: family={}, table={}", family, table); + return {}; +} + +Result NftablesNft::EnsureChain(std::string_view family, + std::string_view table, + std::string_view chain, + std::string_view content) { + Command cmd{std::string(kNftBinary)}; + cmd.AddParameter("add"); + cmd.AddParameter("chain"); + cmd.AddParameter(std::string(family)); + cmd.AddParameter(std::string(table)); + cmd.AddParameter(std::string(chain)); + if (!content.empty()) { + cmd.AddParameter(std::string(content)); + } + + CF_EXPECTF( + cmd.Start().Wait() == 0, + "Failed to ensure nft chain: family={}, table={}, chain={}, content={}", + family, table, chain, content); + return {}; +} + +Result NftablesNft::AddRule(std::string_view family, + std::string_view table, + std::string_view chain, + std::string_view content) { + Command cmd{std::string(kNftBinary)}; + cmd.AddParameter("-j"); + cmd.AddParameter("-e"); + cmd.AddParameter("add"); + cmd.AddParameter("rule"); + cmd.AddParameter(std::string(family)); + cmd.AddParameter(std::string(table)); + cmd.AddParameter(std::string(chain)); + cmd.AddParameter(std::string(content)); + + std::string stdout_str = CF_EXPECT(RunAndCaptureStdout(std::move(cmd))); + Json::Value json = CF_EXPECT(ParseJson(stdout_str)); + + CF_EXPECT(json.isMember("nftables") && json["nftables"].isArray(), + "Invalid JSON output from nft: " << stdout_str); + + for (const auto& item : json["nftables"]) { + if (item.isMember("add") && item["add"].isMember("rule") && + item["add"]["rule"].isMember("handle")) { + return item["add"]["rule"]["handle"].asUInt(); + } + } + + return CF_ERR("No rule handle found in nft JSON output: " << stdout_str); +} + +Result NftablesNft::DeleteRule(std::string_view family, + std::string_view table, + std::string_view chain, uint32_t handle) { + Command cmd{std::string(kNftBinary)}; + cmd.AddParameter("delete"); + cmd.AddParameter("rule"); + cmd.AddParameter(std::string(family)); + cmd.AddParameter(std::string(table)); + cmd.AddParameter(std::string(chain)); + cmd.AddParameter("handle"); + cmd.AddParameter(std::to_string(handle)); + + CF_EXPECTF(cmd.Start().Wait() == 0, + "Failed to delete nft rule: family={}, table={}, chain={}, " + "handle={}", + family, table, chain, handle); + return {}; +} + +} // namespace cuttlefish diff --git a/base/cvd/allocd/net/nftables_nft.h b/base/cvd/allocd/net/nftables_nft.h new file mode 100644 index 00000000000..cab69642657 --- /dev/null +++ b/base/cvd/allocd/net/nftables_nft.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ALLOCD_NET_NFTABLES_NFT_H_ +#define ALLOCD_NET_NFTABLES_NFT_H_ + +#include + +#include + +#include "allocd/net/nftables.h" +#include "cuttlefish/result/result.h" + +namespace cuttlefish { + +class NftablesNft : public Nftables { + public: + NftablesNft() = default; + ~NftablesNft() override = default; + + // Returns the resolved path to the `nft` binary, or an error if it is not + // available. Static so callers can probe for nft support without having to + // construct an instance. + static Result BinaryPath(); + + Result EnsureTable(std::string_view family, + std::string_view table) override; + Result DeleteTable(std::string_view family, + std::string_view table) override; + Result EnsureChain(std::string_view family, std::string_view table, + std::string_view chain, + std::string_view content) override; + Result AddRule(std::string_view family, std::string_view table, + std::string_view chain, + std::string_view content) override; + Result DeleteRule(std::string_view family, std::string_view table, + std::string_view chain, uint32_t handle) override; +}; + +} // namespace cuttlefish + +#endif // ALLOCD_NET_NFTABLES_NFT_H_ diff --git a/base/cvd/allocd/test/BUILD.bazel b/base/cvd/allocd/test/BUILD.bazel new file mode 100644 index 00000000000..b4d6b23dec9 --- /dev/null +++ b/base/cvd/allocd/test/BUILD.bazel @@ -0,0 +1,14 @@ +load("//cuttlefish/bazel:rules.bzl", "cf_cc_library") + +package( + default_visibility = ["//:android_cuttlefish"], +) + +cf_cc_library( + name = "mock_nftables", + hdrs = ["mock_nftables.h"], + deps = [ + "//allocd/net:nftables", + "@googletest//:gtest", + ], +) diff --git a/base/cvd/allocd/test/mock_nftables.h b/base/cvd/allocd/test/mock_nftables.h new file mode 100644 index 00000000000..7e134082ad1 --- /dev/null +++ b/base/cvd/allocd/test/mock_nftables.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ALLOCD_TEST_MOCK_NFTABLES_H_ +#define ALLOCD_TEST_MOCK_NFTABLES_H_ + +#include +#include + +#include "allocd/net/nftables.h" + +namespace cuttlefish { + +class MockNftables : public Nftables { + public: + MOCK_METHOD(Result, EnsureTable, + (std::string_view family, std::string_view table), (override)); + MOCK_METHOD(Result, DeleteTable, + (std::string_view family, std::string_view table), (override)); + MOCK_METHOD(Result, EnsureChain, + (std::string_view family, std::string_view table, + std::string_view chain, std::string_view content), + (override)); + MOCK_METHOD(Result, AddRule, + (std::string_view family, std::string_view table, + std::string_view chain, std::string_view content), + (override)); + MOCK_METHOD(Result, DeleteRule, + (std::string_view family, std::string_view table, + std::string_view chain, uint32_t handle), + (override)); +}; + +} // namespace cuttlefish + +#endif // ALLOCD_TEST_MOCK_NFTABLES_H_ diff --git a/base/cvd/cuttlefish/host/commands/cvdalloc/BUILD.bazel b/base/cvd/cuttlefish/host/commands/cvdalloc/BUILD.bazel index ae3536b08eb..6c22e942e50 100644 --- a/base/cvd/cuttlefish/host/commands/cvdalloc/BUILD.bazel +++ b/base/cvd/cuttlefish/host/commands/cvdalloc/BUILD.bazel @@ -50,6 +50,7 @@ cf_cc_binary( ":privilege", ":sem", "//allocd:alloc_utils", + "//allocd/net:nftables", "//cuttlefish/common/libs/fs", "//cuttlefish/posix:strerror", "//cuttlefish/result", diff --git a/base/cvd/cuttlefish/host/commands/cvdalloc/cvdalloc.cpp b/base/cvd/cuttlefish/host/commands/cvdalloc/cvdalloc.cpp index f54ac6e59eb..a59491b36e5 100644 --- a/base/cvd/cuttlefish/host/commands/cvdalloc/cvdalloc.cpp +++ b/base/cvd/cuttlefish/host/commands/cvdalloc/cvdalloc.cpp @@ -15,7 +15,10 @@ */ #include +#include #include +#include +#include #include "absl/cleanup/cleanup.h" #include "absl/flags/flag.h" @@ -23,6 +26,7 @@ #include "absl/log/log.h" #include "allocd/alloc_utils.h" +#include "allocd/net/nftables_nft.h" #include "cuttlefish/common/libs/fs/shared_fd.h" #include "cuttlefish/host/commands/cvdalloc/interface.h" #include "cuttlefish/host/commands/cvdalloc/privilege.h" @@ -31,33 +35,73 @@ ABSL_FLAG(int, id, 0, "Id"); ABSL_FLAG(int, socket, 0, "Socket"); +ABSL_FLAG(bool, setup, false, + "Set up nftables tables, chains, and static bridge NAT rules"); +ABSL_FLAG(bool, teardown, false, "Tear down nftables tables"); namespace cuttlefish { namespace { +// The mutually exclusive modes in which cvdalloc can run. +enum class Mode { + kSetup, // Install the static nftables environment. + kTeardown, // Remove the static nftables environment. + kInstance, // Allocate resources for a single instance. +}; + void Usage() { - LOG(ERROR) << "cvdalloc --id=id --socket=fd "; - LOG(ERROR) << "Should only be invoked from run_cvd."; + LOG(ERROR) + << "Usage: cvdalloc [--setup | --teardown | --id=id --socket=fd]"; + LOG(ERROR) << "Should only be invoked from cuttlefish-host-resources or " + "run_cvd."; } -Result Allocate(int id, std::string_view ethernet_bridge_name, - std::string_view wireless_bridge_name) { +std::optional DetermineMode() { + const bool is_setup = absl::GetFlag(FLAGS_setup); + const bool is_teardown = absl::GetFlag(FLAGS_teardown); + + if (is_setup && is_teardown) { + return std::nullopt; + } + if (is_setup) { + return Mode::kSetup; + } + if (is_teardown) { + return Mode::kTeardown; + } + if (absl::GetFlag(FLAGS_id) == 0 || absl::GetFlag(FLAGS_socket) == 0) { + return std::nullopt; + } + return Mode::kInstance; +} + +Result> Allocate(Nftables& nft, int id, + std::string_view ethernet_bridge_name, + std::string_view wireless_bridge_name) { LOG(INFO) << "cvdalloc: allocating network resources"; - CF_EXPECT(CreateMobileIface(CvdallocInterfaceName("mtap", id), id, - kCvdallocMobileIpPrefix)); + std::vector rules; + + rules.push_back(CF_EXPECT(CreateMobileIface( + nft, CvdallocInterfaceName("mtap", id), id, kCvdallocMobileIpPrefix))); + CF_EXPECT(CreateEthernetBridgeIface(wireless_bridge_name, kCvdallocWirelessIpPrefix)); + CF_EXPECT(CreateEthernetIface(CvdallocInterfaceName("wtap", id), wireless_bridge_name)); - CF_EXPECT(CreateMobileIface(CvdallocInterfaceName("wifiap", id), id, - kCvdallocWirelessApIpPrefix)); + + rules.push_back(CF_EXPECT(CreateMobileIface( + nft, CvdallocInterfaceName("wifiap", id), id, + kCvdallocWirelessApIpPrefix))); + CF_EXPECT(CreateEthernetBridgeIface(ethernet_bridge_name, kCvdallocEthernetIpPrefix)); + CF_EXPECT(CreateEthernetIface(CvdallocInterfaceName("etap", id), ethernet_bridge_name)); - return {}; + return rules; } Result Teardown(int id, std::string_view ethernet_bridge_name, @@ -77,71 +121,96 @@ Result Teardown(int id, std::string_view ethernet_bridge_name, return {}; } -} // namespace - -Result CvdallocMain(int argc, char* argv[]) { - std::vector args = absl::ParseCommandLine(argc, argv); - - if (absl::GetFlag(FLAGS_id) == 0 || absl::GetFlag(FLAGS_socket) == 0) { - Usage(); - /* No need to dump a trace for usage. */ - return 1; - } +// Adopts the socket file descriptor passed on the command line: duplicates it +// into a SharedFD and closes the original numeric descriptor. +Result AdoptSocket(int socket_fd) { + SharedFD sock = SharedFD::Dup(socket_fd); + CF_EXPECT(sock->IsOpen(), "cvdalloc: socket is closed: " << sock->StrError()); + CF_EXPECTF(TEMP_FAILURE_RETRY(close(socket_fd)) != -1, "close: {}", + StrError(errno)); + return sock; +} - int id = absl::GetFlag(FLAGS_id); +// Installs the static nftables environment (tables, chains, and the static +// bridge NAT rules) so that dynamic per-instance rules can be added later. +Result CvdallocSetup(Nftables& nft) { + LOG(INFO) << "cvdalloc: running setup"; + CF_EXPECT(SetupFirewall(nft)); + return {}; +} - auto sock = SharedFD::Dup(absl::GetFlag(FLAGS_socket)); - if (!sock->IsOpen()) { - return CF_ERRNO("cvdalloc: socket is closed: " << sock->StrError()); - } - int r = TEMP_FAILURE_RETRY(close(absl::GetFlag(FLAGS_socket))); - if (r == -1) { - return CF_ERRNO("close: " << StrError(errno)); - } +// Removes the static nftables environment, which atomically cleans up every +// chain and rule the tables contain. +Result CvdallocTeardown(Nftables& nft) { + LOG(INFO) << "cvdalloc: running teardown"; + CF_EXPECT(TeardownFirewall(nft)); + return {}; +} +// Allocates per-instance network resources, signals readiness to run_cvd, +// blocks until the shutdown signal, and then releases the resources. +Result CvdallocMain(Nftables& nft, int id, int socket_fd) { + SharedFD sock = CF_EXPECT(AdoptSocket(socket_fd)); absl::Cleanup shutdown = [sock]() { sock->Shutdown(SHUT_RDWR); }; - /* - * Save our current uid, so we can restore it to drop privileges later. - */ - uid_t orig = getuid(); - - absl::Cleanup drop_privileges = [orig]() { - int r = DropPrivileges(orig); - if (r == -1) { - LOG(ERROR) << "cvdalloc: couldn't drop privileges: " << StrError(errno); - } - }; - - r = BeginElevatedPrivileges(); - if (r == -1) { - return CF_ERRF("Couldn't elevate permissions: {}", StrError(errno)); - } - + // Release all resources if we bail out before the normal teardown below. absl::Cleanup teardown = [id]() { LOG(INFO) << "cvdalloc: teardown started"; - // TODO: b/471069557 - diagnose unused Result unused = Teardown(id, kCvdallocEthernetBridgeName, kCvdallocWirelessBridgeName); }; - CF_EXPECT( - Allocate(id, kCvdallocEthernetBridgeName, kCvdallocWirelessBridgeName)); + // Allocate resources, then signal run_cvd that the instance is ready. + std::vector rules = CF_EXPECT(Allocate( + nft, id, kCvdallocEthernetBridgeName, kCvdallocWirelessBridgeName)); CF_EXPECT(cvdalloc::Post(sock)); + // Block until run_cvd signals that the instance is tearing down. LOG(INFO) << "cvdalloc: waiting to teardown"; - CF_EXPECT(cvdalloc::Wait(sock, cvdalloc::kSemNoTimeout)); + + // Release resources and acknowledge completion. std::move(teardown).Invoke(); + rules.clear(); CF_EXPECT(cvdalloc::Post(sock)); - return 0; + return {}; +} + +} // namespace + +Result RunCvdalloc(int argc, char* argv[]) { + absl::ParseCommandLine(argc, argv); + + std::optional mode = DetermineMode(); + if (!mode.has_value()) { + Usage(); + return 1; + } + + ScopedPrivileges privileges = CF_EXPECT(ScopedPrivileges::Elevate()); + + NftablesNft nft; + switch (*mode) { + case Mode::kSetup: + CF_EXPECT(CvdallocSetup(nft)); + return 0; + case Mode::kTeardown: + CF_EXPECT(CvdallocTeardown(nft)); + return 0; + case Mode::kInstance: + CF_EXPECT(CvdallocMain(nft, absl::GetFlag(FLAGS_id), + absl::GetFlag(FLAGS_socket))); + return 0; + } + + return CF_ERR("cvdalloc: unhandled run mode"); } } // namespace cuttlefish int main(int argc, char* argv[]) { - auto res = cuttlefish::CvdallocMain(argc, argv); + auto res = cuttlefish::RunCvdalloc(argc, argv); if (!res.ok()) { LOG(ERROR) << "cvdalloc failed: \n" << res.error(); abort(); diff --git a/base/cvd/cuttlefish/host/commands/cvdalloc/privilege.cpp b/base/cvd/cuttlefish/host/commands/cvdalloc/privilege.cpp index f0b41d43348..db886bf5450 100644 --- a/base/cvd/cuttlefish/host/commands/cvdalloc/privilege.cpp +++ b/base/cvd/cuttlefish/host/commands/cvdalloc/privilege.cpp @@ -29,6 +29,9 @@ #endif #include +#include +#include + #include "absl/log/log.h" #include "cuttlefish/posix/strerror.h" @@ -155,4 +158,22 @@ int DropPrivileges(uid_t orig) { return setuid(orig); } +Result ScopedPrivileges::Elevate() { + uid_t orig = getuid(); + CF_EXPECTF(BeginElevatedPrivileges() != -1, + "Couldn't elevate permissions: {}", StrError(errno)); + return ScopedPrivileges(orig); +} + +ScopedPrivileges::ScopedPrivileges(uid_t orig) : orig_(orig) {} + +ScopedPrivileges::ScopedPrivileges(ScopedPrivileges&& other) noexcept + : orig_(std::exchange(other.orig_, std::nullopt)) {} + +ScopedPrivileges::~ScopedPrivileges() { + if (orig_.has_value() && DropPrivileges(*orig_) == -1) { + LOG(ERROR) << "cvdalloc: couldn't drop privileges: " << StrError(errno); + } +} + } // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvdalloc/privilege.h b/base/cvd/cuttlefish/host/commands/cvdalloc/privilege.h index 64f944adfdb..3be7c8394c9 100644 --- a/base/cvd/cuttlefish/host/commands/cvdalloc/privilege.h +++ b/base/cvd/cuttlefish/host/commands/cvdalloc/privilege.h @@ -13,8 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#ifndef CUTTLEFISH_HOST_COMMANDS_CVDALLOC_PRIVILEGE_H_ +#define CUTTLEFISH_HOST_COMMANDS_CVDALLOC_PRIVILEGE_H_ + #include +#include #include #include "cuttlefish/result/result.h" @@ -25,4 +29,22 @@ int BeginElevatedPrivileges(); int DropPrivileges(uid_t orig); Result ValidateCvdallocBinary(std::string_view path); +class ScopedPrivileges { + public: + static Result Elevate(); + + ScopedPrivileges(ScopedPrivileges&& other) noexcept; + ScopedPrivileges& operator=(ScopedPrivileges&& other) = delete; + ScopedPrivileges(const ScopedPrivileges&) = delete; + ScopedPrivileges& operator=(const ScopedPrivileges&) = delete; + ~ScopedPrivileges(); + + private: + explicit ScopedPrivileges(uid_t orig); + + std::optional orig_; +}; + } // namespace cuttlefish + +#endif // CUTTLEFISH_HOST_COMMANDS_CVDALLOC_PRIVILEGE_H_ diff --git a/base/cvd/cuttlefish/host/commands/run_cvd/launch/BUILD.bazel b/base/cvd/cuttlefish/host/commands/run_cvd/launch/BUILD.bazel index 269b72e7970..f76f4de539f 100644 --- a/base/cvd/cuttlefish/host/commands/run_cvd/launch/BUILD.bazel +++ b/base/cvd/cuttlefish/host/commands/run_cvd/launch/BUILD.bazel @@ -118,7 +118,7 @@ cf_cc_library( hdrs = ["cvdalloc.h"], depend_on_what_you_use_enabled = False, deps = [ - "//allocd:alloc_utils", + "//allocd/net:nftables", "//cuttlefish/common/libs/fs", "//cuttlefish/host/commands/cvdalloc:privilege", "//cuttlefish/host/commands/cvdalloc:sem", diff --git a/base/cvd/cuttlefish/host/commands/run_cvd/launch/cvdalloc.cpp b/base/cvd/cuttlefish/host/commands/run_cvd/launch/cvdalloc.cpp index ab83eac0d97..dd87cf91b83 100644 --- a/base/cvd/cuttlefish/host/commands/run_cvd/launch/cvdalloc.cpp +++ b/base/cvd/cuttlefish/host/commands/run_cvd/launch/cvdalloc.cpp @@ -31,7 +31,7 @@ #include "fruit/component.h" #include "fruit/fruit_forward_decls.h" -#include "allocd/alloc_utils.h" +#include "allocd/net/nftables_nft.h" #include "cuttlefish/host/commands/cvdalloc/privilege.h" #include "cuttlefish/host/commands/cvdalloc/sem.h" #include "cuttlefish/host/libs/config/cuttlefish_config.h" @@ -112,7 +112,7 @@ Result Cvdalloc::BinaryIsValid(std::string_view path) { } Result Cvdalloc::IsUsable() const { - CF_EXPECT(IptablesPath()); + CF_EXPECT(NftablesNft::BinaryPath()); return {}; } diff --git a/base/debian/cuttlefish-base.cuttlefish-host-resources.init b/base/debian/cuttlefish-base.cuttlefish-host-resources.init index 7afa68ddc58..fbb8e9a2224 100755 --- a/base/debian/cuttlefish-base.cuttlefish-host-resources.init +++ b/base/debian/cuttlefish-base.cuttlefish-host-resources.init @@ -44,6 +44,8 @@ ipv4_bridge=${ipv4_bridge:-1} ipv6_bridge=${ipv6_bridge:-1} dns_servers=${dns_servers:-8.8.8.8,8.8.4.4} dns6_servers=${dns6_servers:-2001:4860:4860::8888,2001:4860:4860::8844} +allocate_static_resources=${allocate_static_resources:-1} +use_cvdalloc=${use_cvdalloc:-1} readonly CUTTLEFISH_RUN_DIR="/run/cuttlefish" @@ -109,18 +111,30 @@ destroy_tap() { setup_nftables() { mkdir -p "$CUTTLEFISH_RUN_DIR" - # Ensure nftables tables and chains exist. - nft add table ip cuttlefish_nat - nft add chain ip cuttlefish_nat postrouting '{ type nat hook postrouting priority 100 ; }' + if [ "${use_cvdalloc}" = "1" ] && [ -x /usr/lib/cuttlefish-common/bin/cvdalloc ]; then + /usr/lib/cuttlefish-common/bin/cvdalloc --setup + fi + + if [ "${allocate_static_resources}" = "1"]; then + # Ensure nftables tables and chains exist. + nft add table ip cuttlefish_nat + nft add chain ip cuttlefish_nat postrouting '{ type nat hook postrouting priority 100 ; }' - nft add table bridge cuttlefish_bridge - nft add chain bridge cuttlefish_bridge prerouting '{ type filter hook prerouting priority -250 ; }' - nft add chain bridge cuttlefish_bridge forward '{ type filter hook forward priority 0 ; }' + nft add table bridge cuttlefish_bridge + nft add chain bridge cuttlefish_bridge prerouting '{ type filter hook prerouting priority -250 ; }' + nft add chain bridge cuttlefish_bridge forward '{ type filter hook forward priority 0 ; }' + fi } delete_nftables() { - nft delete table ip cuttlefish_nat - nft delete table bridge cuttlefish_bridge + if [ "${use_cvdalloc}" = "1" ] && [ -x /usr/lib/cuttlefish-common/bin/cvdalloc ]; then + /usr/lib/cuttlefish-common/bin/cvdalloc --teardown + fi + + if [ "${allocate_static_resources}" = "1"]; then + nft delete table ip cuttlefish_nat + nft delete table bridge cuttlefish_bridge + fi } @@ -331,43 +345,44 @@ start() { setup_nftables - - # Ethernet - # 192.168.98.X for cvd-ebr and cvd-etap-XX - create_bridged_interfaces \ - 192.168.98 "${ethernet_bridge_interface}" cvd-etap \ - "${ethernet_ipv6_prefix}" "${ethernet_ipv6_prefix_length}" - - # Mobile Network - # 192.168.97.X from cvd-mtap-01 to cvd-mtap-64 - # 192.168.93.X from cvd-mtap-65 to cvd-mtap-128 - for i in $(seq ${num_cvd_accounts}); do - tap="$(printf cvd-mtap-%02d $i)" - if [ $i -lt 65 ]; then - create_interface $tap 192.168.97 $i - elif [ $i -lt 129 ]; then - create_interface $tap 192.168.93 $(($i - 64)) - fi - done - - # Wireless Network - # cvd-wbr and cvd-wtap-XX for legacy wireless network without distinguished - # subnet between tap interfaces, cvd-wifiap-XX with distinguished subnet for - # running several OpenWRT instances simultaneously. - # 192.168.96.X for cvd-wbr and cvd-wtap-XX - # 192.168.94.X from cvd-wifiap-01 to cvd-wifiap-64 - # 192.168.95.X from cvd-wifiap-65 to cvd-wifiap-128 - create_bridged_interfaces \ - 192.168.96 "${wifi_bridge_interface}" cvd-wtap \ - "${wifi_ipv6_prefix}" "${wifi_ipv6_prefix_length}" - for i in $(seq ${num_cvd_accounts}); do - tap="$(printf cvd-wifiap-%02d $i)" - if [ $i -lt 65 ]; then - create_interface $tap 192.168.94 $i - elif [ $i -lt 129 ]; then - create_interface $tap 192.168.95 $(($i - 64)) - fi - done + if [ "${allocate_static_resources}" = "1"]; then + # Ethernet + # 192.168.98.X for cvd-ebr and cvd-etap-XX + create_bridged_interfaces \ + 192.168.98 "${ethernet_bridge_interface}" cvd-etap \ + "${ethernet_ipv6_prefix}" "${ethernet_ipv6_prefix_length}" + + # Mobile Network + # 192.168.97.X from cvd-mtap-01 to cvd-mtap-64 + # 192.168.93.X from cvd-mtap-65 to cvd-mtap-128 + for i in $(seq ${num_cvd_accounts}); do + tap="$(printf cvd-mtap-%02d $i)" + if [ $i -lt 65 ]; then + create_interface $tap 192.168.97 $i + elif [ $i -lt 129 ]; then + create_interface $tap 192.168.93 $(($i - 64)) + fi + done + + # Wireless Network + # cvd-wbr and cvd-wtap-XX for legacy wireless network without distinguished + # subnet between tap interfaces, cvd-wifiap-XX with distinguished subnet for + # running several OpenWRT instances simultaneously. + # 192.168.96.X for cvd-wbr and cvd-wtap-XX + # 192.168.94.X from cvd-wifiap-01 to cvd-wifiap-64 + # 192.168.95.X from cvd-wifiap-65 to cvd-wifiap-128 + create_bridged_interfaces \ + 192.168.96 "${wifi_bridge_interface}" cvd-wtap \ + "${wifi_ipv6_prefix}" "${wifi_ipv6_prefix_length}" + for i in $(seq ${num_cvd_accounts}); do + tap="$(printf cvd-wifiap-%02d $i)" + if [ $i -lt 65 ]; then + create_interface $tap 192.168.94 $i + elif [ $i -lt 129 ]; then + create_interface $tap 192.168.95 $(($i - 64)) + fi + done + fi # When running inside a privileged container, set the ownership and access # of these device nodes. @@ -385,33 +400,35 @@ start() { } stop() { - # Ethernet - destroy_bridged_interfaces \ - 192.168.98 "${ethernet_bridge_interface}" cvd-etap \ - "${ethernet_ipv6_prefix}" "${ethernet_ipv6_prefix_length}" - - # Mobile Network - for i in $(seq ${num_cvd_accounts}); do - tap="$(printf cvd-mtap-%02d $i)" - if [ $i -lt 65 ]; then - destroy_interface $tap 192.168.97 $i - elif [ $i -lt 129 ]; then - destroy_interface $tap 192.168.93 $(($i - 64)) - fi - done - - # Wireless Network - destroy_bridged_interfaces \ - 192.168.96 "${wifi_bridge_interface}" cvd-wtap \ - "${wifi_ipv6_prefix}" "${wifi_ipv6_prefix_length}" - for i in $(seq ${num_cvd_accounts}); do - tap="$(printf cvd-wifiap-%02d $i)" - if [ $i -lt 65 ]; then - destroy_interface $tap 192.168.94 $i - elif [ $i -lt 129 ]; then - destroy_interface $tap 192.168.95 $(($i - 64)) - fi - done + if [ "${allocate_static_resources}" = "1"]; then + # Ethernet + destroy_bridged_interfaces \ + 192.168.98 "${ethernet_bridge_interface}" cvd-etap \ + "${ethernet_ipv6_prefix}" "${ethernet_ipv6_prefix_length}" + + # Mobile Network + for i in $(seq ${num_cvd_accounts}); do + tap="$(printf cvd-mtap-%02d $i)" + if [ $i -lt 65 ]; then + destroy_interface $tap 192.168.97 $i + elif [ $i -lt 129 ]; then + destroy_interface $tap 192.168.93 $(($i - 64)) + fi + done + + # Wireless Network + destroy_bridged_interfaces \ + 192.168.96 "${wifi_bridge_interface}" cvd-wtap \ + "${wifi_ipv6_prefix}" "${wifi_ipv6_prefix_length}" + for i in $(seq ${num_cvd_accounts}); do + tap="$(printf cvd-wifiap-%02d $i)" + if [ $i -lt 65 ]; then + destroy_interface $tap 192.168.94 $i + elif [ $i -lt 129 ]; then + destroy_interface $tap 192.168.95 $(($i - 64)) + fi + done + fi delete_nftables }